diff --git a/.gitignore b/.gitignore index b934545b0..f19433b97 100644 --- a/.gitignore +++ b/.gitignore @@ -89,6 +89,7 @@ rkt-* # misc /ui/.sass-cache +/ui/.eslintcache /ui/.storybook/preview-head.html /ui/connect.lock /ui/coverage/* diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index 99a0745c2..19fd1e1dc 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -2,17 +2,6 @@ module.exports = { root: true, - globals: { - server: true, - }, - env: { - browser: true, - es6: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:ember/recommended', - ], parser: 'babel-eslint', parserOptions: { ecmaVersion: 2018, @@ -21,37 +10,42 @@ module.exports = { legacyDecorators: true, }, }, - plugins: [ - 'ember' + globals: { + server: true, + }, + env: { + browser: true, + }, + plugins: ['ember'], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended', + 'plugin:prettier/recommended', ], rules: { - indent: ['error', 2, { SwitchCase: 1 }], - 'linebreak-style': ['error', 'unix'], - quotes: ['error', 'single', 'avoid-escape'], - semi: ['error', 'always'], - 'no-constant-condition': [ - 'error', - { - checkLoops: false, - }, - ], 'ember/classic-decorator-hooks': 'error', 'ember/classic-decorator-no-classic-methods': 'error', 'ember/no-get': 'off', 'ember/no-mixins': 'off', + 'ember/no-classic-classes': 'off', + 'ember/no-computed-properties-in-native-classes': 'off', + 'ember/no-classic-components': 'off', + 'ember/no-component-lifecycle-hooks': 'off', + 'ember/require-tagless-components': 'off', }, overrides: [ // node files { files: [ '.eslintrc.js', + '.prettierrc.js', '.template-lintrc.js', 'ember-cli-build.js', 'testem.js', 'blueprints/*/index.js', 'config/**/*.js', - 'server/**/*.js', 'lib/*/index.js', + 'server/**/*.js', ], parserOptions: { sourceType: 'script', @@ -61,14 +55,15 @@ module.exports = { node: true, }, plugins: ['node'], + extends: ['plugin:node/recommended'], rules: { - 'node/no-unpublished-require': 'off' + // this can be removed once the following is fixed + // https://github.com/mysticatea/eslint-plugin-node/issues/77 + 'node/no-unpublished-require': 'off', }, }, { - files: [ - 'stories/**/*.js' - ], + files: ['stories/**/*.js'], parserOptions: { sourceType: 'module', }, diff --git a/ui/.prettierignore b/ui/.prettierignore new file mode 100644 index 000000000..922165552 --- /dev/null +++ b/ui/.prettierignore @@ -0,0 +1,21 @@ +# unconventional js +/blueprints/*/files/ +/vendor/ + +# compiled output +/dist/ +/tmp/ + +# dependencies +/bower_components/ +/node_modules/ + +# misc +/coverage/ +!.* +.eslintcache + +# ember-try +/.node_modules.ember-try/ +/bower.json.ember-try +/package.json.ember-try diff --git a/ui/.prettierrc b/ui/.prettierrc index 546369158..5938b36b5 100644 --- a/ui/.prettierrc +++ b/ui/.prettierrc @@ -1,7 +1,5 @@ { - "printWidth": 100, "singleQuote": true, - "trailingComma": "es5", "overrides": [ { "files": "*.hbs", diff --git a/ui/.prettierrc.js b/ui/.prettierrc.js new file mode 100644 index 000000000..534e6d35a --- /dev/null +++ b/ui/.prettierrc.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + singleQuote: true, +}; diff --git a/ui/.storybook/babel.config.js b/ui/.storybook/babel.config.js index 5dd544f2a..b69a0355e 100644 --- a/ui/.storybook/babel.config.js +++ b/ui/.storybook/babel.config.js @@ -14,7 +14,11 @@ module.exports = { shippedProposals: true, useBuiltIns: 'usage', corejs: '3', - targets: ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions'], + targets: [ + 'last 1 Chrome versions', + 'last 1 Firefox versions', + 'last 1 Safari versions', + ], }, ], ], @@ -27,7 +31,10 @@ module.exports = { ], ['@babel/plugin-proposal-class-properties', { loose: true }], '@babel/plugin-syntax-dynamic-import', - ['@babel/plugin-proposal-object-rest-spread', { loose: true, useBuiltIns: true }], + [ + '@babel/plugin-proposal-object-rest-spread', + { loose: true, useBuiltIns: true }, + ], 'babel-plugin-macros', ['emotion', { sourceMap: true, autoLabel: true }], [ diff --git a/ui/.storybook/preview.js b/ui/.storybook/preview.js index 7352c15a4..82c50d189 100644 --- a/ui/.storybook/preview.js +++ b/ui/.storybook/preview.js @@ -10,7 +10,7 @@ addParameters({ }, }); -addDecorator(storyFn => { +addDecorator((storyFn) => { let { template, context } = storyFn(); let wrapperElementStyle = { diff --git a/ui/.storybook/theme.js b/ui/.storybook/theme.js index ff67962d0..92d425093 100644 --- a/ui/.storybook/theme.js +++ b/ui/.storybook/theme.js @@ -18,7 +18,8 @@ export default create({ // Typography // From variables.scss - fontBase: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif", + fontBase: + "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif", // From Bulma fontCode: 'monospace', diff --git a/ui/.storybook/webpack.config.js b/ui/.storybook/webpack.config.js index 98e79973f..02e11248c 100644 --- a/ui/.storybook/webpack.config.js +++ b/ui/.storybook/webpack.config.js @@ -1,5 +1,5 @@ /* eslint-env node */ -module.exports = function({ config }) { +module.exports = function ({ config }) { config.module.rules.push({ test: /\.stories\.jsx?$/, loaders: [require.resolve('@storybook/source-loader')], diff --git a/ui/app/abilities/abstract.js b/ui/app/abilities/abstract.js index 9dcf5c717..d1bccf543 100644 --- a/ui/app/abilities/abstract.js +++ b/ui/app/abilities/abstract.js @@ -26,17 +26,26 @@ export default class Abstract extends Ability { get rulesForNamespace() { let namespace = this._namespace; - return (this.get('token.selfTokenPolicies') || []).toArray().reduce((rules, policy) => { - let policyNamespaces = get(policy, 'rulesJSON.Namespaces') || []; + return (this.get('token.selfTokenPolicies') || []) + .toArray() + .reduce((rules, policy) => { + let policyNamespaces = get(policy, 'rulesJSON.Namespaces') || []; - let matchingNamespace = this._findMatchingNamespace(policyNamespaces, namespace); + let matchingNamespace = this._findMatchingNamespace( + policyNamespaces, + namespace + ); - if (matchingNamespace) { - rules.push(policyNamespaces.find(namespace => namespace.Name === matchingNamespace)); - } + if (matchingNamespace) { + rules.push( + policyNamespaces.find( + (namespace) => namespace.Name === matchingNamespace + ) + ); + } - return rules; - }, []); + return rules; + }, []); } @computed('token.selfTokenPolicies.[]') @@ -44,15 +53,17 @@ export default class Abstract extends Ability { return (this.get('token.selfTokenPolicies') || []) .toArray() .reduce((allCapabilities, policy) => { - (get(policy, 'rulesJSON.Namespaces') || []).forEach(({ Capabilities }) => { - allCapabilities = allCapabilities.concat(Capabilities); - }); + (get(policy, 'rulesJSON.Namespaces') || []).forEach( + ({ Capabilities }) => { + allCapabilities = allCapabilities.concat(Capabilities); + } + ); return allCapabilities; }, []); } namespaceIncludesCapability(capability) { - return this.rulesForNamespace.some(rules => { + return this.rulesForNamespace.some((rules) => { let capabilities = get(rules, 'Capabilities') || []; return capabilities.includes(capability); }); @@ -76,12 +87,16 @@ export default class Abstract extends Ability { return namespace; } - let globNamespaceNames = namespaceNames.filter(namespaceName => namespaceName.includes('*')); + let globNamespaceNames = namespaceNames.filter((namespaceName) => + namespaceName.includes('*') + ); let matchingNamespaceName = globNamespaceNames.reduce( (mostMatching, namespaceName) => { // Convert * wildcards to .* for regex matching - let namespaceNameRegExp = new RegExp(namespaceName.replace(/\*/g, '.*')); + let namespaceNameRegExp = new RegExp( + namespaceName.replace(/\*/g, '.*') + ); let characterDifference = namespace.length - namespaceName.length; if ( @@ -96,7 +111,10 @@ export default class Abstract extends Ability { return mostMatching; } }, - { mostMatchingNamespaceName: null, mostMatchingCharacterDifference: Number.MAX_SAFE_INTEGER } + { + mostMatchingNamespaceName: null, + mostMatchingCharacterDifference: Number.MAX_SAFE_INTEGER, + } ).mostMatchingNamespaceName; if (matchingNamespaceName) { diff --git a/ui/app/abilities/agent.js b/ui/app/abilities/agent.js index 53450482e..21e65fce3 100644 --- a/ui/app/abilities/agent.js +++ b/ui/app/abilities/agent.js @@ -3,16 +3,20 @@ import { computed, get } from '@ember/object'; import { or } from '@ember/object/computed'; export default class Client extends AbstractAbility { - @or('bypassAuthorization', 'selfTokenIsManagement', 'policiesIncludeAgentReadOrWrite') + @or( + 'bypassAuthorization', + 'selfTokenIsManagement', + 'policiesIncludeAgentReadOrWrite' + ) canRead; @computed('token.selfTokenPolicies.[]') get policiesIncludeAgentReadOrWrite() { const policies = (get(this, 'token.selfTokenPolicies') || []) .toArray() - .map(policy => get(policy, 'rulesJSON.Agent.Policy')) + .map((policy) => get(policy, 'rulesJSON.Agent.Policy')) .compact(); - return policies.some(policy => policy === 'read' || policy === 'write'); + return policies.some((policy) => policy === 'read' || policy === 'write'); } } diff --git a/ui/app/abilities/allocation.js b/ui/app/abilities/allocation.js index 7f288b03c..ed46d781f 100644 --- a/ui/app/abilities/allocation.js +++ b/ui/app/abilities/allocation.js @@ -8,7 +8,7 @@ export default class Allocation extends AbstractAbility { @computed('rulesForNamespace.@each.capabilities') get policiesSupportExec() { - return this.rulesForNamespace.some(rules => { + return this.rulesForNamespace.some((rules) => { let capabilities = get(rules, 'Capabilities') || []; return capabilities.includes('alloc-exec'); }); diff --git a/ui/app/abilities/client.js b/ui/app/abilities/client.js index f48eeb93e..cd1fc8ed1 100644 --- a/ui/app/abilities/client.js +++ b/ui/app/abilities/client.js @@ -10,17 +10,26 @@ export default class Client extends AbstractAbility { @or('bypassAuthorization', 'selfTokenIsManagement', 'policiesIncludeNodeRead') canRead; - @or('bypassAuthorization', 'selfTokenIsManagement', 'policiesIncludeNodeWrite') + @or( + 'bypassAuthorization', + 'selfTokenIsManagement', + 'policiesIncludeNodeWrite' + ) canWrite; @computed('token.selfTokenPolicies.[]') get policiesIncludeNodeRead() { - return policiesIncludePermissions(this.get('token.selfTokenPolicies'), ['read', 'write']); + return policiesIncludePermissions(this.get('token.selfTokenPolicies'), [ + 'read', + 'write', + ]); } @computed('token.selfTokenPolicies.[]') get policiesIncludeNodeWrite() { - return policiesIncludePermissions(this.get('token.selfTokenPolicies'), ['write']); + return policiesIncludePermissions(this.get('token.selfTokenPolicies'), [ + 'write', + ]); } } @@ -28,9 +37,9 @@ function policiesIncludePermissions(policies = [], permissions = []) { // For each policy record, extract the Node policy const nodePolicies = policies .toArray() - .map(policy => get(policy, 'rulesJSON.Node.Policy')) + .map((policy) => get(policy, 'rulesJSON.Node.Policy')) .compact(); // Check for requested permissions - return nodePolicies.some(policy => permissions.includes(policy)); + return nodePolicies.some((policy) => permissions.includes(policy)); } diff --git a/ui/app/abilities/job.js b/ui/app/abilities/job.js index 02e175cb4..fbc8d982c 100644 --- a/ui/app/abilities/job.js +++ b/ui/app/abilities/job.js @@ -20,7 +20,11 @@ export default class Job extends AbstractAbility { @or('bypassAuthorization', 'selfTokenIsManagement') canListAll; - @or('bypassAuthorization', 'selfTokenIsManagement', 'policiesSupportDispatching') + @or( + 'bypassAuthorization', + 'selfTokenIsManagement', + 'policiesSupportDispatching' + ) canDispatch; @computed('rulesForNamespace.@each.capabilities') diff --git a/ui/app/abilities/recommendation.js b/ui/app/abilities/recommendation.js index 11decdb43..ce6f03f1d 100644 --- a/ui/app/abilities/recommendation.js +++ b/ui/app/abilities/recommendation.js @@ -6,7 +6,11 @@ export default class Recommendation extends AbstractAbility { @and('dynamicApplicationSizingIsPresent', 'hasPermissions') canAccept; - @or('bypassAuthorization', 'selfTokenIsManagement', 'policiesSupportAcceptingOnAnyNamespace') + @or( + 'bypassAuthorization', + 'selfTokenIsManagement', + 'policiesSupportAcceptingOnAnyNamespace' + ) hasPermissions; @computed('capabilitiesForAllNamespaces.[]') diff --git a/ui/app/adapters/allocation.js b/ui/app/adapters/allocation.js index 3fcfb6128..64c1ef7da 100644 --- a/ui/app/adapters/allocation.js +++ b/ui/app/adapters/allocation.js @@ -14,13 +14,17 @@ export default class AllocationAdapter extends Watchable { ls(model, path) { return this.token - .authorizedRequest(`/v1/client/fs/ls/${model.id}?path=${encodeURIComponent(path)}`) + .authorizedRequest( + `/v1/client/fs/ls/${model.id}?path=${encodeURIComponent(path)}` + ) .then(handleFSResponse); } stat(model, path) { return this.token - .authorizedRequest(`/v1/client/fs/stat/${model.id}?path=${encodeURIComponent(path)}`) + .authorizedRequest( + `/v1/client/fs/stat/${model.id}?path=${encodeURIComponent(path)}` + ) .then(handleFSResponse); } } @@ -39,8 +43,11 @@ async function handleFSResponse(response) { } function adapterAction(path, verb = 'POST') { - return function(allocation) { - const url = addToPath(this.urlForFindRecord(allocation.id, 'allocation'), path); + return function (allocation) { + const url = addToPath( + this.urlForFindRecord(allocation.id, 'allocation'), + path + ); return this.ajax(url, verb); }; } diff --git a/ui/app/adapters/application.js b/ui/app/adapters/application.js index 9250f72b2..efe4efb0b 100644 --- a/ui/app/adapters/application.js +++ b/ui/app/adapters/application.js @@ -35,7 +35,7 @@ export default class ApplicationAdapter extends RESTAdapter { } findAll() { - return super.findAll(...arguments).catch(error => { + return super.findAll(...arguments).catch((error) => { const errorCodes = codesForError(error); const isNotImplemented = errorCodes.includes('501'); @@ -66,14 +66,14 @@ export default class ApplicationAdapter extends RESTAdapter { // In order to remove stale records from the store, findHasMany has to unload // all records related to the request in question. findHasMany(store, snapshot, link, relationship) { - return super.findHasMany(...arguments).then(payload => { + return super.findHasMany(...arguments).then((payload) => { const relationshipType = relationship.type; const inverse = snapshot.record.inverseFor(relationship.key); if (inverse) { store .peekAll(relationshipType) - .filter(record => record.get(`${inverse.name}.id`) === snapshot.id) - .forEach(record => { + .filter((record) => record.get(`${inverse.name}.id`) === snapshot.id) + .forEach((record) => { removeRecord(store, record); }); } @@ -96,6 +96,7 @@ export default class ApplicationAdapter extends RESTAdapter { let prefix = this.urlPrefix(); if (modelName) { + /* eslint-disable-next-line ember/no-string-prototype-extensions */ path = modelName.camelize(); if (path) { url.push(path); @@ -124,5 +125,7 @@ export default class ApplicationAdapter extends RESTAdapter { } function associateRegion(url, region) { - return url.indexOf('?') !== -1 ? `${url}®ion=${region}` : `${url}?region=${region}`; + return url.indexOf('?') !== -1 + ? `${url}®ion=${region}` + : `${url}?region=${region}`; } diff --git a/ui/app/adapters/deployment.js b/ui/app/adapters/deployment.js index d24628c36..8904038d0 100644 --- a/ui/app/adapters/deployment.js +++ b/ui/app/adapters/deployment.js @@ -13,7 +13,10 @@ export default class DeploymentAdapter extends Watchable { promote(deployment) { const id = deployment.get('id'); - const url = urlForAction(this.urlForFindRecord(id, 'deployment'), '/promote'); + const url = urlForAction( + this.urlForFindRecord(id, 'deployment'), + '/promote' + ); return this.ajax(url, 'POST', { data: { DeploymentId: id, diff --git a/ui/app/adapters/job-version.js b/ui/app/adapters/job-version.js index eb2393a94..fb9eba821 100644 --- a/ui/app/adapters/job-version.js +++ b/ui/app/adapters/job-version.js @@ -5,7 +5,10 @@ export default class JobVersionAdapter extends ApplicationAdapter { revertTo(jobVersion) { const jobAdapter = this.store.adapterFor('job'); - const url = addToPath(jobAdapter.urlForFindRecord(jobVersion.get('job.id'), 'job'), '/revert'); + const url = addToPath( + jobAdapter.urlForFindRecord(jobVersion.get('job.id'), 'job'), + '/revert' + ); const [jobName] = JSON.parse(jobVersion.get('job.id')); return this.ajax(url, 'POST', { diff --git a/ui/app/adapters/job.js b/ui/app/adapters/job.js index 7ff6027ff..94995fd8e 100644 --- a/ui/app/adapters/job.js +++ b/ui/app/adapters/job.js @@ -14,7 +14,10 @@ export default class JobAdapter extends WatchableNamespaceIDs { forcePeriodic(job) { if (job.get('periodic')) { - const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/periodic/force'); + const url = addToPath( + this.urlForFindRecord(job.get('id'), 'job'), + '/periodic/force' + ); return this.ajax(url, 'POST'); } } @@ -44,7 +47,7 @@ export default class JobAdapter extends WatchableNamespaceIDs { Job: job.get('_newDefinitionJSON'), Diff: true, }, - }).then(json => { + }).then((json) => { json.ID = jobId; store.pushPayload('job-plan', { jobPlans: [json] }); return store.peekRecord('job-plan', jobId); @@ -71,7 +74,10 @@ export default class JobAdapter extends WatchableNamespaceIDs { } scale(job, group, count, message) { - const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/scale'); + const url = addToPath( + this.urlForFindRecord(job.get('id'), 'job'), + '/scale' + ); return this.ajax(url, 'POST', { data: { Count: count, @@ -87,7 +93,10 @@ export default class JobAdapter extends WatchableNamespaceIDs { } dispatch(job, meta, payload) { - const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/dispatch'); + const url = addToPath( + this.urlForFindRecord(job.get('id'), 'job'), + '/dispatch' + ); return this.ajax(url, 'POST', { data: { Payload: base64EncodeString(payload), diff --git a/ui/app/adapters/namespace.js b/ui/app/adapters/namespace.js index 9594862d2..8e91ea90c 100644 --- a/ui/app/adapters/namespace.js +++ b/ui/app/adapters/namespace.js @@ -3,7 +3,7 @@ import codesForError from '../utils/codes-for-error'; export default class NamespaceAdapter extends Watchable { findRecord(store, modelClass, id) { - return super.findRecord(...arguments).catch(error => { + return super.findRecord(...arguments).catch((error) => { const errorCodes = codesForError(error); if (errorCodes.includes('501')) { return { Name: id }; diff --git a/ui/app/adapters/node.js b/ui/app/adapters/node.js index 746f51f6e..fdb085a2c 100644 --- a/ui/app/adapters/node.js +++ b/ui/app/adapters/node.js @@ -11,7 +11,10 @@ export default class NodeAdapter extends Watchable { } setEligibility(node, isEligible) { - const url = addToPath(this.urlForFindRecord(node.id, 'node'), '/eligibility'); + const url = addToPath( + this.urlForFindRecord(node.id, 'node'), + '/eligibility' + ); return this.ajax(url, 'POST', { data: { NodeID: node.id, diff --git a/ui/app/adapters/recommendation-summary.js b/ui/app/adapters/recommendation-summary.js index 42754d80b..71b807749 100644 --- a/ui/app/adapters/recommendation-summary.js +++ b/ui/app/adapters/recommendation-summary.js @@ -9,13 +9,20 @@ export default class RecommendationSummaryAdapter extends ApplicationAdapter { } updateRecord(store, type, snapshot) { - const url = `${super.urlForCreateRecord('recommendations', snapshot)}/apply`; + const url = `${super.urlForCreateRecord( + 'recommendations', + snapshot + )}/apply`; - const allRecommendationIds = snapshot.hasMany('recommendations').mapBy('id'); - const excludedRecommendationIds = (snapshot.hasMany('excludedRecommendations') || []).mapBy( - 'id' + const allRecommendationIds = snapshot + .hasMany('recommendations') + .mapBy('id'); + const excludedRecommendationIds = ( + snapshot.hasMany('excludedRecommendations') || [] + ).mapBy('id'); + const includedRecommendationIds = allRecommendationIds.removeObjects( + excludedRecommendationIds ); - const includedRecommendationIds = allRecommendationIds.removeObjects(excludedRecommendationIds); const data = { Apply: includedRecommendationIds, diff --git a/ui/app/adapters/token.js b/ui/app/adapters/token.js index 09ea316af..253673534 100644 --- a/ui/app/adapters/token.js +++ b/ui/app/adapters/token.js @@ -8,7 +8,7 @@ export default class TokenAdapter extends ApplicationAdapter { namespace = namespace + '/acl'; findSelf() { - return this.ajax(`${this.buildURL()}/token/self`, 'GET').then(token => { + return this.ajax(`${this.buildURL()}/token/self`, 'GET').then((token) => { const store = this.store; store.pushPayload('token', { tokens: [token], @@ -23,15 +23,20 @@ export default class TokenAdapter extends ApplicationAdapter { data: { OneTimeSecretID: oneTimeToken, }, - }).then(({ Token: token }) => { - const store = this.store; - store.pushPayload('token', { - tokens: [token], - }); + }) + .then(({ Token: token }) => { + const store = this.store; + store.pushPayload('token', { + tokens: [token], + }); - return store.peekRecord('token', store.normalize('token', token).data.id); - }).catch(() => { - throw new OTTExchangeError(); - }); + return store.peekRecord( + 'token', + store.normalize('token', token).data.id + ); + }) + .catch(() => { + throw new OTTExchangeError(); + }); } } diff --git a/ui/app/adapters/watchable-namespace-ids.js b/ui/app/adapters/watchable-namespace-ids.js index 11219deb9..ffee65fcc 100644 --- a/ui/app/adapters/watchable-namespace-ids.js +++ b/ui/app/adapters/watchable-namespace-ids.js @@ -7,8 +7,8 @@ export default class WatchableNamespaceIDs extends Watchable { @service system; findAll() { - return super.findAll(...arguments).then(data => { - data.forEach(record => { + return super.findAll(...arguments).then((data) => { + data.forEach((record) => { record.Namespace = 'default'; }); return data; @@ -16,8 +16,8 @@ export default class WatchableNamespaceIDs extends Watchable { } query(store, type, { namespace }) { - return super.query(...arguments).then(data => { - data.forEach(record => { + return super.query(...arguments).then((data) => { + data.forEach((record) => { if (!record.Namespace) record.Namespace = namespace; }); return data; @@ -26,7 +26,8 @@ export default class WatchableNamespaceIDs extends Watchable { findRecord(store, type, id, snapshot) { const [, namespace] = JSON.parse(id); - const namespaceQuery = namespace && namespace !== 'default' ? { namespace } : {}; + const namespaceQuery = + namespace && namespace !== 'default' ? { namespace } : {}; return super.findRecord(store, type, id, snapshot, namespaceQuery); } diff --git a/ui/app/adapters/watchable.js b/ui/app/adapters/watchable.js index 456f2a2bc..b8eff658e 100644 --- a/ui/app/adapters/watchable.js +++ b/ui/app/adapters/watchable.js @@ -40,7 +40,10 @@ export default class Watchable extends ApplicationAdapter { params.index = this.watchList.getIndexFor(url); } - const signal = get(snapshotRecordArray || {}, 'adapterOptions.abortController.signal'); + const signal = get( + snapshotRecordArray || {}, + 'adapterOptions.abortController.signal' + ); return this.ajax(url, 'GET', { signal, data: params, @@ -48,9 +51,18 @@ export default class Watchable extends ApplicationAdapter { } findRecord(store, type, id, snapshot, additionalParams = {}) { - const originalUrl = this.buildURL(type.modelName, id, snapshot, 'findRecord'); + const originalUrl = this.buildURL( + type.modelName, + id, + snapshot, + 'findRecord' + ); let [url, params] = originalUrl.split('?'); - params = assign(queryString.parse(params) || {}, this.buildQuery(), additionalParams); + params = assign( + queryString.parse(params) || {}, + this.buildQuery(), + additionalParams + ); if (get(snapshot || {}, 'adapterOptions.watch')) { params.index = this.watchList.getIndexFor(originalUrl); @@ -60,7 +72,7 @@ export default class Watchable extends ApplicationAdapter { return this.ajax(url, 'GET', { signal, data: params, - }).catch(error => { + }).catch((error) => { if (error instanceof AbortError || error.name == 'AbortError') { return; } @@ -68,27 +80,43 @@ export default class Watchable extends ApplicationAdapter { }); } - query(store, type, query, snapshotRecordArray, options, additionalParams = {}) { + query( + store, + type, + query, + snapshotRecordArray, + options, + additionalParams = {} + ) { const url = this.buildURL(type.modelName, null, null, 'query', query); let [urlPath, params] = url.split('?'); - params = assign(queryString.parse(params) || {}, this.buildQuery(), additionalParams, query); + params = assign( + queryString.parse(params) || {}, + this.buildQuery(), + additionalParams, + query + ); if (get(options, 'adapterOptions.watch')) { // The intended query without additional blocking query params is used // to track the appropriate query index. - params.index = this.watchList.getIndexFor(`${urlPath}?${queryString.stringify(query)}`); + params.index = this.watchList.getIndexFor( + `${urlPath}?${queryString.stringify(query)}` + ); } const signal = get(options, 'adapterOptions.abortController.signal'); return this.ajax(urlPath, 'GET', { signal, data: params, - }).then(payload => { + }).then((payload) => { const adapter = store.adapterFor(type.modelName); // Query params may not necessarily map one-to-one to attribute names. // Adapters are responsible for declaring param mappings. - const queryParamsToAttrs = Object.keys(adapter.queryParamsToAttrs || {}).map(key => ({ + const queryParamsToAttrs = Object.keys( + adapter.queryParamsToAttrs || {} + ).map((key) => ({ queryParam: key, attr: adapter.queryParamsToAttrs[key], })); @@ -97,12 +125,12 @@ export default class Watchable extends ApplicationAdapter { // deletes have occurred, the store won't have stale records. store .peekAll(type.modelName) - .filter(record => + .filter((record) => queryParamsToAttrs.some( - mapping => get(record, mapping.attr) === query[mapping.queryParam] + (mapping) => get(record, mapping.attr) === query[mapping.queryParam] ) ) - .forEach(record => { + .forEach((record) => { removeRecord(store, record); }); @@ -110,7 +138,11 @@ export default class Watchable extends ApplicationAdapter { }); } - reloadRelationship(model, relationshipName, options = { watch: false, abortController: null }) { + reloadRelationship( + model, + relationshipName, + options = { watch: false, abortController: null } + ) { const { watch, abortController } = options; const relationship = model.relationshipFor(relationshipName); if (relationship.kind !== 'belongsTo' && relationship.kind !== 'hasMany') { @@ -129,7 +161,7 @@ export default class Watchable extends ApplicationAdapter { // in the URL and in options.data if (url.includes('?')) { const paramsInUrl = queryString.parse(url.split('?')[1]); - Object.keys(paramsInUrl).forEach(key => { + Object.keys(paramsInUrl).forEach((key) => { delete params[key]; }); } @@ -138,7 +170,7 @@ export default class Watchable extends ApplicationAdapter { signal: abortController && abortController.signal, data: params, }).then( - json => { + (json) => { const store = this.store; const normalizeMethod = relationship.kind === 'belongsTo' @@ -146,10 +178,14 @@ export default class Watchable extends ApplicationAdapter { : 'normalizeFindHasManyResponse'; const serializer = store.serializerFor(relationship.type); const modelClass = store.modelFor(relationship.type); - const normalizedData = serializer[normalizeMethod](store, modelClass, json); + const normalizedData = serializer[normalizeMethod]( + store, + modelClass, + json + ); store.push(normalizedData); }, - error => { + (error) => { if (error instanceof AbortError || error.name === 'AbortError') { return relationship.kind === 'belongsTo' ? {} : []; } diff --git a/ui/app/components/agent-monitor.js b/ui/app/components/agent-monitor.js index fe596e220..4c511322e 100644 --- a/ui/app/components/agent-monitor.js +++ b/ui/app/components/agent-monitor.js @@ -46,6 +46,7 @@ export default class AgentMonitor extends Component { } didInsertElement() { + super.didInsertElement(...arguments); this.updateLogger(); } @@ -57,7 +58,7 @@ export default class AgentMonitor extends Component { this.set( 'logger', Log.create({ - logFetch: url => this.token.authorizedRequest(url), + logFetch: (url) => this.token.authorizedRequest(url), params: this.monitorParams, url: this.monitorUrl, tail: currentTail, diff --git a/ui/app/components/allocation-row.js b/ui/app/components/allocation-row.js index 4ef9a5680..4eaddb3fd 100644 --- a/ui/app/components/allocation-row.js +++ b/ui/app/components/allocation-row.js @@ -33,7 +33,7 @@ export default class AllocationRow extends Component { if (!this.get('allocation.isRunning')) return undefined; return AllocationStatsTracker.create({ - fetch: url => this.token.authorizedRequest(url), + fetch: (url) => this.token.authorizedRequest(url), allocation: this.allocation, }); } @@ -48,6 +48,7 @@ export default class AllocationRow extends Component { } didReceiveAttrs() { + super.didReceiveAttrs(); this.updateStatsTracker(); } @@ -61,13 +62,11 @@ export default class AllocationRow extends Component { } } - @(task(function*() { + @(task(function* () { do { if (this.stats) { try { - yield this.get('stats.poll') - .linked() - .perform(); + yield this.get('stats.poll').linked().perform(); this.set('statsError', false); } catch (error) { this.set('statsError', true); @@ -86,7 +85,9 @@ async function qualifyAllocation() { // Make sure the allocation is a complete record and not a partial so we // can show information such as preemptions and rescheduled allocation. if (allocation.isPartial) { - await this.store.findRecord('allocation', allocation.id, { backgroundReload: false }); + await this.store.findRecord('allocation', allocation.id, { + backgroundReload: false, + }); } if (allocation.get('job.isPending')) { diff --git a/ui/app/components/allocation-stat.js b/ui/app/components/allocation-stat.js index b53531070..efac3ad62 100644 --- a/ui/app/components/allocation-stat.js +++ b/ui/app/components/allocation-stat.js @@ -42,8 +42,10 @@ export default class AllocationStat extends Component { @computed('metric', 'statsTracker.{reservedMemory,reservedCPU}') get formattedReserved() { - if (this.metric === 'memory') return formatBytes(this.statsTracker.reservedMemory, 'MiB'); - if (this.metric === 'cpu') return formatHertz(this.statsTracker.reservedCPU, 'MHz'); + if (this.metric === 'memory') + return formatBytes(this.statsTracker.reservedMemory, 'MiB'); + if (this.metric === 'cpu') + return formatHertz(this.statsTracker.reservedCPU, 'MHz'); return undefined; } } diff --git a/ui/app/components/chart-primitives/area.js b/ui/app/components/chart-primitives/area.js index c8e033ae6..ec4e21945 100644 --- a/ui/app/components/chart-primitives/area.js +++ b/ui/app/components/chart-primitives/area.js @@ -7,7 +7,9 @@ export default class ChartPrimitiveArea extends Component { get colorClass() { if (this.args.colorClass) return this.args.colorClass; if (this.args.colorScale && this.args.index != null) - return `${this.args.colorScale} ${this.args.colorScale}-${this.args.index + 1}`; + return `${this.args.colorScale} ${this.args.colorScale}-${ + this.args.index + 1 + }`; return 'is-primary'; } @@ -31,9 +33,9 @@ export default class ChartPrimitiveArea extends Component { const builder = line() .curve(d3Shape[this.curveMethod]) - .defined(d => d[yProp] != null) - .x(d => xScale(d[xProp])) - .y(d => yScale(d[yProp])); + .defined((d) => d[yProp] != null) + .x((d) => xScale(d[xProp])) + .y((d) => yScale(d[yProp])); return builder(this.args.data); } @@ -43,10 +45,10 @@ export default class ChartPrimitiveArea extends Component { const builder = area() .curve(d3Shape[this.curveMethod]) - .defined(d => d[yProp] != null) - .x(d => xScale(d[xProp])) + .defined((d) => d[yProp] != null) + .x((d) => xScale(d[xProp])) .y0(yScale(0)) - .y1(d => yScale(d[yProp])); + .y1((d) => yScale(d[yProp])); return builder(this.args.data); } diff --git a/ui/app/components/chart-primitives/h-annotations.js b/ui/app/components/chart-primitives/h-annotations.js index e189fc23c..29afab595 100644 --- a/ui/app/components/chart-primitives/h-annotations.js +++ b/ui/app/components/chart-primitives/h-annotations.js @@ -19,7 +19,7 @@ export default class ChartPrimitiveVAnnotations extends Component { let sortedAnnotations = annotations.sortBy(prop).reverse(); - return sortedAnnotations.map(annotation => { + return sortedAnnotations.map((annotation) => { const y = scale(annotation[prop]); const x = 0; const formattedY = format()(annotation[prop]); diff --git a/ui/app/components/chart-primitives/v-annotations.js b/ui/app/components/chart-primitives/v-annotations.js index ce6df6238..5f76690ed 100644 --- a/ui/app/components/chart-primitives/v-annotations.js +++ b/ui/app/components/chart-primitives/v-annotations.js @@ -33,7 +33,7 @@ export default class ChartPrimitiveVAnnotations extends Component { let prevX = 0; let prevHigh = false; - return sortedAnnotations.map(annotation => { + return sortedAnnotations.map((annotation) => { const x = scale(annotation[prop]); if (prevX && !prevHigh && Math.abs(x - prevX) < 30) { prevHigh = true; diff --git a/ui/app/components/children-status-bar.js b/ui/app/components/children-status-bar.js index 5ea7d6eb7..b64cbb351 100644 --- a/ui/app/components/children-status-bar.js +++ b/ui/app/components/children-status-bar.js @@ -16,10 +16,22 @@ export default class ChildrenStatusBar extends DistributionBar { return []; } - const children = this.job.getProperties('pendingChildren', 'runningChildren', 'deadChildren'); + const children = this.job.getProperties( + 'pendingChildren', + 'runningChildren', + 'deadChildren' + ); return [ - { label: 'Pending', value: children.pendingChildren, className: 'queued' }, - { label: 'Running', value: children.runningChildren, className: 'running' }, + { + label: 'Pending', + value: children.pendingChildren, + className: 'queued', + }, + { + label: 'Running', + value: children.runningChildren, + className: 'running', + }, { label: 'Dead', value: children.deadChildren, className: 'complete' }, ]; } diff --git a/ui/app/components/client-node-row.js b/ui/app/components/client-node-row.js index 2127b5930..013ffb4be 100644 --- a/ui/app/components/client-node-row.js +++ b/ui/app/components/client-node-row.js @@ -10,7 +10,9 @@ import classic from 'ember-classic-decorator'; @classic @tagName('tr') @classNames('client-node-row', 'is-interactive') -export default class ClientNodeRow extends Component.extend(WithVisibilityDetection) { +export default class ClientNodeRow extends Component.extend( + WithVisibilityDetection +) { @service store; node = null; @@ -22,6 +24,7 @@ export default class ClientNodeRow extends Component.extend(WithVisibilityDetect } didReceiveAttrs() { + super.didReceiveAttrs(); // Reload the node in order to get detail information const node = this.node; if (node) { diff --git a/ui/app/components/copy-button.js b/ui/app/components/copy-button.js index 107786d73..979ecd701 100644 --- a/ui/app/components/copy-button.js +++ b/ui/app/components/copy-button.js @@ -9,7 +9,7 @@ export default class CopyButton extends Component { clipboardText = null; state = null; - @(task(function*() { + @(task(function* () { this.set('state', 'success'); yield timeout(2000); diff --git a/ui/app/components/das/dismissed.js b/ui/app/components/das/dismissed.js index 85fa8e9f1..3fdefa6ec 100644 --- a/ui/app/components/das/dismissed.js +++ b/ui/app/components/das/dismissed.js @@ -4,7 +4,8 @@ import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; export default class DasDismissedComponent extends Component { - @localStorageProperty('nomadRecommendationDismssalUnderstood', false) explanationUnderstood; + @localStorageProperty('nomadRecommendationDismssalUnderstood', false) + explanationUnderstood; @tracked dismissInTheFuture = false; diff --git a/ui/app/components/das/recommendation-accordion.js b/ui/app/components/das/recommendation-accordion.js index 1270a0d27..75c0e1f13 100644 --- a/ui/app/components/das/recommendation-accordion.js +++ b/ui/app/components/das/recommendation-accordion.js @@ -11,9 +11,11 @@ export default class DasRecommendationAccordionComponent extends Component { @tracked closing = false; @tracked animationContainerStyle = htmlSafe(''); - @(task(function*() { + @(task(function* () { this.closing = true; - this.animationContainerStyle = htmlSafe(`height: ${this.accordionElement.clientHeight}px`); + this.animationContainerStyle = htmlSafe( + `height: ${this.accordionElement.clientHeight}px` + ); yield timeout(10); diff --git a/ui/app/components/das/recommendation-card.js b/ui/app/components/das/recommendation-card.js index 29f203e13..7ab471631 100644 --- a/ui/app/components/das/recommendation-card.js +++ b/ui/app/components/das/recommendation-card.js @@ -93,35 +93,39 @@ export default class DasRecommendationCardComponent extends Component { get taskToggleRows() { const taskNameToTaskToggles = {}; - return this.args.summary.recommendations.reduce((taskToggleRows, recommendation) => { - let taskToggleRow = taskNameToTaskToggles[recommendation.task.name]; + return this.args.summary.recommendations.reduce( + (taskToggleRows, recommendation) => { + let taskToggleRow = taskNameToTaskToggles[recommendation.task.name]; - if (!taskToggleRow) { - taskToggleRow = { - recommendations: [], - task: recommendation.task, + if (!taskToggleRow) { + taskToggleRow = { + recommendations: [], + task: recommendation.task, + }; + + taskNameToTaskToggles[recommendation.task.name] = taskToggleRow; + taskToggleRows.push(taskToggleRow); + } + + const isCpu = recommendation.resource === 'CPU'; + const rowResourceProperty = isCpu ? 'cpu' : 'memory'; + + taskToggleRow[rowResourceProperty] = { + recommendation, + isActive: + !this.args.summary.excludedRecommendations.includes(recommendation), }; - taskNameToTaskToggles[recommendation.task.name] = taskToggleRow; - taskToggleRows.push(taskToggleRow); - } + if (isCpu) { + taskToggleRow.recommendations.unshift(recommendation); + } else { + taskToggleRow.recommendations.push(recommendation); + } - const isCpu = recommendation.resource === 'CPU'; - const rowResourceProperty = isCpu ? 'cpu' : 'memory'; - - taskToggleRow[rowResourceProperty] = { - recommendation, - isActive: !this.args.summary.excludedRecommendations.includes(recommendation), - }; - - if (isCpu) { - taskToggleRow.recommendations.unshift(recommendation); - } else { - taskToggleRow.recommendations.push(recommendation); - } - - return taskToggleRows; - }, []); + return taskToggleRows; + }, + [] + ); } get showToggleAllToggles() { @@ -129,23 +133,30 @@ export default class DasRecommendationCardComponent extends Component { } get allCpuToggleDisabled() { - return !this.args.summary.recommendations.filterBy('resource', 'CPU').length; + return !this.args.summary.recommendations.filterBy('resource', 'CPU') + .length; } get allMemoryToggleDisabled() { - return !this.args.summary.recommendations.filterBy('resource', 'MemoryMB').length; + return !this.args.summary.recommendations.filterBy('resource', 'MemoryMB') + .length; } get cannotAccept() { return ( - this.args.summary.excludedRecommendations.length == this.args.summary.recommendations.length + this.args.summary.excludedRecommendations.length == + this.args.summary.recommendations.length ); } get copyButtonLink() { - const path = this.router.urlFor('optimize.summary', this.args.summary.slug, { - queryParams: { namespace: this.args.summary.jobNamespace }, - }); + const path = this.router.urlFor( + 'optimize.summary', + this.args.summary.slug, + { + queryParams: { namespace: this.args.summary.jobNamespace }, + } + ); const { origin } = window.location; return `${origin}${path}`; @@ -173,9 +184,9 @@ export default class DasRecommendationCardComponent extends Component { .save() .then( () => this.onApplied.perform(), - e => this.onError.perform(e) + (e) => this.onError.perform(e) ) - .catch(e => { + .catch((e) => { if (!didCancel(e)) { throw e; } @@ -185,21 +196,23 @@ export default class DasRecommendationCardComponent extends Component { @action dismiss() { this.storeCardHeight(); - this.args.summary.excludedRecommendations.pushObjects(this.args.summary.recommendations); + this.args.summary.excludedRecommendations.pushObjects( + this.args.summary.recommendations + ); this.args.summary .save() .then( () => this.onDismissed.perform(), - e => this.onError.perform(e) + (e) => this.onError.perform(e) ) - .catch(e => { + .catch((e) => { if (!didCancel(e)) { throw e; } }); } - @(task(function*() { + @(task(function* () { this.interstitialComponent = 'accepted'; yield timeout(Ember.testing ? 0 : 2000); @@ -208,8 +221,8 @@ export default class DasRecommendationCardComponent extends Component { }).drop()) onApplied; - @(task(function*() { - const { manuallyDismissed } = yield new Promise(resolve => { + @(task(function* () { + const { manuallyDismissed } = yield new Promise((resolve) => { this.proceedPromiseResolve = resolve; this.interstitialComponent = 'dismissed'; }); @@ -223,8 +236,8 @@ export default class DasRecommendationCardComponent extends Component { }).drop()) onDismissed; - @(task(function*(error) { - yield new Promise(resolve => { + @(task(function* (error) { + yield new Promise((resolve) => { this.proceedPromiseResolve = resolve; this.interstitialComponent = 'error'; this.error = error.toString(); diff --git a/ui/app/components/das/recommendation-chart.js b/ui/app/components/das/recommendation-chart.js index d9e0fc515..9fb82c8a1 100644 --- a/ui/app/components/das/recommendation-chart.js +++ b/ui/app/components/das/recommendation-chart.js @@ -64,10 +64,12 @@ export default class RecommendationChartComponent extends Component { edgeTickHeight = 23; centerTickOffset = 6; - centerY = this.tickTextHeight + this.centerTickOffset + this.edgeTickHeight / 2; + centerY = + this.tickTextHeight + this.centerTickOffset + this.edgeTickHeight / 2; edgeTickY1 = this.tickTextHeight + this.centerTickOffset; - edgeTickY2 = this.tickTextHeight + this.edgeTickHeight + this.centerTickOffset; + edgeTickY2 = + this.tickTextHeight + this.edgeTickHeight + this.centerTickOffset; deltaTextY = this.edgeTickY2; @@ -129,7 +131,10 @@ export default class RecommendationChartComponent extends Component { }, rect: { x: this.gutterWidthLeft, - y: (this.edgeTickHeight - rectHeight) / 2 + this.centerTickOffset + this.tickTextHeight, + y: + (this.edgeTickHeight - rectHeight) / 2 + + this.centerTickOffset + + this.tickTextHeight, width: rectWidth, height: rectHeight, }, @@ -145,7 +150,10 @@ export default class RecommendationChartComponent extends Component { } get maximumX() { - return Math.max(this.higherValue, get(this.args.stats, 'max') || Number.MIN_SAFE_INTEGER); + return Math.max( + this.higherValue, + get(this.args.stats, 'max') || Number.MIN_SAFE_INTEGER + ); } get lowerValue() { @@ -212,9 +220,13 @@ export default class RecommendationChartComponent extends Component { let translateX; if (this.shown) { - translateX = this.isIncrease ? this.higherValueWidth : this.lowerValueWidth; + translateX = this.isIncrease + ? this.higherValueWidth + : this.lowerValueWidth; } else { - translateX = this.isIncrease ? this.lowerValueWidth : this.higherValueWidth; + translateX = this.isIncrease + ? this.lowerValueWidth + : this.higherValueWidth; } return { @@ -222,7 +234,9 @@ export default class RecommendationChartComponent extends Component { points: ` 0,${this.center.y1} 0,${this.center.y1 - this.deltaTriangleHeight / 2} - ${(directionXMultiplier * this.deltaTriangleHeight) / 2},${this.center.y1} + ${(directionXMultiplier * this.deltaTriangleHeight) / 2},${ + this.center.y1 + } 0,${this.center.y1 + this.deltaTriangleHeight / 2} `, }; @@ -236,7 +250,9 @@ export default class RecommendationChartComponent extends Component { }, delta: { style: htmlSafe( - `transform: translateX(${this.shown ? this.higherValueWidth : this.lowerValueWidth}px)` + `transform: translateX(${ + this.shown ? this.higherValueWidth : this.lowerValueWidth + }px)` ), }, }; @@ -247,7 +263,9 @@ export default class RecommendationChartComponent extends Component { }, delta: { style: htmlSafe( - `transform: translateX(${this.shown ? this.lowerValueWidth : this.higherValueWidth}px)` + `transform: translateX(${ + this.shown ? this.lowerValueWidth : this.higherValueWidth + }px)` ), }, }; @@ -271,7 +289,8 @@ export default class RecommendationChartComponent extends Component { }; const percentText = formatPercent( - (this.args.recommendedValue - this.args.currentValue) / this.args.currentValue + (this.args.recommendedValue - this.args.currentValue) / + this.args.currentValue ); const percent = { @@ -316,7 +335,10 @@ export default class RecommendationChartComponent extends Component { }; return Object.keys(statsWithCurrentAndRecommended) - .map(key => ({ label: statsKeyToLabel[key], value: statsWithCurrentAndRecommended[key] })) + .map((key) => ({ + label: statsKeyToLabel[key], + value: statsWithCurrentAndRecommended[key], + })) .sortBy('value'); } else { return []; diff --git a/ui/app/components/distribution-bar.js b/ui/app/components/distribution-bar.js index fa681b8b6..a33c48f3e 100644 --- a/ui/app/components/distribution-bar.js +++ b/ui/app/components/distribution-bar.js @@ -34,24 +34,24 @@ export default class DistributionBar extends Component.extend(WindowResizable) { const data = copy(this.data, true); const sum = data.mapBy('value').reduce(sumAggregate, 0); - return data.map(({ label, value, className, layers, legendLink, help }, index) => ({ - label, - value, - className, - layers, - legendLink, - help, - index, - percent: value / sum, - offset: - data - .slice(0, index) - .mapBy('value') - .reduce(sumAggregate, 0) / sum, - })); + return data.map( + ({ label, value, className, layers, legendLink, help }, index) => ({ + label, + value, + className, + layers, + legendLink, + help, + index, + percent: value / sum, + offset: + data.slice(0, index).mapBy('value').reduce(sumAggregate, 0) / sum, + }) + ); } didInsertElement() { + super.didInsertElement(...arguments); const svg = this.element.querySelector('svg'); const chart = d3.select(svg); const maskId = `dist-mask-${guidFor(this)}`; @@ -74,6 +74,7 @@ export default class DistributionBar extends Component.extend(WindowResizable) { } didUpdateAttrs() { + super.didUpdateAttrs(); this.renderChart(); } diff --git a/ui/app/components/drain-popover.js b/ui/app/components/drain-popover.js index 764147396..94f666538 100644 --- a/ui/app/components/drain-popover.js +++ b/ui/app/components/drain-popover.js @@ -26,6 +26,7 @@ export default class DrainPopover extends Component { @localStorageProperty('nomadDrainOptions', {}) drainOptions; didReceiveAttrs() { + super.didReceiveAttrs(); // Load drain config values from local storage if availabe. [ 'deadlineEnabled', @@ -33,14 +34,14 @@ export default class DrainPopover extends Component { 'forceDrain', 'drainSystemJobs', 'selectedDurationQuickOption', - ].forEach(k => { + ].forEach((k) => { if (k in this.drainOptions) { this[k] = this.drainOptions[k]; } }); } - @overridable(function() { + @overridable(function () { return this.durationQuickOptions[0]; }) selectedDurationQuickOption; @@ -72,7 +73,7 @@ export default class DrainPopover extends Component { return this.selectedDurationQuickOption.value; } - @task(function*(close) { + @task(function* (close) { if (!this.client) return; const isUpdating = this.client.isDraining; diff --git a/ui/app/components/exec-terminal.js b/ui/app/components/exec-terminal.js index e396677a7..18cf3d1b3 100644 --- a/ui/app/components/exec-terminal.js +++ b/ui/app/components/exec-terminal.js @@ -8,6 +8,7 @@ import classic from 'ember-classic-decorator'; @classNames('terminal-container') export default class ExecTerminal extends Component.extend(WindowResizable) { didInsertElement() { + super.didInsertElement(...arguments); let fitAddon = new FitAddon(); this.fitAddon = fitAddon; this.terminal.loadAddon(fitAddon); diff --git a/ui/app/components/exec/task-group-parent.js b/ui/app/components/exec/task-group-parent.js index 3d0b584b0..762616be5 100644 --- a/ui/app/components/exec/task-group-parent.js +++ b/ui/app/components/exec/task-group-parent.js @@ -31,7 +31,9 @@ export default class TaskGroupParent extends Component { @computed('taskGroup.allocations.@each.clientStatus') get hasPendingAllocations() { - return this.taskGroup.allocations.any(allocation => allocation.clientStatus === 'pending'); + return this.taskGroup.allocations.any( + (allocation) => allocation.clientStatus === 'pending' + ); } @mapBy('taskGroup.allocations', 'states') allocationTaskStatesRecordArrays; @@ -39,7 +41,10 @@ export default class TaskGroupParent extends Component { get allocationTaskStates() { const flattenRecordArrays = (accumulator, recordArray) => accumulator.concat(recordArray.toArray()); - return this.allocationTaskStatesRecordArrays.reduce(flattenRecordArrays, []); + return this.allocationTaskStatesRecordArrays.reduce( + flattenRecordArrays, + [] + ); } @filterBy('allocationTaskStates', 'isActive') activeTaskStates; @@ -55,12 +60,17 @@ export default class TaskGroupParent extends Component { ) get tasksWithRunningStates() { const activeTaskStateNames = this.activeTaskStates - .filter(taskState => { - return taskState.task && taskState.task.taskGroup.name === this.taskGroup.name; + .filter((taskState) => { + return ( + taskState.task && + taskState.task.taskGroup.name === this.taskGroup.name + ); }) .mapBy('name'); - return this.taskGroup.tasks.filter(task => activeTaskStateNames.includes(task.name)); + return this.taskGroup.tasks.filter((task) => + activeTaskStateNames.includes(task.name) + ); } taskSorting = ['name']; diff --git a/ui/app/components/flex-masonry.js b/ui/app/components/flex-masonry.js index ff3776afc..29b32501a 100644 --- a/ui/app/components/flex-masonry.js +++ b/ui/app/components/flex-masonry.js @@ -18,7 +18,9 @@ export default class FlexMasonry extends Component { // There's nothing to do if there is no element if (!this.element) return; - const items = this.element.querySelectorAll(':scope > .flex-masonry-item'); + const items = this.element.querySelectorAll( + ':scope > .flex-masonry-item' + ); // Clear out specified order and flex-basis values in case this was once a multi-column layout if (this.args.columns === 1 || !this.args.columns) { @@ -43,7 +45,7 @@ export default class FlexMasonry extends Component { const height = item.clientHeight; // Pick the shortest column accounting for margins - const column = columns[minIndex(columns, c => c.height)]; + const column = columns[minIndex(columns, (c) => c.height)]; // Add the new element's height to the column height column.height += marginTop + height + marginBottom; @@ -62,10 +64,12 @@ export default class FlexMasonry extends Component { // beteen the height of the column and the previous column, then flexbox will naturally place the first // item at the end of the previous column). columns.forEach((column, index) => { - const nextHeight = index < columns.length - 1 ? columns[index + 1].height : 0; + const nextHeight = + index < columns.length - 1 ? columns[index + 1].height : 0; const item = column.elements.lastObject; if (item) { - item.style.flexBasis = item.clientHeight + Math.max(0, nextHeight - column.height) + 'px'; + item.style.flexBasis = + item.clientHeight + Math.max(0, nextHeight - column.height) + 'px'; } }); diff --git a/ui/app/components/fs/browser.js b/ui/app/components/fs/browser.js index 751f60f65..a57365667 100644 --- a/ui/app/components/fs/browser.js +++ b/ui/app/components/fs/browser.js @@ -39,11 +39,18 @@ export default class Browser extends Component { @filterBy('directoryEntries', 'IsDir') directories; @filterBy('directoryEntries', 'IsDir', false) files; - @computed('directories', 'directoryEntries.[]', 'files', 'sortDescending', 'sortProperty') + @computed( + 'directories', + 'directoryEntries.[]', + 'files', + 'sortDescending', + 'sortProperty' + ) get sortedDirectoryEntries() { const sortProperty = this.sortProperty; - const directorySortProperty = sortProperty === 'Size' ? 'Name' : sortProperty; + const directorySortProperty = + sortProperty === 'Size' ? 'Name' : sortProperty; const sortedDirectories = this.directories.sortBy(directorySortProperty); const sortedFiles = this.files.sortBy(sortProperty); diff --git a/ui/app/components/fs/file.js b/ui/app/components/fs/file.js index 15be2642f..1af183478 100644 --- a/ui/app/components/fs/file.js +++ b/ui/app/components/fs/file.js @@ -39,7 +39,10 @@ export default class File extends Component { if (contentType.startsWith('image/')) { return 'image'; - } else if (contentType.startsWith('text/') || contentType.startsWith('application/json')) { + } else if ( + contentType.startsWith('text/') || + contentType.startsWith('application/json') + ) { return 'stream'; } else { return 'unknown'; @@ -108,7 +111,14 @@ export default class File extends Component { } } - @computed('clientTimeout', 'fileParams', 'fileUrl', 'mode', 'serverTimeout', 'useServer') + @computed( + 'clientTimeout', + 'fileParams', + 'fileUrl', + 'mode', + 'serverTimeout', + 'useServer' + ) get logger() { // The cat and readat APIs are in plainText while the stream API is always encoded. const plainText = this.mode === 'head' || this.mode === 'tail'; @@ -116,15 +126,15 @@ export default class File extends Component { // If the file request can't settle in one second, the client // must be unavailable and the server should be used instead const timing = this.useServer ? this.serverTimeout : this.clientTimeout; - const logFetch = url => + const logFetch = (url) => RSVP.race([this.token.authorizedRequest(url), timeout(timing)]).then( - response => { + (response) => { if (!response || !response.ok) { this.nextErrorState(response); } return response; }, - error => this.nextErrorState(error) + (error) => this.nextErrorState(error) ); return Log.create({ diff --git a/ui/app/components/gauge-chart.js b/ui/app/components/gauge-chart.js index 9625dbff2..bbd98bfde 100644 --- a/ui/app/components/gauge-chart.js +++ b/ui/app/components/gauge-chart.js @@ -78,6 +78,7 @@ export default class GaugeChart extends Component.extend(WindowResizable) { } didInsertElement() { + super.didInsertElement(...arguments); this.updateDimensions(); } diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 8f3967cca..e68e5c7be 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -32,23 +32,28 @@ export default class GlobalSearchControl extends Component { } didInsertElement() { + super.didInsertElement(...arguments); set(this, '_keyDownHandler', this.keyDownHandler.bind(this)); document.addEventListener('keydown', this._keyDownHandler); } willDestroyElement() { + super.willDestroyElement(...arguments); document.removeEventListener('keydown', this._keyDownHandler); } - @task(function*(string) { - const searchResponse = yield this.token.authorizedRequest('/v1/search/fuzzy', { - method: 'POST', - body: JSON.stringify({ - Text: string, - Context: 'all', - Namespace: '*', - }), - }); + @task(function* (string) { + const searchResponse = yield this.token.authorizedRequest( + '/v1/search/fuzzy', + { + method: 'POST', + body: JSON.stringify({ + Text: string, + Context: 'all', + Namespace: '*', + }), + } + ); const results = yield searchResponse.json(); @@ -93,11 +98,13 @@ export default class GlobalSearchControl extends Component { label: `${namespace} > ${jobId} > ${id}`, })); - const csiPluginResults = allCSIPluginResults.slice(0, MAXIMUM_RESULTS).map(({ ID: id }) => ({ - type: 'plugin', - id, - label: id, - })); + const csiPluginResults = allCSIPluginResults + .slice(0, MAXIMUM_RESULTS) + .map(({ ID: id }) => ({ + type: 'plugin', + id, + label: id, + })); const { jobs: jobsTruncated, @@ -109,11 +116,21 @@ export default class GlobalSearchControl extends Component { return [ { - groupName: resultsGroupLabel('Jobs', jobResults, allJobResults, jobsTruncated), + groupName: resultsGroupLabel( + 'Jobs', + jobResults, + allJobResults, + jobsTruncated + ), options: jobResults, }, { - groupName: resultsGroupLabel('Clients', nodeResults, allNodeResults, nodesTruncated), + groupName: resultsGroupLabel( + 'Clients', + nodeResults, + allNodeResults, + nodesTruncated + ), options: nodeResults, }, { @@ -189,10 +206,14 @@ export default class GlobalSearchControl extends Component { openOnClickOrTab(select, { target }) { // Bypass having to press enter to access search after clicking/tabbing const targetClassList = target.classList; - const targetIsTrigger = targetClassList.contains('ember-power-select-trigger'); + const targetIsTrigger = targetClassList.contains( + 'ember-power-select-trigger' + ); // Allow tabbing out of search - const triggerIsNotActive = !targetClassList.contains('ember-power-select-trigger--active'); + const triggerIsNotActive = !targetClassList.contains( + 'ember-power-select-trigger--active' + ); if (targetIsTrigger && triggerIsNotActive) { debounce(this, this.open, 150); diff --git a/ui/app/components/gutter-menu.js b/ui/app/components/gutter-menu.js index 469ac039b..e73f58da3 100644 --- a/ui/app/components/gutter-menu.js +++ b/ui/app/components/gutter-menu.js @@ -44,7 +44,9 @@ export default class GutterMenu extends Component { // an intent to reset context, but where to reset to depends on where the namespace // is being switched from. Jobs take precedence, but if the namespace is switched from // a storage-related page, context should be reset to volumes. - const destination = this.router.currentRouteName.startsWith('csi.') ? 'csi.volumes' : 'jobs'; + const destination = this.router.currentRouteName.startsWith('csi.') + ? 'csi.volumes' + : 'jobs'; this.router.transitionTo(destination, { queryParams: { namespace: namespace.get('id') }, diff --git a/ui/app/components/hamburger-menu.js b/ui/app/components/hamburger-menu.js index 133c7241b..9f1bbeabd 100644 --- a/ui/app/components/hamburger-menu.js +++ b/ui/app/components/hamburger-menu.js @@ -2,5 +2,4 @@ import Component from '@ember/component'; import { tagName } from '@ember-decorators/component'; @tagName('') -export default class HamburgerMenu extends Component { -} +export default class HamburgerMenu extends Component {} diff --git a/ui/app/components/job-diff.js b/ui/app/components/job-diff.js index 2f7a6921c..505350877 100644 --- a/ui/app/components/job-diff.js +++ b/ui/app/components/job-diff.js @@ -5,7 +5,11 @@ import classic from 'ember-classic-decorator'; @classic @classNames('job-diff') -@classNameBindings('isEdited:is-edited', 'isAdded:is-added', 'isDeleted:is-deleted') +@classNameBindings( + 'isEdited:is-edited', + 'isAdded:is-added', + 'isDeleted:is-deleted' +) export default class JobDiff extends Component { diff = null; diff --git a/ui/app/components/job-dispatch.js b/ui/app/components/job-dispatch.js index e7ea1392c..d0a5af2d2 100644 --- a/ui/app/components/job-dispatch.js +++ b/ui/app/components/job-dispatch.js @@ -49,7 +49,7 @@ export default class JobDispatch extends Component { // Helper for mapping the params into a useable form. const mapper = (values, required) => values.map( - x => + (x) => new MetaField({ name: x, required, @@ -59,8 +59,14 @@ export default class JobDispatch extends Component { ); // Fetch the different types of parameters. - const required = mapper(this.args.job.parameterizedDetails.MetaRequired || [], true); - const optional = mapper(this.args.job.parameterizedDetails.MetaOptional || [], false); + const required = mapper( + this.args.job.parameterizedDetails.MetaRequired || [], + true + ); + const optional = mapper( + this.args.job.parameterizedDetails.MetaOptional || [], + false + ); // Merge them, required before optional. this.metaFields = required.concat(optional); @@ -94,7 +100,7 @@ export default class JobDispatch extends Component { // Try to create the dispatch. try { let paramValues = {}; - this.metaFields.forEach(m => (paramValues[m.name] = m.value)); + this.metaFields.forEach((m) => (paramValues[m.name] = m.value)); const dispatch = yield this.args.job.dispatch(paramValues, this.payload); // Navigate to the newly created instance. @@ -123,7 +129,7 @@ export default class JobDispatch extends Component { this.resetErrors(); // Make sure that we have all of the meta fields that we need. - this.metaFields.forEach(f => { + this.metaFields.forEach((f) => { f.validate(); if (f.error) { this.errors.pushObject(f.error); diff --git a/ui/app/components/job-editor.js b/ui/app/components/job-editor.js index bfa8bba3e..0e9426851 100644 --- a/ui/app/components/job-editor.js +++ b/ui/app/components/job-editor.js @@ -25,7 +25,10 @@ export default class JobEditor extends Component { set context(value) { const allowedValues = ['new', 'edit']; - assert(`context must be one of: ${allowedValues.join(', ')}`, allowedValues.includes(value)); + assert( + `context must be one of: ${allowedValues.join(', ')}`, + allowedValues.includes(value) + ); this.set('_context', value); } @@ -45,7 +48,7 @@ export default class JobEditor extends Component { return this.planOutput ? 'plan' : 'editor'; } - @(task(function*() { + @(task(function* () { this.reset(); try { @@ -68,7 +71,7 @@ export default class JobEditor extends Component { }).drop()) plan; - @task(function*() { + @task(function* () { try { if (this.context === 'new') { yield this.job.run(); diff --git a/ui/app/components/job-page/parts/job-client-status-summary.js b/ui/app/components/job-page/parts/job-client-status-summary.js index 515f08568..7e923dacf 100644 --- a/ui/app/components/job-page/parts/job-client-status-summary.js +++ b/ui/app/components/job-page/parts/job-client-status-summary.js @@ -24,6 +24,7 @@ export default class JobClientStatusSummary extends Component { @action onSliceClick(ev, slice) { + /* eslint-disable-next-line ember/no-string-prototype-extensions */ this.gotoClients([slice.className.camelize()]); } diff --git a/ui/app/components/job-page/parts/latest-deployment.js b/ui/app/components/job-page/parts/latest-deployment.js index 75d37ae11..8b993ab81 100644 --- a/ui/app/components/job-page/parts/latest-deployment.js +++ b/ui/app/components/job-page/parts/latest-deployment.js @@ -13,7 +13,7 @@ export default class LatestDeployment extends Component { isShowingDeploymentDetails = false; - @task(function*() { + @task(function* () { try { yield this.get('job.latestDeployment.content').promote(); } catch (err) { @@ -25,7 +25,7 @@ export default class LatestDeployment extends Component { }) promote; - @task(function*() { + @task(function* () { try { yield this.get('job.latestDeployment.content').fail(); } catch (err) { diff --git a/ui/app/components/job-page/parts/recent-allocations.js b/ui/app/components/job-page/parts/recent-allocations.js index 507a0fc0f..3afd9f37d 100644 --- a/ui/app/components/job-page/parts/recent-allocations.js +++ b/ui/app/components/job-page/parts/recent-allocations.js @@ -16,11 +16,8 @@ export default class RecentAllocations extends Component { @computed('job.allocations.@each.modifyIndex') get sortedAllocations() { return PromiseArray.create({ - promise: this.get('job.allocations').then(allocations => - allocations - .sortBy('modifyIndex') - .reverse() - .slice(0, 5) + promise: this.get('job.allocations').then((allocations) => + allocations.sortBy('modifyIndex').reverse().slice(0, 5) ), }); } diff --git a/ui/app/components/job-page/parts/summary.js b/ui/app/components/job-page/parts/summary.js index 51aace4a4..ca7e92931 100644 --- a/ui/app/components/job-page/parts/summary.js +++ b/ui/app/components/job-page/parts/summary.js @@ -24,6 +24,7 @@ export default class Summary extends Component { @action onSliceClick(ev, slice) { + /* eslint-disable-next-line ember/no-string-prototype-extensions */ this.gotoAllocations([slice.label.camelize()]); } diff --git a/ui/app/components/job-page/parts/title.js b/ui/app/components/job-page/parts/title.js index cd713938d..24d8fc073 100644 --- a/ui/app/components/job-page/parts/title.js +++ b/ui/app/components/job-page/parts/title.js @@ -12,7 +12,7 @@ export default class Title extends Component { handleError() {} - @task(function*() { + @task(function* () { try { const job = this.job; yield job.stop(); @@ -27,7 +27,7 @@ export default class Title extends Component { }) stopJob; - @task(function*() { + @task(function* () { const job = this.job; const definition = yield job.fetchRawDefinition(); diff --git a/ui/app/components/job-page/periodic.js b/ui/app/components/job-page/periodic.js index 622b18e40..8a3783bb7 100644 --- a/ui/app/components/job-page/periodic.js +++ b/ui/app/components/job-page/periodic.js @@ -12,7 +12,7 @@ export default class Periodic extends AbstractJobPage { @action forceLaunch() { - this.job.forcePeriodic().catch(err => { + this.job.forcePeriodic().catch((err) => { this.set('errorMessage', { title: 'Could Not Force Launch', description: messageForError(err, 'submit jobs'), diff --git a/ui/app/components/job-subnav.js b/ui/app/components/job-subnav.js index 5e180f67b..6fb945a00 100644 --- a/ui/app/components/job-subnav.js +++ b/ui/app/components/job-subnav.js @@ -2,5 +2,4 @@ import Component from '@ember/component'; import { tagName } from '@ember-decorators/component'; @tagName('') -export default class JobSubnav extends Component { -} +export default class JobSubnav extends Component {} diff --git a/ui/app/components/job-version.js b/ui/app/components/job-version.js index d3ba75a75..3e5ad74af 100644 --- a/ui/app/components/job-version.js +++ b/ui/app/components/job-version.js @@ -31,7 +31,9 @@ export default class JobVersion extends Component { return ( fieldChanges(diff) + taskGroups.reduce(arrayOfFieldChanges, 0) + - (taskGroups.mapBy('Tasks') || []).reduce(flatten, []).reduce(arrayOfFieldChanges, 0) + (taskGroups.mapBy('Tasks') || []) + .reduce(flatten, []) + .reduce(arrayOfFieldChanges, 0) ); } @@ -45,7 +47,7 @@ export default class JobVersion extends Component { this.toggleProperty('isOpen'); } - @task(function*() { + @task(function* () { try { const versionBeforeReversion = this.get('version.job.version'); @@ -58,7 +60,8 @@ export default class JobVersion extends Component { this.handleError({ level: 'warn', title: 'Reversion Had No Effect', - description: 'Reverting to an identical older version doesn’t produce a new version', + description: + 'Reverting to an identical older version doesn’t produce a new version', }); } else { const job = this.get('version.job'); @@ -79,7 +82,8 @@ export default class JobVersion extends Component { } const flatten = (accumulator, array) => accumulator.concat(array); -const countChanges = (total, field) => (changeTypes.includes(field.Type) ? total + 1 : total); +const countChanges = (total, field) => + changeTypes.includes(field.Type) ? total + 1 : total; function fieldChanges(diff) { return ( diff --git a/ui/app/components/job-versions-stream.js b/ui/app/components/job-versions-stream.js index 5497565e5..88f7e9668 100644 --- a/ui/app/components/job-versions-stream.js +++ b/ui/app/components/job-versions-stream.js @@ -24,7 +24,9 @@ export default class JobVersionsStream extends Component { meta.showDate = true; } else { const previousVersion = versions.objectAt(index - 1); - const previousStart = moment(previousVersion.get('submitTime')).startOf('day'); + const previousStart = moment(previousVersion.get('submitTime')).startOf( + 'day' + ); const currentStart = moment(version.get('submitTime')).startOf('day'); if (previousStart.diff(currentStart, 'days') > 0) { meta.showDate = true; diff --git a/ui/app/components/lifecycle-chart.js b/ui/app/components/lifecycle-chart.js index 07a938109..6e2f6116a 100644 --- a/ui/app/components/lifecycle-chart.js +++ b/ui/app/components/lifecycle-chart.js @@ -22,7 +22,7 @@ export default class LifecycleChart extends Component { mains: [], }; - tasksOrStates.forEach(taskOrState => { + tasksOrStates.forEach((taskOrState) => { const task = taskOrState.task || taskOrState; if (task.lifecycleName) { @@ -31,7 +31,7 @@ export default class LifecycleChart extends Component { }); const phases = []; - const stateActiveIterator = state => state.state === 'running'; + const stateActiveIterator = (state) => state.state === 'running'; if (lifecycles.mains.length < tasksOrStates.length) { phases.push({ @@ -60,12 +60,12 @@ export default class LifecycleChart extends Component { return phases; } - @sort('taskStates', function(a, b) { + @sort('taskStates', function (a, b) { return getTaskSortPrefix(a.task).localeCompare(getTaskSortPrefix(b.task)); }) sortedLifecycleTaskStates; - @sort('tasks', function(a, b) { + @sort('tasks', function (a, b) { return getTaskSortPrefix(a).localeCompare(getTaskSortPrefix(b)); }) sortedLifecycleTasks; diff --git a/ui/app/components/line-chart.js b/ui/app/components/line-chart.js index e9d7e08b5..8f7bb19f1 100644 --- a/ui/app/components/line-chart.js +++ b/ui/app/components/line-chart.js @@ -23,11 +23,11 @@ const lerp = ([low, high], numPoints) => { }; // Round a number or an array of numbers -const nice = val => (val instanceof Array ? val.map(nice) : Math.round(val)); +const nice = (val) => (val instanceof Array ? val.map(nice) : Math.round(val)); const defaultXScale = (data, yAxisOffset, xProp, timeseries) => { const scale = timeseries ? d3Scale.scaleTime() : d3Scale.scaleLinear(); - const domain = data.length ? d3Array.extent(data, d => d[xProp]) : [0, 1]; + const domain = data.length ? d3Array.extent(data, (d) => d[xProp]) : [0, 1]; scale.rangeRound([10, yAxisOffset]).domain(domain); @@ -35,15 +35,12 @@ const defaultXScale = (data, yAxisOffset, xProp, timeseries) => { }; const defaultYScale = (data, xAxisOffset, yProp) => { - let max = d3Array.max(data, d => d[yProp]) || 1; + let max = d3Array.max(data, (d) => d[yProp]) || 1; if (max > 1) { max = nice(max); } - return d3Scale - .scaleLinear() - .rangeRound([xAxisOffset, 10]) - .domain([0, max]); + return d3Scale.scaleLinear().rangeRound([xAxisOffset, 10]).domain([0, max]); }; export default class LineChart extends Component { @@ -95,7 +92,9 @@ export default class LineChart extends Component { @action xFormat(timeseries) { if (this.args.xFormat) return this.args.xFormat; - return timeseries ? d3TimeFormat.timeFormat('%b %d, %H:%M') : d3Format.format(','); + return timeseries + ? d3TimeFormat.timeFormat('%b %d, %H:%M') + : d3Format.format(','); } @action @@ -134,7 +133,7 @@ export default class LineChart extends Component { get xRange() { const { xProp, data } = this; - const range = d3Array.extent(data, d => d[xProp]); + const range = d3Array.extent(data, (d) => d[xProp]); const formatter = this.xFormat(this.args.timeseries); return range.map(formatter); @@ -142,7 +141,7 @@ export default class LineChart extends Component { get yRange() { const yProp = this.yProp; - const range = d3Array.extent(this.data, d => d[yProp]); + const range = d3Array.extent(this.data, (d) => d[yProp]); const formatter = this.yFormat(); return range.map(formatter); @@ -232,14 +231,14 @@ export default class LineChart extends Component { const updateActiveDatum = this.updateActiveDatum.bind(this); const chart = this; - canvas.on('mouseenter', function(ev) { + canvas.on('mouseenter', function (ev) { const mouseX = d3.pointer(ev, this)[0]; chart.latestMouseX = mouseX; updateActiveDatum(mouseX); run.schedule('afterRender', chart, () => (chart.isActive = true)); }); - canvas.on('mousemove', function(ev) { + canvas.on('mousemove', function (ev) { const mouseX = d3.pointer(ev, this)[0]; chart.latestMouseX = mouseX; updateActiveDatum(mouseX); @@ -264,7 +263,7 @@ export default class LineChart extends Component { } // Map screen coordinates to data domain - const bisector = d3Array.bisector(d => d[xProp]).left; + const bisector = d3Array.bisector((d) => d[xProp]).left; const x = xScale.invert(mouseX); // Find the closest datum to the cursor for each series @@ -308,13 +307,17 @@ export default class LineChart extends Component { // Of the selected data, determine which is closest const closestDatum = activeData .slice() - .sort((a, b) => Math.abs(a.datum.datum[xProp] - x) - Math.abs(b.datum.datum[xProp] - x))[0]; + .sort( + (a, b) => + Math.abs(a.datum.datum[xProp] - x) - + Math.abs(b.datum.datum[xProp] - x) + )[0]; // If any other selected data are beyond a distance threshold, drop them from the list // xScale is used here to measure distance in screen-space rather than data-space. const dist = Math.abs(xScale(closestDatum.datum.datum[xProp]) - mouseX); const filteredData = activeData.filter( - d => Math.abs(xScale(d.datum.datum[xProp]) - mouseX) < dist + 10 + (d) => Math.abs(xScale(d.datum.datum[xProp]) - mouseX) < dist + 10 ); this.activeData = filteredData; @@ -351,7 +354,9 @@ export default class LineChart extends Component { if (!this.isDestroyed && !this.isDestroying) { d3.select(this.element.querySelector('.x-axis')).call(this.xAxis); d3.select(this.element.querySelector('.y-axis')).call(this.yAxis); - d3.select(this.element.querySelector('.y-gridlines')).call(this.yGridlines); + d3.select(this.element.querySelector('.y-gridlines')).call( + this.yGridlines + ); } } diff --git a/ui/app/components/list-accordion.js b/ui/app/components/list-accordion.js index 9674d7a15..030c6f02a 100644 --- a/ui/app/components/list-accordion.js +++ b/ui/app/components/list-accordion.js @@ -20,7 +20,7 @@ export default class ListAccordion extends Component { const deepKey = `item.${key}`; const startExpanded = this.startExpanded; - const decoratedSource = this.source.map(item => { + const decoratedSource = this.source.map((item) => { const cacheItem = stateCache.findBy(deepKey, get(item, key)); return { item, diff --git a/ui/app/components/list-table.js b/ui/app/components/list-table.js index aa9c8fad6..a5851040f 100644 --- a/ui/app/components/list-table.js +++ b/ui/app/components/list-table.js @@ -13,7 +13,7 @@ export default class ListTable extends Component { // Plan for a future with metadata (e.g., isSelected) @computed('source.[]') get decoratedSource() { - return (this.source || []).map(row => ({ + return (this.source || []).map((row) => ({ model: row, })); } diff --git a/ui/app/components/multi-select-dropdown.js b/ui/app/components/multi-select-dropdown.js index a2783f736..692ea7c9f 100644 --- a/ui/app/components/multi-select-dropdown.js +++ b/ui/app/components/multi-select-dropdown.js @@ -30,6 +30,7 @@ export default class MultiSelectDropdown extends Component { } didReceiveAttrs() { + super.didReceiveAttrs(); const dropdown = this.dropdown; if (this.isOpen && dropdown) { run.scheduleOnce('afterRender', this, this.repositionDropdown); @@ -59,8 +60,12 @@ export default class MultiSelectDropdown extends Component { dropdown.actions.open(e); e.preventDefault(); } else if (this.isOpen && (e.keyCode === TAB || e.keyCode === ARROW_DOWN)) { - const optionsId = this.element.querySelector('.dropdown-trigger').getAttribute('aria-owns'); - const firstElement = document.querySelector(`#${optionsId} .dropdown-option`); + const optionsId = this.element + .querySelector('.dropdown-trigger') + .getAttribute('aria-owns'); + const firstElement = document.querySelector( + `#${optionsId} .dropdown-option` + ); if (firstElement) { firstElement.focus(); diff --git a/ui/app/components/popover-menu.js b/ui/app/components/popover-menu.js index 939e940b7..a6498a9aa 100644 --- a/ui/app/components/popover-menu.js +++ b/ui/app/components/popover-menu.js @@ -32,6 +32,7 @@ export default class PopoverMenu extends Component { } didReceiveAttrs() { + super.didReceiveAttrs(); const dropdown = this.dropdown; if (this.isOpen && dropdown) { run.scheduleOnce('afterRender', this, this.repositionDropdown); @@ -48,7 +49,9 @@ export default class PopoverMenu extends Component { dropdown.actions.open(e); e.preventDefault(); } else if (this.isOpen && (e.keyCode === TAB || e.keyCode === ARROW_DOWN)) { - const optionsId = this.element.querySelector('.popover-trigger').getAttribute('aria-owns'); + const optionsId = this.element + .querySelector('.popover-trigger') + .getAttribute('aria-owns'); const popoverContentEl = document.querySelector(`#${optionsId}`); const firstFocusableElement = popoverContentEl.querySelector(FOCUSABLE); diff --git a/ui/app/components/primary-metric/allocation.js b/ui/app/components/primary-metric/allocation.js index d57c58d01..96da05a97 100644 --- a/ui/app/components/primary-metric/allocation.js +++ b/ui/app/components/primary-metric/allocation.js @@ -37,7 +37,7 @@ export default class AllocationPrimaryMetric extends Component { @computed('tracker.tasks.[]', 'metric') get series() { const ret = this.tracker.tasks - .map(task => ({ + .map((task) => ({ name: task.task, data: task[this.metric], })) @@ -64,7 +64,7 @@ export default class AllocationPrimaryMetric extends Component { return 'ordinal'; } - @task(function*() { + @task(function* () { do { this.tracker.poll.perform(); yield timeout(100); @@ -78,6 +78,7 @@ export default class AllocationPrimaryMetric extends Component { } willDestroy() { + super.willDestroy(...arguments); this.poller.cancelAll(); this.tracker.signalPause.perform(); } diff --git a/ui/app/components/primary-metric/node.js b/ui/app/components/primary-metric/node.js index 781d3e38e..a4a7102b9 100644 --- a/ui/app/components/primary-metric/node.js +++ b/ui/app/components/primary-metric/node.js @@ -4,7 +4,10 @@ import { task, timeout } from 'ember-concurrency'; import { assert } from '@ember/debug'; import { inject as service } from '@ember/service'; import { action, get } from '@ember/object'; -import { formatScheduledBytes, formatScheduledHertz } from 'nomad-ui/utils/units'; +import { + formatScheduledBytes, + formatScheduledHertz, +} from 'nomad-ui/utils/units'; export default class NodePrimaryMetric extends Component { @service('stats-trackers-registry') statsTrackersRegistry; @@ -64,7 +67,7 @@ export default class NodePrimaryMetric extends Component { return []; } - @task(function*() { + @task(function* () { do { this.tracker.poll.perform(); yield timeout(100); @@ -78,6 +81,7 @@ export default class NodePrimaryMetric extends Component { } willDestroy() { + super.willDestroy(...arguments); this.poller.cancelAll(); this.tracker.signalPause.perform(); } diff --git a/ui/app/components/primary-metric/task.js b/ui/app/components/primary-metric/task.js index b7c348d04..20d987caf 100644 --- a/ui/app/components/primary-metric/task.js +++ b/ui/app/components/primary-metric/task.js @@ -42,7 +42,7 @@ export default class TaskPrimaryMetric extends Component { return 'is-primary'; } - @task(function*() { + @task(function* () { do { this.tracker.poll.perform(); yield timeout(100); @@ -53,11 +53,14 @@ export default class TaskPrimaryMetric extends Component { @action start() { this.taskState = this.args.taskState; - this.tracker = this.statsTrackersRegistry.getTracker(this.args.taskState.allocation); + this.tracker = this.statsTrackersRegistry.getTracker( + this.args.taskState.allocation + ); this.poller.perform(); } willDestroy() { + super.willDestroy(...arguments); this.poller.cancelAll(); this.tracker.signalPause.perform(); } diff --git a/ui/app/components/region-switcher.js b/ui/app/components/region-switcher.js index 34fc3c98e..e99aec7ab 100644 --- a/ui/app/components/region-switcher.js +++ b/ui/app/components/region-switcher.js @@ -11,9 +11,7 @@ export default class RegionSwitcher extends Component { @computed('system.regions') get sortedRegions() { - return this.get('system.regions') - .toArray() - .sort(); + return this.get('system.regions').toArray().sort(); } gotoRegion(region) { diff --git a/ui/app/components/reschedule-event-row.js b/ui/app/components/reschedule-event-row.js index e6a187478..30eee17c2 100644 --- a/ui/app/components/reschedule-event-row.js +++ b/ui/app/components/reschedule-event-row.js @@ -13,7 +13,7 @@ export default class RescheduleEventRow extends Component { allocationId = null; // An allocation can also be provided directly - @overridable('allocationId', function() { + @overridable('allocationId', function () { if (this.allocationId) { return this.store.findRecord('allocation', this.allocationId); } diff --git a/ui/app/components/scale-events-chart.js b/ui/app/components/scale-events-chart.js index f45509319..8a687f139 100644 --- a/ui/app/components/scale-events-chart.js +++ b/ui/app/components/scale-events-chart.js @@ -32,7 +32,7 @@ export default class ScaleEventsChart extends Component { } get annotations() { - return this.args.events.rejectBy('hasCount').map(ev => ({ + return this.args.events.rejectBy('hasCount').map((ev) => ({ type: ev.error ? 'error' : 'info', time: ev.time, event: copy(ev), @@ -40,7 +40,10 @@ export default class ScaleEventsChart extends Component { } toggleEvent(ev) { - if (this.activeEvent && get(this.activeEvent, 'event.uid') === get(ev, 'event.uid')) { + if ( + this.activeEvent && + get(this.activeEvent, 'event.uid') === get(ev, 'event.uid') + ) { this.closeEventDetails(); } else { this.activeEvent = ev; diff --git a/ui/app/components/server-agent-row.js b/ui/app/components/server-agent-row.js index 3c5ee4632..e0cc0ede2 100644 --- a/ui/app/components/server-agent-row.js +++ b/ui/app/components/server-agent-row.js @@ -3,7 +3,11 @@ import { alias } from '@ember/object/computed'; import Component from '@ember/component'; import { computed } from '@ember/object'; import { lazyClick } from '../helpers/lazy-click'; -import { classNames, classNameBindings, tagName } from '@ember-decorators/component'; +import { + classNames, + classNameBindings, + tagName, +} from '@ember-decorators/component'; import classic from 'ember-classic-decorator'; @classic @@ -38,7 +42,8 @@ export default class ServerAgentRow extends Component { } click() { - const transition = () => this.router.transitionTo('servers.server', this.agent); + const transition = () => + this.router.transitionTo('servers.server', this.agent); lazyClick([transition, event]); } } diff --git a/ui/app/components/stats-time-series.js b/ui/app/components/stats-time-series.js index 6df134b3a..f530fb7af 100644 --- a/ui/app/components/stats-time-series.js +++ b/ui/app/components/stats-time-series.js @@ -22,8 +22,8 @@ export default class StatsTimeSeries extends Component { // Specific a11y descriptors get description() { const data = this.args.data; - const yRange = d3Array.extent(data, d => d.percent); - const xRange = d3Array.extent(data, d => d.timestamp); + const yRange = d3Array.extent(data, (d) => d.percent); + const xRange = d3Array.extent(data, (d) => d.timestamp); const yFormatter = this.yFormat; const duration = formatDuration(xRange[1] - xRange[0], 'ms', true); @@ -36,19 +36,21 @@ export default class StatsTimeSeries extends Component { xScale(data, yAxisOffset) { const scale = scaleTime(); - const [low, high] = d3Array.extent(data, d => d.timestamp); - const minLow = moment(high) - .subtract(5, 'minutes') - .toDate(); + const [low, high] = d3Array.extent(data, (d) => d.timestamp); + const minLow = moment(high).subtract(5, 'minutes').toDate(); - const extent = data.length ? [Math.min(low, minLow), high] : [minLow, new Date()]; + const extent = data.length + ? [Math.min(low, minLow), high] + : [minLow, new Date()]; scale.rangeRound([10, yAxisOffset]).domain(extent); return scale; } yScale(data, xAxisOffset) { - const yValues = (data || []).mapBy(this.args.dataProp ? 'percentStack' : 'percent'); + const yValues = (data || []).mapBy( + this.args.dataProp ? 'percentStack' : 'percent' + ); let [low, high] = [0, 1]; if (yValues.compact().length) { diff --git a/ui/app/components/stepper-input.js b/ui/app/components/stepper-input.js index 8509c3534..f1993144c 100644 --- a/ui/app/components/stepper-input.js +++ b/ui/app/components/stepper-input.js @@ -9,7 +9,12 @@ const ESC = 27; @classic @classNames('stepper-input') -@classNameBindings('class', 'disabled:is-disabled', 'disabled:tooltip', 'disabled:multiline') +@classNameBindings( + 'class', + 'disabled:is-disabled', + 'disabled:tooltip', + 'disabled:multiline' +) export default class StepperInput extends Component { min = 0; max = 10; @@ -41,7 +46,9 @@ export default class StepperInput extends Component { @action setValue(e) { if (e.target.value !== '') { - const newValue = Math.floor(Math.min(this.max, Math.max(this.min, e.target.value))); + const newValue = Math.floor( + Math.min(this.max, Math.max(this.min, e.target.value)) + ); this.set('internalValue', newValue); this.update(this.internalValue); } else { diff --git a/ui/app/components/streaming-file.js b/ui/app/components/streaming-file.js index 9d82dda50..b19dea125 100644 --- a/ui/app/components/streaming-file.js +++ b/ui/app/components/streaming-file.js @@ -22,6 +22,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) { requestFrame = true; didReceiveAttrs() { + super.didReceiveAttrs(); if (!this.logger) { return; } @@ -58,7 +59,10 @@ export default class StreamingFile extends Component.extend(WindowResizable) { if (this.requestFrame) { window.requestAnimationFrame(() => { // If the scroll position is close enough to the bottom, autoscroll to the bottom - this.set('follow', cli.scrollHeight - cli.scrollTop - cli.clientHeight < 20); + this.set( + 'follow', + cli.scrollHeight - cli.scrollTop - cli.clientHeight < 20 + ); this.requestFrame = true; }); } @@ -79,6 +83,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) { } didInsertElement() { + super.didInsertElement(...arguments); this.fillAvailableHeight(); this.set('_scrollHandler', this.scrollHandler.bind(this)); @@ -89,6 +94,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) { } willDestroyElement() { + super.willDestroyElement(...arguments); this.element.removeEventListener('scroll', this._scrollHandler); document.removeEventListener('keydown', this._keyDownHandler); } @@ -102,10 +108,12 @@ export default class StreamingFile extends Component.extend(WindowResizable) { // of having the log window fill available height is worth the hack. const margins = 30; // Account for padding and margin on either side of the CLI const cliWindow = this.element; - cliWindow.style.height = `${window.innerHeight - cliWindow.offsetTop - margins}px`; + cliWindow.style.height = `${ + window.innerHeight - cliWindow.offsetTop - margins + }px`; } - @task(function*() { + @task(function* () { yield this.get('logger.gotoHead').perform(); run.scheduleOnce('afterRender', this, this.scrollToTop); }) @@ -115,7 +123,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) { this.element.scrollTop = 0; } - @task(function*() { + @task(function* () { yield this.get('logger.gotoTail').perform(); }) tail; @@ -126,7 +134,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) { } } - @task(function*() { + @task(function* () { // Follow the log if the scroll position is near the bottom of the cli window this.logger.on('tick', this, 'scheduleScrollSynchronization'); @@ -140,6 +148,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) { } willDestroy() { + super.willDestroy(...arguments); this.logger.stop(); } } diff --git a/ui/app/components/task-group-row.js b/ui/app/components/task-group-row.js index 57d9e9336..3346fe8be 100644 --- a/ui/app/components/task-group-row.js +++ b/ui/app/components/task-group-row.js @@ -27,7 +27,8 @@ export default class TaskGroupRow extends Component { get tooltipText() { if (this.can.cannot('scale job', null, { namespace: this.namespace })) return "You aren't allowed to scale task groups"; - if (this.runningDeployment) return 'You cannot scale task groups during a deployment'; + if (this.runningDeployment) + return 'You cannot scale task groups during a deployment'; return undefined; } diff --git a/ui/app/components/task-log.js b/ui/app/components/task-log.js index fc744129d..2ed7325d0 100644 --- a/ui/app/components/task-log.js +++ b/ui/app/components/task-log.js @@ -58,21 +58,23 @@ export default class TaskLog extends Component { // If the log request can't settle in one second, the client // must be unavailable and the server should be used instead - const aborter = window.AbortController ? new AbortController() : new MockAbortController(); + const aborter = window.AbortController + ? new AbortController() + : new MockAbortController(); const timing = this.useServer ? this.serverTimeout : this.clientTimeout; // Capture the state of useServer at logger create time to avoid a race // between the stdout logger and stderr logger running at once. const useServer = this.useServer; - return url => + return (url) => RSVP.race([ this.token.authorizedRequest(url, { signal: aborter.signal }), timeout(timing), ]).then( - response => { + (response) => { return response; }, - error => { + (error) => { aborter.abort(); if (useServer) { this.set('noConnection', true); diff --git a/ui/app/components/task-row.js b/ui/app/components/task-row.js index e47ff8588..63183a57a 100644 --- a/ui/app/components/task-row.js +++ b/ui/app/components/task-row.js @@ -50,13 +50,11 @@ export default class TaskRow extends Component { lazyClick([this.onClick, event]); } - @(task(function*() { + @(task(function* () { do { if (this.stats) { try { - yield this.get('stats.poll') - .linked() - .perform(); + yield this.get('stats.poll').linked().perform(); this.set('statsError', false); } catch (error) { this.set('statsError', true); @@ -69,6 +67,7 @@ export default class TaskRow extends Component { fetchStats; didReceiveAttrs() { + super.didReceiveAttrs(); const allocation = this.get('task.allocation'); if (allocation) { diff --git a/ui/app/components/toggle.js b/ui/app/components/toggle.js index 6701f64f9..efa1ed87d 100644 --- a/ui/app/components/toggle.js +++ b/ui/app/components/toggle.js @@ -1,5 +1,9 @@ import Component from '@ember/component'; -import { classNames, classNameBindings, tagName } from '@ember-decorators/component'; +import { + classNames, + classNameBindings, + tagName, +} from '@ember-decorators/component'; import classic from 'ember-classic-decorator'; @classic diff --git a/ui/app/components/tooltip.js b/ui/app/components/tooltip.js index fa1420c62..a9fb3120c 100644 --- a/ui/app/components/tooltip.js +++ b/ui/app/components/tooltip.js @@ -8,7 +8,9 @@ export default class Tooltip extends Component { } const prefix = inputText.substr(0, 15).trim(); - const suffix = inputText.substr(inputText.length - 10, inputText.length).trim(); + const suffix = inputText + .substr(inputText.length - 10, inputText.length) + .trim(); return `${prefix}...${suffix}`; } } diff --git a/ui/app/components/topo-viz.js b/ui/app/components/topo-viz.js index 9c9ac0fab..48faa6b7f 100644 --- a/ui/app/components/topo-viz.js +++ b/ui/app/components/topo-viz.js @@ -26,11 +26,14 @@ export default class TopoViz extends Component { @styleStringProperty('tooltipProps') tooltipStyle; get isSingleColumn() { - if (this.topology.datacenters.length <= 1 || this.viewportColumns === 1) return true; + if (this.topology.datacenters.length <= 1 || this.viewportColumns === 1) + return true; // Compute the coefficient of variance to determine if it would be // better to stack datacenters or place them in columns - const nodeCounts = this.topology.datacenters.map(datacenter => datacenter.nodes.length); + const nodeCounts = this.topology.datacenters.map( + (datacenter) => datacenter.nodes.length + ); const variationCoefficient = deviation(nodeCounts) / mean(nodeCounts); // The point at which the varation is too extreme for a two column layout @@ -43,7 +46,10 @@ export default class TopoViz extends Component { // If there are enough nodes, use two columns of nodes within // a single column layout of datacenters to increase density. if (this.viewportColumns === 1) return true; - return !this.isSingleColumn || (this.isSingleColumn && this.args.nodes.length <= 20); + return ( + !this.isSingleColumn || + (this.isSingleColumn && this.args.nodes.length <= 20) + ); } // Once a cluster is large enough, the exact details of a node are @@ -89,7 +95,7 @@ export default class TopoViz extends Component { // Wrap nodes in a topo viz specific data structure and build an index to speed up allocation assignment const nodeContainers = []; const nodeIndex = {}; - nodes.forEach(node => { + nodes.forEach((node) => { if (!node.resources) { badNodes.push(node); return; @@ -103,14 +109,17 @@ export default class TopoViz extends Component { // Wrap allocations in a topo viz specific data structure, assign allocations to nodes, and build an allocation // index keyed off of job and task group const allocationIndex = {}; - allocations.forEach(allocation => { + allocations.forEach((allocation) => { const nodeId = allocation.belongsTo('node').id(); const nodeContainer = nodeIndex[nodeId]; // Ignore orphaned allocations and allocations on nodes with an old Nomad agent version. if (!nodeContainer) return; - const allocationContainer = this.dataForAllocation(allocation, nodeContainer); + const allocationContainer = this.dataForAllocation( + allocation, + nodeContainer + ); nodeContainer.allocations.push(allocationContainer); const key = allocationContainer.groupKey; @@ -119,15 +128,19 @@ export default class TopoViz extends Component { }); // Group nodes into datacenters - const datacentersMap = nodeContainers.reduce((datacenters, nodeContainer) => { - if (!datacenters[nodeContainer.datacenter]) datacenters[nodeContainer.datacenter] = []; - datacenters[nodeContainer.datacenter].push(nodeContainer); - return datacenters; - }, {}); + const datacentersMap = nodeContainers.reduce( + (datacenters, nodeContainer) => { + if (!datacenters[nodeContainer.datacenter]) + datacenters[nodeContainer.datacenter] = []; + datacenters[nodeContainer.datacenter].push(nodeContainer); + return datacenters; + }, + {} + ); // Turn hash of datacenters into a sorted array const datacenters = Object.keys(datacentersMap) - .map(key => ({ name: key, nodes: datacentersMap[key] })) + .map((key) => ({ name: key, nodes: datacentersMap[key] })) .sortBy('name'); const topology = { @@ -191,9 +204,10 @@ export default class TopoViz extends Component { this.activeEdges = []; if (this.topology.selectedKey) { - const selectedAllocations = this.topology.allocationIndex[this.topology.selectedKey]; + const selectedAllocations = + this.topology.allocationIndex[this.topology.selectedKey]; if (selectedAllocations) { - selectedAllocations.forEach(allocation => { + selectedAllocations.forEach((allocation) => { set(allocation, 'isSelected', false); }); } @@ -205,30 +219,37 @@ export default class TopoViz extends Component { } this.activeNode = null; this.activeAllocation = allocation; - const selectedAllocations = this.topology.allocationIndex[this.topology.selectedKey]; + const selectedAllocations = + this.topology.allocationIndex[this.topology.selectedKey]; if (selectedAllocations) { - selectedAllocations.forEach(allocation => { + selectedAllocations.forEach((allocation) => { set(allocation, 'isSelected', false); }); } set(this.topology, 'selectedKey', allocation.groupKey); - const newAllocations = this.topology.allocationIndex[this.topology.selectedKey]; + const newAllocations = + this.topology.allocationIndex[this.topology.selectedKey]; if (newAllocations) { - newAllocations.forEach(allocation => { + newAllocations.forEach((allocation) => { set(allocation, 'isSelected', true); }); } // Only show the lines if the selected allocations are sparse (low count relative to the client count or low count generally). - if (newAllocations.length < 10 || newAllocations.length < this.args.nodes.length * 0.75) { + if ( + newAllocations.length < 10 || + newAllocations.length < this.args.nodes.length * 0.75 + ) { this.computedActiveEdges(); } else { this.activeEdges = []; } } if (this.args.onAllocationSelect) - this.args.onAllocationSelect(this.activeAllocation && this.activeAllocation.allocation); + this.args.onAllocationSelect( + this.activeAllocation && this.activeAllocation.allocation + ); if (this.args.onNodeSelect) this.args.onNodeSelect(this.activeNode); } @@ -251,24 +272,28 @@ export default class TopoViz extends Component { const path = line().curve(curveBasis); // 1. Get the active element const allocation = this.activeAllocation.allocation; - const activeEl = this.element.querySelector(`[data-allocation-id="${allocation.id}"]`); + const activeEl = this.element.querySelector( + `[data-allocation-id="${allocation.id}"]` + ); const activePoint = centerOfBBox(activeEl.getBoundingClientRect()); // 2. Collect the mem and cpu pairs for all selected allocs - const selectedMem = Array.from(this.element.querySelectorAll('.memory .bar.is-selected')); - const selectedPairs = selectedMem.map(mem => { + const selectedMem = Array.from( + this.element.querySelectorAll('.memory .bar.is-selected') + ); + const selectedPairs = selectedMem.map((mem) => { const id = mem.closest('[data-allocation-id]').dataset.allocationId; const cpu = mem .closest('.topo-viz-node') .querySelector(`.cpu .bar[data-allocation-id="${id}"]`); return [mem, cpu]; }); - const selectedPoints = selectedPairs.map(pair => { - return pair.map(el => centerOfBBox(el.getBoundingClientRect())); + const selectedPoints = selectedPairs.map((pair) => { + return pair.map((el) => centerOfBBox(el.getBoundingClientRect())); }); // 3. For each pair, compute the midpoint of the truncated triangle of points [Mem, Cpu, Active] - selectedPoints.forEach(points => { + selectedPoints.forEach((points) => { const d1 = pointBetween(points[0], activePoint, 100, 0.5); const d2 = pointBetween(points[1], activePoint, 100, 0.5); points.push(midpoint(d1, d2)); @@ -281,14 +306,20 @@ export default class TopoViz extends Component { const stepsMain = [0, 0.8, 1.0]; // The second prong the fork does not need to retrace the entire path from the activePoint const stepsSecondary = [0.8, 1.0]; - selectedPoints.forEach(points => { + selectedPoints.forEach((points) => { curves.push( - curveFromPoints(...pointsAlongPath(activePoint, points[2], stepsMain), points[0]), - curveFromPoints(...pointsAlongPath(activePoint, points[2], stepsSecondary), points[1]) + curveFromPoints( + ...pointsAlongPath(activePoint, points[2], stepsMain), + points[0] + ), + curveFromPoints( + ...pointsAlongPath(activePoint, points[2], stepsSecondary), + points[1] + ) ); }); - this.activeEdges = curves.map(curve => path(curve)); + this.activeEdges = curves.map((curve) => path(curve)); this.edgeOffset = { x: window.scrollX, y: window.scrollY }; }); } @@ -319,7 +350,7 @@ function pointBetweenPct(p1, p2, pct) { } function pointsAlongPath(p1, p2, pcts) { - return pcts.map(pct => pointBetweenPct(p1, p2, pct)); + return pcts.map((pct) => pointBetweenPct(p1, p2, pct)); } function midpoint(p1, p2) { @@ -327,5 +358,5 @@ function midpoint(p1, p2) { } function curveFromPoints(...points) { - return points.map(p => [p.x, p.y]); + return points.map((p) => [p.x, p.y]); } diff --git a/ui/app/components/topo-viz/datacenter.js b/ui/app/components/topo-viz/datacenter.js index 0750fc1eb..965af2f77 100644 --- a/ui/app/components/topo-viz/datacenter.js +++ b/ui/app/components/topo-viz/datacenter.js @@ -3,7 +3,8 @@ import Component from '@glimmer/component'; export default class TopoVizDatacenter extends Component { get scheduledAllocations() { return this.args.datacenter.nodes.reduce( - (all, node) => all.concat(node.allocations.filterBy('allocation.isScheduled')), + (all, node) => + all.concat(node.allocations.filterBy('allocation.isScheduled')), [] ); } diff --git a/ui/app/components/topo-viz/node.js b/ui/app/components/topo-viz/node.js index 43ed0da90..400b69c0b 100644 --- a/ui/app/components/topo-viz/node.js +++ b/ui/app/components/topo-viz/node.js @@ -10,7 +10,9 @@ export default class TopoVizNode extends Component { @tracked activeAllocation = null; get height() { - return this.args.heightScale ? this.args.heightScale(this.args.node.memory) : 15; + return this.args.heightScale + ? this.args.heightScale(this.args.node.memory) + : 15; } get labelHeight() { @@ -57,11 +59,13 @@ export default class TopoVizNode extends Component { get allocations() { // Sort by the delta between memory and cpu percent. This creates the least amount of // drift between the positional alignment of an alloc's cpu and memory representations. - return this.args.node.allocations.filterBy('allocation.isScheduled').sort((a, b) => { - const deltaA = Math.abs(a.memoryPercent - a.cpuPercent); - const deltaB = Math.abs(b.memoryPercent - b.cpuPercent); - return deltaA - deltaB; - }); + return this.args.node.allocations + .filterBy('allocation.isScheduled') + .sort((a, b) => { + const deltaA = Math.abs(a.memoryPercent - a.cpuPercent); + const deltaB = Math.abs(b.memoryPercent - b.cpuPercent); + return deltaA - deltaB; + }); } @action @@ -91,7 +95,8 @@ export default class TopoVizNode extends Component { @action highlightAllocation(allocation, { target }) { this.activeAllocation = allocation; - this.args.onAllocationFocus && this.args.onAllocationFocus(allocation, target); + this.args.onAllocationFocus && + this.args.onAllocationFocus(allocation, target); } @action @@ -118,7 +123,7 @@ export default class TopoVizNode extends Component { containsActiveTaskGroup() { return this.args.node.allocations.some( - allocation => + (allocation) => allocation.taskGroupName === this.args.activeTaskGroup && allocation.belongsTo('job').id() === this.args.activeJobId ); diff --git a/ui/app/components/trigger.js b/ui/app/components/trigger.js index 17f981b87..d141e6c33 100644 --- a/ui/app/components/trigger.js +++ b/ui/app/components/trigger.js @@ -49,7 +49,7 @@ export default class Trigger extends Component { this.error = null; } - @task(function*() { + @task(function* () { this._reset(); try { this.result = yield this.args.do(); diff --git a/ui/app/components/two-step-button.js b/ui/app/components/two-step-button.js index 70ff34010..cd29dd8aa 100644 --- a/ui/app/components/two-step-button.js +++ b/ui/app/components/two-step-button.js @@ -9,7 +9,10 @@ import classic from 'ember-classic-decorator'; @classic @classNames('two-step-button') -@classNameBindings('inlineText:has-inline-text', 'fadingBackground:has-fading-background') +@classNameBindings( + 'inlineText:has-inline-text', + 'fadingBackground:has-fading-background' +) export default class TwoStepButton extends Component { idleText = ''; cancelText = ''; @@ -26,7 +29,7 @@ export default class TwoStepButton extends Component { @equal('state', 'idle') isIdle; @equal('state', 'prompt') isPendingConfirmation; - @task(function*() { + @task(function* () { while (true) { let ev = yield waitForEvent(document.body, 'click'); if (!this.element.contains(ev.target) && !this.awaitingConfirmation) { diff --git a/ui/app/controllers/allocations/allocation.js b/ui/app/controllers/allocations/allocation.js index b29d7b6cb..142c9f3ff 100644 --- a/ui/app/controllers/allocations/allocation.js +++ b/ui/app/controllers/allocations/allocation.js @@ -35,7 +35,12 @@ export default class AllocationsAllocationController extends Controller { { title: 'Task Group', label: allocation.taskGroupName, - args: ['jobs.job.task-group', job.plainId, allocation.taskGroupName, jobQueryParams], + args: [ + 'jobs.job.task-group', + job.plainId, + allocation.taskGroupName, + jobQueryParams, + ], }, { title: 'Allocation', diff --git a/ui/app/controllers/allocations/allocation/index.js b/ui/app/controllers/allocations/allocation/index.js index 4068de300..3fdc17216 100644 --- a/ui/app/controllers/allocations/allocation/index.js +++ b/ui/app/controllers/allocations/allocation/index.js @@ -34,7 +34,7 @@ export default class IndexController extends Controller.extend(Sortable) { // Set in the route preempter = null; - @overridable(function() { + @overridable(function () { // { title, description } return null; }) @@ -66,7 +66,7 @@ export default class IndexController extends Controller.extend(Sortable) { } } - @task(function*() { + @task(function* () { try { yield this.model.stop(); // Eagerly update the allocation clientStatus to avoid flickering @@ -80,7 +80,7 @@ export default class IndexController extends Controller.extend(Sortable) { }) stopAllocation; - @task(function*() { + @task(function* () { try { yield this.model.restart(); } catch (err) { diff --git a/ui/app/controllers/allocations/allocation/task.js b/ui/app/controllers/allocations/allocation/task.js index 3e3c9a034..7ebc8b67a 100644 --- a/ui/app/controllers/allocations/allocation/task.js +++ b/ui/app/controllers/allocations/allocation/task.js @@ -9,7 +9,11 @@ export default class AllocationsAllocationTaskController extends Controller { return { title: 'Task', label: this.task.get('name'), - args: ['allocations.allocation.task', this.task.get('allocation'), this.task], + args: [ + 'allocations.allocation.task', + this.task.get('allocation'), + this.task, + ], }; } } diff --git a/ui/app/controllers/allocations/allocation/task/index.js b/ui/app/controllers/allocations/allocation/task/index.js index 73f0f208f..f880ece05 100644 --- a/ui/app/controllers/allocations/allocation/task/index.js +++ b/ui/app/controllers/allocations/allocation/task/index.js @@ -16,7 +16,7 @@ export default class IndexController extends Controller { this.set('error', null); } - @task(function*() { + @task(function* () { try { yield this.model.restart(); } catch (err) { diff --git a/ui/app/controllers/clients/client/index.js b/ui/app/controllers/clients/client/index.js index bc4eeec79..6a9b2475c 100644 --- a/ui/app/controllers/clients/client/index.js +++ b/ui/app/controllers/clients/client/index.js @@ -10,11 +10,17 @@ import intersection from 'lodash.intersection'; import Sortable from 'nomad-ui/mixins/sortable'; import Searchable from 'nomad-ui/mixins/searchable'; import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error'; -import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize'; +import { + serialize, + deserializedQueryParam as selection, +} from 'nomad-ui/utils/qp-serialize'; import classic from 'ember-classic-decorator'; @classic -export default class ClientController extends Controller.extend(Sortable, Searchable) { +export default class ClientController extends Controller.extend( + Sortable, + Searchable +) { queryParams = [ { currentPage: 'page', @@ -66,18 +72,32 @@ export default class ClientController extends Controller.extend(Sortable, Search return this.onlyPreemptions ? this.preemptions : this.model.allocations; } - @computed('visibleAllocations.[]', 'selectionNamespace', 'selectionJob', 'selectionStatus') + @computed( + 'visibleAllocations.[]', + 'selectionNamespace', + 'selectionJob', + 'selectionStatus' + ) get filteredAllocations() { const { selectionNamespace, selectionJob, selectionStatus } = this; - return this.visibleAllocations.filter(alloc => { - if (selectionNamespace.length && !selectionNamespace.includes(alloc.get('namespace'))) { + return this.visibleAllocations.filter((alloc) => { + if ( + selectionNamespace.length && + !selectionNamespace.includes(alloc.get('namespace')) + ) { return false; } - if (selectionJob.length && !selectionJob.includes(alloc.get('plainJobId'))) { + if ( + selectionJob.length && + !selectionJob.includes(alloc.get('plainJobId')) + ) { return false; } - if (selectionStatus.length && !selectionStatus.includes(alloc.clientStatus)) { + if ( + selectionStatus.length && + !selectionStatus.includes(alloc.clientStatus) + ) { return false; } return true; @@ -106,9 +126,7 @@ export default class ClientController extends Controller.extend(Sortable, Search @computed('model.events.@each.time') get sortedEvents() { - return this.get('model.events') - .sortBy('time') - .reverse(); + return this.get('model.events').sortBy('time').reverse(); } @computed('model.drivers.@each.name') @@ -121,7 +139,7 @@ export default class ClientController extends Controller.extend(Sortable, Search return this.model.hostVolumes.sortBy('name'); } - @(task(function*(value) { + @(task(function* (value) { try { yield value ? this.model.setEligible() : this.model.setIneligible(); } catch (err) { @@ -131,7 +149,7 @@ export default class ClientController extends Controller.extend(Sortable, Search }).drop()) setEligibility; - @(task(function*() { + @(task(function* () { try { this.set('flagAsDraining', false); yield this.model.cancelDrain(); @@ -144,7 +162,7 @@ export default class ClientController extends Controller.extend(Sortable, Search }).drop()) stopDrain; - @(task(function*() { + @(task(function* () { try { yield this.model.forceDrain({ IgnoreSystemJobs: this.model.drainStrategy.ignoreSystemJobs, @@ -203,7 +221,7 @@ export default class ClientController extends Controller.extend(Sortable, Search const jobs = Array.from( new Set( this.model.allocations - .filter(a => ns.length === 0 || ns.includes(a.namespace)) + .filter((a) => ns.length === 0 || ns.includes(a.namespace)) .mapBy('plainJobId') ) ).compact(); @@ -214,20 +232,25 @@ export default class ClientController extends Controller.extend(Sortable, Search this.set('qpJob', serialize(intersection(jobs, this.selectionJob))); }); - return jobs.sort().map(job => ({ key: job, label: job })); + return jobs.sort().map((job) => ({ key: job, label: job })); } @computed('model.allocations.[]', 'selectionNamespace') get optionsNamespace() { - const ns = Array.from(new Set(this.model.allocations.mapBy('namespace'))).compact(); + const ns = Array.from( + new Set(this.model.allocations.mapBy('namespace')) + ).compact(); // Update query param when the list of namespaces changes. scheduleOnce('actions', () => { // eslint-disable-next-line ember/no-side-effects - this.set('qpNamespace', serialize(intersection(ns, this.selectionNamespace))); + this.set( + 'qpNamespace', + serialize(intersection(ns, this.selectionNamespace)) + ); }); - return ns.sort().map(n => ({ key: n, label: n })); + return ns.sort().map((n) => ({ key: n, label: n })); } setFacetQueryParam(queryParam, selection) { diff --git a/ui/app/controllers/clients/index.js b/ui/app/controllers/clients/index.js index f801bc8fe..3ed795c69 100644 --- a/ui/app/controllers/clients/index.js +++ b/ui/app/controllers/clients/index.js @@ -7,14 +7,17 @@ import { scheduleOnce } from '@ember/runloop'; import intersection from 'lodash.intersection'; import SortableFactory from 'nomad-ui/mixins/sortable-factory'; import Searchable from 'nomad-ui/mixins/searchable'; -import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize'; +import { + serialize, + deserializedQueryParam as selection, +} from 'nomad-ui/utils/qp-serialize'; import classic from 'ember-classic-decorator'; @classic export default class IndexController extends Controller.extend( - SortableFactory(['id', 'name', 'compositeStatus', 'datacenter', 'version']), - Searchable - ) { + SortableFactory(['id', 'name', 'compositeStatus', 'datacenter', 'version']), + Searchable +) { @service userSettings; @controller('clients') clientsController; @@ -83,10 +86,13 @@ export default class IndexController extends 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))); + this.set( + 'qpClass', + serialize(intersection(classes, this.selectionClass)) + ); }); - return classes.sort().map(dc => ({ key: dc, label: dc })); + return classes.sort().map((dc) => ({ key: dc, label: dc })); } @computed @@ -102,15 +108,20 @@ export default class IndexController extends Controller.extend( @computed('nodes.[]', 'selectionDatacenter') get optionsDatacenter() { - const datacenters = Array.from(new Set(this.nodes.mapBy('datacenter'))).compact(); + 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))); + this.set( + 'qpDatacenter', + serialize(intersection(datacenters, this.selectionDatacenter)) + ); }); - return datacenters.sort().map(dc => ({ key: dc, label: dc })); + return datacenters.sort().map((dc) => ({ key: dc, label: dc })); } @computed('nodes.[]', 'selectionVersion') @@ -120,10 +131,13 @@ export default class IndexController extends Controller.extend( // Remove any invalid versions from the query param/selection scheduleOnce('actions', () => { // eslint-disable-next-line ember/no-side-effects - this.set('qpVersion', serialize(intersection(versions, this.selectionVersion))); + this.set( + 'qpVersion', + serialize(intersection(versions, this.selectionVersion)) + ); }); - return versions.sort().map(v => ({ key: v, label: v })); + return versions.sort().map((v) => ({ key: v, label: v })); } @computed('nodes.[]', 'selectionVolume') @@ -135,10 +149,13 @@ export default class IndexController extends Controller.extend( scheduleOnce('actions', () => { // eslint-disable-next-line ember/no-side-effects - this.set('qpVolume', serialize(intersection(volumes, this.selectionVolume))); + this.set( + 'qpVolume', + serialize(intersection(volumes, this.selectionVolume)) + ); }); - return volumes.sort().map(volume => ({ key: volume, label: volume })); + return volumes.sort().map((volume) => ({ key: volume, label: volume })); } @computed( @@ -164,12 +181,19 @@ export default class IndexController extends Controller.extend( // states is a composite of node status and other node states const statuses = states.without('ineligible').without('draining'); - return this.nodes.filter(node => { - if (classes.length && !classes.includes(node.get('nodeClass'))) return false; - if (statuses.length && !statuses.includes(node.get('status'))) return false; - if (datacenters.length && !datacenters.includes(node.get('datacenter'))) return false; - if (versions.length && !versions.includes(node.get('version'))) return false; - if (volumes.length && !node.hostVolumes.find(volume => volumes.includes(volume.name))) + return this.nodes.filter((node) => { + if (classes.length && !classes.includes(node.get('nodeClass'))) + return false; + if (statuses.length && !statuses.includes(node.get('status'))) + return false; + if (datacenters.length && !datacenters.includes(node.get('datacenter'))) + return false; + if (versions.length && !versions.includes(node.get('version'))) + return false; + if ( + volumes.length && + !node.hostVolumes.find((volume) => volumes.includes(volume.name)) + ) return false; if (onlyIneligible && node.get('isEligible')) return false; diff --git a/ui/app/controllers/csi/plugins/index.js b/ui/app/controllers/csi/plugins/index.js index bd9b3d2e5..b4c88ad9c 100644 --- a/ui/app/controllers/csi/plugins/index.js +++ b/ui/app/controllers/csi/plugins/index.js @@ -9,14 +9,14 @@ import classic from 'ember-classic-decorator'; @classic export default class IndexController extends Controller.extend( - SortableFactory([ - 'plainId', - 'controllersHealthyProportion', - 'nodesHealthyProportion', - 'provider', - ]), - Searchable - ) { + SortableFactory([ + 'plainId', + 'controllersHealthyProportion', + 'nodesHealthyProportion', + 'provider', + ]), + Searchable +) { @service userSettings; @controller('csi/plugins') pluginsController; @@ -59,6 +59,9 @@ export default class IndexController extends Controller.extend( @action gotoPlugin(plugin, event) { - lazyClick([() => this.transitionToRoute('csi.plugins.plugin', plugin.plainId), event]); + lazyClick([ + () => this.transitionToRoute('csi.plugins.plugin', plugin.plainId), + event, + ]); } } diff --git a/ui/app/controllers/csi/plugins/plugin/allocations.js b/ui/app/controllers/csi/plugins/plugin/allocations.js index 53f7712a4..32c5ba669 100644 --- a/ui/app/controllers/csi/plugins/plugin/allocations.js +++ b/ui/app/controllers/csi/plugins/plugin/allocations.js @@ -4,13 +4,16 @@ import { action, computed } from '@ember/object'; import { alias, readOnly } from '@ember/object/computed'; import SortableFactory from 'nomad-ui/mixins/sortable-factory'; import { lazyClick } from 'nomad-ui/helpers/lazy-click'; -import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize'; +import { + serialize, + deserializedQueryParam as selection, +} from 'nomad-ui/utils/qp-serialize'; import classic from 'ember-classic-decorator'; @classic export default class AllocationsController extends Controller.extend( - SortableFactory(['updateTime', 'healthy']) - ) { + SortableFactory(['updateTime', 'healthy']) +) { @service userSettings; queryParams = [ @@ -45,12 +48,18 @@ export default class AllocationsController extends Controller.extend( @computed get optionsType() { - return [{ key: 'controller', label: 'Controller' }, { key: 'node', label: 'Node' }]; + return [ + { key: 'controller', label: 'Controller' }, + { key: 'node', label: 'Node' }, + ]; } @computed get optionsHealth() { - return [{ key: 'true', label: 'Healthy' }, { key: 'false', label: 'Unhealthy' }]; + return [ + { key: 'true', label: 'Healthy' }, + { key: 'false', label: 'Unhealthy' }, + ]; } @computed('model.{controllers.[],nodes.[]}') @@ -76,7 +85,8 @@ export default class AllocationsController extends Controller.extend( listToFilter = this.model.nodes; } - if (healths.length === 1 && healths[0] === 'true') return listToFilter.filterBy('healthy'); + if (healths.length === 1 && healths[0] === 'true') + return listToFilter.filterBy('healthy'); if (healths.length === 1 && healths[0] === 'false') return listToFilter.filterBy('healthy', false); return listToFilter; @@ -97,6 +107,9 @@ export default class AllocationsController extends Controller.extend( @action gotoAllocation(allocation, event) { - lazyClick([() => this.transitionToRoute('allocations.allocation', allocation), event]); + lazyClick([ + () => this.transitionToRoute('allocations.allocation', allocation), + event, + ]); } } diff --git a/ui/app/controllers/csi/volumes/index.js b/ui/app/controllers/csi/volumes/index.js index eb3b12e78..a7ca5b9ef 100644 --- a/ui/app/controllers/csi/volumes/index.js +++ b/ui/app/controllers/csi/volumes/index.js @@ -1,3 +1,4 @@ +import { set } from '@ember/object'; import { inject as service } from '@ember/service'; import { action, computed } from '@ember/object'; import { alias, readOnly } from '@ember/object/computed'; @@ -11,15 +12,15 @@ import classic from 'ember-classic-decorator'; @classic export default class IndexController extends Controller.extend( - SortableFactory([ - 'id', - 'schedulable', - 'controllersHealthyProportion', - 'nodesHealthyProportion', - 'provider', - ]), - Searchable - ) { + SortableFactory([ + 'id', + 'schedulable', + 'controllersHealthyProportion', + 'nodesHealthyProportion', + 'provider', + ]), + Searchable +) { @service system; @service userSettings; @controller('csi/volumes') volumesController; @@ -65,7 +66,7 @@ export default class IndexController extends Controller.extend( @computed('qpNamespace', 'model.namespaces.[]', 'system.cachedNamespace') get optionsNamespaces() { - const availableNamespaces = this.model.namespaces.map(namespace => ({ + const availableNamespaces = this.model.namespaces.map((namespace) => ({ key: namespace.name, label: namespace.name, })); @@ -102,7 +103,7 @@ export default class IndexController extends Controller.extend( @action cacheNamespace(namespace) { - this.system.cachedNamespace = namespace; + set(this, 'system.cachedNamespace', namespace); } setFacetQueryParam(queryParam, selection) { diff --git a/ui/app/controllers/csi/volumes/volume.js b/ui/app/controllers/csi/volumes/volume.js index c151e5dec..46bf9a235 100644 --- a/ui/app/controllers/csi/volumes/volume.js +++ b/ui/app/controllers/csi/volumes/volume.js @@ -25,7 +25,9 @@ export default class VolumeController extends Controller { label: 'Volumes', args: [ 'csi.volumes', - qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default' }), + qpBuilder({ + volumeNamespace: volume.get('namespace.name') || 'default', + }), ], }, { @@ -33,7 +35,9 @@ export default class VolumeController extends Controller { args: [ 'csi.volumes.volume', volume.plainId, - qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default' }), + qpBuilder({ + volumeNamespace: volume.get('namespace.name') || 'default', + }), ], }, ]; diff --git a/ui/app/controllers/exec.js b/ui/app/controllers/exec.js index 9e2691527..1738300d8 100644 --- a/ui/app/controllers/exec.js +++ b/ui/app/controllers/exec.js @@ -25,18 +25,25 @@ export default class ExecController extends Controller { @computed('model.allocations.@each.clientStatus') get pendingAndRunningAllocations() { return this.model.allocations.filter( - allocation => allocation.clientStatus === 'pending' || allocation.clientStatus === 'running' + (allocation) => + allocation.clientStatus === 'pending' || + allocation.clientStatus === 'running' ); } - @mapBy('pendingAndRunningAllocations', 'taskGroup') pendingAndRunningTaskGroups; + @mapBy('pendingAndRunningAllocations', 'taskGroup') + pendingAndRunningTaskGroups; @uniq('pendingAndRunningTaskGroups') uniquePendingAndRunningTaskGroups; taskGroupSorting = ['name']; - @sort('uniquePendingAndRunningTaskGroups', 'taskGroupSorting') sortedTaskGroups; + @sort('uniquePendingAndRunningTaskGroups', 'taskGroupSorting') + sortedTaskGroups; setUpTerminal(Terminal) { - this.terminal = new Terminal({ fontFamily: 'monospace', fontWeight: '400' }); + this.terminal = new Terminal({ + fontFamily: 'monospace', + fontWeight: '400', + }); window.execTerminal = this.terminal; // Issue to improve: https://github.com/hashicorp/nomad/issues/7457 this.terminal.write(ANSI_UI_GRAY_400); @@ -63,7 +70,7 @@ export default class ExecController extends Controller { if (this.allocationShortId) { allocation = this.allocations.findBy('shortId', this.allocationShortId); } else { - allocation = this.allocations.find(allocation => + allocation = this.allocations.find((allocation) => allocation.states .filterBy('isActive') .mapBy('name') @@ -72,7 +79,7 @@ export default class ExecController extends Controller { } if (allocation) { - return allocation.states.find(state => state.name === this.taskName); + return allocation.states.find((state) => state.name === this.taskName); } return undefined; @@ -97,7 +104,9 @@ export default class ExecController extends Controller { this.terminal.writeln(''); } - this.terminal.writeln('Customize your command, then hit ‘return’ to run.'); + this.terminal.writeln( + 'Customize your command, then hit ‘return’ to run.' + ); this.terminal.writeln(''); this.terminal.write( `$ nomad alloc exec -i -t -task ${escapeTaskName(taskName)} ${ @@ -129,7 +138,9 @@ export default class ExecController extends Controller { new ExecSocketXtermAdapter(this.terminal, this.socket, this.token.secret); } else { - this.terminal.writeln(`Failed to open a socket because task ${this.taskName} is not active.`); + this.terminal.writeln( + `Failed to open a socket because task ${this.taskName} is not active.` + ); } } } diff --git a/ui/app/controllers/jobs/index.js b/ui/app/controllers/jobs/index.js index 0fb9e0288..ff38e023b 100644 --- a/ui/app/controllers/jobs/index.js +++ b/ui/app/controllers/jobs/index.js @@ -1,4 +1,5 @@ /* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */ +import { set } from '@ember/object'; import { inject as service } from '@ember/service'; import { alias, readOnly } from '@ember/object/computed'; import Controller from '@ember/controller'; @@ -7,11 +8,17 @@ import { scheduleOnce } from '@ember/runloop'; import intersection from 'lodash.intersection'; import Sortable from 'nomad-ui/mixins/sortable'; import Searchable from 'nomad-ui/mixins/searchable'; -import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize'; +import { + serialize, + deserializedQueryParam as selection, +} from 'nomad-ui/utils/qp-serialize'; import classic from 'ember-classic-decorator'; @classic -export default class IndexController extends Controller.extend(Sortable, Searchable) { +export default class IndexController extends Controller.extend( + Sortable, + Searchable +) { @service system; @service userSettings; @@ -99,7 +106,9 @@ export default class IndexController extends Controller.extend(Sortable, Searcha @computed('selectionDatacenter', 'visibleJobs.[]') get optionsDatacenter() { const flatten = (acc, val) => acc.concat(val); - const allDatacenters = new Set(this.visibleJobs.mapBy('datacenters').reduce(flatten, [])); + const allDatacenters = new Set( + this.visibleJobs.mapBy('datacenters').reduce(flatten, []) + ); // Remove any invalid datacenters from the query param/selection const availableDatacenters = Array.from(allDatacenters).compact(); @@ -111,7 +120,7 @@ export default class IndexController extends Controller.extend(Sortable, Searcha ); }); - return availableDatacenters.sort().map(dc => ({ key: dc, label: dc })); + return availableDatacenters.sort().map((dc) => ({ key: dc, label: dc })); } @computed('selectionPrefix', 'visibleJobs.[]') @@ -131,23 +140,26 @@ export default class IndexController extends Controller.extend(Sortable, Searcha }, {}); // Convert to an array - const nameTable = Object.keys(nameHistogram).map(key => ({ + const nameTable = Object.keys(nameHistogram).map((key) => ({ prefix: key, count: nameHistogram[key], })); // Only consider prefixes that match more than one name - const prefixes = nameTable.filter(name => name.count > 1); + const prefixes = nameTable.filter((name) => name.count > 1); // 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))); + this.set( + 'qpPrefix', + serialize(intersection(availablePrefixes, this.selectionPrefix)) + ); }); // Sort, format, and include the count in the label - return prefixes.sortBy('prefix').map(name => ({ + return prefixes.sortBy('prefix').map((name) => ({ key: name.prefix, label: `${name.prefix} (${name.count})`, })); @@ -155,7 +167,7 @@ export default class IndexController extends Controller.extend(Sortable, Searcha @computed('qpNamespace', 'model.namespaces.[]', 'system.cachedNamespace') get optionsNamespaces() { - const availableNamespaces = this.model.namespaces.map(namespace => ({ + const availableNamespaces = this.model.namespaces.map((namespace) => ({ key: namespace.name, label: namespace.name, })); @@ -185,8 +197,8 @@ export default class IndexController extends Controller.extend(Sortable, Searcha if (!this.model || !this.model.jobs) return []; return this.model.jobs .compact() - .filter(job => !job.isNew) - .filter(job => !job.get('parent.content')); + .filter((job) => !job.isNew) + .filter((job) => !job.get('parent.content')); } @computed( @@ -206,7 +218,7 @@ export default class IndexController extends Controller.extend(Sortable, Searcha // A job must match ALL filter facets, but it can match ANY selection within a facet // Always return early to prevent unnecessary facet predicates. - return this.visibleJobs.filter(job => { + return this.visibleJobs.filter((job) => { if (types.length && !types.includes(job.get('displayType'))) { return false; } @@ -215,12 +227,18 @@ export default class IndexController extends Controller.extend(Sortable, Searcha return false; } - if (datacenters.length && !job.get('datacenters').find(dc => datacenters.includes(dc))) { + if ( + datacenters.length && + !job.get('datacenters').find((dc) => datacenters.includes(dc)) + ) { return false; } const name = job.get('name'); - if (prefixes.length && !prefixes.find(prefix => name.startsWith(prefix))) { + if ( + prefixes.length && + !prefixes.find((prefix) => name.startsWith(prefix)) + ) { return false; } @@ -236,7 +254,7 @@ export default class IndexController extends Controller.extend(Sortable, Searcha @action cacheNamespace(namespace) { - this.system.cachedNamespace = namespace; + set(this, 'system.cachedNamespace', namespace); } setFacetQueryParam(queryParam, selection) { diff --git a/ui/app/controllers/jobs/job/allocations.js b/ui/app/controllers/jobs/job/allocations.js index d6ec836c4..466b7567d 100644 --- a/ui/app/controllers/jobs/job/allocations.js +++ b/ui/app/controllers/jobs/job/allocations.js @@ -7,15 +7,18 @@ import intersection from 'lodash.intersection'; import Sortable from 'nomad-ui/mixins/sortable'; import Searchable from 'nomad-ui/mixins/searchable'; import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting'; -import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize'; +import { + serialize, + deserializedQueryParam as selection, +} from 'nomad-ui/utils/qp-serialize'; import classic from 'ember-classic-decorator'; @classic export default class AllocationsController extends Controller.extend( - Sortable, - Searchable, - WithNamespaceResetting - ) { + Sortable, + Searchable, + WithNamespaceResetting +) { queryParams = [ { currentPage: 'page', @@ -61,18 +64,32 @@ export default class AllocationsController extends Controller.extend( return this.get('model.allocations') || []; } - @computed('allocations.[]', 'selectionStatus', 'selectionClient', 'selectionTaskGroup') + @computed( + 'allocations.[]', + 'selectionStatus', + 'selectionClient', + 'selectionTaskGroup' + ) get filteredAllocations() { const { selectionStatus, selectionClient, selectionTaskGroup } = this; - return this.allocations.filter(alloc => { - if (selectionStatus.length && !selectionStatus.includes(alloc.clientStatus)) { + return this.allocations.filter((alloc) => { + if ( + selectionStatus.length && + !selectionStatus.includes(alloc.clientStatus) + ) { return false; } - if (selectionClient.length && !selectionClient.includes(alloc.get('node.shortId'))) { + if ( + selectionClient.length && + !selectionClient.includes(alloc.get('node.shortId')) + ) { return false; } - if (selectionTaskGroup.length && !selectionTaskGroup.includes(alloc.taskGroupName)) { + if ( + selectionTaskGroup.length && + !selectionTaskGroup.includes(alloc.taskGroupName) + ) { return false; } return true; @@ -104,28 +121,38 @@ export default class AllocationsController extends Controller.extend( @computed('model.allocations.[]', 'selectionClient') get optionsClients() { - const clients = Array.from(new Set(this.model.allocations.mapBy('node.shortId'))).compact(); + const clients = Array.from( + new Set(this.model.allocations.mapBy('node.shortId')) + ).compact(); // Update query param when the list of clients changes. scheduleOnce('actions', () => { // eslint-disable-next-line ember/no-side-effects - this.set('qpClient', serialize(intersection(clients, this.selectionClient))); + this.set( + 'qpClient', + serialize(intersection(clients, this.selectionClient)) + ); }); - return clients.sort().map(c => ({ key: c, label: c })); + return clients.sort().map((c) => ({ key: c, label: c })); } @computed('model.allocations.[]', 'selectionTaskGroup') get optionsTaskGroups() { - const taskGroups = Array.from(new Set(this.model.allocations.mapBy('taskGroupName'))).compact(); + const taskGroups = Array.from( + new Set(this.model.allocations.mapBy('taskGroupName')) + ).compact(); // Update query param when the list of task groups changes. scheduleOnce('actions', () => { // eslint-disable-next-line ember/no-side-effects - this.set('qpTaskGroup', serialize(intersection(taskGroups, this.selectionTaskGroup))); + this.set( + 'qpTaskGroup', + serialize(intersection(taskGroups, this.selectionTaskGroup)) + ); }); - return taskGroups.sort().map(tg => ({ key: tg, label: tg })); + return taskGroups.sort().map((tg) => ({ key: tg, label: tg })); } setFacetQueryParam(queryParam, selection) { diff --git a/ui/app/controllers/jobs/job/clients.js b/ui/app/controllers/jobs/job/clients.js index 2e14798bf..a4b7f05df 100644 --- a/ui/app/controllers/jobs/job/clients.js +++ b/ui/app/controllers/jobs/job/clients.js @@ -8,15 +8,18 @@ import SortableFactory from 'nomad-ui/mixins/sortable-factory'; import Searchable from 'nomad-ui/mixins/searchable'; import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting'; import jobClientStatus from 'nomad-ui/utils/properties/job-client-status'; -import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize'; +import { + serialize, + deserializedQueryParam as selection, +} from 'nomad-ui/utils/qp-serialize'; import classic from 'ember-classic-decorator'; @classic export default class ClientsController extends Controller.extend( - SortableFactory(['id', 'name', 'jobStatus']), - Searchable, - WithNamespaceResetting - ) { + SortableFactory(['id', 'name', 'jobStatus']), + Searchable, + WithNamespaceResetting +) { queryParams = [ { currentPage: 'page', @@ -71,7 +74,7 @@ export default class ClientsController extends Controller.extend( @computed('allNodes', 'jobClientStatus.byNode') get nodes() { - return this.allNodes.filter(node => this.jobClientStatus.byNode[node.id]); + return this.allNodes.filter((node) => this.jobClientStatus.byNode[node.id]); } @computed @@ -95,8 +98,11 @@ export default class ClientsController extends Controller.extend( } = this; return this.nodes - .filter(node => { - if (statuses.length && !statuses.includes(this.jobClientStatus.byNode[node.id])) { + .filter((node) => { + if ( + statuses.length && + !statuses.includes(this.jobClientStatus.byNode[node.id]) + ) { return false; } if (datacenters.length && !datacenters.includes(node.datacenter)) { @@ -108,8 +114,10 @@ export default class ClientsController extends Controller.extend( return true; }) - .map(node => { - const allocations = this.job.allocations.filter(alloc => alloc.get('node.id') == node.id); + .map((node) => { + const allocations = this.job.allocations.filter( + (alloc) => alloc.get('node.id') == node.id + ); return { node, @@ -137,28 +145,40 @@ export default class ClientsController extends Controller.extend( @computed('selectionDatacenter', 'nodes') get optionsDatacenter() { - const datacenters = Array.from(new Set(this.nodes.mapBy('datacenter'))).compact(); + const datacenters = Array.from( + new Set(this.nodes.mapBy('datacenter')) + ).compact(); // Update query param when the list of datacenters changes. scheduleOnce('actions', () => { // eslint-disable-next-line ember/no-side-effects - this.set('qpDatacenter', serialize(intersection(datacenters, this.selectionDatacenter))); + this.set( + 'qpDatacenter', + serialize(intersection(datacenters, this.selectionDatacenter)) + ); }); - return datacenters.sort().map(dc => ({ key: dc, label: dc })); + return datacenters.sort().map((dc) => ({ key: dc, label: dc })); } @computed('selectionClientClass', 'nodes') get optionsClientClass() { - const clientClasses = Array.from(new Set(this.nodes.mapBy('nodeClass'))).compact(); + const clientClasses = Array.from( + new Set(this.nodes.mapBy('nodeClass')) + ).compact(); // Update query param when the list of datacenters changes. scheduleOnce('actions', () => { // eslint-disable-next-line ember/no-side-effects - this.set('qpClientClass', serialize(intersection(clientClasses, this.selectionClientClass))); + this.set( + 'qpClientClass', + serialize(intersection(clientClasses, this.selectionClientClass)) + ); }); - return clientClasses.sort().map(clientClass => ({ key: clientClass, label: clientClass })); + return clientClasses + .sort() + .map((clientClass) => ({ key: clientClass, label: clientClass })); } @action diff --git a/ui/app/controllers/jobs/job/definition.js b/ui/app/controllers/jobs/job/definition.js index faa94d92f..a428f941b 100644 --- a/ui/app/controllers/jobs/job/definition.js +++ b/ui/app/controllers/jobs/job/definition.js @@ -4,7 +4,9 @@ import { alias } from '@ember/object/computed'; import classic from 'ember-classic-decorator'; @classic -export default class DefinitionController extends Controller.extend(WithNamespaceResetting) { +export default class DefinitionController extends Controller.extend( + WithNamespaceResetting +) { @alias('model.job') job; @alias('model.definition') definition; diff --git a/ui/app/controllers/jobs/job/deployments.js b/ui/app/controllers/jobs/job/deployments.js index cbce847e2..f6b71c167 100644 --- a/ui/app/controllers/jobs/job/deployments.js +++ b/ui/app/controllers/jobs/job/deployments.js @@ -4,6 +4,8 @@ import { alias } from '@ember/object/computed'; import classic from 'ember-classic-decorator'; @classic -export default class DeploymentsController extends Controller.extend(WithNamespaceResetting) { +export default class DeploymentsController extends Controller.extend( + WithNamespaceResetting +) { @alias('model') job; } diff --git a/ui/app/controllers/jobs/job/evaluations.js b/ui/app/controllers/jobs/job/evaluations.js index 831cb3f7c..475038f8d 100644 --- a/ui/app/controllers/jobs/job/evaluations.js +++ b/ui/app/controllers/jobs/job/evaluations.js @@ -6,9 +6,9 @@ import classic from 'ember-classic-decorator'; @classic export default class EvaluationsController extends Controller.extend( - WithNamespaceResetting, - Sortable - ) { + WithNamespaceResetting, + Sortable +) { queryParams = [ { sortProperty: 'sort', diff --git a/ui/app/controllers/jobs/job/index.js b/ui/app/controllers/jobs/job/index.js index b57a44343..9feccefa5 100644 --- a/ui/app/controllers/jobs/job/index.js +++ b/ui/app/controllers/jobs/job/index.js @@ -6,7 +6,9 @@ import { action } from '@ember/object'; import classic from 'ember-classic-decorator'; @classic -export default class IndexController extends Controller.extend(WithNamespaceResetting) { +export default class IndexController extends Controller.extend( + WithNamespaceResetting +) { @service system; queryParams = [ @@ -38,7 +40,11 @@ export default class IndexController extends Controller.extend(WithNamespaceRese @action gotoTaskGroup(taskGroup) { - this.transitionToRoute('jobs.job.task-group', taskGroup.get('job'), taskGroup); + this.transitionToRoute( + 'jobs.job.task-group', + taskGroup.get('job'), + taskGroup + ); } @action diff --git a/ui/app/controllers/jobs/job/task-group.js b/ui/app/controllers/jobs/job/task-group.js index 8a2c39ed9..592f1508e 100644 --- a/ui/app/controllers/jobs/job/task-group.js +++ b/ui/app/controllers/jobs/job/task-group.js @@ -9,15 +9,18 @@ import { qpBuilder } from 'nomad-ui/utils/classes/query-params'; import Sortable from 'nomad-ui/mixins/sortable'; import Searchable from 'nomad-ui/mixins/searchable'; import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting'; -import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize'; +import { + serialize, + deserializedQueryParam as selection, +} from 'nomad-ui/utils/qp-serialize'; import classic from 'ember-classic-decorator'; @classic export default class TaskGroupController extends Controller.extend( - Sortable, - Searchable, - WithNamespaceResetting - ) { + Sortable, + Searchable, + WithNamespaceResetting +) { @service userSettings; @service can; @@ -64,11 +67,17 @@ export default class TaskGroupController extends Controller.extend( get filteredAllocations() { const { selectionStatus, selectionClient } = this; - return this.allocations.filter(alloc => { - if (selectionStatus.length && !selectionStatus.includes(alloc.clientStatus)) { + return this.allocations.filter((alloc) => { + if ( + selectionStatus.length && + !selectionStatus.includes(alloc.clientStatus) + ) { return false; } - if (selectionClient.length && !selectionClient.includes(alloc.get('node.shortId'))) { + if ( + selectionClient.length && + !selectionClient.includes(alloc.get('node.shortId')) + ) { return false; } @@ -83,7 +92,7 @@ export default class TaskGroupController extends Controller.extend( @selection('qpStatus') selectionStatus; @selection('qpClient') selectionClient; - @computed('model.scaleState.events.@each.time', function() { + @computed('model.scaleState.events.@each.time', function () { const events = get(this, 'model.scaleState.events'); if (events) { return events.sortBy('time').reverse(); @@ -92,17 +101,25 @@ export default class TaskGroupController extends Controller.extend( }) sortedScaleEvents; - @computed('sortedScaleEvents.@each.hasCount', function() { + @computed('sortedScaleEvents.@each.hasCount', function () { const countEventsCount = this.sortedScaleEvents.filterBy('hasCount').length; - return countEventsCount > 1 && countEventsCount >= this.sortedScaleEvents.length / 2; + return ( + countEventsCount > 1 && + countEventsCount >= this.sortedScaleEvents.length / 2 + ); }) shouldShowScaleEventTimeline; @computed('model.job.{namespace,runningDeployment}') get tooltipText() { - if (this.can.cannot('scale job', null, { namespace: this.model.job.namespace.get('name') })) + if ( + this.can.cannot('scale job', null, { + namespace: this.model.job.namespace.get('name'), + }) + ) return "You aren't allowed to scale task groups"; - if (this.model.job.runningDeployment) return 'You cannot scale task groups during a deployment'; + if (this.model.job.runningDeployment) + return 'You cannot scale task groups during a deployment'; return undefined; } @@ -128,15 +145,20 @@ export default class TaskGroupController extends Controller.extend( @computed('model.allocations.[]', 'selectionClient') get optionsClients() { - const clients = Array.from(new Set(this.model.allocations.mapBy('node.shortId'))).compact(); + const clients = Array.from( + new Set(this.model.allocations.mapBy('node.shortId')) + ).compact(); // Update query param when the list of clients changes. scheduleOnce('actions', () => { // eslint-disable-next-line ember/no-side-effects - this.set('qpClient', serialize(intersection(clients, this.selectionClient))); + this.set( + 'qpClient', + serialize(intersection(clients, this.selectionClient)) + ); }); - return clients.sort().map(dc => ({ key: dc, label: dc })); + return clients.sort().map((dc) => ({ key: dc, label: dc })); } setFacetQueryParam(queryParam, selection) { diff --git a/ui/app/controllers/jobs/job/versions.js b/ui/app/controllers/jobs/job/versions.js index 087100ea0..195f73619 100644 --- a/ui/app/controllers/jobs/job/versions.js +++ b/ui/app/controllers/jobs/job/versions.js @@ -12,14 +12,18 @@ const errorLevelToAlertClass = { }; @classic -export default class VersionsController extends Controller.extend(WithNamespaceResetting) { +export default class VersionsController extends Controller.extend( + WithNamespaceResetting +) { error = null; @alias('model') job; @computed('error.level') get errorLevelClass() { - return errorLevelToAlertClass[this.get('error.level')] || alertClassFallback; + return ( + errorLevelToAlertClass[this.get('error.level')] || alertClassFallback + ); } onDismiss() { diff --git a/ui/app/controllers/optimize.js b/ui/app/controllers/optimize.js index b5c438a99..53331cc8f 100644 --- a/ui/app/controllers/optimize.js +++ b/ui/app/controllers/optimize.js @@ -7,7 +7,10 @@ import { inject as service } from '@ember/service'; import { scheduleOnce } from '@ember/runloop'; import { task } from 'ember-concurrency'; import intersection from 'lodash.intersection'; -import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize'; +import { + serialize, + deserializedQueryParam as selection, +} from 'nomad-ui/utils/qp-serialize'; import EmberObject, { computed } from '@ember/object'; import { alias } from '@ember/object/computed'; @@ -70,7 +73,7 @@ export default class OptimizeController extends Controller { @selection('qpPrefix') selectionPrefix; get optionsNamespaces() { - const availableNamespaces = this.namespaces.map(namespace => ({ + const availableNamespaces = this.namespaces.map((namespace) => ({ key: namespace.name, label: namespace.name, })); @@ -104,16 +107,20 @@ export default class OptimizeController extends Controller { get optionsDatacenter() { const flatten = (acc, val) => acc.concat(val); - const allDatacenters = new Set(this.summaries.mapBy('job.datacenters').reduce(flatten, [])); + const allDatacenters = new Set( + this.summaries.mapBy('job.datacenters').reduce(flatten, []) + ); // 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.qpDatacenter = serialize(intersection(availableDatacenters, this.selectionDatacenter)); + this.qpDatacenter = serialize( + intersection(availableDatacenters, this.selectionDatacenter) + ); }); - return availableDatacenters.sort().map(dc => ({ key: dc, label: dc })); + return availableDatacenters.sort().map((dc) => ({ key: dc, label: dc })); } get optionsPrefix() { @@ -132,23 +139,25 @@ export default class OptimizeController extends Controller { }, {}); // Convert to an array - const nameTable = Object.keys(nameHistogram).map(key => ({ + const nameTable = Object.keys(nameHistogram).map((key) => ({ prefix: key, count: nameHistogram[key], })); // Only consider prefixes that match more than one name - const prefixes = nameTable.filter(name => name.count > 1); + const prefixes = nameTable.filter((name) => name.count > 1); // 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.qpPrefix = serialize(intersection(availablePrefixes, this.selectionPrefix)); + this.qpPrefix = serialize( + intersection(availablePrefixes, this.selectionPrefix) + ); }); // Sort, format, and include the count in the label - return prefixes.sortBy('prefix').map(name => ({ + return prefixes.sortBy('prefix').map((name) => ({ key: name.prefix, label: `${name.prefix} (${name.count})`, })); @@ -164,14 +173,17 @@ export default class OptimizeController extends Controller { // A summary’s job must match ALL filter facets, but it can match ANY selection within a facet // Always return early to prevent unnecessary facet predicates. - return this.summarySearch.listSearched.filter(summary => { + return this.summarySearch.listSearched.filter((summary) => { const job = summary.get('job'); if (job.isDestroying) { return false; } - if (this.qpNamespace !== '*' && job.get('namespace.name') !== this.qpNamespace) { + if ( + this.qpNamespace !== '*' && + job.get('namespace.name') !== this.qpNamespace + ) { return false; } @@ -183,12 +195,18 @@ export default class OptimizeController extends Controller { return false; } - if (datacenters.length && !job.get('datacenters').find(dc => datacenters.includes(dc))) { + if ( + datacenters.length && + !job.get('datacenters').find((dc) => datacenters.includes(dc)) + ) { return false; } const name = job.get('name'); - if (prefixes.length && !prefixes.find(prefix => name.startsWith(prefix))) { + if ( + prefixes.length && + !prefixes.find((prefix) => name.startsWith(prefix)) + ) { return false; } @@ -206,9 +224,13 @@ export default class OptimizeController extends Controller { // This is a task because the accordion uses timeouts for animation // eslint-disable-next-line require-yield - @(task(function*() { - const currentSummaryIndex = this.filteredSummaries.indexOf(this.activeRecommendationSummary); - const nextSummary = this.filteredSummaries.objectAt(currentSummaryIndex + 1); + @(task(function* () { + const currentSummaryIndex = this.filteredSummaries.indexOf( + this.activeRecommendationSummary + ); + const nextSummary = this.filteredSummaries.objectAt( + currentSummaryIndex + 1 + ); if (nextSummary) { this.transitionToSummary(nextSummary); diff --git a/ui/app/controllers/servers/server/index.js b/ui/app/controllers/servers/server/index.js index 1b511c22f..861a82f97 100644 --- a/ui/app/controllers/servers/server/index.js +++ b/ui/app/controllers/servers/server/index.js @@ -10,7 +10,7 @@ export default class ServerController extends Controller { get sortedTags() { const tags = this.get('model.tags') || {}; return Object.keys(tags) - .map(name => ({ + .map((name) => ({ name, value: tags[name], })) diff --git a/ui/app/controllers/settings/tokens.js b/ui/app/controllers/settings/tokens.js index 3ac048863..7fe6df52f 100644 --- a/ui/app/controllers/settings/tokens.js +++ b/ui/app/controllers/settings/tokens.js @@ -48,9 +48,7 @@ export default class Tokens extends Controller { this.resetStore(); // Refetch the token and associated policies - this.get('token.fetchSelfTokenAndPolicies') - .perform() - .catch(); + this.get('token.fetchSelfTokenAndPolicies').perform().catch(); this.setProperties({ tokenIsValid: true, diff --git a/ui/app/controllers/topology.js b/ui/app/controllers/topology.js index 4b9a38b06..c3866eb5e 100644 --- a/ui/app/controllers/topology.js +++ b/ui/app/controllers/topology.js @@ -31,13 +31,17 @@ export default class TopologyControllers extends Controller { @computed('model.nodes.@each.resources') get totalMemory() { - const mibs = this.model.nodes.mapBy('resources.memory').reduce(sumAggregator, 0); + const mibs = this.model.nodes + .mapBy('resources.memory') + .reduce(sumAggregator, 0); return mibs * 1024 * 1024; } @computed('model.nodes.@each.resources') get totalCPU() { - return this.model.nodes.mapBy('resources.cpu').reduce((sum, cpu) => sum + (cpu || 0), 0); + return this.model.nodes + .mapBy('resources.cpu') + .reduce((sum, cpu) => sum + (cpu || 0), 0); } @computed('totalMemory') @@ -70,7 +74,9 @@ export default class TopologyControllers extends Controller { @computed('scheduledAllocations.@each.allocatedResources') get totalReservedCPU() { - return this.scheduledAllocations.mapBy('allocatedResources.cpu').reduce(sumAggregator, 0); + return this.scheduledAllocations + .mapBy('allocatedResources.cpu') + .reduce(sumAggregator, 0); } @computed('totalMemory', 'totalReservedMemory') @@ -85,23 +91,35 @@ export default class TopologyControllers extends Controller { return this.totalReservedCPU / this.totalCPU; } - @computed('activeAllocation.taskGroupName', 'scheduledAllocations.@each.{job,taskGroupName}') + @computed( + 'activeAllocation.taskGroupName', + 'scheduledAllocations.@each.{job,taskGroupName}' + ) get siblingAllocations() { if (!this.activeAllocation) return []; const taskGroup = this.activeAllocation.taskGroupName; const jobId = this.activeAllocation.belongsTo('job').id(); - return this.scheduledAllocations.filter(allocation => { - return allocation.taskGroupName === taskGroup && allocation.belongsTo('job').id() === jobId; + return this.scheduledAllocations.filter((allocation) => { + return ( + allocation.taskGroupName === taskGroup && + allocation.belongsTo('job').id() === jobId + ); }); } @computed('activeNode') get nodeUtilization() { const node = this.activeNode; - const [formattedMemory, memoryUnits] = reduceBytes(node.memory * 1024 * 1024); - const totalReservedMemory = node.allocations.mapBy('memory').reduce(sumAggregator, 0); - const totalReservedCPU = node.allocations.mapBy('cpu').reduce(sumAggregator, 0); + const [formattedMemory, memoryUnits] = reduceBytes( + node.memory * 1024 * 1024 + ); + const totalReservedMemory = node.allocations + .mapBy('memory') + .reduce(sumAggregator, 0); + const totalReservedCPU = node.allocations + .mapBy('cpu') + .reduce(sumAggregator, 0); return { totalMemoryFormatted: formattedMemory.toFixed(2), diff --git a/ui/app/helpers/bind.js b/ui/app/helpers/bind.js index 36e3bf4e2..d48dab528 100644 --- a/ui/app/helpers/bind.js +++ b/ui/app/helpers/bind.js @@ -9,7 +9,10 @@ import { assert } from '@ember/debug'; * Returns a version of a function bound to the template target (e.g., component or controller) */ export function bind([func, target]) { - assert('A function is required as the first argument', typeof func === 'function'); + assert( + 'A function is required as the first argument', + typeof func === 'function' + ); assert('A context is required as the second argument', target); return func.bind(target); } diff --git a/ui/app/helpers/css-class.js b/ui/app/helpers/css-class.js index d7f30127c..ca4748ed9 100644 --- a/ui/app/helpers/css-class.js +++ b/ui/app/helpers/css-class.js @@ -9,6 +9,7 @@ import { helper } from '@ember/component/helper'; * Differs from dasherize by handling slashes. */ export function cssClass([updateType]) { + /* eslint-disable-next-line ember/no-string-prototype-extensions */ return updateType.replace(/\//g, '-').dasherize(); } diff --git a/ui/app/helpers/is-object.js b/ui/app/helpers/is-object.js index 97dd42e65..53804c904 100644 --- a/ui/app/helpers/is-object.js +++ b/ui/app/helpers/is-object.js @@ -1,7 +1,8 @@ import Helper from '@ember/component/helper'; export function isObject([value]) { - const isObject = !Array.isArray(value) && value !== null && typeof value === 'object'; + const isObject = + !Array.isArray(value) && value !== null && typeof value === 'object'; return isObject; } diff --git a/ui/app/helpers/x-icon.js b/ui/app/helpers/x-icon.js index f53a50543..770428299 100644 --- a/ui/app/helpers/x-icon.js +++ b/ui/app/helpers/x-icon.js @@ -13,7 +13,9 @@ import SVGs from '../svgs'; */ export function xIcon(params, options) { const name = params[0]; - const classes = [options.class, 'icon', `icon-is-${name}`].compact().join(' '); + const classes = [options.class, 'icon', `icon-is-${name}`] + .compact() + .join(' '); return inlineSvg(SVGs, name, { class: classes }); } diff --git a/ui/app/mixins/searchable.js b/ui/app/mixins/searchable.js index 8ef120731..04cc7ef3a 100644 --- a/ui/app/mixins/searchable.js +++ b/ui/app/mixins/searchable.js @@ -25,7 +25,7 @@ import Fuse from 'fuse.js'; // eslint-disable-next-line ember/no-new-mixins export default Mixin.create({ searchTerm: '', - listToSearch: computed(function() { + listToSearch: computed(function () { return []; }), @@ -50,23 +50,28 @@ export default Mixin.create({ } }, - fuse: computed('fuzzySearchProps.[]', 'includeFuzzySearchMatches', 'listToSearch.[]', function() { - return new Fuse(this.listToSearch, { - shouldSort: true, - threshold: 0.4, - location: 0, - distance: 100, - tokenize: true, - matchAllTokens: true, - maxPatternLength: 32, - minMatchCharLength: 1, - includeMatches: this.includeFuzzySearchMatches, - keys: this.fuzzySearchProps || [], - getFn(item, key) { - return get(item, key); - }, - }); - }), + fuse: computed( + 'fuzzySearchProps.[]', + 'includeFuzzySearchMatches', + 'listToSearch.[]', + function () { + return new Fuse(this.listToSearch, { + shouldSort: true, + threshold: 0.4, + location: 0, + distance: 100, + tokenize: true, + matchAllTokens: true, + maxPatternLength: 32, + minMatchCharLength: 1, + includeMatches: this.includeFuzzySearchMatches, + keys: this.fuzzySearchProps || [], + getFn(item, key) { + return get(item, key); + }, + }); + } + ), listSearched: computed( 'exactMatchEnabled', @@ -79,7 +84,7 @@ export default Mixin.create({ 'regexEnabled', 'regexSearchProps.[]', 'searchTerm', - function() { + function () { const searchTerm = this.searchTerm.trim(); if (!searchTerm || !searchTerm.length) { @@ -90,7 +95,11 @@ export default Mixin.create({ if (this.exactMatchEnabled) { results.push( - ...exactMatchSearch(searchTerm, this.listToSearch, this.exactMatchSearchProps) + ...exactMatchSearch( + searchTerm, + this.listToSearch, + this.exactMatchSearchProps + ) ); } @@ -98,7 +107,7 @@ export default Mixin.create({ let fuseSearchResults = this.fuse.search(searchTerm); if (this.includeFuzzySearchMatches) { - fuseSearchResults = fuseSearchResults.map(result => { + fuseSearchResults = fuseSearchResults.map((result) => { const item = result.item; item.set('fuzzySearchMatches', result.matches); return item; @@ -109,7 +118,9 @@ 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(); @@ -119,7 +130,7 @@ export default Mixin.create({ function exactMatchSearch(term, list, keys) { if (term.length) { - return list.filter(item => keys.some(key => get(item, key) === term)); + return list.filter((item) => keys.some((key) => get(item, key) === term)); } } @@ -129,7 +140,9 @@ function regexSearch(term, list, keys) { const regex = new RegExp(term, 'i'); // Test the value of each key for each object against the regex // All that match are returned. - return list.filter(item => keys.some(key => regex.test(get(item, key)))); + return list.filter((item) => + keys.some((key) => regex.test(get(item, key))) + ); } catch (e) { // Swallow the error; most likely due to an eager search of an incomplete regex } diff --git a/ui/app/mixins/sortable-factory.js b/ui/app/mixins/sortable-factory.js index 814d42d6c..38510b9fb 100644 --- a/ui/app/mixins/sortable-factory.js +++ b/ui/app/mixins/sortable-factory.js @@ -19,14 +19,16 @@ import { warn } from '@ember/debug'; - listSorted: a copy of listToSort that has been sorted */ export default function sortableFactory(properties, fromSortableMixin) { - const eachProperties = properties.map(property => `listToSort.@each.${property}`); + 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(function() { + listToSort: computed(function () { return []; }), @@ -38,16 +40,19 @@ export default function sortableFactory(properties, fromSortableMixin) { 'listToSort.[]', 'sortDescending', 'sortProperty', - function() { + function () { if (!this._sortableFactoryWarningPrinted && !Ember.testing) { let message = 'Using SortableFactory without property keys means the list will only sort when the members change, not when any of their properties change.'; if (fromSortableMixin) { - message += ' The Sortable mixin is deprecated in favor of SortableFactory.'; + message += + ' The Sortable mixin is deprecated in favor of SortableFactory.'; } - warn(message, properties.length > 0, { id: 'nomad.no-sortable-properties' }); + warn(message, properties.length > 0, { + id: 'nomad.no-sortable-properties', + }); // eslint-disable-next-line ember/no-side-effects this.set('_sortableFactoryWarningPrinted', true); } diff --git a/ui/app/mixins/window-resizable.js b/ui/app/mixins/window-resizable.js index d5d2eb159..164cb4c66 100644 --- a/ui/app/mixins/window-resizable.js +++ b/ui/app/mixins/window-resizable.js @@ -6,10 +6,13 @@ 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); + assert( + 'windowResizeHandler needs to be overridden in the Component', + false + ); }, - setupWindowResize: on('didInsertElement', function() { + setupWindowResize: on('didInsertElement', function () { run.scheduleOnce('afterRender', this, this.addResizeListener); }), @@ -18,7 +21,7 @@ export default Mixin.create({ window.addEventListener('resize', this._windowResizeHandler); }, - removeWindowResize: on('willDestroyElement', function() { + removeWindowResize: on('willDestroyElement', function () { window.removeEventListener('resize', this._windowResizeHandler); }), }); diff --git a/ui/app/mixins/with-component-visibility-detection.js b/ui/app/mixins/with-component-visibility-detection.js index 57c341930..96d76dffd 100644 --- a/ui/app/mixins/with-component-visibility-detection.js +++ b/ui/app/mixins/with-component-visibility-detection.js @@ -9,14 +9,14 @@ export default Mixin.create({ assert('visibilityHandler needs to be overridden in the Component', false); }, - setupDocumentVisibility: on('init', function() { + setupDocumentVisibility: on('init', function () { if (!Ember.testing) { this.set('_visibilityHandler', this.visibilityHandler.bind(this)); document.addEventListener('visibilitychange', this._visibilityHandler); } }), - removeDocumentVisibility: on('init', function() { + removeDocumentVisibility: on('init', function () { if (!Ember.testing) { document.removeEventListener('visibilitychange', this._visibilityHandler); } diff --git a/ui/app/mixins/with-route-visibility-detection.js b/ui/app/mixins/with-route-visibility-detection.js index 707146df8..e54f4da5d 100644 --- a/ui/app/mixins/with-route-visibility-detection.js +++ b/ui/app/mixins/with-route-visibility-detection.js @@ -9,14 +9,14 @@ export default Mixin.create({ assert('visibilityHandler needs to be overridden in the Route', false); }, - setupDocumentVisibility: on('activate', function() { + setupDocumentVisibility: on('activate', function () { if (!Ember.testing) { this.set('_visibilityHandler', this.visibilityHandler.bind(this)); document.addEventListener('visibilitychange', this._visibilityHandler); } }), - removeDocumentVisibility: on('deactivate', function() { + removeDocumentVisibility: on('deactivate', function () { if (!Ember.testing) { document.removeEventListener('visibilitychange', this._visibilityHandler); } diff --git a/ui/app/mixins/with-watchers.js b/ui/app/mixins/with-watchers.js index 811a2c792..b74a2b031 100644 --- a/ui/app/mixins/with-watchers.js +++ b/ui/app/mixins/with-watchers.js @@ -5,12 +5,12 @@ import WithVisibilityDetection from './with-route-visibility-detection'; // eslint-disable-next-line ember/no-new-mixins export default Mixin.create(WithVisibilityDetection, { - watchers: computed(function() { + watchers: computed(function () { return []; }), cancelAllWatchers() { - this.watchers.forEach(watcher => { + this.watchers.forEach((watcher) => { assert('Watchers must be Ember Concurrency Tasks.', !!watcher.cancelAll); watcher.cancelAll(); }); @@ -36,7 +36,10 @@ export default Mixin.create(WithVisibilityDetection, { actions: { willTransition(transition) { // Don't cancel watchers if transitioning into a sub-route - if (!transition.intent.name || !transition.intent.name.startsWith(this.routeName)) { + if ( + !transition.intent.name || + !transition.intent.name.startsWith(this.routeName) + ) { this.cancelAllWatchers(); } diff --git a/ui/app/models/allocation.js b/ui/app/models/allocation.js index 79db30dd1..45d42727e 100644 --- a/ui/app/models/allocation.js +++ b/ui/app/models/allocation.js @@ -35,9 +35,7 @@ export default class Allocation extends Model { @attr('string') nodeName; @computed get shortNodeId() { - return this.belongsTo('node') - .id() - .split('-')[0]; + return this.belongsTo('node').id().split('-')[0]; } @attr('number') modifyIndex; @@ -77,8 +75,10 @@ export default class Allocation extends Model { @belongsTo('allocation', { inverse: 'nextAllocation' }) previousAllocation; @belongsTo('allocation', { inverse: 'previousAllocation' }) nextAllocation; - @hasMany('allocation', { inverse: 'preemptedByAllocation' }) preemptedAllocations; - @belongsTo('allocation', { inverse: 'preemptedAllocations' }) preemptedByAllocation; + @hasMany('allocation', { inverse: 'preemptedByAllocation' }) + preemptedAllocations; + @belongsTo('allocation', { inverse: 'preemptedAllocations' }) + preemptedByAllocation; @attr('boolean') wasPreempted; @belongsTo('evaluation') followUpEvaluation; @@ -135,7 +135,11 @@ export default class Allocation extends Model { return this.get('rescheduleEvents.length') > 0 || this.nextAllocation; } - @computed('clientStatus', 'followUpEvaluation.content', 'nextAllocation.content') + @computed( + 'clientStatus', + 'followUpEvaluation.content', + 'nextAllocation.content' + ) get hasStoppedRescheduling() { return ( !this.get('nextAllocation.content') && diff --git a/ui/app/models/deployment.js b/ui/app/models/deployment.js index 97504e01d..0136f6380 100644 --- a/ui/app/models/deployment.js +++ b/ui/app/models/deployment.js @@ -24,7 +24,10 @@ export default class Deployment extends Model { this.status === 'running' && this.taskGroupSummaries .toArray() - .some(summary => summary.get('requiresPromotion') && !summary.get('promoted')) + .some( + (summary) => + summary.get('requiresPromotion') && !summary.get('promoted') + ) ); } @@ -38,7 +41,10 @@ export default class Deployment extends Model { @computed('versionNumber', 'job.versions.content.@each.number') get version() { - return (this.get('job.versions') || []).findBy('number', this.versionNumber); + return (this.get('job.versions') || []).findBy( + 'number', + this.versionNumber + ); } // Dependent keys can only go one level past an @each so an alias is needed @@ -65,7 +71,10 @@ export default class Deployment extends Model { } promote() { - assert('A deployment needs to requirePromotion to be promoted', this.requiresPromotion); + assert( + 'A deployment needs to requirePromotion to be promoted', + this.requiresPromotion + ); return this.store.adapterFor('deployment').promote(this); } diff --git a/ui/app/models/evaluation.js b/ui/app/models/evaluation.js index 8cd50ba2a..944d03e10 100644 --- a/ui/app/models/evaluation.js +++ b/ui/app/models/evaluation.js @@ -11,7 +11,8 @@ export default class Evaluation extends Model { @attr('string') triggeredBy; @attr('string') status; @attr('string') statusDescription; - @fragmentArray('placement-failure', { defaultValue: () => [] }) failedTGAllocs; + @fragmentArray('placement-failure', { defaultValue: () => [] }) + failedTGAllocs; @bool('failedTGAllocs.length') hasPlacementFailures; @equal('status', 'blocked') isBlocked; diff --git a/ui/app/models/job-plan.js b/ui/app/models/job-plan.js index b7a06db06..7cebc1cc0 100644 --- a/ui/app/models/job-plan.js +++ b/ui/app/models/job-plan.js @@ -5,6 +5,7 @@ import { hasMany } from '@ember-data/model'; export default class JobPlan extends Model { @attr() diff; - @fragmentArray('placement-failure', { defaultValue: () => [] }) failedTGAllocs; + @fragmentArray('placement-failure', { defaultValue: () => [] }) + failedTGAllocs; @hasMany('allocation') preemptions; } diff --git a/ui/app/models/job.js b/ui/app/models/job.js index 43c82e5d1..b157592ef 100644 --- a/ui/app/models/job.js +++ b/ui/app/models/job.js @@ -51,7 +51,9 @@ export default class Job extends Model { // The parent job name is prepended to child launch job names @computed('name', 'parent.content') get trimmedName() { - return this.get('parent.content') ? this.name.replace(/.+?\//, '') : this.name; + return this.get('parent.content') + ? this.name.replace(/.+?\//, '') + : this.name; } // A composite of type and other job attributes to determine @@ -69,7 +71,12 @@ export default class Job extends Model { // A composite of type and other job attributes to determine // type for templating rather than scheduling - @computed('type', 'periodic', 'parameterized', 'parent.{periodic,parameterized}') + @computed( + 'type', + 'periodic', + 'parameterized', + 'parent.{periodic,parameterized}' + ) get templateType() { const type = this.type; @@ -158,7 +165,9 @@ export default class Job extends Model { @computed('evaluations.@each.isBlocked') get hasBlockedEvaluation() { - return this.evaluations.toArray().some(evaluation => evaluation.get('isBlocked')); + return this.evaluations + .toArray() + .some((evaluation) => evaluation.get('isBlocked')); } @and('latestFailureEvaluation', 'hasBlockedEvaluation') hasPlacementFailures; @@ -246,7 +255,7 @@ export default class Job extends Model { promise = this.store .adapterFor('job') .parse(this._newDefinition) - .then(response => { + .then((response) => { this.set('_newDefinitionJSON', response); this.setIdByPayload(response); }); @@ -256,7 +265,8 @@ export default class Job extends Model { } scale(group, count, message) { - if (message == null) message = `Manually scaled to ${count} from the Nomad UI`; + if (message == null) + message = `Manually scaled to ${count} from the Nomad UI`; return this.store.adapterFor('job').scale(this, group, count, message); } @@ -278,7 +288,10 @@ export default class Job extends Model { } resetId() { - this.set('id', JSON.stringify([this.plainId, this.get('namespace.name') || 'default'])); + this.set( + 'id', + JSON.stringify([this.plainId, this.get('namespace.name') || 'default']) + ); } @computed('status') diff --git a/ui/app/models/node.js b/ui/app/models/node.js index abb0958a2..3e985bd4d 100644 --- a/ui/app/models/node.js +++ b/ui/app/models/node.js @@ -63,7 +63,9 @@ export default class Node extends Model { @computed('allocations.@each.{isMigrating,isRunning}') get migratingAllocations() { - return this.allocations.filter(alloc => alloc.isRunning && alloc.isMigrating); + return this.allocations.filter( + (alloc) => alloc.isRunning && alloc.isMigrating + ); } @computed('allocations.@each.{isMigrating,isRunning,modifyTime}') diff --git a/ui/app/models/recommendation-summary.js b/ui/app/models/recommendation-summary.js index 459b780e5..e3ca79226 100644 --- a/ui/app/models/recommendation-summary.js +++ b/ui/app/models/recommendation-summary.js @@ -5,7 +5,8 @@ import { action } from '@ember/object'; export default class RecommendationSummary extends Model { @hasMany('recommendation') recommendations; - @hasMany('recommendation', { defaultValue: () => [] }) excludedRecommendations; + @hasMany('recommendation', { defaultValue: () => [] }) + excludedRecommendations; @belongsTo('job') job; @attr('string') jobId; @@ -30,7 +31,8 @@ export default class RecommendationSummary extends Model { @action toggleRecommendation(recommendation) { if (this.excludedRecommendations.includes(recommendation)) { - this.excludedRecommendations = this.excludedRecommendations.removeObject(recommendation); + this.excludedRecommendations = + this.excludedRecommendations.removeObject(recommendation); } else { this.excludedRecommendations.pushObject(recommendation); } @@ -39,9 +41,14 @@ export default class RecommendationSummary extends Model { @action toggleAllRecommendationsForResource(resource, enabled) { if (enabled) { - this.excludedRecommendations = this.excludedRecommendations.rejectBy('resource', resource); + this.excludedRecommendations = this.excludedRecommendations.rejectBy( + 'resource', + resource + ); } else { - this.excludedRecommendations.pushObjects(this.recommendations.filterBy('resource', resource)); + this.excludedRecommendations.pushObjects( + this.recommendations.filterBy('resource', resource) + ); } } diff --git a/ui/app/models/recommendation.js b/ui/app/models/recommendation.js index cb36e2504..36e2ed341 100644 --- a/ui/app/models/recommendation.js +++ b/ui/app/models/recommendation.js @@ -4,7 +4,8 @@ import { get } from '@ember/object'; export default class Recommendation extends Model { @belongsTo('job') job; - @belongsTo('recommendation-summary', { inverse: 'recommendations' }) recommendationSummary; + @belongsTo('recommendation-summary', { inverse: 'recommendations' }) + recommendationSummary; @attr('date') submitTime; @@ -22,7 +23,8 @@ export default class Recommendation extends Model { @attr('number') value; get currentValue() { - const resourceProperty = this.resource === 'CPU' ? 'reservedCPU' : 'reservedMemory'; + const resourceProperty = + this.resource === 'CPU' ? 'reservedCPU' : 'reservedMemory'; return get(this, `task.${resourceProperty}`); } diff --git a/ui/app/models/scale-event.js b/ui/app/models/scale-event.js index 1ff4063c7..083f35a51 100644 --- a/ui/app/models/scale-event.js +++ b/ui/app/models/scale-event.js @@ -11,12 +11,12 @@ export default class ScaleEvent extends Fragment { @attr('boolean') error; @attr('string') evalId; - @computed('count', function() { + @computed('count', function () { return this.count != null; }) hasCount; - @computed('count', 'previousCount', function() { + @computed('count', 'previousCount', function () { return this.count > this.previousCount; }) increased; @@ -27,13 +27,13 @@ export default class ScaleEvent extends Fragment { @attr('string') message; @attr() meta; - @computed('meta', function() { + @computed('meta', function () { return Object.keys(this.meta).length > 0; }) hasMeta; // Since scale events don't have proper IDs, this UID is a compromise - @computed('time', 'timeNanos', 'message', function() { + @computed('time', 'timeNanos', 'message', function () { return `${+this.time}${this.timeNanos}_${this.message}`; }) uid; diff --git a/ui/app/models/task-group-scale.js b/ui/app/models/task-group-scale.js index 5429a2837..9eb5238d4 100644 --- a/ui/app/models/task-group-scale.js +++ b/ui/app/models/task-group-scale.js @@ -1,7 +1,10 @@ import { computed } from '@ember/object'; import Fragment from 'ember-data-model-fragments/fragment'; import { attr } from '@ember-data/model'; -import { fragmentOwner, fragmentArray } from 'ember-data-model-fragments/attributes'; +import { + fragmentOwner, + fragmentArray, +} from 'ember-data-model-fragments/attributes'; export default class TaskGroupScale extends Fragment { @fragmentOwner() jobScale; diff --git a/ui/app/models/task-group.js b/ui/app/models/task-group.js index 5ea8f2667..afeafe69c 100644 --- a/ui/app/models/task-group.js +++ b/ui/app/models/task-group.js @@ -1,11 +1,15 @@ import { computed } from '@ember/object'; import Fragment from 'ember-data-model-fragments/fragment'; import { attr } from '@ember-data/model'; -import { fragmentOwner, fragmentArray, fragment } from 'ember-data-model-fragments/attributes'; +import { + fragmentOwner, + fragmentArray, + fragment, +} from 'ember-data-model-fragments/attributes'; import sumAggregation from '../utils/properties/sum-aggregation'; import classic from 'ember-classic-decorator'; -const maybe = arr => arr || []; +const maybe = (arr) => arr || []; @classic export default class TaskGroup extends Fragment { @@ -39,7 +43,10 @@ export default class TaskGroup extends Fragment { @computed('job.allocations.@each.taskGroup', 'name') get allocations() { - return maybe(this.get('job.allocations')).filterBy('taskGroupName', this.name); + return maybe(this.get('job.allocations')).filterBy( + 'taskGroupName', + this.name + ); } @sumAggregation('tasks', 'reservedCPU') reservedCPU; @@ -49,7 +56,7 @@ export default class TaskGroup extends Fragment { @computed('tasks.@each.{reservedMemory,reservedMemoryMax}') get reservedMemoryMax() { return this.get('tasks') - .map(t => t.get('reservedMemoryMax') || t.get('reservedMemory')) + .map((t) => t.get('reservedMemoryMax') || t.get('reservedMemory')) .reduce((sum, count) => sum + count, 0); } @@ -57,13 +64,17 @@ export default class TaskGroup extends Fragment { @computed('job.latestFailureEvaluation.failedTGAllocs.[]', 'name') get placementFailures() { - const placementFailures = this.get('job.latestFailureEvaluation.failedTGAllocs'); + const placementFailures = this.get( + 'job.latestFailureEvaluation.failedTGAllocs' + ); return placementFailures && placementFailures.findBy('name', this.name); } @computed('summary.{queuedAllocs,startingAllocs}') get queuedOrStartingAllocs() { - return this.get('summary.queuedAllocs') + this.get('summary.startingAllocs'); + return ( + this.get('summary.queuedAllocs') + this.get('summary.startingAllocs') + ); } @computed('job.taskGroupSummaries.[]', 'name') @@ -73,7 +84,10 @@ export default class TaskGroup extends Fragment { @computed('job.scaleState.taskGroupScales.[]', 'name') get scaleState() { - return maybe(this.get('job.scaleState.taskGroupScales')).findBy('name', this.name); + return maybe(this.get('job.scaleState.taskGroupScales')).findBy( + 'name', + this.name + ); } scale(count, message) { diff --git a/ui/app/models/task-state.js b/ui/app/models/task-state.js index 416a89c8e..0f80cdc30 100644 --- a/ui/app/models/task-state.js +++ b/ui/app/models/task-state.js @@ -2,7 +2,11 @@ import { computed } from '@ember/object'; import { alias, none, and } from '@ember/object/computed'; import Fragment from 'ember-data-model-fragments/fragment'; import { attr } from '@ember-data/model'; -import { fragment, fragmentOwner, fragmentArray } from 'ember-data-model-fragments/attributes'; +import { + fragment, + fragmentOwner, + fragmentArray, +} from 'ember-data-model-fragments/attributes'; import classic from 'ember-classic-decorator'; @classic diff --git a/ui/app/models/task.js b/ui/app/models/task.js index bf9a0a3eb..83c22f64e 100644 --- a/ui/app/models/task.js +++ b/ui/app/models/task.js @@ -1,6 +1,10 @@ import { attr } from '@ember-data/model'; import Fragment from 'ember-data-model-fragments/fragment'; -import { fragment, fragmentArray, fragmentOwner } from 'ember-data-model-fragments/attributes'; +import { + fragment, + fragmentArray, + fragmentOwner, +} from 'ember-data-model-fragments/attributes'; import { computed } from '@ember/object'; export default class Task extends Fragment { diff --git a/ui/app/models/volume.js b/ui/app/models/volume.js index 6e290af5f..f7de4b1f0 100644 --- a/ui/app/models/volume.js +++ b/ui/app/models/volume.js @@ -14,7 +14,10 @@ export default class Volume extends Model { @computed('writeAllocations.[]', 'readAllocations.[]') get allocations() { - return [...this.writeAllocations.toArray(), ...this.readAllocations.toArray()]; + return [ + ...this.writeAllocations.toArray(), + ...this.readAllocations.toArray(), + ]; } @attr('number') currentWriters; diff --git a/ui/app/modifiers/window-resize.js b/ui/app/modifiers/window-resize.js index f70b20976..d361a652f 100644 --- a/ui/app/modifiers/window-resize.js +++ b/ui/app/modifiers/window-resize.js @@ -1,7 +1,7 @@ import { modifier } from 'ember-modifier'; export default modifier(function windowResize(element, [handler]) { - const boundHandler = ev => handler(element, ev); + const boundHandler = (ev) => handler(element, ev); window.addEventListener('resize', boundHandler); return () => { diff --git a/ui/app/router.js b/ui/app/router.js index 8e5c20708..d567714f1 100644 --- a/ui/app/router.js +++ b/ui/app/router.js @@ -6,16 +6,16 @@ export default class Router extends EmberRouter { rootURL = config.rootURL; } -Router.map(function() { - this.route('exec', { path: '/exec/:job_name' }, function() { - this.route('task-group', { path: '/:task_group_name' }, function() { +Router.map(function () { + this.route('exec', { path: '/exec/:job_name' }, function () { + this.route('task-group', { path: '/:task_group_name' }, function () { this.route('task', { path: '/:task_name' }); }); }); - this.route('jobs', function() { + this.route('jobs', function () { this.route('run'); - this.route('job', { path: '/:job_name' }, function() { + this.route('job', { path: '/:job_name' }, function () { this.route('task-group', { path: '/:name' }); this.route('definition'); this.route('versions'); @@ -27,42 +27,42 @@ Router.map(function() { }); }); - this.route('optimize', function() { + this.route('optimize', function () { this.route('summary', { path: '*slug' }); }); - this.route('clients', function() { - this.route('client', { path: '/:node_id' }, function() { + this.route('clients', function () { + this.route('client', { path: '/:node_id' }, function () { this.route('monitor'); }); }); - this.route('servers', function() { - this.route('server', { path: '/:agent_id' }, function() { + this.route('servers', function () { + this.route('server', { path: '/:agent_id' }, function () { this.route('monitor'); }); }); this.route('topology'); - this.route('csi', function() { - this.route('volumes', function() { + this.route('csi', function () { + this.route('volumes', function () { this.route('volume', { path: '/:volume_name' }); }); - this.route('plugins', function() { - this.route('plugin', { path: '/:plugin_name' }, function() { + this.route('plugins', function () { + this.route('plugin', { path: '/:plugin_name' }, function () { this.route('allocations'); }); }); }); - this.route('allocations', function() { - this.route('allocation', { path: '/:allocation_id' }, function() { + this.route('allocations', function () { + this.route('allocation', { path: '/:allocation_id' }, function () { this.route('fs-root', { path: '/fs' }); this.route('fs', { path: '/fs/*path' }); - this.route('task', { path: '/:name' }, function() { + this.route('task', { path: '/:name' }, function () { this.route('logs'); this.route('fs-root', { path: '/fs' }); this.route('fs', { path: '/fs/*path' }); @@ -70,7 +70,7 @@ Router.map(function() { }); }); - this.route('settings', function() { + this.route('settings', function () { this.route('tokens'); }); diff --git a/ui/app/routes/allocations/allocation.js b/ui/app/routes/allocations/allocation.js index bb5ce7bfc..f95ebc31e 100644 --- a/ui/app/routes/allocations/allocation.js +++ b/ui/app/routes/allocations/allocation.js @@ -17,7 +17,7 @@ export default class AllocationRoute extends Route.extend(WithWatchers) { // Preload the job for the allocation since it's required for the breadcrumb trail return super .model(...arguments) - .then(allocation => + .then((allocation) => allocation .get('job') .then(() => this.store.findAll('namespace')) // namespaces belong to a job and are an asynchronous relationship so we can peak them later on diff --git a/ui/app/routes/allocations/allocation/fs.js b/ui/app/routes/allocations/allocation/fs.js index c988b7e05..1f01725df 100644 --- a/ui/app/routes/allocations/allocation/fs.js +++ b/ui/app/routes/allocations/allocation/fs.js @@ -15,7 +15,9 @@ export default class FsRoute extends Route { return RSVP.hash({ path: decodedPath, allocation, - directoryEntries: allocation.ls(decodedPath).catch(notifyError(this)), + directoryEntries: allocation + .ls(decodedPath) + .catch(notifyError(this)), isFile: false, }); } else { @@ -30,8 +32,17 @@ export default class FsRoute extends Route { .catch(notifyError(this)); } - setupController(controller, { path, allocation, directoryEntries, isFile, stat } = {}) { + setupController( + controller, + { path, allocation, directoryEntries, isFile, stat } = {} + ) { super.setupController(...arguments); - controller.setProperties({ path, allocation, directoryEntries, isFile, stat }); + controller.setProperties({ + path, + allocation, + directoryEntries, + isFile, + stat, + }); } } diff --git a/ui/app/routes/allocations/allocation/index.js b/ui/app/routes/allocations/allocation/index.js index e4de01cdb..672ce3330 100644 --- a/ui/app/routes/allocations/allocation/index.js +++ b/ui/app/routes/allocations/allocation/index.js @@ -4,7 +4,8 @@ export default class IndexRoute extends Route { setupController(controller, model) { // Suppress the preemptedByAllocation fetch error in the event it's a 404 if (model) { - const setPreempter = () => controller.set('preempter', model.preemptedByAllocation); + const setPreempter = () => + controller.set('preempter', model.preemptedByAllocation); model.preemptedByAllocation.then(setPreempter, setPreempter); } diff --git a/ui/app/routes/allocations/allocation/task.js b/ui/app/routes/allocations/allocation/task.js index 03be52f3c..8513aecbc 100644 --- a/ui/app/routes/allocations/allocation/task.js +++ b/ui/app/routes/allocations/allocation/task.js @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-controller-access-in-routes */ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; import EmberError from '@ember/error'; @@ -15,7 +16,9 @@ export default class TaskRoute extends Route { const task = allocation.get('states').findBy('name', name); if (!task) { - const err = new EmberError(`Task ${name} not found for allocation ${allocation.get('id')}`); + const err = new EmberError( + `Task ${name} not found for allocation ${allocation.get('id')}` + ); err.code = '404'; this.controllerFor('application').set('error', err); } diff --git a/ui/app/routes/allocations/allocation/task/fs.js b/ui/app/routes/allocations/allocation/task/fs.js index 8ba69555c..3143c9017 100644 --- a/ui/app/routes/allocations/allocation/task/fs.js +++ b/ui/app/routes/allocations/allocation/task/fs.js @@ -14,13 +14,18 @@ export default class FsRoute extends Route { decodedPath.startsWith('/') ? '' : '/' }${decodedPath}`; - return RSVP.all([allocation.stat(pathWithTaskName), taskState.get('allocation.node')]) + return RSVP.all([ + allocation.stat(pathWithTaskName), + taskState.get('allocation.node'), + ]) .then(([statJson]) => { if (statJson.IsDir) { return RSVP.hash({ path: decodedPath, taskState, - directoryEntries: allocation.ls(pathWithTaskName).catch(notifyError(this)), + directoryEntries: allocation + .ls(pathWithTaskName) + .catch(notifyError(this)), isFile: false, }); } else { @@ -35,8 +40,17 @@ export default class FsRoute extends Route { .catch(notifyError(this)); } - setupController(controller, { path, taskState, directoryEntries, isFile, stat } = {}) { + setupController( + controller, + { path, taskState, directoryEntries, isFile, stat } = {} + ) { super.setupController(...arguments); - controller.setProperties({ path, taskState, directoryEntries, isFile, stat }); + controller.setProperties({ + path, + taskState, + directoryEntries, + isFile, + stat, + }); } } diff --git a/ui/app/routes/application.js b/ui/app/routes/application.js index f92fd5b3c..be815bd44 100644 --- a/ui/app/routes/application.js +++ b/ui/app/routes/application.js @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-controller-access-in-routes */ import { inject as service } from '@ember/service'; import { later, next } from '@ember/runloop'; import Route from '@ember/routing/route'; @@ -33,11 +34,12 @@ export default class ApplicationRoute extends Route { if (transition.queryParamsOnly) { promises = Promise.resolve(true); } else { - let exchangeOneTimeToken; if (transition.to.queryParams.ott) { - exchangeOneTimeToken = this.get('token').exchangeOneTimeToken(transition.to.queryParams.ott); + exchangeOneTimeToken = this.get('token').exchangeOneTimeToken( + transition.to.queryParams.ott + ); } else { exchangeOneTimeToken = Promise.resolve(true); } @@ -48,15 +50,17 @@ export default class ApplicationRoute extends Route { this.controllerFor('application').set('error', e); } - const fetchSelfTokenAndPolicies = this.get('token.fetchSelfTokenAndPolicies') + const fetchSelfTokenAndPolicies = this.get( + 'token.fetchSelfTokenAndPolicies' + ) .perform() .catch(); - const fetchLicense = this.get('system.fetchLicense') - .perform() - .catch(); + const fetchLicense = this.get('system.fetchLicense').perform().catch(); - const checkFuzzySearchPresence = this.get('system.checkFuzzySearchPresence') + const checkFuzzySearchPresence = this.get( + 'system.checkFuzzySearchPresence' + ) .perform() .catch(); diff --git a/ui/app/routes/clients/client.js b/ui/app/routes/clients/client.js index 7dc94a49d..3593cf107 100644 --- a/ui/app/routes/clients/client.js +++ b/ui/app/routes/clients/client.js @@ -11,7 +11,7 @@ export default class ClientRoute extends Route { afterModel(model) { if (model && model.get('isPartial')) { - return model.reload().then(node => node.get('allocations')); + return model.reload().then((node) => node.get('allocations')); } return model && model.get('allocations'); } diff --git a/ui/app/routes/clients/client/index.js b/ui/app/routes/clients/client/index.js index 04f66faa6..b1383ca47 100644 --- a/ui/app/routes/clients/client/index.js +++ b/ui/app/routes/clients/client/index.js @@ -1,7 +1,10 @@ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; import { collect } from '@ember/object/computed'; -import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; +import { + watchRecord, + watchRelationship, +} from 'nomad-ui/utils/properties/watch'; import WithWatchers from 'nomad-ui/mixins/with-watchers'; export default class ClientRoute extends Route.extend(WithWatchers) { diff --git a/ui/app/routes/csi/plugins.js b/ui/app/routes/csi/plugins.js index 25728b2d2..9d24bc239 100644 --- a/ui/app/routes/csi/plugins.js +++ b/ui/app/routes/csi/plugins.js @@ -7,6 +7,8 @@ export default class PluginsRoute extends Route.extend(WithForbiddenState) { @service store; model() { - return this.store.query('plugin', { type: 'csi' }).catch(notifyForbidden(this)); + return this.store + .query('plugin', { type: 'csi' }) + .catch(notifyForbidden(this)); } } diff --git a/ui/app/routes/csi/plugins/plugin.js b/ui/app/routes/csi/plugins/plugin.js index 9728e5be1..41cbfb381 100644 --- a/ui/app/routes/csi/plugins/plugin.js +++ b/ui/app/routes/csi/plugins/plugin.js @@ -11,6 +11,8 @@ export default class PluginRoute extends Route { } model(params) { - return this.store.findRecord('plugin', `csi/${params.plugin_name}`).catch(notifyError(this)); + return this.store + .findRecord('plugin', `csi/${params.plugin_name}`) + .catch(notifyError(this)); } } diff --git a/ui/app/routes/csi/volumes/index.js b/ui/app/routes/csi/volumes/index.js index f2db7cfa0..62d3db557 100644 --- a/ui/app/routes/csi/volumes/index.js +++ b/ui/app/routes/csi/volumes/index.js @@ -7,7 +7,10 @@ import WithWatchers from 'nomad-ui/mixins/with-watchers'; import notifyForbidden from 'nomad-ui/utils/notify-forbidden'; import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state'; -export default class IndexRoute extends Route.extend(WithWatchers, WithForbiddenState) { +export default class IndexRoute extends Route.extend( + WithWatchers, + WithForbiddenState +) { @service store; queryParams = { @@ -29,7 +32,10 @@ export default class IndexRoute extends Route.extend(WithWatchers, WithForbidden controller.set('namespacesWatch', this.watchNamespaces.perform()); controller.set( 'modelWatch', - this.watchVolumes.perform({ type: 'csi', namespace: controller.qpNamespace }) + this.watchVolumes.perform({ + type: 'csi', + namespace: controller.qpNamespace, + }) ); } diff --git a/ui/app/routes/csi/volumes/volume.js b/ui/app/routes/csi/volumes/volume.js index 01afa4b4c..e5722374a 100644 --- a/ui/app/routes/csi/volumes/volume.js +++ b/ui/app/routes/csi/volumes/volume.js @@ -32,7 +32,7 @@ export default class VolumeRoute extends Route.extend(WithWatchers) { volume: this.store.findRecord('volume', fullId, { reload: true }), namespaces: this.store.findAll('namespace'), }) - .then(hash => hash.volume) + .then((hash) => hash.volume) .catch(notifyError(this)); } diff --git a/ui/app/routes/exec.js b/ui/app/routes/exec.js index 4a9d009bd..33493e7a4 100644 --- a/ui/app/routes/exec.js +++ b/ui/app/routes/exec.js @@ -3,7 +3,10 @@ import Route from '@ember/routing/route'; import notifyError from 'nomad-ui/utils/notify-error'; import { collect } from '@ember/object/computed'; import WithWatchers from 'nomad-ui/mixins/with-watchers'; -import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; +import { + watchRecord, + watchRelationship, +} from 'nomad-ui/utils/properties/watch'; import classic from 'ember-classic-decorator'; @classic @@ -22,12 +25,12 @@ export default class ExecRoute extends Route.extend(WithWatchers) { const jobPromise = this.store .findRecord('job', fullId) - .then(job => { + .then((job) => { return job.get('allocations').then(() => job); }) .catch(notifyError(this)); - const xtermImport = import('xterm').then(module => module.Terminal); + const xtermImport = import('xterm').then((module) => module.Terminal); return Promise.all([jobPromise, xtermImport]); } diff --git a/ui/app/routes/exec/task-group/task.js b/ui/app/routes/exec/task-group/task.js index 39ce0a899..b20d209b5 100644 --- a/ui/app/routes/exec/task-group/task.js +++ b/ui/app/routes/exec/task-group/task.js @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-controller-access-in-routes */ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; diff --git a/ui/app/routes/jobs/index.js b/ui/app/routes/jobs/index.js index a9d4edf39..93bc9d39e 100644 --- a/ui/app/routes/jobs/index.js +++ b/ui/app/routes/jobs/index.js @@ -7,7 +7,10 @@ import WithWatchers from 'nomad-ui/mixins/with-watchers'; import notifyForbidden from 'nomad-ui/utils/notify-forbidden'; import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state'; -export default class IndexRoute extends Route.extend(WithWatchers, WithForbiddenState) { +export default class IndexRoute extends Route.extend( + WithWatchers, + WithForbiddenState +) { @service store; queryParams = { @@ -18,14 +21,19 @@ export default class IndexRoute extends Route.extend(WithWatchers, WithForbidden model(params) { return RSVP.hash({ - jobs: this.store.query('job', { namespace: params.qpNamespace }).catch(notifyForbidden(this)), + jobs: this.store + .query('job', { namespace: params.qpNamespace }) + .catch(notifyForbidden(this)), namespaces: this.store.findAll('namespace'), }); } startWatchers(controller) { controller.set('namespacesWatch', this.watchNamespaces.perform()); - controller.set('modelWatch', this.watchJobs.perform({ namespace: controller.qpNamesapce })); + controller.set( + 'modelWatch', + this.watchJobs.perform({ namespace: controller.qpNamesapce }) + ); } @watchQuery('job') watchJobs; diff --git a/ui/app/routes/jobs/job.js b/ui/app/routes/jobs/job.js index 971d5be80..3a01c84ba 100644 --- a/ui/app/routes/jobs/job.js +++ b/ui/app/routes/jobs/job.js @@ -21,7 +21,7 @@ export default class JobRoute extends Route { return this.store .findRecord('job', fullId, { reload: true }) - .then(job => { + .then((job) => { const relatedModelsQueries = [ job.get('allocations'), job.get('evaluations'), diff --git a/ui/app/routes/jobs/job/clients.js b/ui/app/routes/jobs/job/clients.js index 71b9d23e8..bcd3a5af3 100644 --- a/ui/app/routes/jobs/job/clients.js +++ b/ui/app/routes/jobs/job/clients.js @@ -1,6 +1,10 @@ import Route from '@ember/routing/route'; import WithWatchers from 'nomad-ui/mixins/with-watchers'; -import { watchRecord, watchRelationship, watchAll } from 'nomad-ui/utils/properties/watch'; +import { + watchRecord, + watchRelationship, + watchAll, +} from 'nomad-ui/utils/properties/watch'; import { collect } from '@ember/object/computed'; export default class ClientsRoute extends Route.extend(WithWatchers) { diff --git a/ui/app/routes/jobs/job/definition.js b/ui/app/routes/jobs/job/definition.js index 4b089e871..b122e3d5f 100644 --- a/ui/app/routes/jobs/job/definition.js +++ b/ui/app/routes/jobs/job/definition.js @@ -5,7 +5,7 @@ export default class DefinitionRoute extends Route { const job = this.modelFor('jobs.job'); if (!job) return; - return job.fetchRawDefinition().then(definition => ({ + return job.fetchRawDefinition().then((definition) => ({ job, definition, })); diff --git a/ui/app/routes/jobs/job/deployments.js b/ui/app/routes/jobs/job/deployments.js index e1e9d6b49..94aa0c1da 100644 --- a/ui/app/routes/jobs/job/deployments.js +++ b/ui/app/routes/jobs/job/deployments.js @@ -7,7 +7,10 @@ import WithWatchers from 'nomad-ui/mixins/with-watchers'; export default class DeploymentsRoute extends Route.extend(WithWatchers) { model() { const job = this.modelFor('jobs.job'); - return job && RSVP.all([job.get('deployments'), job.get('versions')]).then(() => job); + return ( + job && + RSVP.all([job.get('deployments'), job.get('versions')]).then(() => job) + ); } startWatchers(controller, model) { diff --git a/ui/app/routes/jobs/job/index.js b/ui/app/routes/jobs/job/index.js index b085ca744..8accf8734 100644 --- a/ui/app/routes/jobs/job/index.js +++ b/ui/app/routes/jobs/job/index.js @@ -38,10 +38,13 @@ export default class IndexRoute extends Route.extend(WithWatchers) { allocations: this.watchAllocations.perform(model.job), evaluations: this.watchEvaluations.perform(model.job), latestDeployment: - model.job.get('supportsDeployments') && this.watchLatestDeployment.perform(model.job), + model.job.get('supportsDeployments') && + this.watchLatestDeployment.perform(model.job), list: model.job.get('hasChildren') && - this.watchAllJobs.perform({ namespace: model.job.namespace.get('name') }), + this.watchAllJobs.perform({ + namespace: model.job.namespace.get('name'), + }), nodes: this.can.can('read client') && model.job.get('hasClientStatus') && @@ -52,7 +55,10 @@ export default class IndexRoute extends Route.extend(WithWatchers) { setupController(controller, model) { // Parameterized and periodic detail pages, which list children jobs, // should sort by submit time. - if (model.job && ['periodic', 'parameterized'].includes(model.job.templateType)) { + if ( + model.job && + ['periodic', 'parameterized'].includes(model.job.templateType) + ) { controller.setProperties({ sortProperty: 'submitTime', sortDescending: true, diff --git a/ui/app/routes/jobs/job/task-group.js b/ui/app/routes/jobs/job/task-group.js index 17ea124f4..88b654301 100644 --- a/ui/app/routes/jobs/job/task-group.js +++ b/ui/app/routes/jobs/job/task-group.js @@ -2,7 +2,10 @@ import Route from '@ember/routing/route'; import { collect } from '@ember/object/computed'; import EmberError from '@ember/error'; import { resolve, all } from 'rsvp'; -import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; +import { + watchRecord, + watchRelationship, +} from 'nomad-ui/utils/properties/watch'; import WithWatchers from 'nomad-ui/mixins/with-watchers'; import notifyError from 'nomad-ui/utils/notify-error'; @@ -21,15 +24,18 @@ export default class TaskGroupRoute extends Route.extend(WithWatchers) { .then(() => { const taskGroup = job.get('taskGroups').findBy('name', name); if (!taskGroup) { - const err = new EmberError(`Task group ${name} for job ${job.get('name')} not found`); + const err = new EmberError( + `Task group ${name} for job ${job.get('name')} not found` + ); err.code = '404'; throw err; } // Refresh job allocations before-hand (so page sort works on load) - return all([job.hasMany('allocations').reload(), job.get('scaleState')]).then( - () => taskGroup - ); + return all([ + job.hasMany('allocations').reload(), + job.get('scaleState'), + ]).then(() => taskGroup); }) .catch(notifyError(this)); } @@ -42,7 +48,9 @@ export default class TaskGroupRoute extends Route.extend(WithWatchers) { summary: this.watchSummary.perform(job.get('summary')), scale: this.watchScale.perform(job.get('scaleState')), allocations: this.watchAllocations.perform(job), - latestDeployment: job.get('supportsDeployments') && this.watchLatestDeployment.perform(job), + latestDeployment: + job.get('supportsDeployments') && + this.watchLatestDeployment.perform(job), }); } } @@ -53,6 +61,12 @@ export default class TaskGroupRoute extends Route.extend(WithWatchers) { @watchRelationship('allocations') watchAllocations; @watchRelationship('latestDeployment') watchLatestDeployment; - @collect('watchJob', 'watchSummary', 'watchScale', 'watchAllocations', 'watchLatestDeployment') + @collect( + 'watchJob', + 'watchSummary', + 'watchScale', + 'watchAllocations', + 'watchLatestDeployment' + ) watchers; } diff --git a/ui/app/routes/jobs/job/versions.js b/ui/app/routes/jobs/job/versions.js index 2923c321c..dc924197f 100644 --- a/ui/app/routes/jobs/job/versions.js +++ b/ui/app/routes/jobs/job/versions.js @@ -1,6 +1,9 @@ import Route from '@ember/routing/route'; import { collect } from '@ember/object/computed'; -import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; +import { + watchRecord, + watchRelationship, +} from 'nomad-ui/utils/properties/watch'; import WithWatchers from 'nomad-ui/mixins/with-watchers'; export default class VersionsRoute extends Route.extend(WithWatchers) { diff --git a/ui/app/routes/jobs/run.js b/ui/app/routes/jobs/run.js index 6d456ead9..915854ebf 100644 --- a/ui/app/routes/jobs/run.js +++ b/ui/app/routes/jobs/run.js @@ -9,7 +9,11 @@ export default class RunRoute extends Route { @service system; beforeModel(transition) { - if (this.can.cannot('run job', null, { namespace: transition.to.queryParams.namespace })) { + if ( + this.can.cannot('run job', null, { + namespace: transition.to.queryParams.namespace, + }) + ) { this.transitionTo('jobs'); } } diff --git a/ui/app/routes/not-found.js b/ui/app/routes/not-found.js index 2c7ece2a2..4c1223592 100644 --- a/ui/app/routes/not-found.js +++ b/ui/app/routes/not-found.js @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-controller-access-in-routes */ import Route from '@ember/routing/route'; import EmberError from '@ember/error'; diff --git a/ui/app/routes/optimize.js b/ui/app/routes/optimize.js index 4ba930951..35f348a0c 100644 --- a/ui/app/routes/optimize.js +++ b/ui/app/routes/optimize.js @@ -21,9 +21,9 @@ export default class OptimizeRoute extends Route { const [namespaces] = await RSVP.all([ this.store.findAll('namespace'), ...jobs - .filter(job => job) + .filter((job) => job) .filterBy('isPartial') - .map(j => j.reload()), + .map((j) => j.reload()), ]); return { diff --git a/ui/app/routes/optimize/index.js b/ui/app/routes/optimize/index.js index 605a6479e..63fd0de2a 100644 --- a/ui/app/routes/optimize/index.js +++ b/ui/app/routes/optimize/index.js @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-controller-access-in-routes */ import Route from '@ember/routing/route'; export default class OptimizeIndexRoute extends Route { diff --git a/ui/app/routes/optimize/summary.js b/ui/app/routes/optimize/summary.js index 20822f4c2..13e6f30cd 100644 --- a/ui/app/routes/optimize/summary.js +++ b/ui/app/routes/optimize/summary.js @@ -4,11 +4,14 @@ import notifyError from 'nomad-ui/utils/notify-error'; export default class OptimizeSummaryRoute extends Route { async model({ jobNamespace, slug }) { const model = this.modelFor('optimize').summaries.find( - summary => summary.slug === slug && summary.jobNamespace === jobNamespace + (summary) => + summary.slug === slug && summary.jobNamespace === jobNamespace ); if (!model) { - const error = new Error(`Unable to find summary for ${slug} in namespace ${jobNamespace}`); + const error = new Error( + `Unable to find summary for ${slug} in namespace ${jobNamespace}` + ); error.code = 404; notifyError(this)(error); } else { diff --git a/ui/app/serializers/agent.js b/ui/app/serializers/agent.js index 75b4ce111..ec6b393c9 100644 --- a/ui/app/serializers/agent.js +++ b/ui/app/serializers/agent.js @@ -15,7 +15,8 @@ export default class AgentSerializer extends ApplicationSerializer { // acts like the API in this case. const error = new AdapterError([{ status: '404' }]); - error.message = 'Requested Agent was not found in set of available Agents'; + error.message = + 'Requested Agent was not found in set of available Agents'; throw error; } @@ -28,10 +29,21 @@ export default class AgentSerializer extends ApplicationSerializer { } normalizeResponse(store, typeClass, hash, ...args) { - return super.normalizeResponse(store, typeClass, hash.Members || [], ...args); + return super.normalizeResponse( + store, + typeClass, + hash.Members || [], + ...args + ); } normalizeSingleResponse(store, typeClass, hash, id, ...args) { - return super.normalizeSingleResponse(store, typeClass, hash.findBy('Name', id), id, ...args); + return super.normalizeSingleResponse( + store, + typeClass, + hash.findBy('Name', id), + id, + ...args + ); } } diff --git a/ui/app/serializers/allocation.js b/ui/app/serializers/allocation.js index 46922c357..6e471cb75 100644 --- a/ui/app/serializers/allocation.js +++ b/ui/app/serializers/allocation.js @@ -5,11 +5,12 @@ import classic from 'ember-classic-decorator'; const taskGroupFromJob = (job, taskGroupName) => { const taskGroups = job && job.TaskGroups; - const taskGroup = taskGroups && taskGroups.find(group => group.Name === taskGroupName); + const taskGroup = + taskGroups && taskGroups.find((group) => group.Name === taskGroupName); return taskGroup ? taskGroup : null; }; -const merge = tasks => { +const merge = (tasks) => { const mergedResources = { Cpu: { CpuShares: 0 }, Memory: { MemoryMB: 0 }, @@ -41,15 +42,19 @@ export default class AllocationSerializer extends ApplicationSerializer { const states = hash.TaskStates || {}; hash.TaskStates = Object.keys(states) .sort() - .map(key => { + .map((key) => { const state = states[key] || {}; const summary = { Name: key }; - Object.keys(state).forEach(stateKey => (summary[stateKey] = state[stateKey])); - summary.Resources = hash.AllocatedResources && hash.AllocatedResources.Tasks[key]; + Object.keys(state).forEach( + (stateKey) => (summary[stateKey] = state[stateKey]) + ); + summary.Resources = + hash.AllocatedResources && hash.AllocatedResources.Tasks[key]; return summary; }); - hash.JobVersion = hash.JobVersion != null ? hash.JobVersion : get(hash, 'Job.Version'); + hash.JobVersion = + hash.JobVersion != null ? hash.JobVersion : get(hash, 'Job.Version'); hash.PlainJobId = hash.JobID; hash.Namespace = hash.Namespace || get(hash, 'Job.Namespace') || 'default'; @@ -60,9 +65,13 @@ export default class AllocationSerializer extends ApplicationSerializer { hash.IsMigrating = (hash.DesiredTransition || {}).Migrate; // API returns empty strings instead of null - hash.PreviousAllocationID = hash.PreviousAllocation ? hash.PreviousAllocation : null; + hash.PreviousAllocationID = hash.PreviousAllocation + ? hash.PreviousAllocation + : null; hash.NextAllocationID = hash.NextAllocation ? hash.NextAllocation : null; - hash.FollowUpEvaluationID = hash.FollowupEvalID ? hash.FollowupEvalID : null; + hash.FollowUpEvaluationID = hash.FollowupEvalID + ? hash.FollowupEvalID + : null; hash.PreemptedAllocationIDs = hash.PreemptedAllocations || []; hash.PreemptedByAllocationID = hash.PreemptedByAllocation || null; @@ -70,14 +79,17 @@ export default class AllocationSerializer extends ApplicationSerializer { const shared = hash.AllocatedResources && hash.AllocatedResources.Shared; hash.AllocatedResources = - hash.AllocatedResources && merge(Object.values(hash.AllocatedResources.Tasks)); + hash.AllocatedResources && + merge(Object.values(hash.AllocatedResources.Tasks)); if (shared) { hash.AllocatedResources.Ports = shared.Ports; hash.AllocatedResources.Networks = shared.Networks; } // The Job definition for an allocation is only included in findRecord responses. - hash.AllocationTaskGroup = !hash.Job ? null : taskGroupFromJob(hash.Job, hash.TaskGroup); + hash.AllocationTaskGroup = !hash.Job + ? null + : taskGroupFromJob(hash.Job, hash.TaskGroup); return super.normalize(typeHash, hash); } diff --git a/ui/app/serializers/application.js b/ui/app/serializers/application.js index 7a6d41237..c37983af6 100644 --- a/ui/app/serializers/application.js +++ b/ui/app/serializers/application.js @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-string-prototype-extensions */ import { copy } from 'ember-copy'; import { get } from '@ember/object'; import { makeArray } from '@ember/array'; @@ -62,9 +63,7 @@ export default class Application extends JSONSerializer { } keyForRelationship(attr, relationshipType) { - const key = `${singularize(attr) - .camelize() - .capitalize()}ID`; + const key = `${singularize(attr).camelize().capitalize()}ID`; return relationshipType === 'hasMany' ? pluralize(key) : key; } @@ -75,12 +74,12 @@ export default class Application extends JSONSerializer { included: [], }; - Object.keys(payload).forEach(key => { + Object.keys(payload).forEach((key) => { const modelName = this.modelNameFromPayloadKey(key); const serializer = store.serializerFor(modelName); const type = store.modelFor(modelName); - makeArray(payload[key]).forEach(hash => { + makeArray(payload[key]).forEach((hash) => { const { data, included } = serializer.normalize(type, hash, key); documentHash.data.push(data); if (included) { @@ -95,14 +94,14 @@ export default class Application extends JSONSerializer { normalize(modelClass, hash) { if (hash) { if (this.arrayNullOverrides) { - this.arrayNullOverrides.forEach(key => { + this.arrayNullOverrides.forEach((key) => { if (!hash[key]) { hash[key] = []; } }); } if (this.objectNullOverrides) { - this.objectNullOverrides.forEach(key => { + this.objectNullOverrides.forEach((key) => { if (!hash[key]) { hash[key] = {}; } @@ -110,7 +109,7 @@ export default class Application extends JSONSerializer { } if (this.mapToArray) { - this.mapToArray.forEach(conversion => { + this.mapToArray.forEach((conversion) => { let apiKey, uiKey; if (conversion.beforeName) { @@ -125,7 +124,7 @@ export default class Application extends JSONSerializer { hash[uiKey] = Object.keys(map) .sort() - .map(mapKey => { + .map((mapKey) => { const propertiesForKey = map[mapKey] || {}; const convertedMap = { Name: mapKey }; @@ -137,7 +136,7 @@ export default class Application extends JSONSerializer { } if (this.separateNanos) { - this.separateNanos.forEach(key => { + this.separateNanos.forEach((key) => { const timeWithNanos = hash[key]; hash[`${key}Nanos`] = timeWithNanos % 1000000; hash[key] = Math.floor(timeWithNanos / 1000000); @@ -157,13 +156,15 @@ export default class Application extends JSONSerializer { // When records are removed server-side, and therefore don't show up in requests, // the local copies of those records need to be unloaded from the store. cullStore(store, type, records, storeFilter = () => true) { - const newRecords = copy(records).filter(record => get(record, 'id')); + const newRecords = copy(records).filter((record) => get(record, 'id')); const oldRecords = store.peekAll(type); oldRecords - .filter(record => get(record, 'id')) + .filter((record) => get(record, 'id')) .filter(storeFilter) - .forEach(old => { - const newRecord = newRecords.find(record => get(record, 'id') === get(old, 'id')); + .forEach((old) => { + const newRecord = newRecords.find( + (record) => get(record, 'id') === get(old, 'id') + ); if (!newRecord) { removeRecord(store, old); } else { diff --git a/ui/app/serializers/deployment.js b/ui/app/serializers/deployment.js index 49e8ddcdb..d947a4ad1 100644 --- a/ui/app/serializers/deployment.js +++ b/ui/app/serializers/deployment.js @@ -14,20 +14,26 @@ export default class DeploymentSerializer extends ApplicationSerializer { normalize(typeHash, hash) { if (hash) { hash.PlainJobId = hash.JobID; - hash.Namespace = hash.Namespace || get(hash, 'Job.Namespace') || 'default'; + hash.Namespace = + hash.Namespace || get(hash, 'Job.Namespace') || 'default'; // Ember Data doesn't support multiple inverses. This means that since jobs have // two relationships to a deployment (hasMany deployments, and belongsTo latestDeployment), // the deployment must in turn have two relationships to the job, despite it being the // same job. - hash.JobID = hash.JobForLatestID = JSON.stringify([hash.JobID, hash.Namespace]); + hash.JobID = hash.JobForLatestID = JSON.stringify([ + hash.JobID, + hash.Namespace, + ]); } return super.normalize(typeHash, hash); } extractRelationships(modelClass, hash) { - const namespace = this.store.adapterFor(modelClass.modelName).get('namespace'); + const namespace = this.store + .adapterFor(modelClass.modelName) + .get('namespace'); const id = this.extractId(modelClass, hash); return assign( diff --git a/ui/app/serializers/job-plan.js b/ui/app/serializers/job-plan.js index 1893f8c9d..ea8971451 100644 --- a/ui/app/serializers/job-plan.js +++ b/ui/app/serializers/job-plan.js @@ -5,7 +5,9 @@ export default class JobPlan extends ApplicationSerializer { mapToArray = ['FailedTGAllocs']; normalize(typeHash, hash) { - hash.PreemptionIDs = (get(hash, 'Annotations.PreemptedAllocs') || []).mapBy('ID'); + hash.PreemptionIDs = (get(hash, 'Annotations.PreemptedAllocs') || []).mapBy( + 'ID' + ); return super.normalize(...arguments); } } diff --git a/ui/app/serializers/job-summary.js b/ui/app/serializers/job-summary.js index bf65f6c38..23af75c7c 100644 --- a/ui/app/serializers/job-summary.js +++ b/ui/app/serializers/job-summary.js @@ -13,12 +13,12 @@ export default class JobSummary extends ApplicationSerializer { const fullSummary = hash.Summary || {}; hash.TaskGroupSummaries = Object.keys(fullSummary) .sort() - .map(key => { + .map((key) => { const allocStats = fullSummary[key] || {}; const summary = { Name: key }; Object.keys(allocStats).forEach( - allocKey => (summary[`${allocKey}Allocs`] = allocStats[allocKey]) + (allocKey) => (summary[`${allocKey}Allocs`] = allocStats[allocKey]) ); return summary; @@ -28,7 +28,8 @@ export default class JobSummary extends ApplicationSerializer { const childrenStats = get(hash, 'Children'); if (childrenStats) { Object.keys(childrenStats).forEach( - childrenKey => (hash[`${childrenKey}Children`] = childrenStats[childrenKey]) + (childrenKey) => + (hash[`${childrenKey}Children`] = childrenStats[childrenKey]) ); } diff --git a/ui/app/serializers/job.js b/ui/app/serializers/job.js index 8c24bea0a..65a2001bb 100644 --- a/ui/app/serializers/job.js +++ b/ui/app/serializers/job.js @@ -20,7 +20,10 @@ export default class JobSerializer extends ApplicationSerializer { if (!hash.ParentID) { hash.ParentID = null; } else { - hash.ParentID = JSON.stringify([hash.ParentID, hash.NamespaceID || 'default']); + hash.ParentID = JSON.stringify([ + hash.ParentID, + hash.NamespaceID || 'default', + ]); } // Job Summary is always at /:job-id/summary, but since it can also come from @@ -52,10 +55,14 @@ export default class JobSerializer extends ApplicationSerializer { extractRelationships(modelClass, hash) { const namespace = - !hash.NamespaceID || hash.NamespaceID === 'default' ? undefined : hash.NamespaceID; + !hash.NamespaceID || hash.NamespaceID === 'default' + ? undefined + : hash.NamespaceID; const { modelName } = modelClass; - const apiNamespace = this.store.adapterFor(modelClass.modelName).get('namespace'); + const apiNamespace = this.store + .adapterFor(modelClass.modelName) + .get('namespace'); const [jobURL] = this.store .adapterFor(modelName) diff --git a/ui/app/serializers/network.js b/ui/app/serializers/network.js index 3310db18c..605f4bc33 100644 --- a/ui/app/serializers/network.js +++ b/ui/app/serializers/network.js @@ -15,14 +15,14 @@ export default class NetworkSerializer extends ApplicationSerializer { hash.IP = `[${ip}]`; } - const reservedPorts = (hash.ReservedPorts || []).map(port => ({ + const reservedPorts = (hash.ReservedPorts || []).map((port) => ({ name: port.Label, port: port.Value, to: port.To, isDynamic: false, })); - const dynamicPorts = (hash.DynamicPorts || []).map(port => ({ + const dynamicPorts = (hash.DynamicPorts || []).map((port) => ({ name: port.Label, port: port.Value, to: port.To, diff --git a/ui/app/serializers/node.js b/ui/app/serializers/node.js index a689c40b2..69cce417a 100644 --- a/ui/app/serializers/node.js +++ b/ui/app/serializers/node.js @@ -17,7 +17,12 @@ export default class NodeSerializer extends ApplicationSerializer { const { modelName } = modelClass; const nodeURL = this.store .adapterFor(modelName) - .buildURL(modelName, this.extractId(modelClass, hash), hash, 'findRecord'); + .buildURL( + modelName, + this.extractId(modelClass, hash), + hash, + 'findRecord' + ); return { allocations: { diff --git a/ui/app/serializers/plugin.js b/ui/app/serializers/plugin.js index d113551e3..5f368a9fd 100644 --- a/ui/app/serializers/plugin.js +++ b/ui/app/serializers/plugin.js @@ -8,7 +8,7 @@ import ApplicationSerializer from './application'; const unmap = (hash, propKey) => Object.keys(hash) .sort() - .map(key => { + .map((key) => { const record = hash[key]; record[propKey] = key; return record; diff --git a/ui/app/serializers/recommendation-summary.js b/ui/app/serializers/recommendation-summary.js index a484d4d02..c04bb6458 100644 --- a/ui/app/serializers/recommendation-summary.js +++ b/ui/app/serializers/recommendation-summary.js @@ -16,10 +16,11 @@ export default class RecommendationSummarySerializer extends ApplicationSerializ const slugToSummaryObject = {}; const allRecommendations = []; - payload.forEach(recommendationHash => { - const slug = `${JSON.stringify([recommendationHash.JobID, recommendationHash.Namespace])}/${ - recommendationHash.Group - }`; + payload.forEach((recommendationHash) => { + const slug = `${JSON.stringify([ + recommendationHash.JobID, + recommendationHash.Namespace, + ])}/${recommendationHash.Group}`; if (!slugToSummaryObject[slug]) { slugToSummaryObject[slug] = { @@ -37,15 +38,14 @@ export default class RecommendationSummarySerializer extends ApplicationSerializ }); return { - data: Object.values(slugToSummaryObject).map(summaryObject => { - const latest = Math.max(...summaryObject.recommendations.mapBy('SubmitTime')); + data: Object.values(slugToSummaryObject).map((summaryObject) => { + const latest = Math.max( + ...summaryObject.recommendations.mapBy('SubmitTime') + ); return { type: 'recommendation-summary', - id: summaryObject.recommendations - .mapBy('ID') - .sort() - .join('-'), + id: summaryObject.recommendations.mapBy('ID').sort().join('-'), attributes: { ...summaryObject.attributes, submitTime: new Date(Math.floor(latest / 1000000)), @@ -61,7 +61,7 @@ export default class RecommendationSummarySerializer extends ApplicationSerializ }, }, recommendations: { - data: summaryObject.recommendations.map(r => { + data: summaryObject.recommendations.map((r) => { return { type: 'recommendation', id: r.ID, @@ -72,8 +72,11 @@ export default class RecommendationSummarySerializer extends ApplicationSerializ }; }), included: allRecommendations.map( - recommendationHash => - recommendationSerializer.normalize(RecommendationModel, recommendationHash).data + (recommendationHash) => + recommendationSerializer.normalize( + RecommendationModel, + recommendationHash + ).data ), }; } diff --git a/ui/app/serializers/recommendation.js b/ui/app/serializers/recommendation.js index bb9bd785a..32b062052 100644 --- a/ui/app/serializers/recommendation.js +++ b/ui/app/serializers/recommendation.js @@ -12,7 +12,10 @@ export default class RecommendationSerializer extends ApplicationSerializer { separateNanos = ['SubmitTime']; extractRelationships(modelClass, hash) { - const namespace = !hash.Namespace || hash.Namespace === 'default' ? undefined : hash.Namespace; + const namespace = + !hash.Namespace || hash.Namespace === 'default' + ? undefined + : hash.Namespace; const [jobURL] = this.store .adapterFor('job') diff --git a/ui/app/serializers/task-group.js b/ui/app/serializers/task-group.js index 6c5034d28..28e86e003 100644 --- a/ui/app/serializers/task-group.js +++ b/ui/app/serializers/task-group.js @@ -7,7 +7,7 @@ export default class TaskGroup extends ApplicationSerializer { normalize(typeHash, hash) { // Provide EphemeralDisk to each task - hash.Tasks.forEach(task => { + hash.Tasks.forEach((task) => { task.EphemeralDisk = copy(hash.EphemeralDisk); }); diff --git a/ui/app/serializers/volume.js b/ui/app/serializers/volume.js index c3aa3a57d..faf2b398d 100644 --- a/ui/app/serializers/volume.js +++ b/ui/app/serializers/volume.js @@ -31,7 +31,7 @@ export default class VolumeSerializer extends ApplicationSerializer { hash.WriteAllocations = []; if (hash.Allocations) { - hash.Allocations.forEach(function(alloc) { + hash.Allocations.forEach(function (alloc) { const id = alloc.ID; if (id in readAllocs) { hash.ReadAllocations.push(alloc); @@ -44,11 +44,17 @@ export default class VolumeSerializer extends ApplicationSerializer { } const normalizedHash = super.normalize(typeHash, hash); - return this.extractEmbeddedRecords(this, this.store, typeHash, normalizedHash); + return this.extractEmbeddedRecords( + this, + this.store, + typeHash, + normalizedHash + ); } keyForRelationship(attr, relationshipType) { //Embedded relationship attributes don't end in IDs + /* eslint-disable-next-line ember/no-string-prototype-extensions */ if (this.embeddedRelationships.includes(attr)) return attr.capitalize(); return super.keyForRelationship(attr, relationshipType); } @@ -57,7 +63,7 @@ export default class VolumeSerializer extends ApplicationSerializer { extractEmbeddedRecords(serializer, store, typeHash, partial) { partial.included = partial.included || []; - this.embeddedRelationships.forEach(embed => { + this.embeddedRelationships.forEach((embed) => { const relationshipMeta = typeHash.relationshipsByName.get(embed); const relationship = get(partial, `data.relationships.${embed}.data`); diff --git a/ui/app/services/breadcrumbs.js b/ui/app/services/breadcrumbs.js index eacf9f955..5d92984be 100644 --- a/ui/app/services/breadcrumbs.js +++ b/ui/app/services/breadcrumbs.js @@ -13,7 +13,7 @@ export default class BucketService extends Service { } @action deregisterBreadcrumb(crumb) { - const newCrumbs = this.crumbs.filter(c => c !== crumb); + const newCrumbs = this.crumbs.filter((c) => c !== crumb); this.crumbs = newCrumbs; } diff --git a/ui/app/services/sockets.js b/ui/app/services/sockets.js index c6bebc6be..236d77630 100644 --- a/ui/app/services/sockets.js +++ b/ui/app/services/sockets.js @@ -19,7 +19,11 @@ export default class SocketsService extends Service { send(e) { if (!this.messageDisplayed) { this.messageDisplayed = true; - this.onmessage({ data: `{"stdout":{"data":"${btoa('unsupported in Mirage\n\r')}"}}` }); + this.onmessage({ + data: `{"stdout":{"data":"${btoa( + 'unsupported in Mirage\n\r' + )}"}}`, + }); } else { this.onmessage({ data: e.replace('stdin', 'stdout') }); } @@ -28,8 +32,9 @@ export default class SocketsService extends Service { } else { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const applicationAdapter = getOwner(this).lookup('adapter:application'); - const prefix = `${applicationAdapter.host || - window.location.host}/${applicationAdapter.urlPrefix()}`; + const prefix = `${ + applicationAdapter.host || window.location.host + }/${applicationAdapter.urlPrefix()}`; const region = this.system.activeRegion; return new WebSocket( diff --git a/ui/app/services/stats-trackers-registry.js b/ui/app/services/stats-trackers-registry.js index 8c2fb2ab7..6d222dc08 100644 --- a/ui/app/services/stats-trackers-registry.js +++ b/ui/app/services/stats-trackers-registry.js @@ -12,7 +12,9 @@ const MAX_STAT_TRACKERS = 10; let registry; const exists = (tracker, prop) => - tracker.get(prop) && !tracker.get(prop).isDestroyed && !tracker.get(prop).isDestroying; + tracker.get(prop) && + !tracker.get(prop).isDestroyed && + !tracker.get(prop).isDestroying; export default class StatsTrackersRegistryService extends Service { @service token; @@ -38,19 +40,21 @@ export default class StatsTrackersRegistryService extends Service { const type = resource && resource.constructor.modelName; const key = `${type}:${resource.get('id')}`; - const Constructor = type === 'node' ? NodeStatsTracker : AllocationStatsTracker; + const Constructor = + type === 'node' ? NodeStatsTracker : AllocationStatsTracker; const resourceProp = type === 'node' ? 'node' : 'allocation'; const cachedTracker = registry.get(key); if (cachedTracker) { // It's possible for the resource on a cachedTracker to have been // deleted. Rebind it if that's the case. - if (!exists(cachedTracker, resourceProp)) cachedTracker.set(resourceProp, resource); + if (!exists(cachedTracker, resourceProp)) + cachedTracker.set(resourceProp, resource); return cachedTracker; } const tracker = Constructor.create({ - fetch: url => this.token.authorizedRequest(url), + fetch: (url) => this.token.authorizedRequest(url), [resourceProp]: resource, }); diff --git a/ui/app/services/system.js b/ui/app/services/system.js index cd296f338..1859c701e 100644 --- a/ui/app/services/system.js +++ b/ui/app/services/system.js @@ -21,9 +21,9 @@ export default class SystemService extends Service { return PromiseObject.create({ promise: token .authorizedRequest(`/${namespace}/status/leader`) - .then(res => res.json()) - .then(rpcAddr => ({ rpcAddr })) - .then(leader => { + .then((res) => res.json()) + .then((rpcAddr) => ({ rpcAddr })) + .then((leader) => { // Dirty self so leader can be used as a dependent key this.notifyPropertyChange('leader.rpcAddr'); return leader; @@ -38,12 +38,15 @@ export default class SystemService extends Service { promise: token .authorizedRawRequest(`/${namespace}/agent/self`) .then(jsonWithDefault({})) - .then(agent => { + .then((agent) => { if (agent?.config?.Version) { - const { Version, VersionPrerelease, VersionMetadata } = agent.config.Version; + const { Version, VersionPrerelease, VersionMetadata } = + agent.config.Version; agent.version = Version; - if (VersionPrerelease) agent.version = `${agent.version}-${VersionPrerelease}`; - if (VersionMetadata) agent.version = `${agent.version}+${VersionMetadata}`; + if (VersionPrerelease) + agent.version = `${agent.version}-${VersionPrerelease}`; + if (VersionMetadata) + agent.version = `${agent.version}+${VersionMetadata}`; } return agent; }), @@ -57,7 +60,7 @@ export default class SystemService extends Service { promise: token .authorizedRawRequest(`/${namespace}/agent/members`) .then(jsonWithDefault({})) - .then(json => { + .then((json) => { return { region: json.ServerRegion }; }), }); @@ -68,7 +71,9 @@ export default class SystemService extends Service { const token = this.token; return PromiseArray.create({ - promise: token.authorizedRawRequest(`/${namespace}/regions`).then(jsonWithDefault([])), + promise: token + .authorizedRawRequest(`/${namespace}/regions`) + .then(jsonWithDefault([])), }); } @@ -103,20 +108,28 @@ export default class SystemService extends Service { @computed('activeRegion', 'defaultRegion.region', 'shouldShowRegions') get shouldIncludeRegion() { - return this.shouldShowRegions && this.activeRegion !== this.get('defaultRegion.region'); + return ( + this.shouldShowRegions && + this.activeRegion !== this.get('defaultRegion.region') + ); } @computed('activeRegion') get namespaces() { return PromiseArray.create({ - promise: this.store.findAll('namespace').then(namespaces => namespaces.compact()), + promise: this.store + .findAll('namespace') + .then((namespaces) => namespaces.compact()), }); } @computed('namespaces.[]') get shouldShowNamespaces() { const namespaces = this.namespaces.toArray(); - return namespaces.length && namespaces.some(namespace => namespace.get('id') !== 'default'); + return ( + namespaces.length && + namespaces.some((namespace) => namespace.get('id') !== 'default') + ); } // The cachedNamespace is set on pages that have a namespaces filter. @@ -125,7 +138,7 @@ export default class SystemService extends Service { // to 'default' or '*'. @tracked cachedNamespace = null; - @task(function*() { + @task(function* () { const emptyLicense = { License: { Features: [] } }; try { @@ -138,7 +151,7 @@ export default class SystemService extends Service { }) fetchLicense; - @task(function*() { + @task(function* () { try { const request = yield this.token.authorizedRequest('/v1/search/fuzzy', { method: 'POST', diff --git a/ui/app/services/token.js b/ui/app/services/token.js index c11453e23..13755155c 100644 --- a/ui/app/services/token.js +++ b/ui/app/services/token.js @@ -28,13 +28,13 @@ export default class TokenService extends Service { } } - @task(function*() { + @task(function* () { const TokenAdapter = getOwner(this).lookup('adapter:token'); try { return yield TokenAdapter.findSelf(); } catch (e) { const errors = e.errors ? e.errors.mapBy('detail') : []; - if (errors.find(error => error === 'ACL support disabled')) { + if (errors.find((error) => error === 'ACL support disabled')) { this.set('aclEnabled', false); } return null; @@ -51,7 +51,7 @@ export default class TokenService extends Service { this.secret = token.secret; } - @task(function*() { + @task(function* () { try { if (this.selfToken) { return yield this.selfToken.get('policies'); @@ -67,7 +67,7 @@ export default class TokenService extends Service { @alias('fetchSelfTokenPolicies.lastSuccessful.value') selfTokenPolicies; - @task(function*() { + @task(function* () { yield this.fetchSelfToken.perform(); if (this.aclEnabled) { yield this.fetchSelfTokenPolicies.perform(); diff --git a/ui/app/services/user-settings.js b/ui/app/services/user-settings.js index dbe650ed1..3c8362dfa 100644 --- a/ui/app/services/user-settings.js +++ b/ui/app/services/user-settings.js @@ -4,5 +4,6 @@ import localStorageProperty from 'nomad-ui/utils/properties/local-storage'; export default class UserSettingsService extends Service { @localStorageProperty('nomadPageSize', 25) pageSize; @localStorageProperty('nomadLogMode', 'stdout') logMode; - @localStorageProperty('nomadTopoVizPollingNotice', true) showTopoVizPollingNotice; + @localStorageProperty('nomadTopoVizPollingNotice', true) + showTopoVizPollingNotice; } diff --git a/ui/app/utils/classes/abstract-logger.js b/ui/app/utils/classes/abstract-logger.js index 8dc78ff1c..e8b404958 100644 --- a/ui/app/utils/classes/abstract-logger.js +++ b/ui/app/utils/classes/abstract-logger.js @@ -12,12 +12,14 @@ export default Mixin.create({ url: '', params: overridable(() => ({})), logFetch() { - assert('Loggers need a logFetch method, which should have an interface like window.fetch'); + assert( + 'Loggers need a logFetch method, which should have an interface like window.fetch' + ); }, endOffset: null, - offsetParams: computed('endOffset', function() { + offsetParams: computed('endOffset', function () { const endOffset = this.endOffset; return endOffset ? { origin: 'start', offset: endOffset } @@ -26,10 +28,16 @@ export default Mixin.create({ additionalParams: overridable(() => ({})), - fullUrl: computed('url', 'params', 'offsetParams', 'additionalParams', function() { - const queryParams = queryString.stringify( - assign({}, this.params, this.offsetParams, this.additionalParams) - ); - return `${this.url}?${queryParams}`; - }), + fullUrl: computed( + 'url', + 'params', + 'offsetParams', + 'additionalParams', + function () { + const queryParams = queryString.stringify( + assign({}, this.params, this.offsetParams, this.additionalParams) + ); + return `${this.url}?${queryParams}`; + } + ), }); diff --git a/ui/app/utils/classes/abstract-stats-tracker.js b/ui/app/utils/classes/abstract-stats-tracker.js index dd51c11f4..2c45f2f49 100644 --- a/ui/app/utils/classes/abstract-stats-tracker.js +++ b/ui/app/utils/classes/abstract-stats-tracker.js @@ -18,7 +18,9 @@ export default Mixin.create({ maxFrameMisses: 5, fetch() { - assert('StatsTrackers need a fetch method, which should have an interface like window.fetch'); + assert( + 'StatsTrackers need a fetch method, which should have an interface like window.fetch' + ); }, append(/* frame */) { @@ -56,7 +58,7 @@ export default Mixin.create({ // references to the same tracker from flooding the tracker, // but also avoiding the issue where different places where the // same tracker is used needs to coordinate. - poll: task(function*() { + poll: task(function* () { // Interrupt any pause attempt this.signalPause.cancelAll(); @@ -66,7 +68,7 @@ export default Mixin.create({ yield this.fetch(url) .then(jsonWithDefault({ error: true })) - .then(frame => this.handleResponse(frame)); + .then((frame) => this.handleResponse(frame)); } catch (error) { throw new Error(error); } @@ -74,7 +76,7 @@ export default Mixin.create({ yield timeout(Ember.testing ? 0 : 2000); }).drop(), - signalPause: task(function*() { + signalPause: task(function* () { // wait 2 seconds yield timeout(Ember.testing ? 0 : 2000); // if no poll called in 2 seconds, pause diff --git a/ui/app/utils/classes/allocation-stats-tracker.js b/ui/app/utils/classes/allocation-stats-tracker.js index 18782ba39..092357d18 100644 --- a/ui/app/utils/classes/allocation-stats-tracker.js +++ b/ui/app/utils/classes/allocation-stats-tracker.js @@ -11,7 +11,7 @@ const percent = (numerator, denominator) => { return numerator / denominator; }; -const empty = ts => ({ timestamp: ts, used: null, percent: null }); +const empty = (ts) => ({ timestamp: ts, used: null, percent: null }); // Tasks are sorted by their lifecycle phase in this order: const sortMap = [ @@ -26,7 +26,8 @@ const sortMap = [ return map; }, {}); -const taskPrioritySort = (a, b) => sortMap[a.lifecycleName] - sortMap[b.lifecycleName]; +const taskPrioritySort = (a, b) => + sortMap[a.lifecycleName] - sortMap[b.lifecycleName]; @classic class AllocationStatsTracker extends EmberObject.extend(AbstractStatsTracker) { @@ -64,9 +65,12 @@ class AllocationStatsTracker extends EmberObject.extend(AbstractStatsTracker) { // it has already stopped), just keep going. if (!taskFrame) continue; - const frameTimestamp = new Date(Math.floor(taskFrame.Timestamp / 1000000)); + const frameTimestamp = new Date( + Math.floor(taskFrame.Timestamp / 1000000) + ); - const taskCpuUsed = Math.floor(taskFrame.ResourceUsage.CpuStats.TotalTicks) || 0; + const taskCpuUsed = + Math.floor(taskFrame.ResourceUsage.CpuStats.TotalTicks) || 0; const percentCpuTotal = percent(taskCpuUsed, this.reservedCPU); stats.cpu.pushObject({ timestamp: frameTimestamp, @@ -77,7 +81,10 @@ class AllocationStatsTracker extends EmberObject.extend(AbstractStatsTracker) { }); const taskMemoryUsed = taskFrame.ResourceUsage.MemoryStats.RSS; - const percentMemoryTotal = percent(taskMemoryUsed / 1024 / 1024, this.reservedMemory); + const percentMemoryTotal = percent( + taskMemoryUsed / 1024 / 1024, + this.reservedMemory + ); stats.memory.pushObject({ timestamp: frameTimestamp, used: taskMemoryUsed, @@ -95,7 +102,7 @@ class AllocationStatsTracker extends EmberObject.extend(AbstractStatsTracker) { const ts = new Date(); this.memory.pushObject(empty(ts)); this.cpu.pushObject(empty(ts)); - this.tasks.forEach(task => { + this.tasks.forEach((task) => { task.memory.pushObject(empty(ts)); task.cpu.pushObject(empty(ts)); }); @@ -124,7 +131,7 @@ class AllocationStatsTracker extends EmberObject.extend(AbstractStatsTracker) { return tasks .slice() .sort(taskPrioritySort) - .map(task => ({ + .map((task) => ({ task: get(task, 'name'), // Static figures, denominators for stats @@ -142,7 +149,7 @@ class AllocationStatsTracker extends EmberObject.extend(AbstractStatsTracker) { export default AllocationStatsTracker; export function stats(allocationProp, fetch) { - return computed(allocationProp, function() { + return computed(allocationProp, function () { return AllocationStatsTracker.create({ fetch: fetch.call(this), allocation: this.get(allocationProp), diff --git a/ui/app/utils/classes/exec-command-editor-xterm-adapter.js b/ui/app/utils/classes/exec-command-editor-xterm-adapter.js index 45212f4f1..89fa68fcc 100644 --- a/ui/app/utils/classes/exec-command-editor-xterm-adapter.js +++ b/ui/app/utils/classes/exec-command-editor-xterm-adapter.js @@ -13,7 +13,7 @@ export default class ExecCommandEditorXtermAdapter { this.command = command; - this.dataListener = terminal.onData(data => { + this.dataListener = terminal.onData((data) => { this.handleDataEvent(data); }); diff --git a/ui/app/utils/classes/exec-socket-xterm-adapter.js b/ui/app/utils/classes/exec-socket-xterm-adapter.js index c97e9cc0e..3eef4c726 100644 --- a/ui/app/utils/classes/exec-socket-xterm-adapter.js +++ b/ui/app/utils/classes/exec-socket-xterm-adapter.js @@ -15,12 +15,12 @@ export default class ExecSocketXtermAdapter { this.sendTtySize(); this.startHeartbeat(); - terminal.onData(data => { + terminal.onData((data) => { this.handleData(data); }); }; - socket.onmessage = e => { + socket.onmessage = (e) => { let json = JSON.parse(e.data); // stderr messages will not be produced as the socket is opened with the tty flag @@ -44,12 +44,16 @@ export default class ExecSocketXtermAdapter { sendTtySize() { this.socket.send( - JSON.stringify({ tty_size: { width: this.terminal.cols, height: this.terminal.rows } }) + JSON.stringify({ + tty_size: { width: this.terminal.cols, height: this.terminal.rows }, + }) ); } sendWsHandshake() { - this.socket.send(JSON.stringify({ version: 1, auth_token: this.token || '' })); + this.socket.send( + JSON.stringify({ version: 1, auth_token: this.token || '' }) + ); } startHeartbeat() { @@ -63,6 +67,8 @@ export default class ExecSocketXtermAdapter { } handleData(data) { - this.socket.send(JSON.stringify({ stdin: { data: base64EncodeString(data) } })); + this.socket.send( + JSON.stringify({ stdin: { data: base64EncodeString(data) } }) + ); } } diff --git a/ui/app/utils/classes/log.js b/ui/app/utils/classes/log.js index b23d14704..5ea91dc85 100644 --- a/ui/app/utils/classes/log.js +++ b/ui/app/utils/classes/log.js @@ -16,7 +16,8 @@ import classic from 'ember-classic-decorator'; const MAX_OUTPUT_LENGTH = 50000; // eslint-disable-next-line -export const fetchFailure = url => () => console.warn(`LOG FETCH: Couldn't connect to ${url}`); +export const fetchFailure = (url) => () => + console.warn(`LOG FETCH: Couldn't connect to ${url}`); @classic class Log extends EmberObject.extend(Evented) { @@ -30,7 +31,9 @@ class Log extends EmberObject.extend(Evented) { plainText = false; logFetch() { - assert('Log objects need a logFetch method, which should have an interface like window.fetch'); + assert( + 'Log objects need a logFetch method, which should have an interface like window.fetch' + ); } // Read-only state @@ -61,7 +64,7 @@ class Log extends EmberObject.extend(Evented) { super.init(); const args = this.getProperties('url', 'params', 'logFetch'); - args.write = chunk => { + args.write = (chunk) => { let newTail = this.tail + chunk; if (newTail.length > MAX_OUTPUT_LENGTH) { newTail = newTail.substr(newTail.length - MAX_OUTPUT_LENGTH); @@ -82,7 +85,7 @@ class Log extends EmberObject.extend(Evented) { super.destroy(); } - @task(function*() { + @task(function* () { const logFetch = this.logFetch; const queryParams = queryString.stringify( assign( @@ -96,19 +99,23 @@ class Log extends EmberObject.extend(Evented) { const url = `${this.url}?${queryParams}`; this.stop(); - const response = yield logFetch(url).then(res => res.text(), fetchFailure(url)); + const response = yield logFetch(url).then( + (res) => res.text(), + fetchFailure(url) + ); let text = this.plainText ? response : decode(response).message; if (text && text.length > MAX_OUTPUT_LENGTH) { text = text.substr(0, MAX_OUTPUT_LENGTH); - text += '\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------'; + text += + '\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------'; } this.set('head', text); this.set('logPointer', 'head'); }) gotoHead; - @task(function*() { + @task(function* () { const logFetch = this.logFetch; const queryParams = queryString.stringify( assign( @@ -122,7 +129,10 @@ class Log extends EmberObject.extend(Evented) { const url = `${this.url}?${queryParams}`; this.stop(); - const response = yield logFetch(url).then(res => res.text(), fetchFailure(url)); + const response = yield logFetch(url).then( + (res) => res.text(), + fetchFailure(url) + ); let text = this.plainText ? response : decode(response).message; this.set('tail', text); @@ -143,7 +153,7 @@ class Log extends EmberObject.extend(Evented) { export default Log; export function logger(urlProp, params, logFetch) { - return computed(urlProp, params, function() { + return computed(urlProp, params, function () { return Log.create({ logFetch: logFetch.call(this), params: this.get(params), diff --git a/ui/app/utils/classes/node-stats-tracker.js b/ui/app/utils/classes/node-stats-tracker.js index 995f26500..7427a1e8d 100644 --- a/ui/app/utils/classes/node-stats-tracker.js +++ b/ui/app/utils/classes/node-stats-tracker.js @@ -11,7 +11,7 @@ const percent = (numerator, denominator) => { return numerator / denominator; }; -const empty = ts => ({ timestamp: ts, used: null, percent: null }); +const empty = (ts) => ({ timestamp: ts, used: null, percent: null }); @classic class NodeStatsTracker extends EmberObject.extend(AbstractStatsTracker) { @@ -67,7 +67,7 @@ class NodeStatsTracker extends EmberObject.extend(AbstractStatsTracker) { export default NodeStatsTracker; export function stats(nodeProp, fetch) { - return computed(nodeProp, function() { + return computed(nodeProp, function () { return NodeStatsTracker.create({ fetch: fetch.call(this), node: this.get(nodeProp), diff --git a/ui/app/utils/classes/poll-logger.js b/ui/app/utils/classes/poll-logger.js index 6fe015c2e..9f874cf38 100644 --- a/ui/app/utils/classes/poll-logger.js +++ b/ui/app/utils/classes/poll-logger.js @@ -17,11 +17,11 @@ export default class PollLogger extends EmberObject.extend(AbstractLogger) { return this.poll.cancelAll(); } - @task(function*() { + @task(function* () { const { interval, logFetch } = this; while (true) { const url = this.fullUrl; - let response = yield logFetch(url).then(res => res, fetchFailure(url)); + let response = yield logFetch(url).then((res) => res, fetchFailure(url)); if (!response) { return; diff --git a/ui/app/utils/classes/promise-array.js b/ui/app/utils/classes/promise-array.js index 1bf2efb1a..49615cfb2 100644 --- a/ui/app/utils/classes/promise-array.js +++ b/ui/app/utils/classes/promise-array.js @@ -3,4 +3,6 @@ import PromiseProxyMixin from '@ember/object/promise-proxy-mixin'; import classic from 'ember-classic-decorator'; @classic -export default class PromiseArray extends ArrayProxy.extend(PromiseProxyMixin) {} +export default class PromiseArray extends ArrayProxy.extend( + PromiseProxyMixin +) {} diff --git a/ui/app/utils/classes/promise-object.js b/ui/app/utils/classes/promise-object.js index dbab8f7ec..bd8940b4a 100644 --- a/ui/app/utils/classes/promise-object.js +++ b/ui/app/utils/classes/promise-object.js @@ -3,4 +3,6 @@ import PromiseProxyMixin from '@ember/object/promise-proxy-mixin'; import classic from 'ember-classic-decorator'; @classic -export default class PromiseObject extends ObjectProxy.extend(PromiseProxyMixin) {} +export default class PromiseObject extends ObjectProxy.extend( + PromiseProxyMixin +) {} diff --git a/ui/app/utils/classes/query-params.js b/ui/app/utils/classes/query-params.js index 9c61582d4..7450e2877 100644 --- a/ui/app/utils/classes/query-params.js +++ b/ui/app/utils/classes/query-params.js @@ -9,6 +9,6 @@ class QueryParams extends EmberObject { values = null; } -export const qpBuilder = values => QueryParams.create({ values }); +export const qpBuilder = (values) => QueryParams.create({ values }); export default QueryParams; diff --git a/ui/app/utils/classes/rolling-array.js b/ui/app/utils/classes/rolling-array.js index 02790b88e..50269ce46 100644 --- a/ui/app/utils/classes/rolling-array.js +++ b/ui/app/utils/classes/rolling-array.js @@ -16,34 +16,34 @@ export default function RollingArray(maxLength, ...items) { array.maxLength = maxLength; // Bring the length back down to maxLength by removing from the front - array._limit = function() { + array._limit = function () { const surplus = this.length - this.maxLength; if (surplus > 0) { this.splice(0, surplus); } }; - array.push = function(...items) { + array.push = function (...items) { push.apply(this, items); this._limit(); return this.length; }; - array.splice = function(...args) { + array.splice = function (...args) { const returnValue = splice.apply(this, args); this._limit(); return returnValue; }; // All mutable array methods build on top of insertAt - array.insertAt = function(...args) { + array.insertAt = function (...args) { const returnValue = insertAt.apply(this, args); this._limit(); this.arrayContentDidChange(); return returnValue; }; - array.unshift = function() { + array.unshift = function () { throw new Error('Cannot unshift onto a RollingArray'); }; diff --git a/ui/app/utils/classes/stream-logger.js b/ui/app/utils/classes/stream-logger.js index aedd18f47..e935f199f 100644 --- a/ui/app/utils/classes/stream-logger.js +++ b/ui/app/utils/classes/stream-logger.js @@ -29,11 +29,11 @@ export default class StreamLogger extends EmberObject.extend(AbstractLogger) { return this.poll.cancelAll(); } - @task(function*() { + @task(function* () { const url = this.fullUrl; const logFetch = this.logFetch; - const reader = yield logFetch(url).then(res => { + const reader = yield logFetch(url).then((res) => { const reader = res.body.getReader(); // It's possible that the logger was stopped between the time // polling was started and the log request responded. diff --git a/ui/app/utils/classes/text-decoder.js b/ui/app/utils/classes/text-decoder.js index f3bb66c81..552b152ed 100644 --- a/ui/app/utils/classes/text-decoder.js +++ b/ui/app/utils/classes/text-decoder.js @@ -5,8 +5,8 @@ // A complete polyfill exists if this becomes problematic: // https://github.com/inexorabletash/text-encoding export default window.TextDecoder || - function() { - this.decode = function(value) { + function () { + this.decode = function (value) { let text = ''; for (let i = 3; i < value.byteLength; i++) { text += String.fromCharCode(value[i]); diff --git a/ui/app/utils/codes-for-error.js b/ui/app/utils/codes-for-error.js index 199290fa6..7669e96ed 100644 --- a/ui/app/utils/codes-for-error.js +++ b/ui/app/utils/codes-for-error.js @@ -3,7 +3,7 @@ export default function codesForError(error) { const codes = [error.code]; if (error.errors) { - error.errors.forEach(err => { + error.errors.forEach((err) => { codes.push(err.status); }); } @@ -11,5 +11,5 @@ export default function codesForError(error) { return codes .compact() .uniq() - .map(code => '' + code); + .map((code) => '' + code); } diff --git a/ui/app/utils/format-duration.js b/ui/app/utils/format-duration.js index feed6b262..768e0b43e 100644 --- a/ui/app/utils/format-duration.js +++ b/ui/app/utils/format-duration.js @@ -13,9 +13,27 @@ const allUnits = [ { name: 'years', suffix: 'year', inMoment: true, pluralizable: true }, { name: 'months', suffix: 'month', inMoment: true, pluralizable: true }, { name: 'days', suffix: 'day', inMoment: true, pluralizable: true }, - { name: 'hours', suffix: 'h', longSuffix: 'hour', inMoment: true, pluralizable: false }, - { name: 'minutes', suffix: 'm', longSuffix: 'minute', inMoment: true, pluralizable: false }, - { name: 'seconds', suffix: 's', longSuffix: 'second', inMoment: true, pluralizable: false }, + { + name: 'hours', + suffix: 'h', + longSuffix: 'hour', + inMoment: true, + pluralizable: false, + }, + { + name: 'minutes', + suffix: 'm', + longSuffix: 'minute', + inMoment: true, + pluralizable: false, + }, + { + name: 'seconds', + suffix: 's', + longSuffix: 'second', + inMoment: true, + pluralizable: false, + }, { name: 'milliseconds', suffix: 'ms', inMoment: true, pluralizable: false }, { name: 'microseconds', suffix: 'µs', inMoment: false, pluralizable: false }, { name: 'nanoseconds', suffix: 'ns', inMoment: false, pluralizable: false }, @@ -30,7 +48,8 @@ const pluralizeUnits = (amount, unit, longForm) => { suffix = amount === 1 ? unit.longSuffix : pluralize(unit.longSuffix); } else { // In the normal case, only pluralize based on the pluralizable flag - suffix = amount === 1 || !unit.pluralizable ? unit.suffix : pluralize(unit.suffix); + suffix = + amount === 1 || !unit.pluralizable ? unit.suffix : pluralize(unit.suffix); } // A space should go between the value and the unit when the unit is a full word @@ -47,7 +66,11 @@ const pluralizeUnits = (amount, unit, longForm) => { * @param {Boolean} longForm Whether or not to expand single character suffixes, * used to ensure screen readers correctly read units. */ -export default function formatDuration(duration = 0, units = 'ns', longForm = false) { +export default function formatDuration( + duration = 0, + units = 'ns', + longForm = false +) { const durationParts = {}; // Moment only handles up to millisecond precision. @@ -72,7 +95,7 @@ export default function formatDuration(duration = 0, units = 'ns', longForm = fa allUnits .filterBy('inMoment') .mapBy('name') - .forEach(unit => { + .forEach((unit) => { durationParts[unit] = momentDuration[unit](); }); diff --git a/ui/app/utils/generate-exec-url.js b/ui/app/utils/generate-exec-url.js index a5b5d0fb7..ed619460f 100644 --- a/ui/app/utils/generate-exec-url.js +++ b/ui/app/utils/generate-exec-url.js @@ -1,6 +1,9 @@ import { get } from '@ember/object'; -export default function generateExecUrl(router, { job, taskGroup, task, allocation }) { +export default function generateExecUrl( + router, + { job, taskGroup, task, allocation } +) { const queryParams = {}; const namespace = get(job, 'namespace.name'); @@ -33,9 +36,14 @@ export default function generateExecUrl(router, { job, taskGroup, task, allocati } ); } else if (taskGroup) { - return router.urlFor('exec.task-group', get(job, 'plainId'), get(taskGroup, 'name'), { - queryParams, - }); + return router.urlFor( + 'exec.task-group', + get(job, 'plainId'), + get(taskGroup, 'name'), + { + queryParams, + } + ); } else if (allocation) { if (get(allocation, 'taskGroup.tasks.length') === 1) { return router.urlFor( @@ -43,14 +51,24 @@ export default function generateExecUrl(router, { job, taskGroup, task, allocati get(job, 'plainId'), get(allocation, 'taskGroup.name'), get(allocation, 'taskGroup.tasks.firstObject.name'), - { queryParams: { allocation: get(allocation, 'shortId'), ...queryParams } } + { + queryParams: { + allocation: get(allocation, 'shortId'), + ...queryParams, + }, + } ); } else { return router.urlFor( 'exec.task-group', get(job, 'plainId'), get(allocation, 'taskGroup.name'), - { queryParams: { allocation: get(allocation, 'shortId'), ...queryParams } } + { + queryParams: { + allocation: get(allocation, 'shortId'), + ...queryParams, + }, + } ); } } else { diff --git a/ui/app/utils/json-with-default.js b/ui/app/utils/json-with-default.js index 840fefb14..4bd067e6a 100644 --- a/ui/app/utils/json-with-default.js +++ b/ui/app/utils/json-with-default.js @@ -4,7 +4,7 @@ import { copy } from 'ember-copy'; // Fetch only goes into the promise catch if there is a network error. // This means that handling a 4xx or 5xx error is the responsibility // of the developer. -const jsonWithDefault = defaultResponse => res => +const jsonWithDefault = (defaultResponse) => (res) => res.ok ? res.json() : copy(defaultResponse, true); export default jsonWithDefault; diff --git a/ui/app/utils/notify-error.js b/ui/app/utils/notify-error.js index 3ca5f5987..d907ef182 100644 --- a/ui/app/utils/notify-error.js +++ b/ui/app/utils/notify-error.js @@ -1,7 +1,9 @@ +/* eslint-disable ember/no-controller-access-in-routes */ + // An error handler to provide to a promise catch to set an error // on the application controller. export default function notifyError(route) { - return error => { + return (error) => { route.controllerFor('application').set('error', error); }; } diff --git a/ui/app/utils/notify-forbidden.js b/ui/app/utils/notify-forbidden.js index 433d1fc40..eaa4edc4a 100644 --- a/ui/app/utils/notify-forbidden.js +++ b/ui/app/utils/notify-forbidden.js @@ -2,7 +2,7 @@ // forbidden flag on the route import codesForError from './codes-for-error'; export default function notifyForbidden(route) { - return error => { + return (error) => { if (codesForError(error).includes('403')) { route.set('isForbidden', true); } else { diff --git a/ui/app/utils/properties/glimmer-style-string.js b/ui/app/utils/properties/glimmer-style-string.js index cde60808f..d2d05d704 100644 --- a/ui/app/utils/properties/glimmer-style-string.js +++ b/ui/app/utils/properties/glimmer-style-string.js @@ -10,16 +10,18 @@ import { htmlSafe } from '@ember/template'; export default function styleString(target, name, descriptor) { if (!descriptor.get) throw new Error('styleString only works on getters'); const orig = descriptor.get; - descriptor.get = function() { + descriptor.get = function () { const styles = orig.apply(this); let str = ''; if (styles) { str = Object.keys(styles) - .reduce(function(arr, key) { + .reduce(function (arr, key) { const val = styles[key]; - arr.push(key + ':' + (typeof val === 'number' ? val.toFixed(2) + 'px' : val)); + arr.push( + key + ':' + (typeof val === 'number' ? val.toFixed(2) + 'px' : val) + ); return arr; }, []) .join(';'); diff --git a/ui/app/utils/properties/job-client-status.js b/ui/app/utils/properties/job-client-status.js index d0e5e78fc..a71578681 100644 --- a/ui/app/utils/properties/job-client-status.js +++ b/ui/app/utils/properties/job-client-status.js @@ -19,12 +19,12 @@ export default function jobClientStatus(nodesKey, jobKey) { return computed( `${nodesKey}.[]`, `${jobKey}.{datacenters,status,allocations.@each.clientStatus,taskGroups}`, - function() { + function () { const job = this.get(jobKey); const nodes = this.get(nodesKey); // Filter nodes by the datacenters defined in the job. - const filteredNodes = nodes.filter(n => { + const filteredNodes = nodes.filter((n) => { return job.datacenters.indexOf(n.datacenter) >= 0; }); @@ -34,7 +34,7 @@ export default function jobClientStatus(nodesKey, jobKey) { // Group the job allocations by the ID of the client that is running them. const allocsByNodeID = {}; - job.allocations.forEach(a => { + job.allocations.forEach((a) => { const nodeId = a.belongsTo('node').id(); if (!allocsByNodeID[nodeId]) { allocsByNodeID[nodeId] = []; @@ -47,7 +47,7 @@ export default function jobClientStatus(nodesKey, jobKey) { byStatus: {}, totalNodes: filteredNodes.length, }; - filteredNodes.forEach(n => { + filteredNodes.forEach((n) => { const status = jobStatus(allocsByNodeID[n.id], job.taskGroups.length); result.byNode[n.id] = status; @@ -63,9 +63,9 @@ export default function jobClientStatus(nodesKey, jobKey) { } function allQueued(nodes) { - const nodeIDs = nodes.map(n => n.id); + const nodeIDs = nodes.map((n) => n.id); return { - byNode: Object.fromEntries(nodeIDs.map(id => [id, 'queued'])), + byNode: Object.fromEntries(nodeIDs.map((id) => [id, 'queued'])), byStatus: canonicalizeStatus({ queued: nodeIDs }), totalNodes: nodes.length, }; @@ -105,7 +105,7 @@ function jobStatus(allocs, expected) { // Count how many allocations are in each `clientStatus` value. const summary = allocs - .filter(a => !a.isOld) + .filter((a) => !a.isOld) .reduce((acc, a) => { const status = a.clientStatus; if (!acc[status]) { diff --git a/ui/app/utils/properties/short-uuid.js b/ui/app/utils/properties/short-uuid.js index 0a7af211c..4c7862f4c 100644 --- a/ui/app/utils/properties/short-uuid.js +++ b/ui/app/utils/properties/short-uuid.js @@ -6,7 +6,7 @@ import { computed } from '@ember/object'; // ex. id: 123456-7890-abcd-efghijk // short: shortUUIDProperty('id') // 123456 export default function shortUUIDProperty(uuidKey) { - return computed(uuidKey, function() { + return computed(uuidKey, function () { return this.get(uuidKey).split('-')[0]; }); } diff --git a/ui/app/utils/properties/style-string.js b/ui/app/utils/properties/style-string.js index 3f3a225f2..804fd5218 100644 --- a/ui/app/utils/properties/style-string.js +++ b/ui/app/utils/properties/style-string.js @@ -7,15 +7,17 @@ import { htmlSafe } from '@ember/template'; // ex. styleProps: { color: '#FF0', border-width: '1px' } // styleStr: styleStringProperty('styleProps') // color:#FF0;border-width:1px export default function styleStringProperty(prop) { - return computed(prop, function() { + return computed(prop, function () { const styles = get(this, prop); let str = ''; if (styles) { str = Object.keys(styles) - .reduce(function(arr, key) { + .reduce(function (arr, key) { const val = styles[key]; - arr.push(key + ':' + (typeof val === 'number' ? val.toFixed(2) + 'px' : val)); + arr.push( + key + ':' + (typeof val === 'number' ? val.toFixed(2) + 'px' : val) + ); return arr; }, []) .join(';'); diff --git a/ui/app/utils/properties/sum-aggregation.js b/ui/app/utils/properties/sum-aggregation.js index 41a486a0e..54e82ed5b 100644 --- a/ui/app/utils/properties/sum-aggregation.js +++ b/ui/app/utils/properties/sum-aggregation.js @@ -6,7 +6,7 @@ import { computed } from '@ember/object'; // ex. list: [ { foo: 1 }, { foo: 3 } ] // sum: sumAggregationProperty('list', 'foo') // 4 export default function sumAggregationProperty(listKey, propKey) { - return computed(`${listKey}.@each.${propKey}`, function() { + return computed(`${listKey}.@each.${propKey}`, function () { return this.get(listKey) .mapBy(propKey) .reduce((sum, count) => sum + count, 0); diff --git a/ui/app/utils/properties/uniquely.js b/ui/app/utils/properties/uniquely.js index 96ce520e4..c8f32032a 100644 --- a/ui/app/utils/properties/uniquely.js +++ b/ui/app/utils/properties/uniquely.js @@ -6,7 +6,7 @@ import { guidFor } from '@ember/object/internals'; // // ex. @uniquely('name') // 'name-ember129383' export default function uniquely(prefix) { - return computed(function() { + return computed(function () { return `${prefix}-${guidFor(this)}`; }); } diff --git a/ui/app/utils/properties/watch.js b/ui/app/utils/properties/watch.js index ee890ef2b..08dc18842 100644 --- a/ui/app/utils/properties/watch.js +++ b/ui/app/utils/properties/watch.js @@ -11,7 +11,7 @@ import config from 'nomad-ui/config/environment'; const isEnabled = config.APP.blockingQueries !== false; export function watchRecord(modelName) { - return task(function*(id, throttle = 2000) { + return task(function* (id, throttle = 2000) { assert( 'To watch a record, the record adapter MUST extend Watchable', this.store.adapterFor(modelName) instanceof Watchable @@ -40,7 +40,7 @@ export function watchRecord(modelName) { } export function watchRelationship(relationshipName) { - return task(function*(model, throttle = 2000) { + return task(function* (model, throttle = 2000) { assert( 'To watch a relationship, the adapter of the model provided to the watchRelationship task MUST extend Watchable', this.store.adapterFor(model.constructor.modelName) instanceof Watchable @@ -68,7 +68,7 @@ export function watchRelationship(relationshipName) { } export function watchAll(modelName) { - return task(function*(throttle = 2000) { + return task(function* (throttle = 2000) { assert( 'To watch all, the respective adapter MUST extend Watchable', this.store.adapterFor(modelName) instanceof Watchable @@ -94,7 +94,7 @@ export function watchAll(modelName) { } export function watchQuery(modelName) { - return task(function*(params, throttle = 10000) { + return task(function* (params, throttle = 10000) { assert( 'To watch a query, the adapter for the type being queried MUST extend Watchable', this.store.adapterFor(modelName) instanceof Watchable diff --git a/ui/app/utils/qp-serialize.js b/ui/app/utils/qp-serialize.js index 7717efc28..b3f146274 100644 --- a/ui/app/utils/qp-serialize.js +++ b/ui/app/utils/qp-serialize.js @@ -1,23 +1,21 @@ import { computed } from '@ember/object'; // An unattractive but robust way to encode query params -export const serialize = val => { +export const serialize = (val) => { if (typeof val === 'string' || typeof val === 'number') return val; return val.length ? JSON.stringify(val) : ''; }; -export const deserialize = str => { +export const deserialize = (str) => { try { - return JSON.parse(str) - .compact() - .without(''); + return JSON.parse(str).compact().without(''); } catch (e) { return []; } }; // A computed property macro for deserializing a query param -export const deserializedQueryParam = qpKey => - computed(qpKey, function() { +export const deserializedQueryParam = (qpKey) => + computed(qpKey, function () { return deserialize(this.get(qpKey)); }); diff --git a/ui/app/utils/resources-diffs.js b/ui/app/utils/resources-diffs.js index 068469f6a..7a301f11d 100644 --- a/ui/app/utils/resources-diffs.js +++ b/ui/app/utils/resources-diffs.js @@ -10,7 +10,9 @@ export default class ResourcesDiffs { this.model = model; this.multiplier = multiplier; this.recommendations = recommendations; - this.excludedRecommendations = excludedRecommendations.filter(r => recommendations.includes(r)); + this.excludedRecommendations = excludedRecommendations.filter((r) => + recommendations.includes(r) + ); } get cpu() { @@ -28,8 +30,14 @@ export default class ResourcesDiffs { } get memory() { - const included = this.includedRecommendations.filterBy('resource', 'MemoryMB'); - const excluded = this.excludedRecommendations.filterBy('resource', 'MemoryMB'); + const included = this.includedRecommendations.filterBy( + 'resource', + 'MemoryMB' + ); + const excluded = this.excludedRecommendations.filterBy( + 'resource', + 'MemoryMB' + ); return new ResourceDiffs( this.model.reservedMemory, @@ -42,7 +50,9 @@ export default class ResourcesDiffs { } get includedRecommendations() { - return this.recommendations.reject(r => this.excludedRecommendations.includes(r)); + return this.recommendations.reject((r) => + this.excludedRecommendations.includes(r) + ); } } @@ -67,7 +77,9 @@ class ResourceDiffs { if (this.included.length) { return ( this.included.mapBy('value').reduce(sumAggregate, 0) + - this.excluded.mapBy(`task.${this.baseTaskPropertyName}`).reduce(sumAggregate, 0) + this.excluded + .mapBy(`task.${this.baseTaskPropertyName}`) + .reduce(sumAggregate, 0) ); } else { return this.base; diff --git a/ui/app/utils/stream-frames.js b/ui/app/utils/stream-frames.js index a4a4f54df..7a861fb8b 100644 --- a/ui/app/utils/stream-frames.js +++ b/ui/app/utils/stream-frames.js @@ -13,14 +13,13 @@ const decoder = new TextDecoderLite('utf-8'); * object represents. */ export function decode(chunk) { - const lines = chunk - .replace(/\}\{/g, '}\n{') - .split('\n') - .without(''); - const frames = lines.map(line => JSON.parse(line)).filter(frame => frame.Data); + const lines = chunk.replace(/\}\{/g, '}\n{').split('\n').without(''); + const frames = lines + .map((line) => JSON.parse(line)) + .filter((frame) => frame.Data); if (frames.length) { - frames.forEach(frame => (frame.Data = b64decode(frame.Data))); + frames.forEach((frame) => (frame.Data = b64decode(frame.Data))); return { offset: frames[frames.length - 1].Offset, message: frames.mapBy('Data').join(''), diff --git a/ui/app/utils/styleguide/product-metadata.js b/ui/app/utils/styleguide/product-metadata.js index 37031d155..e515c3371 100644 --- a/ui/app/utils/styleguide/product-metadata.js +++ b/ui/app/utils/styleguide/product-metadata.js @@ -2,29 +2,25 @@ export default [ { name: 'Nomad', lang: 'golang', - desc: - 'Nomad is a flexible, enterprise-grade cluster scheduler designed to easily integrate into existing workflows. Nomad can run a diverse workload of micro-service, batch, containerized and non-containerized applications.', + desc: 'Nomad is a flexible, enterprise-grade cluster scheduler designed to easily integrate into existing workflows. Nomad can run a diverse workload of micro-service, batch, containerized and non-containerized applications.', link: 'https://www.nomadproject.io/', }, { name: 'Terraform', lang: 'golang', - desc: - 'Terraform is a tool for building, changing, and combining infrastructure safely and efficiently.', + desc: 'Terraform is a tool for building, changing, and combining infrastructure safely and efficiently.', link: 'https://www.terraform.io/', }, { name: 'Vault', lang: 'golang', - desc: - 'A tool for secrets management, encryption as a service, and privileged access management', + desc: 'A tool for secrets management, encryption as a service, and privileged access management', link: 'https://www.vaultproject.io/', }, { name: 'Consul', lang: 'golang', - desc: - 'Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.', + desc: 'Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.', link: 'https://www.consul.io/', }, { @@ -36,8 +32,7 @@ export default [ { name: 'Packer', lang: 'golang', - desc: - 'Packer is a tool for creating identical machine images for multiple platforms from a single source configuration.', + desc: 'Packer is a tool for creating identical machine images for multiple platforms from a single source configuration.', link: 'https://www.packer.io/', }, ]; diff --git a/ui/app/utils/wait.js b/ui/app/utils/wait.js index 3949cf23a..351b2a9a8 100644 --- a/ui/app/utils/wait.js +++ b/ui/app/utils/wait.js @@ -2,7 +2,7 @@ import RSVP from 'rsvp'; // An always passing promise used to throttle other promises export default function wait(duration) { - return new RSVP.Promise(resolve => { + return new RSVP.Promise((resolve) => { setTimeout(() => { resolve(`Waited ${duration}ms`); }, duration); diff --git a/ui/blueprints/story/index.js b/ui/blueprints/story/index.js index 8cb37d653..56fd9194c 100644 --- a/ui/blueprints/story/index.js +++ b/ui/blueprints/story/index.js @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-string-prototype-extensions */ const getPathOption = require('ember-cli-get-component-path-option'); const stringUtil = require('ember-cli-string-utils'); const path = require('path'); @@ -5,22 +6,22 @@ const path = require('path'); module.exports = { description: 'generates a story for storybook', - fileMapTokens: function() { + fileMapTokens: function () { let { project } = this; return { - __path__: function() { + __path__: function () { return path.relative(project.root, project.root); }, - __markdownname__: function(options) { + __markdownname__: function (options) { return options.dasherizedModuleName; }, - __name__: function(options) { + __name__: function (options) { return options.dasherizedModuleName; }, }; }, - locals: function(options) { + locals: function (options) { let contents = ''; return { @@ -29,7 +30,7 @@ module.exports = { header: stringUtil .dasherize(options.entity.name) .split('-') - .map(word => stringUtil.capitalize(word)) + .map((word) => stringUtil.capitalize(word)) .join(' '), }; }, diff --git a/ui/config/deprecation-workflow.js b/ui/config/deprecation-workflow.js index e7c8c41fe..abb58e926 100644 --- a/ui/config/deprecation-workflow.js +++ b/ui/config/deprecation-workflow.js @@ -5,7 +5,10 @@ self.deprecationWorkflow.config = { { handler: 'throw', matchId: 'ember-inflector.globals' }, { handler: 'throw', matchId: 'ember-runtime.deprecate-copy-copyable' }, { handler: 'throw', matchId: 'ember-console.deprecate-logger' }, - { handler: 'throw', matchId: 'ember-test-helpers.rendering-context.jquery-element' }, + { + handler: 'throw', + matchId: 'ember-test-helpers.rendering-context.jquery-element', + }, { handler: 'throw', matchId: 'ember-cli-page-object.is-property' }, { handler: 'throw', matchId: 'ember-views.partial' }, ], diff --git a/ui/config/environment.js b/ui/config/environment.js index 35f112b9b..d0bfae307 100644 --- a/ui/config/environment.js +++ b/ui/config/environment.js @@ -6,7 +6,7 @@ if (process.env.USE_MIRAGE) { USE_MIRAGE = process.env.USE_MIRAGE == 'true'; } -module.exports = function(environment) { +module.exports = function (environment) { var ENV = { modulePrefix: 'nomad-ui', environment: environment, diff --git a/ui/config/targets.js b/ui/config/targets.js index 9f6cc6396..1e48e0599 100644 --- a/ui/config/targets.js +++ b/ui/config/targets.js @@ -1,6 +1,10 @@ 'use strict'; -const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; +const browsers = [ + 'last 1 Chrome versions', + 'last 1 Firefox versions', + 'last 1 Safari versions', +]; module.exports = { browsers, diff --git a/ui/ember-cli-build.js b/ui/ember-cli-build.js index 5b4d0eb85..bbeaf43f4 100644 --- a/ui/ember-cli-build.js +++ b/ui/ember-cli-build.js @@ -5,10 +5,13 @@ const environment = EmberApp.env(); const isProd = environment === 'production'; const isTest = environment === 'test'; -module.exports = function(defaults) { +module.exports = function (defaults) { var app = new EmberApp(defaults, { svg: { - paths: ['node_modules/@hashicorp/structure-icons/dist', 'public/images/icons'], + paths: [ + 'node_modules/@hashicorp/structure-icons/dist', + 'public/images/icons', + ], optimize: { plugins: [{ removeViewBox: false }], }, diff --git a/ui/jsconfig.json b/ui/jsconfig.json new file mode 100644 index 000000000..8d067cec4 --- /dev/null +++ b/ui/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "experimentalDecorators": true + } +} \ No newline at end of file diff --git a/ui/lib/bulma/index.js b/ui/lib/bulma/index.js index 0e773d0a7..3c7875e25 100644 --- a/ui/lib/bulma/index.js +++ b/ui/lib/bulma/index.js @@ -7,11 +7,11 @@ var Funnel = require('broccoli-funnel'); module.exports = { name: 'bulma', - isDevelopingAddon: function() { + isDevelopingAddon: function () { return true; }, - included: function(app) { + included: function (app) { this._super.included.apply(this, arguments); // see: https://github.com/ember-cli/ember-cli/issues/3718 @@ -23,7 +23,7 @@ module.exports = { return app; }, - treeForStyles: function() { + treeForStyles: function () { return new Funnel(this.bulmaPath, { srcDir: '/', destDir: 'app/styles/bulma', diff --git a/ui/package.json b/ui/package.json index 1f7250733..8f5a619ba 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,9 +10,12 @@ "scripts": { "build": "ember build --environment=production", "precommit": "lint-staged", - "lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*", + "lint": "npm-run-all --aggregate-output --continue-on-error --parallel 'lint:!(fix)'", + "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", "lint:hbs": "ember-template-lint .", - "lint:js": "eslint .", + "lint:hbs:fix": "ember-template-lint . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", "start": "ember server", "build-storybook": "STORYBOOK=true ember build && build-storybook -s dist", "storybook": "STORYBOOK=true start-storybook -p 6006 -s dist", @@ -102,10 +105,13 @@ "ember-template-lint": "^2.9.1", "ember-test-selectors": "^5.0.0", "ember-truth-helpers": "^2.0.0", - "eslint": "^7.5.0", - "eslint-plugin-ember": "^8.9.1", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-ember": "^10.5.8", "eslint-plugin-ember-a11y-testing": "a11y-tool-sandbox/eslint-plugin-ember-a11y-testing#ca31c9698c7cb105f1c9761d98fcaca7d6874459", "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-qunit": "^6.2.0", "faker": "^4.1.0", "flat": "^5.0.2", "fuse.js": "^3.4.4", @@ -120,7 +126,7 @@ "morgan": "^1.3.2", "npm-run-all": "^4.1.5", "pretender": "^3.0.1", - "prettier": "^1.4.4", + "prettier": "^2.5.1", "query-string": "^7.0.1", "qunit-dom": "^2.0.0", "sass": "^1.17.3", diff --git a/ui/server/.eslintrc.js b/ui/server/.eslintrc.js index 1147d299f..1a4431d85 100644 --- a/ui/server/.eslintrc.js +++ b/ui/server/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { env: { - node: true - } + node: true, + }, }; diff --git a/ui/server/index.js b/ui/server/index.js index 701da9928..d0eb3d395 100644 --- a/ui/server/index.js +++ b/ui/server/index.js @@ -1,14 +1,16 @@ 'use strict'; -module.exports = function(app, options) { - const globSync = require('glob').sync; - const mocks = globSync('./mocks/**/*.js', { cwd: __dirname }).map(require); - const proxies = globSync('./proxies/**/*.js', { cwd: __dirname }).map(require); +module.exports = function (app, options) { + const globSync = require('glob').sync; + const mocks = globSync('./mocks/**/*.js', { cwd: __dirname }).map(require); + const proxies = globSync('./proxies/**/*.js', { cwd: __dirname }).map( + require + ); // Log proxy requests const morgan = require('morgan'); app.use(morgan('dev')); - mocks.forEach(route => route(app, options)); - proxies.forEach(route => route(app, options)); + mocks.forEach((route) => route(app, options)); + proxies.forEach((route) => route(app, options)); }; diff --git a/ui/server/proxies/api.js b/ui/server/proxies/api.js index a8a0aeff7..3e6ab01e1 100644 --- a/ui/server/proxies/api.js +++ b/ui/server/proxies/api.js @@ -2,7 +2,7 @@ const proxyPath = '/v1'; -module.exports = function(app, options) { +module.exports = function (app, options) { // For options, see: // https://github.com/nodejitsu/node-http-proxy @@ -29,19 +29,22 @@ module.exports = function(app, options) { changeOrigin: true, }); - proxy.on('error', function(err, req) { + proxy.on('error', function (err, req) { // eslint-disable-next-line console.error(err, req.url); }); - app.use(proxyPath, function(req, res) { + app.use(proxyPath, function (req, res) { // include root path in proxied request req.url = proxyPath + req.url; proxy.web(req, res, { target: proxyAddress }); }); - server.on('upgrade', function(req, socket, head) { - if (req.url.startsWith('/v1/client/allocation') && req.url.includes('exec?')) { + server.on('upgrade', function (req, socket, head) { + if ( + req.url.startsWith('/v1/client/allocation') && + req.url.includes('exec?') + ) { req.headers.origin = proxyAddress; proxy.ws(req, socket, head, { target: proxyAddress }); } diff --git a/ui/stories/charts/distribution-bar.stories.js b/ui/stories/charts/distribution-bar.stories.js index eb5596d8e..2826d29b9 100644 --- a/ui/stories/charts/distribution-bar.stories.js +++ b/ui/stories/charts/distribution-bar.stories.js @@ -98,7 +98,7 @@ export let LiveUpdating = () => { controller: EmberObject.extend({ timerTicks: 0, - startTimer: on('init', function() { + startTimer: on('init', function () { this.set( 'timer', setInterval(() => { @@ -111,7 +111,7 @@ export let LiveUpdating = () => { clearInterval(this.timer); }, - distributionBarDataRotating: computed('timerTicks', function() { + distributionBarDataRotating: computed('timerTicks', function () { return [ { label: 'one', value: Math.round(Math.random() * 50) }, { label: 'two', value: Math.round(Math.random() * 50) }, diff --git a/ui/stories/charts/line-chart.stories.js b/ui/stories/charts/line-chart.stories.js index 08e4f00f2..f97fcb33e 100644 --- a/ui/stories/charts/line-chart.stories.js +++ b/ui/stories/charts/line-chart.stories.js @@ -148,7 +148,7 @@ export let LiveData = () => { `, context: { controller: EmberObject.extend({ - startTimer: on('init', function() { + startTimer: on('init', function () { this.lineChartLive = []; this.set( @@ -170,7 +170,7 @@ export let LiveData = () => { }, get secondsFormat() { - return date => moment(date).format('HH:mm:ss'); + return (date) => moment(date).format('HH:mm:ss'); }, }).create(), }, @@ -272,9 +272,7 @@ export let VerticalAnnotations = () => { data: DelayedArray.create( new Array(180).fill(null).map((_, idx) => ({ y: Math.sin((idx * 4 * Math.PI) / 180) * 100 + 200, - x: moment() - .add(idx, 'd') - .toDate(), + x: moment().add(idx, 'd').toDate(), })) ), annotations: [ @@ -291,16 +289,12 @@ export let VerticalAnnotations = () => { info: 'This is the max of the sine curve', }, { - x: moment() - .add(89, 'd') - .toDate(), + x: moment().add(89, 'd').toDate(), type: 'info', info: 'This is the end of the first period', }, { - x: moment() - .add(96, 'd') - .toDate(), + x: moment().add(96, 'd').toDate(), type: 'info', info: 'A close annotation for staggering purposes', }, @@ -312,9 +306,7 @@ export let VerticalAnnotations = () => { info: 'This is the min of the sine curve', }, { - x: moment() - .add(179, 'd') - .toDate(), + x: moment().add(179, 'd').toDate(), type: 'info', info: 'Far right', }, @@ -348,9 +340,7 @@ export let HorizontalAnnotations = () => { data: DelayedArray.create( new Array(180).fill(null).map((_, idx) => ({ y: Math.sin((idx * 4 * Math.PI) / 180) * 100 + 200, - x: moment() - .add(idx, 'd') - .toDate(), + x: moment().add(idx, 'd').toDate(), })) ), annotations: [ diff --git a/ui/stories/charts/progress-bar.stories.js b/ui/stories/charts/progress-bar.stories.js index cf42ed60b..617fbd20e 100644 --- a/ui/stories/charts/progress-bar.stories.js +++ b/ui/stories/charts/progress-bar.stories.js @@ -101,7 +101,7 @@ export let LiveUpdates = () => { data: EmberObject.extend({ timerTicks: 0, - startTimer: on('init', function() { + startTimer: on('init', function () { this.set( 'timer', setInterval(() => { @@ -114,21 +114,26 @@ export let LiveUpdates = () => { clearInterval(this.timer); }, - denominator: computed('timerTicks', function() { + denominator: computed('timerTicks', function () { return Math.round(Math.random() * 1000); }), - percentage: computed('timerTicks', function() { + percentage: computed('timerTicks', function () { return Math.round(Math.random() * 100) / 100; }), - numerator: computed('denominator', 'percentage', function() { + numerator: computed('denominator', 'percentage', function () { return Math.round(this.denominator * this.percentage * 100) / 100; }), - liveDetails: computed('denominator', 'numerator', 'percentage', function() { - return this.getProperties('denominator', 'numerator', 'percentage'); - }), + liveDetails: computed( + 'denominator', + 'numerator', + 'percentage', + function () { + return this.getProperties('denominator', 'numerator', 'percentage'); + } + ), }).create(), }, }; diff --git a/ui/stories/charts/stats-time-series.stories.js b/ui/stories/charts/stats-time-series.stories.js index 83f3284af..959dda4b9 100644 --- a/ui/stories/charts/stats-time-series.stories.js +++ b/ui/stories/charts/stats-time-series.stories.js @@ -10,10 +10,7 @@ export default { title: 'Charts/Stats Time Series', }; -let ts = offset => - moment() - .subtract(offset, 'm') - .toDate(); +let ts = (offset) => moment().subtract(offset, 'm').toDate(); export let Standard = () => { return { @@ -66,19 +63,23 @@ export let HighLowComparison = () => { data: EmberObject.extend({ timerTicks: 0, - startTimer: on('init', function() { + startTimer: on('init', function () { this.set( 'timer', setInterval(() => { let metricsHigh = this.metricsHigh; - let prev = metricsHigh.length ? metricsHigh[metricsHigh.length - 1].percent : 0.9; + let prev = metricsHigh.length + ? metricsHigh[metricsHigh.length - 1].percent + : 0.9; this.appendTSValue( metricsHigh, Math.min(Math.max(prev + Math.random() * 0.05 - 0.025, 0.5), 1) ); let metricsLow = this.metricsLow; - let prev2 = metricsLow.length ? metricsLow[metricsLow.length - 1].percent : 0.1; + let prev2 = metricsLow.length + ? metricsLow[metricsLow.length - 1].percent + : 0.1; this.appendTSValue( metricsLow, Math.min(Math.max(prev2 + Math.random() * 0.05 - 0.025, 0), 0.5) @@ -102,16 +103,16 @@ export let HighLowComparison = () => { clearInterval(this.timer); }, - metricsHigh: computed(function() { + metricsHigh: computed(function () { return []; }), - metricsLow: computed(function() { + metricsLow: computed(function () { return []; }), secondsFormat() { - return date => moment(date).format('HH:mm:ss'); + return (date) => moment(date).format('HH:mm:ss'); }, }).create(), }, diff --git a/ui/stories/components/copy-button.stories.js b/ui/stories/components/copy-button.stories.js index 3ebbd7082..f67b0369e 100644 --- a/ui/stories/components/copy-button.stories.js +++ b/ui/stories/components/copy-button.stories.js @@ -20,7 +20,10 @@ export let CopyButton = () => { `, context: { - clipboardText: text('Clipboard Text', 'e8c898a0-794b-9063-7a7f-bf0c4a405f83'), + clipboardText: text( + 'Clipboard Text', + 'e8c898a0-794b-9063-7a7f-bf0c4a405f83' + ), }, }; }; diff --git a/ui/stories/components/diff-viewer.stories.js b/ui/stories/components/diff-viewer.stories.js index cbadcb0b7..4a13e5eeb 100644 --- a/ui/stories/components/diff-viewer.stories.js +++ b/ui/stories/components/diff-viewer.stories.js @@ -16,10 +16,34 @@ export let DiffViewerWithInsertions = () => { `, context: { insertionsOnly: generateDiff([ - { Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' }, - { Annotations: null, Name: 'Delay', New: '25000000000', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'Interval', New: '900000000000', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'Mode', New: 'delay', Old: 'delay', Type: 'None' }, + { + Annotations: null, + Name: 'Attempts', + New: '15', + Old: '15', + Type: 'None', + }, + { + Annotations: null, + Name: 'Delay', + New: '25000000000', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'Interval', + New: '900000000000', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'Mode', + New: 'delay', + Old: 'delay', + Type: 'None', + }, ]), }, }; @@ -37,7 +61,13 @@ export let DiffViewerWithDeletions = () => { `, context: { deletionsOnly: generateDiff([ - { Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' }, + { + Annotations: null, + Name: 'Attempts', + New: '15', + Old: '15', + Type: 'None', + }, { Annotations: null, Name: 'Delay', @@ -52,7 +82,13 @@ export let DiffViewerWithDeletions = () => { Old: '900000000000', Type: 'None', }, - { Annotations: null, Name: 'Mode', New: '', Old: 'delay', Type: 'Deleted' }, + { + Annotations: null, + Name: 'Mode', + New: '', + Old: 'delay', + Type: 'Deleted', + }, ]), }, }; @@ -71,7 +107,13 @@ export let DiffViewerWithEdits = () => { `, context: { editsOnly: generateDiff([ - { Annotations: null, Name: 'Attempts', New: '15', Old: '15', Type: 'None' }, + { + Annotations: null, + Name: 'Attempts', + New: '15', + Old: '15', + Type: 'None', + }, { Annotations: null, Name: 'Delay', @@ -86,7 +128,13 @@ export let DiffViewerWithEdits = () => { Old: '250000000000', Type: 'Edited', }, - { Annotations: null, Name: 'Mode', New: 'delay', Old: 'delay', Type: 'None' }, + { + Annotations: null, + Name: 'Mode', + New: 'delay', + Old: 'delay', + Type: 'None', + }, ]), }, }; @@ -156,9 +204,27 @@ export let DiffViewerWithManyChanges = () => { }, { Fields: [ - { Annotations: null, Name: 'CPU', New: '1000', Old: '500', Type: 'Edited' }, - { Annotations: null, Name: 'DiskMB', New: '0', Old: '0', Type: 'None' }, - { Annotations: null, Name: 'IOPS', New: '0', Old: '0', Type: 'None' }, + { + Annotations: null, + Name: 'CPU', + New: '1000', + Old: '500', + Type: 'Edited', + }, + { + Annotations: null, + Name: 'DiskMB', + New: '0', + Old: '0', + Type: 'None', + }, + { + Annotations: null, + Name: 'IOPS', + New: '0', + Old: '0', + Type: 'None', + }, { Annotations: null, Name: 'MemoryMB', @@ -171,7 +237,13 @@ export let DiffViewerWithManyChanges = () => { Objects: [ { Fields: [ - { Annotations: null, Name: 'MBits', New: '100', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'MBits', + New: '100', + Old: '', + Type: 'Added', + }, ], Name: 'Network', Objects: [ @@ -194,7 +266,13 @@ export let DiffViewerWithManyChanges = () => { }, { Fields: [ - { Annotations: null, Name: 'MBits', New: '', Old: '10', Type: 'Deleted' }, + { + Annotations: null, + Name: 'MBits', + New: '', + Old: '10', + Type: 'Deleted', + }, ], Name: 'Network', Objects: [ @@ -234,13 +312,25 @@ export let DiffViewerWithManyChanges = () => { Old: 'redis-cache', Type: 'None', }, - { Annotations: null, Name: 'PortLabel', New: 'db', Old: 'db', Type: 'None' }, + { + Annotations: null, + Name: 'PortLabel', + New: 'db', + Old: 'db', + Type: 'None', + }, ], Name: 'Service', Objects: [ { Fields: [ - { Annotations: null, Name: 'Tags', New: 'redis', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'Tags', + New: 'redis', + Old: '', + Type: 'Added', + }, { Annotations: null, Name: 'Tags', @@ -269,7 +359,13 @@ export let DiffViewerWithManyChanges = () => { Old: '', Type: 'None', }, - { Annotations: null, Name: 'Command', New: '', Old: '', Type: 'None' }, + { + Annotations: null, + Name: 'Command', + New: '', + Old: '', + Type: 'None', + }, { Annotations: null, Name: 'GRPCService', @@ -298,7 +394,13 @@ export let DiffViewerWithManyChanges = () => { Old: '10000000000', Type: 'Edited', }, - { Annotations: null, Name: 'Method', New: '', Old: '', Type: 'None' }, + { + Annotations: null, + Name: 'Method', + New: '', + Old: '', + Type: 'None', + }, { Annotations: null, Name: 'Name', @@ -306,9 +408,27 @@ export let DiffViewerWithManyChanges = () => { Old: 'alive', Type: 'None', }, - { Annotations: null, Name: 'Path', New: '', Old: '', Type: 'None' }, - { Annotations: null, Name: 'PortLabel', New: '', Old: '', Type: 'None' }, - { Annotations: null, Name: 'Protocol', New: '', Old: '', Type: 'None' }, + { + Annotations: null, + Name: 'Path', + New: '', + Old: '', + Type: 'None', + }, + { + Annotations: null, + Name: 'PortLabel', + New: '', + Old: '', + Type: 'None', + }, + { + Annotations: null, + Name: 'Protocol', + New: '', + Old: '', + Type: 'None', + }, { Annotations: null, Name: 'TLSSkipVerify', @@ -323,7 +443,13 @@ export let DiffViewerWithManyChanges = () => { Old: '2000000000', Type: 'Edited', }, - { Annotations: null, Name: 'Type', New: 'tcp', Old: 'tcp', Type: 'None' }, + { + Annotations: null, + Name: 'Type', + New: 'tcp', + Old: 'tcp', + Type: 'None', + }, ], Name: 'Check', Objects: null, @@ -341,16 +467,46 @@ export let DiffViewerWithManyChanges = () => { }, { Fields: [ - { Annotations: null, Name: 'Count', New: '1', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'Meta[key]', New: 'value', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'Meta[red]', New: 'fish', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'Count', + New: '1', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'Meta[key]', + New: 'value', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'Meta[red]', + New: 'fish', + Old: '', + Type: 'Added', + }, ], Name: 'cache2', Objects: [ { Fields: [ - { Annotations: null, Name: 'Attempts', New: '2', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'Delay', New: '15000000000', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'Attempts', + New: '2', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'Delay', + New: '15000000000', + Old: '', + Type: 'Added', + }, { Annotations: null, Name: 'Interval', @@ -358,7 +514,13 @@ export let DiffViewerWithManyChanges = () => { Old: '', Type: 'Added', }, - { Annotations: null, Name: 'Mode', New: 'fail', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'Mode', + New: 'fail', + Old: '', + Type: 'Added', + }, ], Name: 'RestartPolicy', Objects: null, @@ -366,9 +528,27 @@ export let DiffViewerWithManyChanges = () => { }, { Fields: [ - { Annotations: null, Name: 'Migrate', New: 'false', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'SizeMB', New: '300', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'Sticky', New: 'false', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'Migrate', + New: 'false', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'SizeMB', + New: '300', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'Sticky', + New: 'false', + Old: '', + Type: 'Added', + }, ], Name: 'EphemeralDisk', Objects: null, @@ -379,7 +559,13 @@ export let DiffViewerWithManyChanges = () => { { Annotations: null, Fields: [ - { Annotations: null, Name: 'Driver', New: 'docker', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'Driver', + New: 'docker', + Old: '', + Type: 'Added', + }, { Annotations: null, Name: 'KillTimeout', @@ -387,8 +573,20 @@ export let DiffViewerWithManyChanges = () => { Old: '', Type: 'Added', }, - { Annotations: null, Name: 'Leader', New: 'false', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'ShutdownDelay', New: '0', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'Leader', + New: 'false', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'ShutdownDelay', + New: '0', + Old: '', + Type: 'Added', + }, ], Name: 'redis', Objects: [ @@ -415,16 +613,46 @@ export let DiffViewerWithManyChanges = () => { }, { Fields: [ - { Annotations: null, Name: 'CPU', New: '500', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'DiskMB', New: '0', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'IOPS', New: '0', Old: '', Type: 'Added' }, - { Annotations: null, Name: 'MemoryMB', New: '256', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'CPU', + New: '500', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'DiskMB', + New: '0', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'IOPS', + New: '0', + Old: '', + Type: 'Added', + }, + { + Annotations: null, + Name: 'MemoryMB', + New: '256', + Old: '', + Type: 'Added', + }, ], Name: 'Resources', Objects: [ { Fields: [ - { Annotations: null, Name: 'MBits', New: '10', Old: '', Type: 'Added' }, + { + Annotations: null, + Name: 'MBits', + New: '10', + Old: '', + Type: 'Added', + }, ], Name: 'Network', Objects: [ @@ -469,7 +697,15 @@ function generateDiff(changeset) { Objects: null, TaskGroups: [ { - Fields: [{ Annotations: null, Name: 'Count', New: '2', Old: '2', Type: 'None' }], + Fields: [ + { + Annotations: null, + Name: 'Count', + New: '2', + Old: '2', + Type: 'None', + }, + ], Name: 'cache', Objects: [ { diff --git a/ui/stories/components/dropdown.stories.js b/ui/stories/components/dropdown.stories.js index 528935cd0..57baf059a 100644 --- a/ui/stories/components/dropdown.stories.js +++ b/ui/stories/components/dropdown.stories.js @@ -77,7 +77,7 @@ export let Search = () => { 'Thirteen', 'Fourteen', 'Fifteen', - ].map(name => ({ name })), + ].map((name) => ({ name })), }, }; }; diff --git a/ui/stories/components/json-viewer.stories.js b/ui/stories/components/json-viewer.stories.js index 236bd1da8..5c313a557 100644 --- a/ui/stories/components/json-viewer.stories.js +++ b/ui/stories/components/json-viewer.stories.js @@ -21,7 +21,14 @@ export let Standard = () => { data: { foo: 'bar', number: 123456789, - products: ['Consul', 'Nomad', 'Packer', 'Terraform', 'Vagrant', 'Vault'], + products: [ + 'Consul', + 'Nomad', + 'Packer', + 'Terraform', + 'Vagrant', + 'Vault', + ], currentTime: '2019-10-16T14:24:12.378Z', nested: { obj: 'ject', diff --git a/ui/stories/components/table.stories.js b/ui/stories/components/table.stories.js index 309beab3f..0cce0359c 100644 --- a/ui/stories/components/table.stories.js +++ b/ui/stories/components/table.stories.js @@ -20,15 +20,16 @@ export default { * as the `controller` property so its query parameters are accessible from the template. */ function injectRoutedController(controllerClass) { - return on('init', function() { + return on('init', function () { let container = getOwner(this); container.register('controller:storybook', controllerClass); let routerFactory = container.factoryFor('router:main'); - routerFactory.class.map(function() { + routerFactory.class.map(function () { this.route('storybook'); }); + /* eslint-disable-next-line ember/no-private-routing-service */ let router = container.lookup('router:main'); router.initialURL = 'storybook'; router.startRouting(true); @@ -38,10 +39,34 @@ function injectRoutedController(controllerClass) { } let longList = [ - { city: 'New York', growth: 0.048, population: '8405837', rank: '1', state: 'New York' }, - { city: 'Los Angeles', growth: 0.048, population: '3884307', rank: '2', state: 'California' }, - { city: 'Chicago', growth: -0.061, population: '2718782', rank: '3', state: 'Illinois' }, - { city: 'Houston', growth: 0.11, population: '2195914', rank: '4', state: 'Texas' }, + { + city: 'New York', + growth: 0.048, + population: '8405837', + rank: '1', + state: 'New York', + }, + { + city: 'Los Angeles', + growth: 0.048, + population: '3884307', + rank: '2', + state: 'California', + }, + { + city: 'Chicago', + growth: -0.061, + population: '2718782', + rank: '3', + state: 'Illinois', + }, + { + city: 'Houston', + growth: 0.11, + population: '2195914', + rank: '4', + state: 'Texas', + }, { city: 'Philadelphia', growth: 0.026, @@ -49,14 +74,62 @@ let longList = [ rank: '5', state: 'Pennsylvania', }, - { city: 'Phoenix', growth: 0.14, population: '1513367', rank: '6', state: 'Arizona' }, - { city: 'San Antonio', growth: 0.21, population: '1409019', rank: '7', state: 'Texas' }, - { city: 'San Diego', growth: 0.105, population: '1355896', rank: '8', state: 'California' }, - { city: 'Dallas', growth: 0.056, population: '1257676', rank: '9', state: 'Texas' }, - { city: 'San Jose', growth: 0.105, population: '998537', rank: '10', state: 'California' }, - { city: 'Austin', growth: 0.317, population: '885400', rank: '11', state: 'Texas' }, - { city: 'Indianapolis', growth: 0.078, population: '843393', rank: '12', state: 'Indiana' }, - { city: 'Jacksonville', growth: 0.143, population: '842583', rank: '13', state: 'Florida' }, + { + city: 'Phoenix', + growth: 0.14, + population: '1513367', + rank: '6', + state: 'Arizona', + }, + { + city: 'San Antonio', + growth: 0.21, + population: '1409019', + rank: '7', + state: 'Texas', + }, + { + city: 'San Diego', + growth: 0.105, + population: '1355896', + rank: '8', + state: 'California', + }, + { + city: 'Dallas', + growth: 0.056, + population: '1257676', + rank: '9', + state: 'Texas', + }, + { + city: 'San Jose', + growth: 0.105, + population: '998537', + rank: '10', + state: 'California', + }, + { + city: 'Austin', + growth: 0.317, + population: '885400', + rank: '11', + state: 'Texas', + }, + { + city: 'Indianapolis', + growth: 0.078, + population: '843393', + rank: '12', + state: 'Indiana', + }, + { + city: 'Jacksonville', + growth: 0.143, + population: '842583', + rank: '13', + state: 'Florida', + }, { city: 'San Francisco', growth: 0.077, @@ -64,7 +137,13 @@ let longList = [ rank: '14', state: 'California', }, - { city: 'Columbus', growth: 0.148, population: '822553', rank: '15', state: 'Ohio' }, + { + city: 'Columbus', + growth: 0.148, + population: '822553', + rank: '15', + state: 'Ohio', + }, { city: 'Charlotte', growth: 0.391, @@ -72,12 +151,48 @@ let longList = [ rank: '16', state: 'North Carolina', }, - { city: 'Fort Worth', growth: 0.451, population: '792727', rank: '17', state: 'Texas' }, - { city: 'Detroit', growth: -0.271, population: '688701', rank: '18', state: 'Michigan' }, - { city: 'El Paso', growth: 0.194, population: '674433', rank: '19', state: 'Texas' }, - { city: 'Memphis', growth: -0.053, population: '653450', rank: '20', state: 'Tennessee' }, - { city: 'Seattle', growth: 0.156, population: '652405', rank: '21', state: 'Washington' }, - { city: 'Denver', growth: 0.167, population: '649495', rank: '22', state: 'Colorado' }, + { + city: 'Fort Worth', + growth: 0.451, + population: '792727', + rank: '17', + state: 'Texas', + }, + { + city: 'Detroit', + growth: -0.271, + population: '688701', + rank: '18', + state: 'Michigan', + }, + { + city: 'El Paso', + growth: 0.194, + population: '674433', + rank: '19', + state: 'Texas', + }, + { + city: 'Memphis', + growth: -0.053, + population: '653450', + rank: '20', + state: 'Tennessee', + }, + { + city: 'Seattle', + growth: 0.156, + population: '652405', + rank: '21', + state: 'Washington', + }, + { + city: 'Denver', + growth: 0.167, + population: '649495', + rank: '22', + state: 'Colorado', + }, { city: 'Washington', growth: 0.13, @@ -85,7 +200,13 @@ let longList = [ rank: '23', state: 'District of Columbia', }, - { city: 'Boston', growth: 0.094, population: '645966', rank: '24', state: 'Massachusetts' }, + { + city: 'Boston', + growth: 0.094, + population: '645966', + rank: '24', + state: 'Massachusetts', + }, { city: 'Nashville-Davidson', growth: 0.162, @@ -93,8 +214,20 @@ let longList = [ rank: '25', state: 'Tennessee', }, - { city: 'Baltimore', growth: -0.04, population: '622104', rank: '26', state: 'Maryland' }, - { city: 'Oklahoma City', growth: 0.202, population: '610613', rank: '27', state: 'Oklahoma' }, + { + city: 'Baltimore', + growth: -0.04, + population: '622104', + rank: '26', + state: 'Maryland', + }, + { + city: 'Oklahoma City', + growth: 0.202, + population: '610613', + rank: '27', + state: 'Oklahoma', + }, { city: 'Louisville/Jefferson County', growth: 0.1, @@ -102,18 +235,90 @@ let longList = [ rank: '28', state: 'Kentucky', }, - { city: 'Portland', growth: 0.15, population: '609456', rank: '29', state: 'Oregon' }, - { city: 'Las Vegas', growth: 0.245, population: '603488', rank: '30', state: 'Nevada' }, - { city: 'Milwaukee', growth: 0.003, population: '599164', rank: '31', state: 'Wisconsin' }, - { city: 'Albuquerque', growth: 0.235, population: '556495', rank: '32', state: 'New Mexico' }, - { city: 'Tucson', growth: 0.075, population: '526116', rank: '33', state: 'Arizona' }, - { city: 'Fresno', growth: 0.183, population: '509924', rank: '34', state: 'California' }, - { city: 'Sacramento', growth: 0.172, population: '479686', rank: '35', state: 'California' }, - { city: 'Long Beach', growth: 0.015, population: '469428', rank: '36', state: 'California' }, - { city: 'Kansas City', growth: 0.055, population: '467007', rank: '37', state: 'Missouri' }, - { city: 'Mesa', growth: 0.135, population: '457587', rank: '38', state: 'Arizona' }, - { city: 'Virginia Beach', growth: 0.051, population: '448479', rank: '39', state: 'Virginia' }, - { city: 'Atlanta', growth: 0.062, population: '447841', rank: '40', state: 'Georgia' }, + { + city: 'Portland', + growth: 0.15, + population: '609456', + rank: '29', + state: 'Oregon', + }, + { + city: 'Las Vegas', + growth: 0.245, + population: '603488', + rank: '30', + state: 'Nevada', + }, + { + city: 'Milwaukee', + growth: 0.003, + population: '599164', + rank: '31', + state: 'Wisconsin', + }, + { + city: 'Albuquerque', + growth: 0.235, + population: '556495', + rank: '32', + state: 'New Mexico', + }, + { + city: 'Tucson', + growth: 0.075, + population: '526116', + rank: '33', + state: 'Arizona', + }, + { + city: 'Fresno', + growth: 0.183, + population: '509924', + rank: '34', + state: 'California', + }, + { + city: 'Sacramento', + growth: 0.172, + population: '479686', + rank: '35', + state: 'California', + }, + { + city: 'Long Beach', + growth: 0.015, + population: '469428', + rank: '36', + state: 'California', + }, + { + city: 'Kansas City', + growth: 0.055, + population: '467007', + rank: '37', + state: 'Missouri', + }, + { + city: 'Mesa', + growth: 0.135, + population: '457587', + rank: '38', + state: 'Arizona', + }, + { + city: 'Virginia Beach', + growth: 0.051, + population: '448479', + rank: '39', + state: 'Virginia', + }, + { + city: 'Atlanta', + growth: 0.062, + population: '447841', + rank: '40', + state: 'Georgia', + }, { city: 'Colorado Springs', growth: 0.214, @@ -121,15 +326,69 @@ let longList = [ rank: '41', state: 'Colorado', }, - { city: 'Omaha', growth: 0.059, population: '434353', rank: '42', state: 'Nebraska' }, - { city: 'Raleigh', growth: 0.487, population: '431746', rank: '43', state: 'North Carolina' }, - { city: 'Miami', growth: 0.149, population: '417650', rank: '44', state: 'Florida' }, - { city: 'Oakland', growth: 0.013, population: '406253', rank: '45', state: 'California' }, - { city: 'Minneapolis', growth: 0.045, population: '400070', rank: '46', state: 'Minnesota' }, - { city: 'Tulsa', growth: 0.013, population: '398121', rank: '47', state: 'Oklahoma' }, - { city: 'Cleveland', growth: -0.181, population: '390113', rank: '48', state: 'Ohio' }, - { city: 'Wichita', growth: 0.097, population: '386552', rank: '49', state: 'Kansas' }, - { city: 'Arlington', growth: 0.133, population: '379577', rank: '50', state: 'Texas' }, + { + city: 'Omaha', + growth: 0.059, + population: '434353', + rank: '42', + state: 'Nebraska', + }, + { + city: 'Raleigh', + growth: 0.487, + population: '431746', + rank: '43', + state: 'North Carolina', + }, + { + city: 'Miami', + growth: 0.149, + population: '417650', + rank: '44', + state: 'Florida', + }, + { + city: 'Oakland', + growth: 0.013, + population: '406253', + rank: '45', + state: 'California', + }, + { + city: 'Minneapolis', + growth: 0.045, + population: '400070', + rank: '46', + state: 'Minnesota', + }, + { + city: 'Tulsa', + growth: 0.013, + population: '398121', + rank: '47', + state: 'Oklahoma', + }, + { + city: 'Cleveland', + growth: -0.181, + population: '390113', + rank: '48', + state: 'Ohio', + }, + { + city: 'Wichita', + growth: 0.097, + population: '386552', + rank: '49', + state: 'Kansas', + }, + { + city: 'Arlington', + growth: 0.133, + population: '379577', + rank: '50', + state: 'Texas', + }, ]; export let Standard = () => { @@ -201,9 +460,11 @@ export let Search = () => { controller: EmberObject.extend({ searchTerm: '', - filteredShortList: computed('searchTerm', function() { + filteredShortList: computed('searchTerm', function () { let term = this.searchTerm.toLowerCase(); - return productMetadata.filter(product => product.name.toLowerCase().includes(term)); + return productMetadata.filter((product) => + product.name.toLowerCase().includes(term) + ); }), }).create(), }, @@ -240,10 +501,17 @@ export let SortableColumns = () => { }) ), - sortedShortList: computed('controller.{sortProperty,sortDescending}', function() { - let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name'); - return this.get('controller.sortDescending') ? sorted.reverse() : sorted; - }), + sortedShortList: computed( + 'controller.{sortProperty,sortDescending}', + function () { + let sorted = productMetadata.sortBy( + this.get('controller.sortProperty') || 'name' + ); + return this.get('controller.sortDescending') + ? sorted.reverse() + : sorted; + } + ), }, }; }; @@ -278,10 +546,17 @@ export let MultiRow = () => { }) ), - sortedShortList: computed('controller.{sortProperty,sortDescending}', function() { - let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name'); - return this.get('controller.sortDescending') ? sorted.reverse() : sorted; - }), + sortedShortList: computed( + 'controller.{sortProperty,sortDescending}', + function () { + let sorted = productMetadata.sortBy( + this.get('controller.sortProperty') || 'name' + ); + return this.get('controller.sortDescending') + ? sorted.reverse() + : sorted; + } + ), }, }; }; diff --git a/ui/stories/theme/colors.stories.js b/ui/stories/theme/colors.stories.js index b849613a1..da59a6c52 100644 --- a/ui/stories/theme/colors.stories.js +++ b/ui/stories/theme/colors.stories.js @@ -129,8 +129,8 @@ export let Colors = () => { }, ], }, - ].map(palette => { - palette.colors.forEach(color => { + ].map((palette) => { + palette.colors.forEach((color) => { color.style = htmlSafe(`background-color: ${color.base}`); }); return palette; diff --git a/ui/stories/theme/font-stacks.stories.js b/ui/stories/theme/font-stacks.stories.js index f91002571..35394d756 100644 --- a/ui/stories/theme/font-stacks.stories.js +++ b/ui/stories/theme/font-stacks.stories.js @@ -33,7 +33,7 @@ export let FontStacks = () => { 'Helvetica Neue', 'sans-serif', 'monospace', - ].map(family => { + ].map((family) => { return { name: family, style: htmlSafe(`font-family: ${family}`), diff --git a/ui/testem.js b/ui/testem.js index f15024e93..f0d81dfdf 100644 --- a/ui/testem.js +++ b/ui/testem.js @@ -25,20 +25,27 @@ const config = { '--disable-software-rasterizer', '--mute-audio', '--remote-debugging-port=0', - '--window-size=1440,900' - ].filter(Boolean) - } - } + '--window-size=1440,900', + ].filter(Boolean), + }, + }, }; if (process.env.CI) { - const reporters = [{ - ReporterClass: TapReporter, - args: [false, null, { get: () => false }] - }, { - ReporterClass: XunitReporter, - args: [false, fs.createWriteStream('/tmp/test-reports/ui.xml'), { get: () => false }] - }]; + const reporters = [ + { + ReporterClass: TapReporter, + args: [false, null, { get: () => false }], + }, + { + ReporterClass: XunitReporter, + args: [ + false, + fs.createWriteStream('/tmp/test-reports/ui.xml'), + { get: () => false }, + ], + }, + ]; const multiReporter = new MultiReporter({ reporters }); diff --git a/ui/tests/.eslintrc.js b/ui/tests/.eslintrc.js index 66934a875..057b1bcb3 100644 --- a/ui/tests/.eslintrc.js +++ b/ui/tests/.eslintrc.js @@ -10,6 +10,7 @@ module.exports = { env: { embertest: true, }, + extends: ['plugin:qunit/recommended'], overrides: [ { files: ['acceptance/**/*-test.js'], diff --git a/ui/tests/acceptance/allocation-detail-test.js b/ui/tests/acceptance/allocation-detail-test.js index d1fd9defe..00861d08a 100644 --- a/ui/tests/acceptance/allocation-detail-test.js +++ b/ui/tests/acceptance/allocation-detail-test.js @@ -1,3 +1,5 @@ +/* eslint-disable qunit/require-expect */ +/* Mirage fixtures are random so we can't expect a set number of assertions */ import { run } from '@ember/runloop'; import { currentURL } from '@ember/test-helpers'; import { assign } from '@ember/polyfills'; @@ -14,11 +16,11 @@ let job; let node; let allocation; -module('Acceptance | allocation detail', function(hooks) { +module('Acceptance | allocation detail', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); node = server.create('node'); @@ -51,13 +53,20 @@ module('Acceptance | allocation detail', function(hooks) { await Allocation.visit({ id: allocation.id }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await a11yAudit(assert); }); - test('/allocation/:id should name the allocation and link to the corresponding job and node', async function(assert) { - assert.ok(Allocation.title.includes(allocation.name), 'Allocation name is in the heading'); - assert.equal(Allocation.details.job, job.name, 'Job name is in the subheading'); + test('/allocation/:id should name the allocation and link to the corresponding job and node', async function (assert) { + assert.ok( + Allocation.title.includes(allocation.name), + 'Allocation name is in the heading' + ); + assert.equal( + Allocation.details.job, + job.name, + 'Job name is in the subheading' + ); assert.equal( Allocation.details.client, node.id.split('-')[0], @@ -68,21 +77,41 @@ module('Acceptance | allocation detail', function(hooks) { assert.equal(document.title, `Allocation ${allocation.name} - Nomad`); await Allocation.details.visitJob(); - assert.equal(currentURL(), `/jobs/${job.id}`, 'Job link navigates to the job'); + assert.equal( + currentURL(), + `/jobs/${job.id}`, + 'Job link navigates to the job' + ); await Allocation.visit({ id: allocation.id }); await Allocation.details.visitClient(); - assert.equal(currentURL(), `/clients/${node.id}`, 'Client link navigates to the client'); + assert.equal( + currentURL(), + `/clients/${node.id}`, + 'Client link navigates to the client' + ); }); - test('/allocation/:id should include resource utilization graphs', async function(assert) { - assert.equal(Allocation.resourceCharts.length, 2, 'Two resource utilization graphs'); - assert.equal(Allocation.resourceCharts.objectAt(0).name, 'CPU', 'First chart is CPU'); - assert.equal(Allocation.resourceCharts.objectAt(1).name, 'Memory', 'Second chart is Memory'); + test('/allocation/:id should include resource utilization graphs', async function (assert) { + assert.equal( + Allocation.resourceCharts.length, + 2, + 'Two resource utilization graphs' + ); + assert.equal( + Allocation.resourceCharts.objectAt(0).name, + 'CPU', + 'First chart is CPU' + ); + assert.equal( + Allocation.resourceCharts.objectAt(1).name, + 'Memory', + 'Second chart is Memory' + ); }); - test('/allocation/:id should present task lifecycles', async function(assert) { + test('/allocation/:id should present task lifecycles', async function (assert) { const job = server.create('job', { groupsCount: 1, groupTaskCount: 6, @@ -107,15 +136,22 @@ module('Acceptance | allocation detail', function(hooks) { const prestartEphemeralTask = server.db.taskStates .where({ allocationId: allocation.id }) .sortBy('name') - .find(taskState => { + .find((taskState) => { const task = server.db.tasks.findBy({ name: taskState.name }); - return task.Lifecycle && task.Lifecycle.Hook === 'prestart' && !task.Lifecycle.Sidecar; + return ( + task.Lifecycle && + task.Lifecycle.Hook === 'prestart' && + !task.Lifecycle.Sidecar + ); }); - assert.equal(currentURL(), `/allocations/${allocation.id}/${prestartEphemeralTask.name}`); + assert.equal( + currentURL(), + `/allocations/${allocation.id}/${prestartEphemeralTask.name}` + ); }); - test('/allocation/:id should list all tasks for the allocation', async function(assert) { + test('/allocation/:id should list all tasks for the allocation', async function (assert) { assert.equal( Allocation.tasks.length, server.db.taskStates.where({ allocationId: allocation.id }).length, @@ -124,8 +160,10 @@ module('Acceptance | allocation detail', function(hooks) { assert.notOk(Allocation.isEmpty, 'Task table empty state is not shown'); }); - test('each task row should list high-level information for the task', async function(assert) { - const task = server.db.taskStates.where({ allocationId: allocation.id }).sortBy('name')[0]; + test('each task row should list high-level information for the task', async function (assert) { + const task = server.db.taskStates + .where({ allocationId: allocation.id }) + .sortBy('name')[0]; const events = server.db.taskEvents.where({ taskStateId: task.id }); const event = events[events.length - 1]; @@ -134,13 +172,13 @@ module('Acceptance | allocation detail', function(hooks) { name: allocation.taskGroup, }).models[0]; - const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); - const volumes = jobTask.volumeMounts.map(volume => ({ + const jobTask = taskGroup.tasks.models.find((m) => m.name === task.name); + const volumes = jobTask.volumeMounts.map((volume) => ({ name: volume.Volume, source: taskGroup.volumes[volume.Volume].Source, })); - Allocation.tasks[0].as(taskRow => { + Allocation.tasks[0].as((taskRow) => { assert.equal(taskRow.name, task.name, 'Name'); assert.equal(taskRow.state, task.state, 'State'); assert.equal(taskRow.message, event.displayMessage, 'Event Message'); @@ -151,18 +189,27 @@ module('Acceptance | allocation detail', function(hooks) { ); const volumesText = taskRow.volumes; - volumes.forEach(volume => { - assert.ok(volumesText.includes(volume.name), `Found label ${volume.name}`); - assert.ok(volumesText.includes(volume.source), `Found value ${volume.source}`); + volumes.forEach((volume) => { + assert.ok( + volumesText.includes(volume.name), + `Found label ${volume.name}` + ); + assert.ok( + volumesText.includes(volume.source), + `Found value ${volume.source}` + ); }); }); }); - test('each task row should link to the task detail page', async function(assert) { - const task = server.db.taskStates.where({ allocationId: allocation.id }).sortBy('name')[0]; + test('each task row should link to the task detail page', async function (assert) { + const task = server.db.taskStates + .where({ allocationId: allocation.id }) + .sortBy('name')[0]; await Allocation.tasks.objectAt(0).clickLink(); + // Make sure the allocation is pending in order to ensure there are no tasks assert.equal( currentURL(), `/allocations/${allocation.id}/${task.name}`, @@ -179,7 +226,7 @@ module('Acceptance | allocation detail', function(hooks) { ); }); - test('tasks with an unhealthy driver have a warning icon', async function(assert) { + test('tasks with an unhealthy driver have a warning icon', async function (assert) { // Driver health status require node:read permission. const policy = server.create('policy', { id: 'node-read', @@ -200,10 +247,13 @@ module('Acceptance | allocation detail', function(hooks) { await Tokens.secret(clientToken.secretId).submit(); await Allocation.visit({ id: allocation.id }); - assert.ok(Allocation.firstUnhealthyTask().hasUnhealthyDriver, 'Warning is shown'); + assert.ok( + Allocation.firstUnhealthyTask().hasUnhealthyDriver, + 'Warning is shown' + ); }); - test('proxy task has a proxy tag', async function(assert) { + test('proxy task has a proxy tag', async function (assert) { // Must create a new job as existing one has loaded and it contains the tasks job = server.create('job', { groupsCount: 1, @@ -226,19 +276,24 @@ module('Acceptance | allocation detail', function(hooks) { assert.ok(Allocation.tasks[0].hasProxyTag); }); - test('when there are no tasks, an empty state is shown', async function(assert) { + test('when there are no tasks, an empty state is shown', async function (assert) { // Make sure the allocation is pending in order to ensure there are no tasks - allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'pending' }); + allocation = server.create('allocation', 'withTaskWithPorts', { + clientStatus: 'pending', + }); await Allocation.visit({ id: allocation.id }); assert.ok(Allocation.isEmpty, 'Task table empty state is shown'); }); - test('when the allocation has not been rescheduled, the reschedule events section is not rendered', async function(assert) { - assert.notOk(Allocation.hasRescheduleEvents, 'Reschedule Events section exists'); + test('when the allocation has not been rescheduled, the reschedule events section is not rendered', async function (assert) { + assert.notOk( + Allocation.hasRescheduleEvents, + 'Reschedule Events section exists' + ); }); - test('ports are listed', async function(assert) { + test('ports are listed', async function (assert) { const allServerPorts = allocation.taskResources.models[0].resources.Ports; allServerPorts.sortBy('Label').forEach((serverPort, index) => { @@ -246,12 +301,17 @@ module('Acceptance | allocation detail', function(hooks) { assert.equal(renderedPort.name, serverPort.Label); assert.equal(renderedPort.to, serverPort.To); - assert.equal(renderedPort.address, formatHost(serverPort.HostIP, serverPort.Value)); + assert.equal( + renderedPort.address, + formatHost(serverPort.HostIP, serverPort.Value) + ); }); }); - test('services are listed', async function(assert) { - const taskGroup = server.schema.taskGroups.findBy({ name: allocation.taskGroup }); + test('services are listed', async function (assert) { + const taskGroup = server.schema.taskGroups.findBy({ + name: allocation.taskGroup, + }); assert.equal(Allocation.services.length, taskGroup.services.length); @@ -263,46 +323,59 @@ module('Acceptance | allocation detail', function(hooks) { assert.equal(renderedService.onUpdate, serverService.onUpdate); assert.equal(renderedService.tags, (serverService.tags || []).join(', ')); - assert.equal(renderedService.connect, serverService.Connect ? 'Yes' : 'No'); + assert.equal( + renderedService.connect, + serverService.Connect ? 'Yes' : 'No' + ); const upstreams = serverService.Connect.SidecarService.Proxy.Upstreams; const serverUpstreamsString = upstreams - .map(upstream => `${upstream.DestinationName}:${upstream.LocalBindPort}`) + .map( + (upstream) => `${upstream.DestinationName}:${upstream.LocalBindPort}` + ) .join(' '); assert.equal(renderedService.upstreams, serverUpstreamsString); }); }); - test('when the allocation is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the allocation is not found, an error message is shown, but the URL persists', async function (assert) { await Allocation.visit({ id: 'not-a-real-allocation' }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/allocation/not-a-real-allocation', 'A request to the nonexistent allocation is made' ); - assert.equal(currentURL(), '/allocations/not-a-real-allocation', 'The URL persists'); + assert.equal( + currentURL(), + '/allocations/not-a-real-allocation', + 'The URL persists' + ); assert.ok(Allocation.error.isShown, 'Error message is shown'); - assert.equal(Allocation.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + Allocation.error.title, + 'Not Found', + 'Error message is for 404' + ); }); - test('allocation can be stopped', async function(assert) { + test('allocation can be stopped', async function (assert) { await Allocation.stop.idle(); await Allocation.stop.confirm(); assert.equal( server.pretender.handledRequests - .reject(request => request.url.includes('fuzzy')) + .reject((request) => request.url.includes('fuzzy')) .findBy('method', 'POST').url, `/v1/allocation/${allocation.id}/stop`, 'Stop request is made for the allocation' ); }); - test('allocation can be restarted', async function(assert) { + test('allocation can be restarted', async function (assert) { await Allocation.restart.idle(); await Allocation.restart.confirm(); @@ -313,7 +386,7 @@ module('Acceptance | allocation detail', function(hooks) { ); }); - test('while an allocation is being restarted, the stop button is disabled', async function(assert) { + test('while an allocation is being restarted, the stop button is disabled', async function (assert) { server.pretender.post('/v1/allocation/:id/stop', () => [204, {}, ''], true); await Allocation.stop.idle(); @@ -327,7 +400,7 @@ module('Acceptance | allocation detail', function(hooks) { await Allocation.stop.confirm(); }); - test('if stopping or restarting fails, an error message is shown', async function(assert) { + test('if stopping or restarting fails, an error message is shown', async function (assert) { server.pretender.post('/v1/allocation/:id/stop', () => [403, {}, '']); await Allocation.stop.idle(); @@ -345,15 +418,18 @@ module('Acceptance | allocation detail', function(hooks) { await Allocation.inlineError.dismiss(); - assert.notOk(Allocation.inlineError.isShown, 'Inline error is no longer shown'); + assert.notOk( + Allocation.inlineError.isShown, + 'Inline error is no longer shown' + ); }); }); -module('Acceptance | allocation detail (rescheduled)', function(hooks) { +module('Acceptance | allocation detail (rescheduled)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); node = server.create('node'); @@ -363,16 +439,19 @@ module('Acceptance | allocation detail (rescheduled)', function(hooks) { await Allocation.visit({ id: allocation.id }); }); - test('when the allocation has been rescheduled, the reschedule events section is rendered', async function(assert) { - assert.ok(Allocation.hasRescheduleEvents, 'Reschedule Events section exists'); + test('when the allocation has been rescheduled, the reschedule events section is rendered', async function (assert) { + assert.ok( + Allocation.hasRescheduleEvents, + 'Reschedule Events section exists' + ); }); }); -module('Acceptance | allocation detail (not running)', function(hooks) { +module('Acceptance | allocation detail (not running)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); node = server.create('node'); @@ -382,7 +461,7 @@ module('Acceptance | allocation detail (not running)', function(hooks) { await Allocation.visit({ id: allocation.id }); }); - test('when the allocation is not running, the utilization graphs are replaced by an empty message', async function(assert) { + test('when the allocation is not running, the utilization graphs are replaced by an empty message', async function (assert) { assert.equal(Allocation.resourceCharts.length, 0, 'No resource charts'); assert.equal( Allocation.resourceEmptyMessage, @@ -391,34 +470,45 @@ module('Acceptance | allocation detail (not running)', function(hooks) { ); }); - test('the exec and stop/restart buttons are absent', async function(assert) { + test('the exec and stop/restart buttons are absent', async function (assert) { assert.notOk(Allocation.execButton.isPresent); assert.notOk(Allocation.stop.isPresent); assert.notOk(Allocation.restart.isPresent); }); }); -module('Acceptance | allocation detail (preemptions)', function(hooks) { +module('Acceptance | allocation detail (preemptions)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); node = server.create('node'); job = server.create('job', { createAllocations: false }); window.localStorage.clear(); }); - test('shows a dedicated section to the allocation that preempted this allocation', async function(assert) { + test('shows a dedicated section to the allocation that preempted this allocation', async function (assert) { allocation = server.create('allocation', 'preempted'); - const preempter = server.schema.find('allocation', allocation.preemptedByAllocation); + const preempter = server.schema.find( + 'allocation', + allocation.preemptedByAllocation + ); const preempterJob = server.schema.find('job', preempter.jobId); const preempterClient = server.schema.find('node', preempter.nodeId); await Allocation.visit({ id: allocation.id }); assert.ok(Allocation.wasPreempted, 'Preempted allocation section is shown'); - assert.equal(Allocation.preempter.status, preempter.clientStatus, 'Preempter status matches'); - assert.equal(Allocation.preempter.name, preempter.name, 'Preempter name matches'); + assert.equal( + Allocation.preempter.status, + preempter.clientStatus, + 'Preempter status matches' + ); + assert.equal( + Allocation.preempter.name, + preempter.name, + 'Preempter name matches' + ); assert.equal( Allocation.preempter.priority, preempterJob.priority, @@ -449,18 +539,21 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) { ); }); - test('shows a dedicated section to the allocations this allocation preempted', async function(assert) { + test('shows a dedicated section to the allocations this allocation preempted', async function (assert) { allocation = server.create('allocation', 'preempter'); await Allocation.visit({ id: allocation.id }); - assert.ok(Allocation.preempted, 'The allocations this allocation preempted are shown'); + assert.ok( + Allocation.preempted, + 'The allocations this allocation preempted are shown' + ); }); - test('each preempted allocation in the table lists basic allocation information', async function(assert) { + test('each preempted allocation in the table lists basic allocation information', async function (assert) { allocation = server.create('allocation', 'preempter'); await Allocation.visit({ id: allocation.id }); const preemption = allocation.preemptedAllocations - .map(id => server.schema.find('allocation', id)) + .map((id) => server.schema.find('allocation', id)) .sortBy('modifyIndex') .reverse()[0]; const preemptionRow = Allocation.preemptions.objectAt(0); @@ -471,7 +564,11 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) { 'The preemptions table has a row for each preempted allocation' ); - assert.equal(preemptionRow.shortId, preemption.id.split('-')[0], 'Preemption short id'); + assert.equal( + preemptionRow.shortId, + preemption.id.split('-')[0], + 'Preemption short id' + ); assert.equal( preemptionRow.createTime, moment(preemption.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'), @@ -482,8 +579,16 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) { moment(preemption.modifyTime / 1000000).fromNow(), 'Preemption modify time' ); - assert.equal(preemptionRow.status, preemption.clientStatus, 'Client status'); - assert.equal(preemptionRow.jobVersion, preemption.jobVersion, 'Job Version'); + assert.equal( + preemptionRow.status, + preemption.clientStatus, + 'Client status' + ); + assert.equal( + preemptionRow.jobVersion, + preemption.jobVersion, + 'Job Version' + ); assert.equal( preemptionRow.client, server.db.nodes.find(preemption.nodeId).id.split('-')[0], @@ -491,7 +596,7 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) { ); }); - test('clicking the client ID in the preempted allocation row naviates to the client page', async function(assert) { + test('clicking the client ID in the preempted allocation row naviates to the client page', async function (assert) { // Navigating to the client page requires node:read permission. const policy = server.create('policy', { id: 'node-read', @@ -511,19 +616,26 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) { await Allocation.visit({ id: allocation.id }); const preemption = allocation.preemptedAllocations - .map(id => server.schema.find('allocation', id)) + .map((id) => server.schema.find('allocation', id)) .sortBy('modifyIndex') .reverse()[0]; const preemptionRow = Allocation.preemptions.objectAt(0); await preemptionRow.visitClient(); - assert.equal(currentURL(), `/clients/${preemption.nodeId}`, 'Node links to node page'); + assert.equal( + currentURL(), + `/clients/${preemption.nodeId}`, + 'Node links to node page' + ); }); - test('when an allocation both preempted allocations and was preempted itself, both preemptions sections are shown', async function(assert) { + test('when an allocation both preempted allocations and was preempted itself, both preemptions sections are shown', async function (assert) { allocation = server.create('allocation', 'preempter', 'preempted'); await Allocation.visit({ id: allocation.id }); - assert.ok(Allocation.preempted, 'The allocations this allocation preempted are shown'); + assert.ok( + Allocation.preempted, + 'The allocations this allocation preempted are shown' + ); assert.ok(Allocation.wasPreempted, 'Preempted allocation section is shown'); }); }); diff --git a/ui/tests/acceptance/allocation-fs-test.js b/ui/tests/acceptance/allocation-fs-test.js index 2712a325f..5f88d72ab 100644 --- a/ui/tests/acceptance/allocation-fs-test.js +++ b/ui/tests/acceptance/allocation-fs-test.js @@ -9,16 +9,19 @@ import browseFilesystem from './behaviors/fs'; let allocation; let files; -module('Acceptance | allocation fs', function(hooks) { +module('Acceptance | allocation fs', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node', 'forceIPv4'); const job = server.create('job', { createAllocations: false }); - allocation = server.create('allocation', { jobId: job.id, clientStatus: 'running' }); + allocation = server.create('allocation', { + jobId: job.id, + clientStatus: 'running', + }); this.allocation = allocation; @@ -27,7 +30,13 @@ module('Acceptance | allocation fs', function(hooks) { // Nested files files.push(server.create('allocFile', { isDir: true, name: 'directory' })); - files.push(server.create('allocFile', { isDir: true, name: 'another', parent: files[0] })); + files.push( + server.create('allocFile', { + isDir: true, + name: 'another', + parent: files[0], + }) + ); files.push( server.create('allocFile', 'file', { name: 'something.txt', @@ -36,7 +45,9 @@ module('Acceptance | allocation fs', function(hooks) { }) ); - files.push(server.create('allocFile', { isDir: true, name: 'empty-directory' })); + files.push( + server.create('allocFile', { isDir: true, name: 'empty-directory' }) + ); files.push(server.create('allocFile', 'file', { fileType: 'txt' })); files.push(server.create('allocFile', 'file', { fileType: 'txt' })); @@ -47,8 +58,10 @@ module('Acceptance | allocation fs', function(hooks) { browseFilesystem({ visitSegments: ({ allocation }) => ({ id: allocation.id }), - getExpectedPathBase: ({ allocation }) => `/allocations/${allocation.id}/fs/`, - getTitleComponent: ({ allocation }) => `Allocation ${allocation.id.split('-')[0]} filesystem`, + getExpectedPathBase: ({ allocation }) => + `/allocations/${allocation.id}/fs/`, + getTitleComponent: ({ allocation }) => + `Allocation ${allocation.id.split('-')[0]} filesystem`, getBreadcrumbComponent: ({ allocation }) => allocation.id.split('-')[0], getFilesystemRoot: () => '', pageObjectVisitFunctionName: 'visitAllocation', diff --git a/ui/tests/acceptance/application-errors-test.js b/ui/tests/acceptance/application-errors-test.js index ab609b615..ecd390be5 100644 --- a/ui/tests/acceptance/application-errors-test.js +++ b/ui/tests/acceptance/application-errors-test.js @@ -7,33 +7,38 @@ import ClientsList from 'nomad-ui/tests/pages/clients/list'; import JobsList from 'nomad-ui/tests/pages/jobs/list'; import Job from 'nomad-ui/tests/pages/jobs/detail'; -module('Acceptance | application errors ', function(hooks) { +module('Acceptance | application errors ', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('agent'); server.create('node'); server.create('job'); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + server.pretender.get('/v1/nodes', () => [500, {}, null]); await ClientsList.visit(); await a11yAudit(assert); }); - test('transitioning away from an error page resets the global error', async function(assert) { + test('transitioning away from an error page resets the global error', async function (assert) { server.pretender.get('/v1/nodes', () => [500, {}, null]); await ClientsList.visit(); assert.ok(ClientsList.error.isPresent, 'Application has errored'); await JobsList.visit(); - assert.notOk(JobsList.error.isPresent, 'Application is no longer in an error state'); + assert.notOk( + JobsList.error.isPresent, + 'Application is no longer in an error state' + ); }); - test('the 403 error page links to the ACL tokens page', async function(assert) { + test('the 403 error page links to the ACL tokens page', async function (assert) { const job = server.db.jobs[0]; server.pretender.get(`/v1/job/${job.id}`, () => [403, {}, null]); @@ -51,7 +56,7 @@ module('Acceptance | application errors ', function(hooks) { ); }); - test('the no leader error state gets its own error message', async function(assert) { + test('the no leader error state gets its own error message', async function (assert) { server.pretender.get('/v1/jobs', () => [500, {}, 'No cluster leader']); await JobsList.visit(); @@ -64,7 +69,7 @@ module('Acceptance | application errors ', function(hooks) { ); }); - test('error pages include links to the jobs and clients pages', async function(assert) { + test('error pages include links to the jobs and clients pages', async function (assert) { await visit('/a/non-existent/page'); assert.ok(JobsList.error.isPresent, 'An error is shown'); diff --git a/ui/tests/acceptance/behaviors/fs.js b/ui/tests/acceptance/behaviors/fs.js index 6a1665075..c6a1f4a57 100644 --- a/ui/tests/acceptance/behaviors/fs.js +++ b/ui/tests/acceptance/behaviors/fs.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { test } from 'qunit'; import { currentURL, visit } from '@ember/test-helpers'; @@ -13,7 +14,7 @@ import FS from 'nomad-ui/tests/pages/allocations/fs'; const fileSort = (prop, files) => { let dir = []; let file = []; - files.forEach(f => { + files.forEach((f) => { if (f.isDir) { dir.push(f); } else { @@ -33,14 +34,14 @@ export default function browseFilesystem({ getBreadcrumbComponent, getFilesystemRoot, }) { - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await FS[pageObjectVisitFunctionName]( visitSegments({ allocation: this.allocation, task: this.task }) ); await a11yAudit(assert); }); - test('visiting filesystem root', async function(assert) { + test('visiting filesystem root', async function (assert) { await FS[pageObjectVisitFunctionName]( visitSegments({ allocation: this.allocation, task: this.task }) ); @@ -54,10 +55,15 @@ export default function browseFilesystem({ assert.equal(currentURL(), pathBaseWithoutTrailingSlash, 'No redirect'); }); - test('visiting filesystem paths', async function(assert) { - const paths = ['some-file.log', 'a/deep/path/to/a/file.log', '/', 'Unicode™®']; + test('visiting filesystem paths', async function (assert) { + const paths = [ + 'some-file.log', + 'a/deep/path/to/a/file.log', + '/', + 'Unicode™®', + ]; - const testPath = async filePath => { + const testPath = async (filePath) => { let pathWithLeadingSlash = filePath; if (!pathWithLeadingSlash.startsWith('/')) { @@ -98,13 +104,17 @@ export default function browseFilesystem({ }, Promise.resolve()); }); - test('navigating allocation filesystem', async function(assert) { + test('navigating allocation filesystem', async function (assert) { const objects = { allocation: this.allocation, task: this.task }; - await FS[pageObjectVisitPathFunctionName]({ ...visitSegments(objects), path: '/' }); + await FS[pageObjectVisitPathFunctionName]({ + ...visitSegments(objects), + path: '/', + }); const sortedFiles = fileSort( 'name', - filesForPath(this.server.schema.allocFiles, getFilesystemRoot(objects)).models + filesForPath(this.server.schema.allocFiles, getFilesystemRoot(objects)) + .models ); assert.ok(FS.fileViewer.isHidden); @@ -117,16 +127,26 @@ export default function browseFilesystem({ assert.ok(FS.breadcrumbs[0].isActive); assert.equal(FS.breadcrumbs[0].text, getBreadcrumbComponent(objects)); - FS.directoryEntries[0].as(directory => { + FS.directoryEntries[0].as((directory) => { const fileRecord = sortedFiles[0]; - assert.equal(directory.name, fileRecord.name, 'directories should come first'); + assert.equal( + directory.name, + fileRecord.name, + 'directories should come first' + ); assert.ok(directory.isDirectory); assert.equal(directory.size, '', 'directory sizes are hidden'); - assert.equal(directory.lastModified, moment(fileRecord.modTime).fromNow()); - assert.notOk(directory.path.includes('//'), 'paths shouldn’t have redundant separators'); + assert.equal( + directory.lastModified, + moment(fileRecord.modTime).fromNow() + ); + assert.notOk( + directory.path.includes('//'), + 'paths shouldn’t have redundant separators' + ); }); - FS.directoryEntries[2].as(file => { + FS.directoryEntries[2].as((file) => { const fileRecord = sortedFiles[2]; assert.equal(file.name, fileRecord.name); assert.ok(file.isFile); @@ -139,7 +159,10 @@ export default function browseFilesystem({ assert.equal(FS.directoryEntries.length, 1); assert.equal(FS.breadcrumbs.length, 2); - assert.equal(FS.breadcrumbsText, `${getBreadcrumbComponent(objects)} ${this.directory.name}`); + assert.equal( + FS.breadcrumbsText, + `${getBreadcrumbComponent(objects)} ${this.directory.name}` + ); assert.notOk(FS.breadcrumbs[0].isActive); @@ -157,7 +180,9 @@ export default function browseFilesystem({ assert.equal(FS.breadcrumbs.length, 3); assert.equal( FS.breadcrumbsText, - `${getBreadcrumbComponent(objects)} ${this.directory.name} ${this.nestedDirectory.name}` + `${getBreadcrumbComponent(objects)} ${this.directory.name} ${ + this.nestedDirectory.name + }` ); assert.equal(FS.breadcrumbs[2].text, this.nestedDirectory.name); @@ -171,28 +196,27 @@ export default function browseFilesystem({ ); await FS.breadcrumbs[1].visit(); - assert.equal(FS.breadcrumbsText, `${getBreadcrumbComponent(objects)} ${this.directory.name}`); + assert.equal( + FS.breadcrumbsText, + `${getBreadcrumbComponent(objects)} ${this.directory.name}` + ); assert.equal(FS.breadcrumbs.length, 2); }); - test('sorting allocation filesystem directory', async function(assert) { + test('sorting allocation filesystem directory', async function (assert) { this.server.get('/client/fs/ls/:allocation_id', () => { return [ { Name: 'aaa-big-old-file', IsDir: false, Size: 19190000, - ModTime: moment() - .subtract(1, 'year') - .format(), + ModTime: moment().subtract(1, 'year').format(), }, { Name: 'mmm-small-mid-file', IsDir: false, Size: 1919, - ModTime: moment() - .subtract(6, 'month') - .format(), + ModTime: moment().subtract(6, 'month').format(), }, { Name: 'zzz-med-new-file', @@ -204,17 +228,13 @@ export default function browseFilesystem({ Name: 'aaa-big-old-directory', IsDir: true, Size: 19190000, - ModTime: moment() - .subtract(1, 'year') - .format(), + ModTime: moment().subtract(1, 'year').format(), }, { Name: 'mmm-small-mid-directory', IsDir: true, Size: 1919, - ModTime: moment() - .subtract(6, 'month') - .format(), + ModTime: moment().subtract(6, 'month').format(), }, { Name: 'zzz-med-new-directory', @@ -303,26 +323,36 @@ export default function browseFilesystem({ ); }); - test('viewing a file', async function(assert) { + test('viewing a file', async function (assert) { const objects = { allocation: this.allocation, task: this.task }; const node = server.db.nodes.find(this.allocation.nodeId); - server.get(`http://${node.httpAddr}/v1/client/fs/readat/:allocation_id`, function() { - return new Response(500); - }); + server.get( + `http://${node.httpAddr}/v1/client/fs/readat/:allocation_id`, + function () { + return new Response(500); + } + ); - await FS[pageObjectVisitPathFunctionName]({ ...visitSegments(objects), path: '/' }); + await FS[pageObjectVisitPathFunctionName]({ + ...visitSegments(objects), + path: '/', + }); const sortedFiles = fileSort( 'name', - filesForPath(this.server.schema.allocFiles, getFilesystemRoot(objects)).models + filesForPath(this.server.schema.allocFiles, getFilesystemRoot(objects)) + .models ); - const fileRecord = sortedFiles.find(f => !f.isDir); + const fileRecord = sortedFiles.find((f) => !f.isDir); const fileIndex = sortedFiles.indexOf(fileRecord); await FS.directoryEntries[fileIndex].visit(); - assert.equal(FS.breadcrumbsText, `${getBreadcrumbComponent(objects)} ${fileRecord.name}`); + assert.equal( + FS.breadcrumbsText, + `${getBreadcrumbComponent(objects)} ${fileRecord.name}` + ); assert.ok(FS.fileViewer.isPresent); @@ -343,7 +373,7 @@ export default function browseFilesystem({ ); }); - test('viewing an empty directory', async function(assert) { + test('viewing an empty directory', async function (assert) { await FS[pageObjectVisitPathFunctionName]({ ...visitSegments({ allocation: this.allocation, task: this.task }), path: 'empty-directory', @@ -352,7 +382,7 @@ export default function browseFilesystem({ assert.ok(FS.isEmptyDirectory); }); - test('viewing paths that produce stat API errors', async function(assert) { + test('viewing paths that produce stat API errors', async function (assert) { this.server.get('/client/fs/stat/:allocation_id', () => { return new Response(500, {}, 'no such file or directory'); }); @@ -361,8 +391,16 @@ export default function browseFilesystem({ ...visitSegments({ allocation: this.allocation, task: this.task }), path: '/what-is-this', }); - assert.notEqual(FS.error.title, 'Not Found', '500 is not interpreted as 404'); - assert.equal(FS.error.title, 'Server Error', '500 is not interpreted as 500'); + assert.notEqual( + FS.error.title, + 'Not Found', + '500 is not interpreted as 404' + ); + assert.equal( + FS.error.title, + 'Server Error', + '500 is not interpreted as 500' + ); await visit('/'); @@ -377,7 +415,7 @@ export default function browseFilesystem({ assert.equal(FS.error.title, 'Error', 'other statuses are passed through'); }); - test('viewing paths that produce ls API errors', async function(assert) { + test('viewing paths that produce ls API errors', async function (assert) { this.server.get('/client/fs/ls/:allocation_id', () => { return new Response(500, {}, 'no such file or directory'); }); @@ -386,8 +424,16 @@ export default function browseFilesystem({ ...visitSegments({ allocation: this.allocation, task: this.task }), path: this.directory.name, }); - assert.notEqual(FS.error.title, 'Not Found', '500 is not interpreted as 404'); - assert.equal(FS.error.title, 'Server Error', '500 is not interpreted as 404'); + assert.notEqual( + FS.error.title, + 'Not Found', + '500 is not interpreted as 404' + ); + assert.equal( + FS.error.title, + 'Server Error', + '500 is not interpreted as 404' + ); await visit('/'); diff --git a/ui/tests/acceptance/behaviors/page-size-select.js b/ui/tests/acceptance/behaviors/page-size-select.js index 35d261449..f20204372 100644 --- a/ui/tests/acceptance/behaviors/page-size-select.js +++ b/ui/tests/acceptance/behaviors/page-size-select.js @@ -2,10 +2,15 @@ import { pluralize } from 'ember-inflector'; import { test } from 'qunit'; import { selectChoose } from 'ember-power-select/test-support'; -export default function pageSizeSelect({ resourceName, pageObject, pageObjectList, setup }) { +export default function pageSizeSelect({ + resourceName, + pageObject, + pageObjectList, + setup, +}) { test(`the number of ${pluralize( resourceName - )} is equal to the localStorage user setting for page size`, async function(assert) { + )} is equal to the localStorage user setting for page size`, async function (assert) { const storedPageSize = 10; window.localStorage.nomadPageSize = storedPageSize; @@ -15,7 +20,7 @@ export default function pageSizeSelect({ resourceName, pageObject, pageObjectLis assert.equal(pageObject.pageSizeSelect.selectedOption, storedPageSize); }); - test('when the page size user setting is unset, the default page size is 25', async function(assert) { + test('when the page size user setting is unset, the default page size is 25', async function (assert) { await setup.call(this); assert.equal(pageObjectList.length, pageObject.pageSize); @@ -24,7 +29,7 @@ export default function pageSizeSelect({ resourceName, pageObject, pageObjectLis test(`changing the page size updates the ${pluralize( resourceName - )} list and also updates the user setting in localStorage`, async function(assert) { + )} list and also updates the user setting in localStorage`, async function (assert) { const desiredPageSize = 10; await setup.call(this); diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js index 2b2049b54..ff7827cef 100644 --- a/ui/tests/acceptance/client-detail-test.js +++ b/ui/tests/acceptance/client-detail-test.js @@ -1,3 +1,6 @@ +/* eslint-disable qunit/require-expect */ +/* eslint-disable qunit/no-conditional-assertions */ +/* Mirage fixtures are random so we can't expect a set number of assertions */ import { currentURL, waitUntil, settled } from '@ember/test-helpers'; import { assign } from '@ember/polyfills'; import { module, test } from 'qunit'; @@ -15,19 +18,19 @@ let node; let managementToken; let clientToken; -const wasPreemptedFilter = allocation => !!allocation.preemptedByAllocation; +const wasPreemptedFilter = (allocation) => !!allocation.preemptedByAllocation; function nonSearchPOSTS() { return server.pretender.handledRequests - .reject(request => request.url.includes('fuzzy')) + .reject((request) => request.url.includes('fuzzy')) .filterBy('method', 'POST'); } -module('Acceptance | client detail', function(hooks) { +module('Acceptance | client detail', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { window.localStorage.clear(); server.create('node', 'forceIPv4', { schedulingEligibility: 'eligible' }); @@ -46,17 +49,17 @@ module('Acceptance | client detail', function(hooks) { // Force all allocations into the running state so now allocation rows are missing // CPU/Mem runtime metrics - server.schema.allocations.all().models.forEach(allocation => { + server.schema.allocations.all().models.forEach((allocation) => { allocation.update({ clientStatus: 'running' }); }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await ClientDetail.visit({ id: node.id }); await a11yAudit(assert); }); - test('/clients/:id should have a breadcrumb trail linking back to clients', async function(assert) { + test('/clients/:id should have a breadcrumb trail linking back to clients', async function (assert) { await ClientDetail.visit({ id: node.id }); assert.equal(document.title, `Client ${node.name} - Nomad`); @@ -72,11 +75,18 @@ module('Acceptance | client detail', function(hooks) { 'Second breadcrumb is a titled breadcrumb saying the node short id' ); await Layout.breadcrumbFor('clients.index').visit(); - assert.equal(currentURL(), '/clients', 'First breadcrumb links back to clients'); + assert.equal( + currentURL(), + '/clients', + 'First breadcrumb links back to clients' + ); }); - test('/clients/:id should list immediate details for the node in the title', async function(assert) { - node = server.create('node', 'forceIPv4', { schedulingEligibility: 'eligible', drain: false }); + test('/clients/:id should list immediate details for the node in the title', async function (assert) { + node = server.create('node', 'forceIPv4', { + schedulingEligibility: 'eligible', + drain: false, + }); await ClientDetail.visit({ id: node.id }); @@ -89,7 +99,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('/clients/:id should list additional detail for the node below the title', async function(assert) { + test('/clients/:id should list additional detail for the node below the title', async function (assert) { await ClientDetail.visit({ id: node.id }); assert.ok( @@ -110,16 +120,30 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('/clients/:id should include resource utilization graphs', async function(assert) { + test('/clients/:id should include resource utilization graphs', async function (assert) { await ClientDetail.visit({ id: node.id }); - assert.equal(ClientDetail.resourceCharts.length, 2, 'Two resource utilization graphs'); - assert.equal(ClientDetail.resourceCharts.objectAt(0).name, 'CPU', 'First chart is CPU'); - assert.equal(ClientDetail.resourceCharts.objectAt(1).name, 'Memory', 'Second chart is Memory'); + assert.equal( + ClientDetail.resourceCharts.length, + 2, + 'Two resource utilization graphs' + ); + assert.equal( + ClientDetail.resourceCharts.objectAt(0).name, + 'CPU', + 'First chart is CPU' + ); + assert.equal( + ClientDetail.resourceCharts.objectAt(1).name, + 'Memory', + 'Second chart is Memory' + ); }); - test('/clients/:id should list all allocations on the node', async function(assert) { - const allocationsCount = server.db.allocations.where({ nodeId: node.id }).length; + test('/clients/:id should list all allocations on the node', async function (assert) { + const allocationsCount = server.db.allocations.where({ + nodeId: node.id, + }).length; await ClientDetail.visit({ id: node.id }); @@ -130,16 +154,19 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('/clients/:id should show empty message if there are no allocations on the node', async function(assert) { + test('/clients/:id should show empty message if there are no allocations on the node', async function (assert) { const emptyNode = server.create('node'); await ClientDetail.visit({ id: emptyNode.id }); - assert.true(ClientDetail.emptyAllocations.isVisible, 'Empty message is visible'); + assert.true( + ClientDetail.emptyAllocations.isVisible, + 'Empty message is visible' + ); assert.equal(ClientDetail.emptyAllocations.headline, 'No Allocations'); }); - test('each allocation should have high-level details for the allocation', async function(assert) { + test('each allocation should have high-level details for the allocation', async function (assert) { const allocation = server.db.allocations .where({ nodeId: node.id }) .sortBy('modifyIndex') @@ -151,15 +178,22 @@ module('Acceptance | client detail', function(hooks) { jobId: allocation.jobId, }); - const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); + const tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0); - const memoryUsed = tasks.reduce((sum, task) => sum + task.resources.MemoryMB, 0); + const memoryUsed = tasks.reduce( + (sum, task) => sum + task.resources.MemoryMB, + 0 + ); await ClientDetail.visit({ id: node.id }); const allocationRow = ClientDetail.allocations.objectAt(0); - assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short ID'); + assert.equal( + allocationRow.shortId, + allocation.id.split('-')[0], + 'Allocation short ID' + ); assert.equal( allocationRow.createTime, moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'), @@ -170,8 +204,16 @@ module('Acceptance | client detail', function(hooks) { moment(allocation.modifyTime / 1000000).fromNow(), 'Allocation modify time' ); - assert.equal(allocationRow.status, allocation.clientStatus, 'Client status'); - assert.equal(allocationRow.job, server.db.jobs.find(allocation.jobId).name, 'Job name'); + assert.equal( + allocationRow.status, + allocation.clientStatus, + 'Client status' + ); + assert.equal( + allocationRow.job, + server.db.jobs.find(allocation.jobId).name, + 'Job name' + ); assert.ok(allocationRow.taskGroup, 'Task group name'); assert.ok(allocationRow.jobVersion, 'Job Version'); assert.equal(allocationRow.volume, 'Yes', 'Volume'); @@ -180,7 +222,9 @@ module('Acceptance | client detail', function(hooks) { Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, 'CPU %' ); - const roundedTicks = Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks); + const roundedTicks = Math.floor( + allocStats.resourceUsage.CpuStats.TotalTicks + ); assert.equal( allocationRow.cpuTooltip, `${formatHertz(roundedTicks, 'MHz')} / ${formatHertz(cpuUsed, 'MHz')}`, @@ -201,7 +245,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('each allocation should show job information even if the job is incomplete and already in the store', async function(assert) { + test('each allocation should show job information even if the job is incomplete and already in the store', async function (assert) { // First, visit clients to load the allocations for each visible node. // Don't load the job belongsTo of the allocation! Leave it unfulfilled. @@ -224,11 +268,18 @@ module('Acceptance | client detail', function(hooks) { .sortBy('modifyIndex') .reverse()[0]; - assert.equal(allocationRow.job, server.db.jobs.find(allocation.jobId).name, 'Job name'); - assert.ok(allocationRow.taskGroup.includes(allocation.taskGroup), 'Task group name'); + assert.equal( + allocationRow.job, + server.db.jobs.find(allocation.jobId).name, + 'Job name' + ); + assert.ok( + allocationRow.taskGroup.includes(allocation.taskGroup), + 'Task group name' + ); }); - test('each allocation should link to the allocation detail page', async function(assert) { + test('each allocation should link to the allocation detail page', async function (assert) { const allocation = server.db.allocations .where({ nodeId: node.id }) .sortBy('modifyIndex') @@ -244,7 +295,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('each allocation should link to the job the allocation belongs to', async function(assert) { + test('each allocation should link to the job the allocation belongs to', async function (assert) { await ClientDetail.visit({ id: node.id }); const allocation = server.db.allocations.where({ nodeId: node.id })[0]; @@ -259,7 +310,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('the allocation section should show the count of preempted allocations on the client', async function(assert) { + test('the allocation section should show the count of preempted allocations on the client', async function (assert) { const allocations = server.db.allocations.where({ nodeId: node.id }); await ClientDetail.visit({ id: node.id }); @@ -277,7 +328,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('clicking the preemption badge filters the allocations table and sets a query param', async function(assert) { + test('clicking the preemption badge filters the allocations table and sets a query param', async function (assert) { const allocations = server.db.allocations.where({ nodeId: node.id }); await ClientDetail.visit({ id: node.id }); @@ -295,18 +346,26 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('clicking the total allocations badge resets the filter and removes the query param', async function(assert) { + test('clicking the total allocations badge resets the filter and removes the query param', async function (assert) { const allocations = server.db.allocations.where({ nodeId: node.id }); await ClientDetail.visit({ id: node.id }); await ClientDetail.allocationFilter.preemptions(); await ClientDetail.allocationFilter.all(); - assert.equal(ClientDetail.allocations.length, allocations.length, 'All allocations are shown'); - assert.equal(currentURL(), `/clients/${node.id}`, 'Filter is persisted in the URL'); + assert.equal( + ClientDetail.allocations.length, + allocations.length, + 'All allocations are shown' + ); + assert.equal( + currentURL(), + `/clients/${node.id}`, + 'Filter is persisted in the URL' + ); }); - test('navigating directly to the client detail page with the preemption query param set will filter the allocations table', async function(assert) { + test('navigating directly to the client detail page with the preemption query param set will filter the allocations table', async function (assert) { const allocations = server.db.allocations.where({ nodeId: node.id }); await ClientDetail.visit({ id: node.id, preemptions: true }); @@ -318,13 +377,13 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('/clients/:id should list all attributes for the node', async function(assert) { + test('/clients/:id should list all attributes for the node', async function (assert) { await ClientDetail.visit({ id: node.id }); assert.ok(ClientDetail.attributesTable, 'Attributes table is on the page'); }); - test('/clients/:id lists all meta attributes', async function(assert) { + test('/clients/:id lists all meta attributes', async function (assert) { node = server.create('node', 'forceIPv4', 'withMeta'); await ClientDetail.visit({ id: node.id }); @@ -346,35 +405,42 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('/clients/:id shows an empty message when there is no meta data', async function(assert) { + test('/clients/:id shows an empty message when there is no meta data', async function (assert) { await ClientDetail.visit({ id: node.id }); - assert.notOk(ClientDetail.metaTable, 'Meta attributes table is not on the page'); + assert.notOk( + ClientDetail.metaTable, + 'Meta attributes table is not on the page' + ); assert.ok(ClientDetail.emptyMetaMessage, 'Meta attributes is empty'); }); - test('when the node is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the node is not found, an error message is shown, but the URL persists', async function (assert) { await ClientDetail.visit({ id: 'not-a-real-node' }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/node/not-a-real-node', 'A request to the nonexistent node is made' ); assert.equal(currentURL(), '/clients/not-a-real-node', 'The URL persists'); assert.ok(ClientDetail.error.isShown, 'Error message is shown'); - assert.equal(ClientDetail.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + ClientDetail.error.title, + 'Not Found', + 'Error message is for 404' + ); }); - test('/clients/:id shows the recent events list', async function(assert) { + test('/clients/:id shows the recent events list', async function (assert) { await ClientDetail.visit({ id: node.id }); assert.ok(ClientDetail.hasEvents, 'Client events section exists'); }); - test('each node event shows basic node event information', async function(assert) { + test('each node event shows basic node event information', async function (assert) { const event = server.db.nodeEvents .where({ nodeId: node.id }) .sortBy('time') @@ -392,12 +458,12 @@ module('Acceptance | client detail', function(hooks) { assert.equal(eventRow.message, event.message, 'Event message'); }); - test('/clients/:id shows the driver status of every driver for the node', async function(assert) { + test('/clients/:id shows the driver status of every driver for the node', async function (assert) { // Set the drivers up so health and detection is well tested const nodeDrivers = node.drivers; const undetectedDriver = 'raw_exec'; - Object.values(nodeDrivers).forEach(driver => { + Object.values(nodeDrivers).forEach((driver) => { driver.Detected = true; }); @@ -405,7 +471,9 @@ module('Acceptance | client detail', function(hooks) { node.drivers = nodeDrivers; const drivers = Object.keys(node.drivers) - .map(driverName => assign({ Name: driverName }, node.drivers[driverName])) + .map((driverName) => + assign({ Name: driverName }, node.drivers[driverName]) + ) .sortBy('Name'); assert.ok(drivers.length > 0, 'Node has drivers'); @@ -415,7 +483,11 @@ module('Acceptance | client detail', function(hooks) { drivers.forEach((driver, index) => { const driverHead = ClientDetail.driverHeads.objectAt(index); - assert.equal(driverHead.name, driver.Name, `${driver.Name}: Name is correct`); + assert.equal( + driverHead.name, + driver.Name, + `${driver.Name}: Name is correct` + ); assert.equal( driverHead.detected, driver.Detected ? 'Yes' : 'No', @@ -439,31 +511,41 @@ module('Acceptance | client detail', function(hooks) { `${driver.Name}: Health is correct` ); assert.ok( - driverHead.healthClass.includes(driver.Healthy ? 'running' : 'failed'), + driverHead.healthClass.includes( + driver.Healthy ? 'running' : 'failed' + ), `${driver.Name}: Swatch with correct class is shown` ); } }); }); - test('each driver can be opened to see a message and attributes', async function(assert) { + test('each driver can be opened to see a message and attributes', async function (assert) { // Only detected drivers can be expanded const nodeDrivers = node.drivers; - Object.values(nodeDrivers).forEach(driver => { + Object.values(nodeDrivers).forEach((driver) => { driver.Detected = true; }); node.drivers = nodeDrivers; const driver = Object.keys(node.drivers) - .map(driverName => assign({ Name: driverName }, node.drivers[driverName])) + .map((driverName) => + assign({ Name: driverName }, node.drivers[driverName]) + ) .sortBy('Name')[0]; await ClientDetail.visit({ id: node.id }); const driverHead = ClientDetail.driverHeads.objectAt(0); const driverBody = ClientDetail.driverBodies.objectAt(0); - assert.notOk(driverBody.descriptionIsShown, 'Driver health description is not shown'); - assert.notOk(driverBody.attributesAreShown, 'Driver attributes section is not shown'); + assert.notOk( + driverBody.descriptionIsShown, + 'Driver health description is not shown' + ); + assert.notOk( + driverBody.attributesAreShown, + 'Driver attributes section is not shown' + ); await driverHead.toggle(); assert.equal( @@ -471,10 +553,13 @@ module('Acceptance | client detail', function(hooks) { driver.HealthDescription, 'Driver health description is now shown' ); - assert.ok(driverBody.attributesAreShown, 'Driver attributes section is now shown'); + assert.ok( + driverBody.attributesAreShown, + 'Driver attributes section is now shown' + ); }); - test('the status light indicates when the node is ineligible for scheduling', async function(assert) { + test('the status light indicates when the node is ineligible for scheduling', async function (assert) { node = server.create('node', { drain: false, schedulingEligibility: 'ineligible', @@ -490,7 +575,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('when the node has a drain strategy with a positive deadline, the drain stategy section prints the duration', async function(assert) { + test('when the node has a drain strategy with a positive deadline, the drain stategy section prints the duration', async function (assert) { const deadline = 5400000000000; // 1.5 hours in nanoseconds const forceDeadline = moment().add(1, 'd'); @@ -523,7 +608,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('when the node has a drain stategy with no deadline, the drain stategy section mentions that and omits the force deadline', async function(assert) { + test('when the node has a drain stategy with no deadline, the drain stategy section mentions that and omits the force deadline', async function (assert) { const deadline = 0; node = server.create('node', { @@ -538,7 +623,10 @@ module('Acceptance | client detail', function(hooks) { await ClientDetail.visit({ id: node.id }); - assert.notOk(ClientDetail.drainDetails.durationIsShown, 'Duration is omitted'); + assert.notOk( + ClientDetail.drainDetails.durationIsShown, + 'Duration is omitted' + ); assert.ok( ClientDetail.drainDetails.deadline.includes('No deadline'), @@ -551,7 +639,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('when the node has a drain stategy with a negative deadline, the drain strategy section shows the force badge', async function(assert) { + test('when the node has a drain stategy with a negative deadline, the drain strategy section shows the force badge', async function (assert) { const deadline = -1; node = server.create('node', { @@ -571,9 +659,15 @@ module('Acceptance | client detail', function(hooks) { 'Forced Drain is described' ); - assert.ok(ClientDetail.drainDetails.duration.includes('--'), 'Duration is shown but unset'); + assert.ok( + ClientDetail.drainDetails.duration.includes('--'), + 'Duration is shown but unset' + ); - assert.ok(ClientDetail.drainDetails.deadline.includes('--'), 'Deadline is shown but unset'); + assert.ok( + ClientDetail.drainDetails.deadline.includes('--'), + 'Deadline is shown but unset' + ); assert.ok( ClientDetail.drainDetails.drainSystemJobsText.endsWith('Yes'), @@ -581,13 +675,17 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('toggling node eligibility disables the toggle and sends the correct POST request', async function(assert) { + test('toggling node eligibility disables the toggle and sends the correct POST request', async function (assert) { node = server.create('node', { drain: false, schedulingEligibility: 'eligible', }); - server.pretender.post('/v1/node/:id/eligibility', () => [200, {}, ''], true); + server.pretender.post( + '/v1/node/:id/eligibility', + () => [200, {}, ''], + true + ); await ClientDetail.visit({ id: node.id }); assert.ok(ClientDetail.eligibilityToggle.isActive); @@ -624,7 +722,7 @@ module('Acceptance | client detail', function(hooks) { }); }); - test('starting a drain sends the correct POST request', async function(assert) { + test('starting a drain sends the correct POST request', async function (assert) { let request; node = server.create('node', { @@ -690,8 +788,11 @@ module('Acceptance | client detail', function(hooks) { await ClientDetail.drainPopover.toggle(); await ClientDetail.drainPopover.deadlineOptions.open(); - const optionsCount = ClientDetail.drainPopover.deadlineOptions.options.length; - await ClientDetail.drainPopover.deadlineOptions.options.objectAt(optionsCount - 1).choose(); + const optionsCount = + ClientDetail.drainPopover.deadlineOptions.options.length; + await ClientDetail.drainPopover.deadlineOptions.options + .objectAt(optionsCount - 1) + .choose(); await ClientDetail.drainPopover.setCustomDeadline('1h40m20s'); await ClientDetail.drainPopover.submit(); @@ -747,7 +848,7 @@ module('Acceptance | client detail', function(hooks) { ); }); - test('starting a drain persists options to localstorage', async function(assert) { + test('starting a drain persists options to localstorage', async function (assert) { const nodes = server.createList('node', 2, { drain: false, schedulingEligibility: 'eligible', @@ -759,8 +860,11 @@ module('Acceptance | client detail', function(hooks) { // Change all options to non-default values. await ClientDetail.drainPopover.deadlineToggle.toggle(); await ClientDetail.drainPopover.deadlineOptions.open(); - const optionsCount = ClientDetail.drainPopover.deadlineOptions.options.length; - await ClientDetail.drainPopover.deadlineOptions.options.objectAt(optionsCount - 1).choose(); + const optionsCount = + ClientDetail.drainPopover.deadlineOptions.options.length; + await ClientDetail.drainPopover.deadlineOptions.options + .objectAt(optionsCount - 1) + .choose(); await ClientDetail.drainPopover.setCustomDeadline('1h40m20s'); await ClientDetail.drainPopover.forceDrainToggle.toggle(); await ClientDetail.drainPopover.systemJobsToggle.toggle(); @@ -786,7 +890,7 @@ module('Acceptance | client detail', function(hooks) { assert.false(ClientDetail.drainPopover.systemJobsToggle.isActive); }); - test('the drain popover cancel button closes the popover', async function(assert) { + test('the drain popover cancel button closes the popover', async function (assert) { node = server.create('node', { drain: false, schedulingEligibility: 'eligible', @@ -803,7 +907,7 @@ module('Acceptance | client detail', function(hooks) { assert.equal(nonSearchPOSTS(), 0); }); - test('toggling eligibility is disabled while a drain is active', async function(assert) { + test('toggling eligibility is disabled while a drain is active', async function (assert) { node = server.create('node', { drain: true, schedulingEligibility: 'ineligible', @@ -813,7 +917,7 @@ module('Acceptance | client detail', function(hooks) { assert.ok(ClientDetail.eligibilityToggle.isDisabled); }); - test('stopping a drain sends the correct POST request', async function(assert) { + test('stopping a drain sends the correct POST request', async function (assert) { node = server.create('node', { drain: true, schedulingEligibility: 'ineligible', @@ -833,7 +937,7 @@ module('Acceptance | client detail', function(hooks) { }); }); - test('when a drain is active, the "drain" popover is labeled as the "update" popover', async function(assert) { + test('when a drain is active, the "drain" popover is labeled as the "update" popover', async function (assert) { node = server.create('node', { drain: true, schedulingEligibility: 'ineligible', @@ -843,7 +947,7 @@ module('Acceptance | client detail', function(hooks) { assert.equal(ClientDetail.drainPopover.label, 'Update Drain'); }); - test('forcing a drain sends the correct POST request', async function(assert) { + test('forcing a drain sends the correct POST request', async function (assert) { node = server.create('node', { drain: true, schedulingEligibility: 'ineligible', @@ -868,7 +972,7 @@ module('Acceptance | client detail', function(hooks) { }); }); - test('when stopping a drain fails, an error is shown', async function(assert) { + test('when stopping a drain fails, an error is shown', async function (assert) { node = server.create('node', { drain: true, schedulingEligibility: 'ineligible', @@ -887,7 +991,7 @@ module('Acceptance | client detail', function(hooks) { assert.notOk(ClientDetail.stopDrainError.isPresent); }); - test('when starting a drain fails, an error message is shown', async function(assert) { + test('when starting a drain fails, an error message is shown', async function (assert) { node = server.create('node', { drain: false, schedulingEligibility: 'eligible', @@ -906,7 +1010,7 @@ module('Acceptance | client detail', function(hooks) { assert.notOk(ClientDetail.drainError.isPresent); }); - test('when updating a drain fails, an error message is shown', async function(assert) { + test('when updating a drain fails, an error message is shown', async function (assert) { node = server.create('node', { drain: true, schedulingEligibility: 'ineligible', @@ -925,7 +1029,7 @@ module('Acceptance | client detail', function(hooks) { assert.notOk(ClientDetail.drainError.isPresent); }); - test('when toggling eligibility fails, an error message is shown', async function(assert) { + test('when toggling eligibility fails, an error message is shown', async function (assert) { node = server.create('node', { drain: false, schedulingEligibility: 'eligible', @@ -937,13 +1041,15 @@ module('Acceptance | client detail', function(hooks) { await ClientDetail.eligibilityToggle.toggle(); assert.ok(ClientDetail.eligibilityError.isPresent); - assert.ok(ClientDetail.eligibilityError.title.includes('Eligibility Error')); + assert.ok( + ClientDetail.eligibilityError.title.includes('Eligibility Error') + ); await ClientDetail.eligibilityError.dismiss(); assert.notOk(ClientDetail.eligibilityError.isPresent); }); - test('when navigating away from a client that has an error message to another client, the error is not shown', async function(assert) { + test('when navigating away from a client that has an error message to another client, the error is not shown', async function (assert) { node = server.create('node', { drain: false, schedulingEligibility: 'eligible', @@ -957,14 +1063,16 @@ module('Acceptance | client detail', function(hooks) { await ClientDetail.eligibilityToggle.toggle(); assert.ok(ClientDetail.eligibilityError.isPresent); - assert.ok(ClientDetail.eligibilityError.title.includes('Eligibility Error')); + assert.ok( + ClientDetail.eligibilityError.title.includes('Eligibility Error') + ); await ClientDetail.visit({ id: node2.id }); assert.notOk(ClientDetail.eligibilityError.isPresent); }); - test('toggling eligibility and node drain are disabled when the active ACL token does not permit node write', async function(assert) { + test('toggling eligibility and node drain are disabled when the active ACL token does not permit node write', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; await ClientDetail.visit({ id: node.id }); @@ -972,37 +1080,43 @@ module('Acceptance | client detail', function(hooks) { assert.ok(ClientDetail.drainPopover.isDisabled); }); - test('the host volumes table lists all host volumes in alphabetical order by name', async function(assert) { + test('the host volumes table lists all host volumes in alphabetical order by name', async function (assert) { await ClientDetail.visit({ id: node.id }); const sortedHostVolumes = Object.keys(node.hostVolumes) - .map(key => node.hostVolumes[key]) + .map((key) => node.hostVolumes[key]) .sortBy('Name'); assert.ok(ClientDetail.hasHostVolumes); - assert.equal(ClientDetail.hostVolumes.length, Object.keys(node.hostVolumes).length); + assert.equal( + ClientDetail.hostVolumes.length, + Object.keys(node.hostVolumes).length + ); ClientDetail.hostVolumes.forEach((volume, index) => { assert.equal(volume.name, sortedHostVolumes[index].Name); }); }); - test('each host volume row contains information about the host volume', async function(assert) { + test('each host volume row contains information about the host volume', async function (assert) { await ClientDetail.visit({ id: node.id }); const sortedHostVolumes = Object.keys(node.hostVolumes) - .map(key => node.hostVolumes[key]) + .map((key) => node.hostVolumes[key]) .sortBy('Name'); - ClientDetail.hostVolumes[0].as(volume => { + ClientDetail.hostVolumes[0].as((volume) => { const volumeRow = sortedHostVolumes[0]; assert.equal(volume.name, volumeRow.Name); assert.equal(volume.path, volumeRow.Path); - assert.equal(volume.permissions, volumeRow.ReadOnly ? 'Read' : 'Read/Write'); + assert.equal( + volume.permissions, + volumeRow.ReadOnly ? 'Read' : 'Read/Write' + ); }); }); - test('the host volumes table is not shown if the client has no host volumes', async function(assert) { + test('the host volumes table is not shown if the client has no host volumes', async function (assert) { node = server.create('node', 'noHostVolumes'); await ClientDetail.visit({ id: node.id }); @@ -1029,7 +1143,7 @@ module('Acceptance | client detail', function(hooks) { expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'], async beforeEach() { server.createList('job', 5, { createAllocations: false }); - ['pending', 'running', 'complete', 'failed', 'lost'].forEach(s => { + ['pending', 'running', 'complete', 'failed', 'lost'].forEach((s) => { server.createList('allocation', 5, { clientStatus: s }); }); @@ -1038,7 +1152,7 @@ module('Acceptance | client detail', function(hooks) { filter: (alloc, selection) => selection.includes(alloc.clientStatus), }); - test('fiter results with no matches display empty message', async function(assert) { + test('fiter results with no matches display empty message', async function (assert) { const job = server.create('job', { createAllocations: false }); server.create('allocation', { jobId: job.id, clientStatus: 'running' }); @@ -1052,11 +1166,11 @@ module('Acceptance | client detail', function(hooks) { }); }); -module('Acceptance | client detail (multi-namespace)', function(hooks) { +module('Acceptance | client detail (multi-namespace)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('node', 'forceIPv4', { schedulingEligibility: 'eligible' }); node = server.db.nodes[0]; @@ -1067,14 +1181,22 @@ module('Acceptance | client detail (multi-namespace)', function(hooks) { server.create('agent'); // Make a job for each namespace, but have both scheduled on the same node - server.create('job', { id: 'job-1', namespaceId: 'default', createAllocations: false }); + server.create('job', { + id: 'job-1', + namespaceId: 'default', + createAllocations: false, + }); server.createList('allocation', 3, { nodeId: node.id, jobId: 'job-1', clientStatus: 'running', }); - server.create('job', { id: 'job-2', namespaceId: 'other-namespace', createAllocations: false }); + server.create('job', { + id: 'job-2', + namespaceId: 'other-namespace', + createAllocations: false, + }); server.createList('allocation', 3, { nodeId: node.id, jobId: 'job-2', @@ -1082,7 +1204,7 @@ module('Acceptance | client detail (multi-namespace)', function(hooks) { }); }); - test('when the node has allocations on different namespaces, the associated jobs are fetched correctly', async function(assert) { + test('when the node has allocations on different namespaces, the associated jobs are fetched correctly', async function (assert) { window.localStorage.nomadActiveNamespace = 'other-namespace'; await ClientDetail.visit({ id: node.id }); @@ -1097,7 +1219,10 @@ module('Acceptance | client detail (multi-namespace)', function(hooks) { 'Job One fetched correctly' ); assert.ok( - server.pretender.handledRequests.findBy('url', '/v1/job/job-2?namespace=other-namespace'), + server.pretender.handledRequests.findBy( + 'url', + '/v1/job/job-2?namespace=other-namespace' + ), 'Job Two fetched correctly' ); }); @@ -1114,7 +1239,7 @@ module('Acceptance | client detail (multi-namespace)', function(hooks) { filter: (alloc, selection) => selection.includes(alloc.namespace), }); - test('facet Namespace | selecting namespace filters job options', async function(assert) { + test('facet Namespace | selecting namespace filters job options', async function (assert) { await ClientDetail.visit({ id: node.id }); const nsFacet = ClientDetail.facets.namespace; @@ -1127,7 +1252,7 @@ module('Acceptance | client detail (multi-namespace)', function(hooks) { await jobFacet.toggle(); assert.deepEqual( - jobFacet.options.map(option => option.label.trim()), + jobFacet.options.map((option) => option.label.trim()), ['job-1', 'job-2'] ); @@ -1137,14 +1262,17 @@ module('Acceptance | client detail (multi-namespace)', function(hooks) { await jobFacet.toggle(); assert.deepEqual( - jobFacet.options.map(option => option.label.trim()), + jobFacet.options.map((option) => option.label.trim()), ['job-1'] ); }); }); -function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) { - test(`facet ${label} | the ${label} facet has the correct options`, async function(assert) { +function testFacet( + label, + { facet, paramName, beforeEach, filter, expectedOptions } +) { + test(`facet ${label} | the ${label} facet has the correct options`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -1156,13 +1284,13 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption } assert.deepEqual( - facet.options.map(option => option.label.trim()), + facet.options.map((option) => option.label.trim()), expectation, 'Options for facet are as expected' ); }); - test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function(assert) { + test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function (assert) { let option; await beforeEach(); @@ -1173,7 +1301,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption const selection = [option.key]; const expectedAllocs = server.db.allocations - .filter(alloc => filter(alloc, selection)) + .filter((alloc) => filter(alloc, selection)) .sortBy('modifyIndex') .reverse(); @@ -1186,7 +1314,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption }); }); - test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { + test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function (assert) { const selection = []; await beforeEach(); @@ -1200,7 +1328,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption selection.push(option2.key); const expectedAllocs = server.db.allocations - .filter(alloc => filter(alloc, selection)) + .filter((alloc) => filter(alloc, selection)) .sortBy('modifyIndex') .reverse(); @@ -1213,7 +1341,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption }); }); - test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) { const selection = []; await beforeEach(); @@ -1228,7 +1356,9 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption assert.equal( currentURL(), - `/clients/${node.id}?${paramName}=${encodeURIComponent(JSON.stringify(selection))}`, + `/clients/${node.id}?${paramName}=${encodeURIComponent( + JSON.stringify(selection) + )}`, 'URL has the correct query param key and value' ); }); diff --git a/ui/tests/acceptance/client-monitor-test.js b/ui/tests/acceptance/client-monitor-test.js index c795345e6..9b7db370a 100644 --- a/ui/tests/acceptance/client-monitor-test.js +++ b/ui/tests/acceptance/client-monitor-test.js @@ -11,11 +11,11 @@ let node; let managementToken; let clientToken; -module('Acceptance | client monitor', function(hooks) { +module('Acceptance | client monitor', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { node = server.create('node'); managementToken = server.create('token'); @@ -27,25 +27,30 @@ module('Acceptance | client monitor', function(hooks) { run.later(run, run.cancelTimers, 500); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + await ClientMonitor.visit({ id: node.id }); await a11yAudit(assert); }); - test('/clients/:id/monitor should have a breadcrumb trail linking back to clients', async function(assert) { + test('/clients/:id/monitor should have a breadcrumb trail linking back to clients', async function (assert) { await ClientMonitor.visit({ id: node.id }); assert.equal(Layout.breadcrumbFor('clients.index').text, 'Clients'); - assert.equal(Layout.breadcrumbFor('clients.client').text, `Client ${node.id.split('-')[0]}`); + assert.equal( + Layout.breadcrumbFor('clients.client').text, + `Client ${node.id.split('-')[0]}` + ); await Layout.breadcrumbFor('clients.index').visit(); assert.equal(currentURL(), '/clients'); }); - test('the monitor page immediately streams agent monitor output at the info level', async function(assert) { + test('the monitor page immediately streams agent monitor output at the info level', async function (assert) { await ClientMonitor.visit({ id: node.id }); - const logRequest = server.pretender.handledRequests.find(req => + const logRequest = server.pretender.handledRequests.find((req) => req.url.startsWith('/v1/agent/monitor') ); assert.ok(ClientMonitor.logsArePresent); @@ -53,13 +58,13 @@ module('Acceptance | client monitor', function(hooks) { assert.ok(logRequest.url.includes('log_level=info')); }); - test('switching the log level persists the new log level as a query param', async function(assert) { + test('switching the log level persists the new log level as a query param', async function (assert) { await ClientMonitor.visit({ id: node.id }); await ClientMonitor.selectLogLevel('Debug'); assert.equal(currentURL(), `/clients/${node.id}/monitor?level=debug`); }); - test('when the current access token does not include the agent:read rule, a descriptive error message is shown', async function(assert) { + test('when the current access token does not include the agent:read rule, a descriptive error message is shown', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; await ClientMonitor.visit({ id: node.id }); diff --git a/ui/tests/acceptance/clients-list-test.js b/ui/tests/acceptance/clients-list-test.js index dd76d5a8c..37b3bf97b 100644 --- a/ui/tests/acceptance/clients-list-test.js +++ b/ui/tests/acceptance/clients-list-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL, settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -6,15 +7,15 @@ import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; import pageSizeSelect from './behaviors/page-size-select'; import ClientsList from 'nomad-ui/tests/pages/clients/list'; -module('Acceptance | clients list', function(hooks) { +module('Acceptance | clients list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { window.localStorage.clear(); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { const nodesCount = ClientsList.pageSize + 1; server.createList('node', nodesCount); @@ -24,7 +25,7 @@ module('Acceptance | clients list', function(hooks) { await a11yAudit(assert); }); - test('/clients should list one page of clients', async function(assert) { + test('/clients should list one page of clients', async function (assert) { // Make sure to make more nodes than 1 page to assert that pagination is working const nodesCount = ClientsList.pageSize + 1; @@ -39,13 +40,17 @@ module('Acceptance | clients list', function(hooks) { const sortedNodes = server.db.nodes.sortBy('modifyIndex').reverse(); ClientsList.nodes.forEach((node, index) => { - assert.equal(node.id, sortedNodes[index].id.split('-')[0], 'Clients are ordered'); + assert.equal( + node.id, + sortedNodes[index].id.split('-')[0], + 'Clients are ordered' + ); }); assert.equal(document.title, 'Clients - Nomad'); }); - test('each client record should show high-level info of the client', async function(assert) { + test('each client record should show high-level info of the client', async function (assert) { const node = server.create('node', 'draining', { status: 'ready', }); @@ -70,7 +75,7 @@ module('Acceptance | clients list', function(hooks) { assert.equal(nodeRow.allocations, allocations.length, '# Allocations'); }); - test('each client record should show running allocations', async function(assert) { + test('each client record should show running allocations', async function (assert) { server.createList('agent', 1); const node = server.create('node', { @@ -82,7 +87,9 @@ module('Acceptance | clients list', function(hooks) { server.create('job', { createAllocations: false }); - const running = server.createList('allocation', 2, { clientStatus: 'running' }); + const running = server.createList('allocation', 2, { + clientStatus: 'running', + }); server.createList('allocation', 3, { clientStatus: 'pending' }); server.createList('allocation', 10, { clientStatus: 'complete' }); @@ -99,7 +106,7 @@ module('Acceptance | clients list', function(hooks) { assert.equal(nodeRow.allocations, running.length, '# Allocations'); }); - test('client status, draining, and eligibility are collapsed into one column that stays sorted', async function(assert) { + test('client status, draining, and eligibility are collapsed into one column that stays sorted', async function (assert) { server.createList('agent', 1); server.create('node', { @@ -139,7 +146,7 @@ module('Acceptance | clients list', function(hooks) { await ClientsList.visit(); - ClientsList.nodes[0].compositeStatus.as(readyClient => { + ClientsList.nodes[0].compositeStatus.as((readyClient) => { assert.equal(readyClient.text, 'ready'); assert.ok(readyClient.isUnformatted, 'expected no status class'); assert.equal(readyClient.tooltip, 'ready / not draining / eligible'); @@ -154,10 +161,16 @@ module('Acceptance | clients list', function(hooks) { ); assert.equal(ClientsList.nodes[4].compositeStatus.text, 'ineligible'); - assert.ok(ClientsList.nodes[4].compositeStatus.isWarning, 'expected warning class'); + assert.ok( + ClientsList.nodes[4].compositeStatus.isWarning, + 'expected warning class' + ); assert.equal(ClientsList.nodes[5].compositeStatus.text, 'draining'); - assert.ok(ClientsList.nodes[5].compositeStatus.isInfo, 'expected info class'); + assert.ok( + ClientsList.nodes[5].compositeStatus.isInfo, + 'expected info class' + ); await ClientsList.sortBy('compositeStatus'); @@ -189,7 +202,7 @@ module('Acceptance | clients list', function(hooks) { ]); }); - test('each client should link to the client detail page', async function(assert) { + test('each client should link to the client detail page', async function (assert) { server.createList('node', 1); server.createList('agent', 1); @@ -201,7 +214,7 @@ module('Acceptance | clients list', function(hooks) { assert.equal(currentURL(), `/clients/${node.id}`); }); - test('when there are no clients, there is an empty message', async function(assert) { + test('when there are no clients, there is an empty message', async function (assert) { server.createList('agent', 1); await ClientsList.visit(); @@ -210,7 +223,7 @@ module('Acceptance | clients list', function(hooks) { assert.equal(ClientsList.empty.headline, 'No Clients'); }); - test('when there are clients, but no matches for a search term, there is an empty message', async function(assert) { + test('when there are clients, but no matches for a search term, there is an empty message', async function (assert) { server.createList('agent', 1); server.create('node', { name: 'node' }); @@ -221,7 +234,7 @@ module('Acceptance | clients list', function(hooks) { assert.equal(ClientsList.empty.headline, 'No Matches'); }); - test('when accessing clients is forbidden, show a message with a link to the tokens page', async function(assert) { + test('when accessing clients is forbidden, show a message with a link to the tokens page', async function (assert) { server.create('agent'); server.create('node', { name: 'node' }); server.pretender.get('/v1/nodes', () => [403, {}, null]); @@ -265,7 +278,13 @@ module('Acceptance | clients list', function(hooks) { testFacet('State', { facet: ClientsList.facets.state, paramName: 'state', - expectedOptions: ['Initializing', 'Ready', 'Down', 'Ineligible', 'Draining'], + expectedOptions: [ + 'Initializing', + 'Ready', + 'Down', + 'Ineligible', + 'Draining', + ], async beforeEach() { server.create('agent'); @@ -273,15 +292,27 @@ module('Acceptance | clients list', function(hooks) { server.createList('node', 2, { status: 'ready' }); server.createList('node', 2, { status: 'down' }); - server.createList('node', 2, { schedulingEligibility: 'eligible', drain: false }); - server.createList('node', 2, { schedulingEligibility: 'ineligible', drain: false }); - server.createList('node', 2, { schedulingEligibility: 'ineligible', drain: true }); + server.createList('node', 2, { + schedulingEligibility: 'eligible', + drain: false, + }); + server.createList('node', 2, { + schedulingEligibility: 'ineligible', + drain: false, + }); + server.createList('node', 2, { + schedulingEligibility: 'ineligible', + drain: true, + }); await ClientsList.visit(); }, filter: (node, selection) => { if (selection.includes('draining') && !node.drain) return false; - if (selection.includes('ineligible') && node.schedulingEligibility === 'eligible') + if ( + selection.includes('ineligible') && + node.schedulingEligibility === 'eligible' + ) return false; return selection.includes(node.status); @@ -325,20 +356,26 @@ module('Acceptance | clients list', function(hooks) { paramName: 'volume', expectedOptions(nodes) { const flatten = (acc, val) => acc.concat(Object.keys(val)); - return Array.from(new Set(nodes.mapBy('hostVolumes').reduce(flatten, []))); + return Array.from( + new Set(nodes.mapBy('hostVolumes').reduce(flatten, [])) + ); }, async beforeEach() { server.create('agent'); server.createList('node', 2, { hostVolumes: { One: { Name: 'One' } } }); - server.createList('node', 2, { hostVolumes: { One: { Name: 'One' }, Two: { Name: 'Two' } } }); + server.createList('node', 2, { + hostVolumes: { One: { Name: 'One' }, Two: { Name: 'Two' } }, + }); server.createList('node', 2, { hostVolumes: { Two: { Name: 'Two' } } }); await ClientsList.visit(); }, filter: (node, selection) => - Object.keys(node.hostVolumes).find(volume => selection.includes(volume)), + Object.keys(node.hostVolumes).find((volume) => + selection.includes(volume) + ), }); - test('when the facet selections result in no matches, the empty state states why', async function(assert) { + test('when the facet selections result in no matches, the empty state states why', async function (assert) { server.create('agent'); server.createList('node', 2, { status: 'ready' }); @@ -347,21 +384,32 @@ module('Acceptance | clients list', function(hooks) { await ClientsList.facets.state.toggle(); await ClientsList.facets.state.options.objectAt(0).toggle(); assert.ok(ClientsList.isEmpty, 'There is an empty message'); - assert.equal(ClientsList.empty.headline, 'No Matches', 'The message is appropriate'); + assert.equal( + ClientsList.empty.headline, + 'No Matches', + 'The message is appropriate' + ); }); - test('the clients list is immediately filtered based on query params', async function(assert) { + test('the clients list is immediately filtered based on query params', async function (assert) { server.create('agent'); server.create('node', { nodeClass: 'omg-large' }); server.create('node', { nodeClass: 'wtf-tiny' }); await ClientsList.visit({ class: JSON.stringify(['wtf-tiny']) }); - assert.equal(ClientsList.nodes.length, 1, 'Only one client shown due to query param'); + assert.equal( + ClientsList.nodes.length, + 1, + 'Only one client shown due to query param' + ); }); - function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) { - test(`the ${label} facet has the correct options`, async function(assert) { + function testFacet( + label, + { facet, paramName, beforeEach, filter, expectedOptions } + ) { + test(`the ${label} facet has the correct options`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -373,13 +421,13 @@ module('Acceptance | clients list', function(hooks) { } assert.deepEqual( - facet.options.map(option => option.label.trim()), + facet.options.map((option) => option.label.trim()), expectation, 'Options for facet are as expected' ); }); - test(`the ${label} facet filters the nodes list by ${label}`, async function(assert) { + test(`the ${label} facet filters the nodes list by ${label}`, async function (assert) { let option; await beforeEach(); @@ -390,7 +438,7 @@ module('Acceptance | clients list', function(hooks) { const selection = [option.key]; const expectedNodes = server.db.nodes - .filter(node => filter(node, selection)) + .filter((node) => filter(node, selection)) .sortBy('modifyIndex') .reverse(); @@ -403,7 +451,7 @@ module('Acceptance | clients list', function(hooks) { }); }); - test(`selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { + test(`selecting multiple options in the ${label} facet results in a broader search`, async function (assert) { const selection = []; await beforeEach(); @@ -417,7 +465,7 @@ module('Acceptance | clients list', function(hooks) { selection.push(option2.key); const expectedNodes = server.db.nodes - .filter(node => filter(node, selection)) + .filter((node) => filter(node, selection)) .sortBy('modifyIndex') .reverse(); @@ -430,7 +478,7 @@ module('Acceptance | clients list', function(hooks) { }); }); - test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) { const selection = []; await beforeEach(); @@ -445,7 +493,9 @@ module('Acceptance | clients list', function(hooks) { assert.equal( currentURL(), - `/clients?${paramName}=${encodeURIComponent(JSON.stringify(selection))}`, + `/clients?${paramName}=${encodeURIComponent( + JSON.stringify(selection) + )}`, 'URL has the correct query param key and value' ); }); diff --git a/ui/tests/acceptance/exec-test.js b/ui/tests/acceptance/exec-test.js index 2ce12ff0d..b37f164fa 100644 --- a/ui/tests/acceptance/exec-test.js +++ b/ui/tests/acceptance/exec-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { module, skip, test } from 'qunit'; import { currentURL, settled } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; @@ -7,11 +8,11 @@ import Service from '@ember/service'; import Exec from 'nomad-ui/tests/pages/exec'; import KEYS from 'nomad-ui/utils/keys'; -module('Acceptance | exec', function(hooks) { +module('Acceptance | exec', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { window.localStorage.clear(); window.sessionStorage.clear(); @@ -25,7 +26,7 @@ module('Acceptance | exec', function(hooks) { status: 'running', }); - this.job.taskGroups.models.forEach(taskGroup => { + this.job.taskGroups.models.forEach((taskGroup) => { server.create('allocation', { jobId: this.job.id, taskGroup: taskGroup.name, @@ -34,12 +35,12 @@ module('Acceptance | exec', function(hooks) { }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await Exec.visitJob({ job: this.job.id }); await a11yAudit(assert); }); - test('/exec/:job should show the region, namespace, and job name', async function(assert) { + test('/exec/:job should show the region, namespace, and job name', async function (assert) { server.create('namespace'); let namespace = server.create('namespace'); @@ -52,7 +53,11 @@ module('Acceptance | exec', function(hooks) { status: 'running', }); - await Exec.visitJob({ job: this.job.id, namespace: namespace.id, region: 'region-2' }); + await Exec.visitJob({ + job: this.job.id, + namespace: namespace.id, + region: 'region-2', + }); assert.equal(document.title, 'Exec - region-2 - Nomad'); @@ -63,14 +68,14 @@ module('Acceptance | exec', function(hooks) { assert.notOk(Exec.jobDead.isPresent); }); - test('/exec/:job should not show region and namespace when there are none', async function(assert) { + test('/exec/:job should not show region and namespace when there are none', async function (assert) { await Exec.visitJob({ job: this.job.id }); assert.ok(Exec.header.region.isHidden); assert.ok(Exec.header.namespace.isHidden); }); - test('/exec/:job should show the task groups collapsed by default and allow the tasks to be shown', async function(assert) { + test('/exec/:job should show the task groups collapsed by default and allow the tasks to be shown', async function (assert) { const firstTaskGroup = this.job.taskGroups.models.sortBy('name')[0]; await Exec.visitJob({ job: this.job.id }); @@ -90,35 +95,38 @@ module('Acceptance | exec', function(hooks) { assert.equal(Exec.taskGroups[0].tasks.length, 0); }); - test('/exec/:job should require selecting a task', async function(assert) { + test('/exec/:job should require selecting a task', async function (assert) { await Exec.visitJob({ job: this.job.id }); assert.equal( - window.execTerminal.buffer.active - .getLine(0) - .translateToString() - .trim(), + window.execTerminal.buffer.active.getLine(0).translateToString().trim(), 'Select a task to start your session.' ); }); - test('a task group with a pending allocation shows a loading spinner', async function(assert) { + test('a task group with a pending allocation shows a loading spinner', async function (assert) { let taskGroup = this.job.taskGroups.models.sortBy('name')[0]; - this.server.db.allocations.update({ taskGroup: taskGroup.name }, { clientStatus: 'pending' }); + this.server.db.allocations.update( + { taskGroup: taskGroup.name }, + { clientStatus: 'pending' } + ); await Exec.visitJob({ job: this.job.id }); assert.ok(Exec.taskGroups[0].isLoading); }); - test('a task group with no running task states or pending allocations should not be shown', async function(assert) { + test('a task group with no running task states or pending allocations should not be shown', async function (assert) { let taskGroup = this.job.taskGroups.models.sortBy('name')[0]; - this.server.db.allocations.update({ taskGroup: taskGroup.name }, { clientStatus: 'failed' }); + this.server.db.allocations.update( + { taskGroup: taskGroup.name }, + { clientStatus: 'failed' } + ); await Exec.visitJob({ job: this.job.id }); assert.notEqual(Exec.taskGroups[0].name, taskGroup.name); }); - test('an inactive task should not be shown', async function(assert) { + test('an inactive task should not be shown', async function (assert) { let notRunningTaskGroup = this.job.taskGroups.models.sortBy('name')[0]; this.server.db.allocations.update( { taskGroup: notRunningTaskGroup.name }, @@ -128,7 +136,10 @@ module('Acceptance | exec', function(hooks) { let runningTaskGroup = this.job.taskGroups.models.sortBy('name')[1]; runningTaskGroup.tasks.models.forEach((task, index) => { if (index > 0) { - this.server.db.taskStates.update({ name: task.name }, { finishedAt: new Date() }); + this.server.db.taskStates.update( + { name: task.name }, + { finishedAt: new Date() } + ); } }); @@ -138,7 +149,7 @@ module('Acceptance | exec', function(hooks) { assert.equal(Exec.taskGroups[0].tasks.length, 1); }); - test('a task that becomes active should appear', async function(assert) { + test('a task that becomes active should appear', async function (assert) { let notRunningTaskGroup = this.job.taskGroups.models.sortBy('name')[0]; this.server.db.allocations.update( { taskGroup: notRunningTaskGroup.name }, @@ -149,7 +160,10 @@ module('Acceptance | exec', function(hooks) { let changingTaskStateName; runningTaskGroup.tasks.models.sortBy('name').forEach((task, index) => { if (index > 0) { - this.server.db.taskStates.update({ name: task.name }, { finishedAt: new Date() }); + this.server.db.taskStates.update( + { name: task.name }, + { finishedAt: new Date() } + ); } if (index === 1) { @@ -166,8 +180,11 @@ module('Acceptance | exec', function(hooks) { this.owner .lookup('service:store') .peekAll('allocation') - .forEach(allocation => { - const changingTaskState = allocation.states.findBy('name', changingTaskStateName); + .forEach((allocation) => { + const changingTaskState = allocation.states.findBy( + 'name', + changingTaskStateName + ); if (changingTaskState) { changingTaskState.set('finishedAt', undefined); @@ -180,7 +197,7 @@ module('Acceptance | exec', function(hooks) { assert.equal(Exec.taskGroups[0].tasks[1].name, changingTaskStateName); }); - test('a dead job has an inert window', async function(assert) { + test('a dead job has an inert window', async function (assert) { this.job.status = 'dead'; this.job.save(); @@ -202,21 +219,21 @@ module('Acceptance | exec', function(hooks) { ); }); - test('when a job dies the exec window becomes inert', async function(assert) { + test('when a job dies the exec window becomes inert', async function (assert) { await Exec.visitJob({ job: this.job.id }); // Approximate live-polling job death this.owner .lookup('service:store') .peekAll('job') - .forEach(job => job.set('status', 'dead')); + .forEach((job) => job.set('status', 'dead')); await settled(); assert.ok(Exec.jobDead.isPresent); }); - test('visiting a path with a task group should open the group by default', async function(assert) { + test('visiting a path with a task group should open the group by default', async function (assert) { let taskGroup = this.job.taskGroups.models.sortBy('name')[0]; await Exec.visitTaskGroup({ job: this.job.id, task_group: taskGroup.name }); @@ -224,13 +241,17 @@ module('Acceptance | exec', function(hooks) { assert.ok(Exec.taskGroups[0].chevron.isDown); let task = taskGroup.tasks.models.sortBy('name')[0]; - await Exec.visitTask({ job: this.job.id, task_group: taskGroup.name, task_name: task.name }); + await Exec.visitTask({ + job: this.job.id, + task_group: taskGroup.name, + task_name: task.name, + }); assert.equal(Exec.taskGroups[0].tasks.length, taskGroup.tasks.length); assert.ok(Exec.taskGroups[0].chevron.isDown); }); - test('navigating to a task adds its name to the route, chooses an allocation, and assigns a default command', async function(assert) { + test('navigating to a task adds its name to the route, chooses an allocation, and assigns a default command', async function (assert) { await Exec.visitJob({ job: this.job.id }); await Exec.taskGroups[0].click(); await Exec.taskGroups[0].tasks[0].click(); @@ -241,39 +262,35 @@ module('Acceptance | exec', function(hooks) { let taskStates = this.server.db.taskStates.where({ name: task.name, }); - let allocationId = taskStates.find(ts => ts.allocationId).allocationId; + let allocationId = taskStates.find((ts) => ts.allocationId).allocationId; await settled(); - assert.equal(currentURL(), `/exec/${this.job.id}/${taskGroup.name}/${task.name}`); + assert.equal( + currentURL(), + `/exec/${this.job.id}/${taskGroup.name}/${task.name}` + ); assert.ok(Exec.taskGroups[0].tasks[0].isActive); assert.equal( - window.execTerminal.buffer.active - .getLine(2) - .translateToString() - .trim(), + window.execTerminal.buffer.active.getLine(2).translateToString().trim(), 'Multiple instances of this task are running. The allocation below was selected by random draw.' ); assert.equal( - window.execTerminal.buffer.active - .getLine(4) - .translateToString() - .trim(), + window.execTerminal.buffer.active.getLine(4).translateToString().trim(), 'Customize your command, then hit ‘return’ to run.' ); assert.equal( - window.execTerminal.buffer.active - .getLine(6) - .translateToString() - .trim(), - `$ nomad alloc exec -i -t -task ${task.name} ${allocationId.split('-')[0]} /bin/bash` + window.execTerminal.buffer.active.getLine(6).translateToString().trim(), + `$ nomad alloc exec -i -t -task ${task.name} ${ + allocationId.split('-')[0] + } /bin/bash` ); }); - test('an allocation can be specified', async function(assert) { + test('an allocation can be specified', async function (assert) { let taskGroup = this.job.taskGroups.models.sortBy('name')[0]; let task = taskGroup.tasks.models.sortBy('name')[0]; let allocations = this.server.db.allocations.where({ @@ -282,7 +299,10 @@ module('Acceptance | exec', function(hooks) { }); let allocation = allocations[allocations.length - 1]; - this.server.db.taskStates.update({ name: task.name }, { name: 'spaced name!' }); + this.server.db.taskStates.update( + { name: task.name }, + { name: 'spaced name!' } + ); task.name = 'spaced name!'; task.save(); @@ -297,15 +317,14 @@ module('Acceptance | exec', function(hooks) { await settled(); assert.equal( - window.execTerminal.buffer.active - .getLine(4) - .translateToString() - .trim(), - `$ nomad alloc exec -i -t -task spaced\\ name\\! ${allocation.id.split('-')[0]} /bin/bash` + window.execTerminal.buffer.active.getLine(4).translateToString().trim(), + `$ nomad alloc exec -i -t -task spaced\\ name\\! ${ + allocation.id.split('-')[0] + } /bin/bash` ); }); - test('running the command opens the socket for reading/writing and detects it closing', async function(assert) { + test('running the command opens the socket for reading/writing and detects it closing', async function (assert) { let mockSocket = new MockSocket(); let mockSockets = Service.extend({ getTaskStateSocket(taskState, command) { @@ -352,10 +371,7 @@ module('Acceptance | exec', function(hooks) { await settled(); assert.equal( - window.execTerminal.buffer.active - .getLine(5) - .translateToString() - .trim(), + window.execTerminal.buffer.active.getLine(5).translateToString().trim(), 'sh-3.2 🥳$' ); @@ -372,15 +388,12 @@ module('Acceptance | exec', function(hooks) { await settled(); assert.equal( - window.execTerminal.buffer.active - .getLine(6) - .translateToString() - .trim(), + window.execTerminal.buffer.active.getLine(6).translateToString().trim(), 'The connection has closed.' ); }); - test('the opening message includes the token if it exists', async function(assert) { + test('the opening message includes the token if it exists', async function (assert) { const { secretId } = server.create('token'); window.localStorage.nomadTokenSecret = secretId; @@ -415,10 +428,13 @@ module('Acceptance | exec', function(hooks) { await Exec.terminal.pressEnter(); await settled(); - assert.equal(mockSocket.sent[0], `{"version":1,"auth_token":"${secretId}"}`); + assert.equal( + mockSocket.sent[0], + `{"version":1,"auth_token":"${secretId}"}` + ); }); - test('only one socket is opened after switching between tasks', async function(assert) { + test('only one socket is opened after switching between tasks', async function (assert) { let mockSockets = Service.extend({ getTaskStateSocket() { assert.step('Socket built'); @@ -445,7 +461,7 @@ module('Acceptance | exec', function(hooks) { assert.verifySteps(['Socket built']); }); - test('the command can be customised', async function(assert) { + test('the command can be customised', async function (assert) { let mockSockets = Service.extend({ getTaskStateSocket(taskState, command) { assert.equal(command, '/sh'); @@ -491,11 +507,10 @@ module('Acceptance | exec', function(hooks) { await settled(); assert.equal( - window.execTerminal.buffer.active - .getLine(6) - .translateToString() - .trim(), - `$ nomad alloc exec -i -t -task ${task.name} ${allocation.id.split('-')[0]}` + window.execTerminal.buffer.active.getLine(6).translateToString().trim(), + `$ nomad alloc exec -i -t -task ${task.name} ${ + allocation.id.split('-')[0] + }` ); await window.execTerminal.simulateCommandDataEvent('/sh'); @@ -506,7 +521,7 @@ module('Acceptance | exec', function(hooks) { assert.verifySteps(['Socket built']); }); - test('a persisted customised command is recalled', async function(assert) { + test('a persisted customised command is recalled', async function (assert) { window.localStorage.setItem('nomadExecCommand', JSON.stringify('/bin/sh')); let taskGroup = this.job.taskGroups.models[0]; @@ -527,15 +542,14 @@ module('Acceptance | exec', function(hooks) { await settled(); assert.equal( - window.execTerminal.buffer.active - .getLine(4) - .translateToString() - .trim(), - `$ nomad alloc exec -i -t -task ${task.name} ${allocation.id.split('-')[0]} /bin/sh` + window.execTerminal.buffer.active.getLine(4).translateToString().trim(), + `$ nomad alloc exec -i -t -task ${task.name} ${ + allocation.id.split('-')[0] + } /bin/sh` ); }); - skip('when a task state finishes submitting a command displays an error', async function(assert) { + skip('when a task state finishes submitting a command displays an error', async function (assert) { let taskGroup = this.job.taskGroups.models.sortBy('name')[0]; let task = taskGroup.tasks.models.sortBy('name')[0]; @@ -549,16 +563,13 @@ module('Acceptance | exec', function(hooks) { this.owner .lookup('service:store') .peekAll('allocation') - .forEach(allocation => allocation.set('clientStatus', 'failed')); + .forEach((allocation) => allocation.set('clientStatus', 'failed')); await Exec.terminal.pressEnter(); await settled(); assert.equal( - window.execTerminal.buffer.active - .getLine(7) - .translateToString() - .trim(), + window.execTerminal.buffer.active.getLine(7).translateToString().trim(), `Failed to open a socket because task ${task.name} is not active.` ); }); diff --git a/ui/tests/acceptance/global-header-test.js b/ui/tests/acceptance/global-header-test.js index 5b4442aee..9234324e4 100644 --- a/ui/tests/acceptance/global-header-test.js +++ b/ui/tests/acceptance/global-header-test.js @@ -5,11 +5,11 @@ import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import Layout from 'nomad-ui/tests/pages/layout'; -module('Acceptance | global header', function(hooks) { +module('Acceptance | global header', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - test('it diplays no links', async function(assert) { + test('it diplays no links', async function (assert) { server.create('agent'); await visit('/'); @@ -18,7 +18,7 @@ module('Acceptance | global header', function(hooks) { assert.false(Layout.navbar.end.vaultLink.isVisible); }); - test('it diplays both links', async function(assert) { + test('it diplays both links', async function (assert) { server.create('agent', 'withConsulLink', 'withVaultLink'); await visit('/'); @@ -27,7 +27,7 @@ module('Acceptance | global header', function(hooks) { assert.true(Layout.navbar.end.vaultLink.isVisible); }); - test('it diplays Consul link', async function(assert) { + test('it diplays Consul link', async function (assert) { server.create('agent', 'withConsulLink'); await visit('/'); @@ -37,7 +37,7 @@ module('Acceptance | global header', function(hooks) { assert.equal(Layout.navbar.end.consulLink.link, 'http://localhost:8500/ui'); }); - test('it diplays Vault link', async function(assert) { + test('it diplays Vault link', async function (assert) { server.create('agent', 'withVaultLink'); await visit('/'); diff --git a/ui/tests/acceptance/job-allocations-test.js b/ui/tests/acceptance/job-allocations-test.js index 73fcc7207..4f276316d 100644 --- a/ui/tests/acceptance/job-allocations-test.js +++ b/ui/tests/acceptance/job-allocations-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -8,7 +9,7 @@ import Allocations from 'nomad-ui/tests/pages/jobs/job/allocations'; let job; let allocations; -const makeSearchAllocations = server => { +const makeSearchAllocations = (server) => { Array(10) .fill(null) .map((_, index) => { @@ -19,26 +20,33 @@ const makeSearchAllocations = server => { }); }; -module('Acceptance | job allocations', function(hooks) { +module('Acceptance | job allocations', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('node'); - job = server.create('job', { noFailedPlacements: true, createAllocations: false }); + job = server.create('job', { + noFailedPlacements: true, + createAllocations: false, + }); }); - test('it passes an accessibility audit', async function(assert) { - server.createList('allocation', Allocations.pageSize - 1, { shallow: true }); + test('it passes an accessibility audit', async function (assert) { + server.createList('allocation', Allocations.pageSize - 1, { + shallow: true, + }); allocations = server.schema.allocations.where({ jobId: job.id }).models; await Allocations.visit({ id: job.id }); await a11yAudit(assert); }); - test('lists all allocations for the job', async function(assert) { - server.createList('allocation', Allocations.pageSize - 1, { shallow: true }); + test('lists all allocations for the job', async function (assert) { + server.createList('allocation', Allocations.pageSize - 1, { + shallow: true, + }); allocations = server.schema.allocations.where({ jobId: job.id }).models; await Allocations.visit({ id: job.id }); @@ -53,13 +61,17 @@ module('Acceptance | job allocations', function(hooks) { Allocations.allocations.forEach((allocation, index) => { const shortId = sortedAllocations[index].id.split('-')[0]; - assert.equal(allocation.shortId, shortId, `Allocation ${index} is ${shortId}`); + assert.equal( + allocation.shortId, + shortId, + `Allocation ${index} is ${shortId}` + ); }); assert.equal(document.title, `Job ${job.name} allocations - Nomad`); }); - test('allocations table is sortable', async function(assert) { + test('allocations table is sortable', async function (assert) { server.createList('allocation', Allocations.pageSize - 1); allocations = server.schema.allocations.where({ jobId: job.id }).models; @@ -82,7 +94,7 @@ module('Acceptance | job allocations', function(hooks) { }); }); - test('allocations table is searchable', async function(assert) { + test('allocations table is searchable', async function (assert) { makeSearchAllocations(server); allocations = server.schema.allocations.where({ jobId: job.id }).models; @@ -90,10 +102,14 @@ module('Acceptance | job allocations', function(hooks) { await Allocations.visit({ id: job.id }); await Allocations.search('ffffff'); - assert.equal(Allocations.allocations.length, 5, 'List is filtered by search term'); + assert.equal( + Allocations.allocations.length, + 5, + 'List is filtered by search term' + ); }); - test('when a search yields no results, the search box remains', async function(assert) { + test('when a search yields no results, the search box remains', async function (assert) { makeSearchAllocations(server); allocations = server.schema.allocations.where({ jobId: job.id }).models; @@ -110,19 +126,27 @@ module('Acceptance | job allocations', function(hooks) { assert.ok(Allocations.hasSearchBox, 'Search box is still shown'); }); - test('when the job for the allocations is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the job for the allocations is not found, an error message is shown, but the URL persists', async function (assert) { await Allocations.visit({ id: 'not-a-real-job' }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); - assert.equal(currentURL(), '/jobs/not-a-real-job/allocations', 'The URL persists'); + assert.equal( + currentURL(), + '/jobs/not-a-real-job/allocations', + 'The URL persists' + ); assert.ok(Allocations.error.isPresent, 'Error message is shown'); - assert.equal(Allocations.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + Allocations.error.title, + 'Not Found', + 'Error message is for 404' + ); }); testFacet('Status', { @@ -130,12 +154,13 @@ module('Acceptance | job allocations', function(hooks) { paramName: 'status', expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'], async beforeEach() { - ['pending', 'running', 'complete', 'failed', 'lost'].forEach(s => { + ['pending', 'running', 'complete', 'failed', 'lost'].forEach((s) => { server.createList('allocation', 5, { clientStatus: s }); }); await Allocations.visit({ id: job.id }); }, - filter: (alloc, selection) => alloc.jobId == job.id && selection.includes(alloc.clientStatus), + filter: (alloc, selection) => + alloc.jobId == job.id && selection.includes(alloc.clientStatus), }); testFacet('Client', { @@ -145,9 +170,9 @@ module('Acceptance | job allocations', function(hooks) { return Array.from( new Set( allocs - .filter(alloc => alloc.jobId == job.id) + .filter((alloc) => alloc.jobId == job.id) .mapBy('nodeId') - .map(id => id.split('-')[0]) + .map((id) => id.split('-')[0]) ) ).sort(); }, @@ -166,7 +191,9 @@ module('Acceptance | job allocations', function(hooks) { paramName: 'taskGroup', expectedOptions(allocs) { return Array.from( - new Set(allocs.filter(alloc => alloc.jobId == job.id).mapBy('taskGroup')) + new Set( + allocs.filter((alloc) => alloc.jobId == job.id).mapBy('taskGroup') + ) ).sort(); }, async beforeEach() { @@ -178,12 +205,16 @@ module('Acceptance | job allocations', function(hooks) { await Allocations.visit({ id: job.id }); }, - filter: (alloc, selection) => alloc.jobId == job.id && selection.includes(alloc.taskGroup), + filter: (alloc, selection) => + alloc.jobId == job.id && selection.includes(alloc.taskGroup), }); }); -function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) { - test(`facet ${label} | the ${label} facet has the correct options`, async function(assert) { +function testFacet( + label, + { facet, paramName, beforeEach, filter, expectedOptions } +) { + test(`facet ${label} | the ${label} facet has the correct options`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -195,13 +226,13 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption } assert.deepEqual( - facet.options.map(option => option.label.trim()), + facet.options.map((option) => option.label.trim()), expectation, 'Options for facet are as expected' ); }); - test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function(assert) { + test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function (assert) { let option; await beforeEach(); @@ -212,7 +243,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption const selection = [option.key]; const expectedAllocs = server.db.allocations - .filter(alloc => filter(alloc, selection)) + .filter((alloc) => filter(alloc, selection)) .sortBy('modifyIndex') .reverse(); @@ -225,7 +256,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption }); }); - test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { + test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function (assert) { const selection = []; await beforeEach(); @@ -239,7 +270,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption selection.push(option2.key); const expectedAllocs = server.db.allocations - .filter(alloc => filter(alloc, selection)) + .filter((alloc) => filter(alloc, selection)) .sortBy('modifyIndex') .reverse(); @@ -252,7 +283,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption }); }); - test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) { const selection = []; await beforeEach(); @@ -267,7 +298,9 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption assert.equal( currentURL(), - `/jobs/${job.id}/allocations?${paramName}=${encodeURIComponent(JSON.stringify(selection))}`, + `/jobs/${job.id}/allocations?${paramName}=${encodeURIComponent( + JSON.stringify(selection) + )}`, 'URL has the correct query param key and value' ); }); diff --git a/ui/tests/acceptance/job-clients-test.js b/ui/tests/acceptance/job-clients-test.js index 34583f1d9..4af17e15d 100644 --- a/ui/tests/acceptance/job-clients-test.js +++ b/ui/tests/acceptance/job-clients-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -21,11 +22,11 @@ const makeSearchableClients = (server, job) => { }); }; -module('Acceptance | job clients', function(hooks) { +module('Acceptance | job clients', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { clients = server.createList('node', 12, { datacenter: 'dc1', status: 'ready', @@ -38,7 +39,7 @@ module('Acceptance | job clients', function(hooks) { resourceSpec: ['M: 256, C: 500'], createAllocations: false, }); - clients.forEach(c => { + clients.forEach((c) => { server.create('allocation', { jobId: job.id, nodeId: c.id }); }); @@ -51,43 +52,52 @@ module('Acceptance | job clients', function(hooks) { ); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await Clients.visit({ id: job.id }); await a11yAudit(assert); }); - test('lists all clients for the job', async function(assert) { + test('lists all clients for the job', async function (assert) { await Clients.visit({ id: job.id }); assert.equal(Clients.clients.length, 15, 'Clients are shown in a table'); - const clientIDs = clients.sortBy('id').map(c => c.id); - const clientsInTable = Clients.clients.map(c => c.id).sort(); + const clientIDs = clients.sortBy('id').map((c) => c.id); + const clientsInTable = Clients.clients.map((c) => c.id).sort(); assert.deepEqual(clientsInTable, clientIDs); assert.equal(document.title, `Job ${job.name} clients - Nomad`); }); - test('dates have tooltip', async function(assert) { + test('dates have tooltip', async function (assert) { await Clients.visit({ id: job.id }); Clients.clients.forEach((clientRow, index) => { const jobStatus = Clients.clientFor(clientRow.id).status; - ['createTime', 'modifyTime'].forEach(col => { + ['createTime', 'modifyTime'].forEach((col) => { if (jobStatus === 'not scheduled') { - assert.equal(clientRow[col].text, '-', `row ${index} doesn't have ${col} tooltip`); + /* eslint-disable-next-line qunit/no-conditional-assertions */ + assert.equal( + clientRow[col].text, + '-', + `row ${index} doesn't have ${col} tooltip` + ); + /* eslint-disable-next-line qunit/no-early-return */ return; } const hasTooltip = clientRow[col].tooltip.isPresent; const tooltipText = clientRow[col].tooltip.text; assert.true(hasTooltip, `row ${index} has ${col} tooltip`); - assert.ok(tooltipText, `row ${index} has ${col} tooltip content ${tooltipText}`); + assert.ok( + tooltipText, + `row ${index} has ${col} tooltip content ${tooltipText}` + ); }); }); }); - test('clients table is sortable', async function(assert) { + test('clients table is sortable', async function (assert) { await Clients.visit({ id: job.id }); await Clients.sortBy('node.name'); @@ -108,7 +118,7 @@ module('Acceptance | job clients', function(hooks) { }); }); - test('clients table is searchable', async function(assert) { + test('clients table is searchable', async function (assert) { makeSearchableClients(server, job); await Clients.visit({ id: job.id }); @@ -117,7 +127,7 @@ module('Acceptance | job clients', function(hooks) { assert.equal(Clients.clients.length, 5, 'List is filtered by search term'); }); - test('when a search yields no results, the search box remains', async function(assert) { + test('when a search yields no results, the search box remains', async function (assert) { makeSearchableClients(server, job); await Clients.visit({ id: job.id }); @@ -132,22 +142,26 @@ module('Acceptance | job clients', function(hooks) { assert.ok(Clients.hasSearchBox, 'Search box is still shown'); }); - test('when the job for the clients is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the job for the clients is not found, an error message is shown, but the URL persists', async function (assert) { await Clients.visit({ id: 'not-a-real-job' }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); - assert.equal(currentURL(), '/jobs/not-a-real-job/clients', 'The URL persists'); + assert.equal( + currentURL(), + '/jobs/not-a-real-job/clients', + 'The URL persists' + ); assert.ok(Clients.error.isPresent, 'Error message is shown'); assert.equal(Clients.error.title, 'Not Found', 'Error message is for 404'); }); - test('clicking row goes to client details', async function(assert) { + test('clicking row goes to client details', async function (assert) { const client = clients[0]; await Clients.visit({ id: job.id }); @@ -182,7 +196,7 @@ module('Acceptance | job clients', function(hooks) { }); function testFacet(label, { facet, paramName, beforeEach, expectedOptions }) { - test(`the ${label} facet has the correct options`, async function(assert) { + test(`the ${label} facet has the correct options`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -194,7 +208,7 @@ module('Acceptance | job clients', function(hooks) { } assert.deepEqual( - facet.options.map(option => option.label.trim()), + facet.options.map((option) => option.label.trim()), expectation, `Options for facet ${paramName} are as expected` ); diff --git a/ui/tests/acceptance/job-definition-test.js b/ui/tests/acceptance/job-definition-test.js index 779d0f813..c8948645f 100644 --- a/ui/tests/acceptance/job-definition-test.js +++ b/ui/tests/acceptance/job-definition-test.js @@ -8,49 +8,58 @@ import Definition from 'nomad-ui/tests/pages/jobs/job/definition'; let job; -module('Acceptance | job definition', function(hooks) { +module('Acceptance | job definition', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); setupCodeMirror(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('node'); server.create('job'); job = server.db.jobs[0]; await Definition.visit({ id: job.id }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + await a11yAudit(assert, 'scrollable-region-focusable'); }); - test('visiting /jobs/:job_id/definition', async function(assert) { + test('visiting /jobs/:job_id/definition', async function (assert) { assert.equal(currentURL(), `/jobs/${job.id}/definition`); assert.equal(document.title, `Job ${job.name} definition - Nomad`); }); - test('the job definition page contains a json viewer component', async function(assert) { + test('the job definition page contains a json viewer component', async function (assert) { assert.ok(Definition.jsonViewer, 'JSON viewer found'); }); - test('the job definition page requests the job to display in an unmutated form', async function(assert) { + test('the job definition page requests the job to display in an unmutated form', async function (assert) { const jobURL = `/v1/job/${job.id}`; const jobRequests = server.pretender.handledRequests - .map(req => req.url.split('?')[0]) - .filter(url => url === jobURL); - assert.ok(jobRequests.length === 2, 'Two requests for the job were made'); + .map((req) => req.url.split('?')[0]) + .filter((url) => url === jobURL); + assert.strictEqual( + jobRequests.length, + 2, + 'Two requests for the job were made' + ); }); - test('the job definition can be edited', async function(assert) { + test('the job definition can be edited', async function (assert) { assert.notOk(Definition.editor.isPresent, 'Editor is not shown on load'); await Definition.edit(); - assert.ok(Definition.editor.isPresent, 'Editor is shown after clicking edit'); + assert.ok( + Definition.editor.isPresent, + 'Editor is shown after clicking edit' + ); assert.notOk(Definition.jsonViewer, 'Editor replaces the JSON viewer'); }); - test('when in editing mode, the action can be canceled, showing the read-only definition again', async function(assert) { + test('when in editing mode, the action can be canceled, showing the read-only definition again', async function (assert) { await Definition.edit(); await Definition.editor.cancelEditing(); @@ -58,10 +67,17 @@ module('Acceptance | job definition', function(hooks) { assert.notOk(Definition.editor.isPresent, 'The editor is gone'); }); - test('when in editing mode, the editor is prepopulated with the job definition', async function(assert) { + test('when in editing mode, the editor is prepopulated with the job definition', async function (assert) { const requests = server.pretender.handledRequests; - const jobDefinition = requests.findBy('url', `/v1/job/${job.id}`).responseText; - const formattedJobDefinition = JSON.stringify(JSON.parse(jobDefinition), null, 2); + const jobDefinition = requests.findBy( + 'url', + `/v1/job/${job.id}` + ).responseText; + const formattedJobDefinition = JSON.stringify( + JSON.parse(jobDefinition), + null, + 2 + ); await Definition.edit(); @@ -72,26 +88,38 @@ module('Acceptance | job definition', function(hooks) { ); }); - test('when changes are submitted, the site redirects to the job overview page', async function(assert) { + test('when changes are submitted, the site redirects to the job overview page', async function (assert) { await Definition.edit(); await Definition.editor.plan(); await Definition.editor.run(); - assert.equal(currentURL(), `/jobs/${job.id}`, 'Now on the job overview page'); + assert.equal( + currentURL(), + `/jobs/${job.id}`, + 'Now on the job overview page' + ); }); - test('when the job for the definition is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the job for the definition is not found, an error message is shown, but the URL persists', async function (assert) { await Definition.visit({ id: 'not-a-real-job' }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); - assert.equal(currentURL(), '/jobs/not-a-real-job/definition', 'The URL persists'); + assert.equal( + currentURL(), + '/jobs/not-a-real-job/definition', + 'The URL persists' + ); assert.ok(Definition.error.isPresent, 'Error message is shown'); - assert.equal(Definition.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + Definition.error.title, + 'Not Found', + 'Error message is for 404' + ); }); }); diff --git a/ui/tests/acceptance/job-deployments-test.js b/ui/tests/acceptance/job-deployments-test.js index c4fb897ed..fd1680755 100644 --- a/ui/tests/acceptance/job-deployments-test.js +++ b/ui/tests/acceptance/job-deployments-test.js @@ -7,24 +7,30 @@ import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; import moment from 'moment'; import Deployments from 'nomad-ui/tests/pages/jobs/job/deployments'; -const sum = (list, key, getter = a => a) => +const sum = (list, key, getter = (a) => a) => list.reduce((sum, item) => sum + getter(get(item, key)), 0); let job; let deployments; let sortedDeployments; -module('Acceptance | job deployments', function(hooks) { +module('Acceptance | job deployments', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('node'); job = server.create('job'); deployments = server.schema.deployments.where({ jobId: job.id }); sortedDeployments = deployments.sort((a, b) => { - const aVersion = server.db.jobVersions.findBy({ jobId: a.jobId, version: a.versionNumber }); - const bVersion = server.db.jobVersions.findBy({ jobId: b.jobId, version: b.versionNumber }); + const aVersion = server.db.jobVersions.findBy({ + jobId: a.jobId, + version: a.versionNumber, + }); + const bVersion = server.db.jobVersions.findBy({ + jobId: b.jobId, + version: b.versionNumber, + }); if (aVersion.submitTime < bVersion.submitTime) { return 1; } else if (aVersion.submitTime > bVersion.submitTime) { @@ -34,15 +40,17 @@ module('Acceptance | job deployments', function(hooks) { }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + await Deployments.visit({ id: job.id }); await a11yAudit(assert); }); - test('/jobs/:id/deployments should list all job deployments', async function(assert) { + test('/jobs/:id/deployments should list all job deployments', async function (assert) { await Deployments.visit({ id: job.id }); - assert.ok( + assert.equal( Deployments.deployments.length, deployments.length, 'Each deployment gets a row in the timeline' @@ -50,7 +58,7 @@ module('Acceptance | job deployments', function(hooks) { assert.equal(document.title, `Job ${job.name} deployments - Nomad`); }); - test('each deployment mentions the deployment shortId, status, version, and time since it was submitted', async function(assert) { + test('each deployment mentions the deployment shortId, status, version, and time since it was submitted', async function (assert) { await Deployments.visit({ id: job.id }); const deployment = sortedDeployments.models[0]; @@ -60,24 +68,32 @@ module('Acceptance | job deployments', function(hooks) { }); const deploymentRow = Deployments.deployments.objectAt(0); - assert.ok(deploymentRow.text.includes(deployment.id.split('-')[0]), 'Short ID'); + assert.ok( + deploymentRow.text.includes(deployment.id.split('-')[0]), + 'Short ID' + ); assert.equal(deploymentRow.status, deployment.status, 'Status'); assert.ok( deploymentRow.statusClass.includes(classForStatus(deployment.status)), 'Status Class' ); - assert.ok(deploymentRow.version.includes(deployment.versionNumber), 'Version #'); assert.ok( - deploymentRow.submitTime.includes(moment(version.submitTime / 1000000).fromNow()), + deploymentRow.version.includes(deployment.versionNumber), + 'Version #' + ); + assert.ok( + deploymentRow.submitTime.includes( + moment(version.submitTime / 1000000).fromNow() + ), 'Submit time ago' ); }); - test('when the deployment is running and needs promotion, the deployment item says so', async function(assert) { + test('when the deployment is running and needs promotion, the deployment item says so', async function (assert) { // Ensure the deployment needs deployment const deployment = sortedDeployments.models[0]; - const taskGroupSummary = deployment.deploymentTaskGroupSummaryIds.map(id => - server.schema.deploymentTaskGroupSummaries.find(id) + const taskGroupSummary = deployment.deploymentTaskGroupSummaryIds.map( + (id) => server.schema.deploymentTaskGroupSummaries.find(id) )[0]; deployment.update('status', 'running'); @@ -94,10 +110,13 @@ module('Acceptance | job deployments', function(hooks) { await Deployments.visit({ id: job.id }); const deploymentRow = Deployments.deployments.objectAt(0); - assert.ok(deploymentRow.promotionIsRequired, 'Requires Promotion badge found'); + assert.ok( + deploymentRow.promotionIsRequired, + 'Requires Promotion badge found' + ); }); - test('each deployment item can be opened to show details', async function(assert) { + test('each deployment item can be opened to show details', async function (assert) { await Deployments.visit({ id: job.id }); const deploymentRow = Deployments.deployments.objectAt(0); @@ -107,20 +126,20 @@ module('Acceptance | job deployments', function(hooks) { assert.ok(deploymentRow.hasDetails, 'Deployment body found'); }); - test('when open, a deployment shows the deployment metrics', async function(assert) { + test('when open, a deployment shows the deployment metrics', async function (assert) { await Deployments.visit({ id: job.id }); const deployment = sortedDeployments.models[0]; const deploymentRow = Deployments.deployments.objectAt(0); - const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id => - server.db.deploymentTaskGroupSummaries.find(id) + const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map( + (id) => server.db.deploymentTaskGroupSummaries.find(id) ); await deploymentRow.toggle(); assert.equal( deploymentRow.metricFor('canaries').text, - `${sum(taskGroupSummaries, 'placedCanaries', a => a.length)} / ${sum( + `${sum(taskGroupSummaries, 'placedCanaries', (a) => a.length)} / ${sum( taskGroupSummaries, 'desiredCanaries' )}`, @@ -158,13 +177,13 @@ module('Acceptance | job deployments', function(hooks) { ); }); - test('when open, a deployment shows a list of all task groups and their respective stats', async function(assert) { + test('when open, a deployment shows a list of all task groups and their respective stats', async function (assert) { await Deployments.visit({ id: job.id }); const deployment = sortedDeployments.models[0]; const deploymentRow = Deployments.deployments.objectAt(0); - const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id => - server.db.deploymentTaskGroupSummaries.find(id) + const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map( + (id) => server.db.deploymentTaskGroupSummaries.find(id) ); await deploymentRow.toggle(); @@ -178,11 +197,22 @@ module('Acceptance | job deployments', function(hooks) { ); const taskGroup = taskGroupSummaries[0]; - const taskGroupRow = deploymentRow.taskGroups.findOneBy('name', taskGroup.name); + const taskGroupRow = deploymentRow.taskGroups.findOneBy( + 'name', + taskGroup.name + ); assert.equal(taskGroupRow.name, taskGroup.name, 'Name'); - assert.equal(taskGroupRow.promotion, promotionTestForTaskGroup(taskGroup), 'Needs Promotion'); - assert.equal(taskGroupRow.autoRevert, taskGroup.autoRevert ? 'Yes' : 'No', 'Auto Revert'); + assert.equal( + taskGroupRow.promotion, + promotionTestForTaskGroup(taskGroup), + 'Needs Promotion' + ); + assert.equal( + taskGroupRow.autoRevert, + taskGroup.autoRevert ? 'Yes' : 'No', + 'Auto Revert' + ); assert.equal( taskGroupRow.canaries, `${taskGroup.placedCanaries.length} / ${taskGroup.desiredCanaries}`, @@ -193,8 +223,16 @@ module('Acceptance | job deployments', function(hooks) { `${taskGroup.placedAllocs} / ${taskGroup.desiredTotal}`, 'Allocs' ); - assert.equal(taskGroupRow.healthy, taskGroup.healthyAllocs, 'Healthy Allocs'); - assert.equal(taskGroupRow.unhealthy, taskGroup.unhealthyAllocs, 'Unhealthy Allocs'); + assert.equal( + taskGroupRow.healthy, + taskGroup.healthyAllocs, + 'Healthy Allocs' + ); + assert.equal( + taskGroupRow.unhealthy, + taskGroup.unhealthyAllocs, + 'Unhealthy Allocs' + ); assert.equal( taskGroupRow.progress, moment(taskGroup.requireProgressBy).format("MMM DD, 'YY HH:mm:ss ZZ"), @@ -202,7 +240,7 @@ module('Acceptance | job deployments', function(hooks) { ); }); - test('when open, a deployment shows a list of all allocations for the deployment', async function(assert) { + test('when open, a deployment shows a list of all allocations for the deployment', async function (assert) { await Deployments.visit({ id: job.id }); const deployment = sortedDeployments.models[0]; @@ -210,31 +248,49 @@ module('Acceptance | job deployments', function(hooks) { // TODO: Make this less brittle. This logic is copied from the mirage config, // since there is no reference to allocations on the deployment model. - const allocations = server.db.allocations.where({ jobId: deployment.jobId }).slice(0, 3); + const allocations = server.db.allocations + .where({ jobId: deployment.jobId }) + .slice(0, 3); await deploymentRow.toggle(); assert.ok(deploymentRow.hasAllocations, 'Allocations found'); - assert.equal(deploymentRow.allocations.length, allocations.length, 'One row per allocation'); + assert.equal( + deploymentRow.allocations.length, + allocations.length, + 'One row per allocation' + ); const allocation = allocations[0]; const allocationRow = deploymentRow.allocations.objectAt(0); - assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation is as expected'); + assert.equal( + allocationRow.shortId, + allocation.id.split('-')[0], + 'Allocation is as expected' + ); }); - test('when the job for the deployments is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the job for the deployments is not found, an error message is shown, but the URL persists', async function (assert) { await Deployments.visit({ id: 'not-a-real-job' }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); - assert.equal(currentURL(), '/jobs/not-a-real-job/deployments', 'The URL persists'); + assert.equal( + currentURL(), + '/jobs/not-a-real-job/deployments', + 'The URL persists' + ); assert.ok(Deployments.error.isPresent, 'Error message is shown'); - assert.equal(Deployments.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + Deployments.error.title, + 'Not Found', + 'Error message is for 404' + ); }); function classForStatus(status) { diff --git a/ui/tests/acceptance/job-detail-test.js b/ui/tests/acceptance/job-detail-test.js index a0cf8409c..e6027ef3c 100644 --- a/ui/tests/acceptance/job-detail-test.js +++ b/ui/tests/acceptance/job-detail-test.js @@ -1,11 +1,14 @@ /* eslint-disable ember/no-test-module-for */ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import moment from 'moment'; import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; -import moduleForJob, { moduleForJobWithClientStatus } from 'nomad-ui/tests/helpers/module-for-job'; +import moduleForJob, { + moduleForJobWithClientStatus, +} from 'nomad-ui/tests/helpers/module-for-job'; import JobDetail from 'nomad-ui/tests/pages/jobs/detail'; moduleForJob('Acceptance | job detail (batch)', 'allocations', () => @@ -16,26 +19,30 @@ moduleForJob('Acceptance | job detail (system)', 'allocations', () => server.create('job', { type: 'system', shallow: true }) ); -moduleForJobWithClientStatus('Acceptance | job detail with client status (system)', () => - server.create('job', { - status: 'running', - datacenters: ['dc1'], - type: 'system', - createAllocations: false, - }) +moduleForJobWithClientStatus( + 'Acceptance | job detail with client status (system)', + () => + server.create('job', { + status: 'running', + datacenters: ['dc1'], + type: 'system', + createAllocations: false, + }) ); moduleForJob('Acceptance | job detail (sysbatch)', 'allocations', () => server.create('job', { type: 'sysbatch', shallow: true }) ); -moduleForJobWithClientStatus('Acceptance | job detail with client status (sysbatch)', () => - server.create('job', { - status: 'running', - datacenters: ['dc1'], - type: 'sysbatch', - createAllocations: false, - }) +moduleForJobWithClientStatus( + 'Acceptance | job detail with client status (sysbatch)', + () => + server.create('job', { + status: 'running', + datacenters: ['dc1'], + type: 'sysbatch', + createAllocations: false, + }) ); moduleForJobWithClientStatus( @@ -61,14 +68,17 @@ moduleForJob('Acceptance | job detail (sysbatch child)', 'allocations', () => { return server.db.jobs.where({ parentId: parent.id })[0]; }); -moduleForJobWithClientStatus('Acceptance | job detail with client status (sysbatch child)', () => { - const parent = server.create('job', 'periodicSysbatch', { - childrenCount: 1, - shallow: true, - datacenters: ['dc1'], - }); - return server.db.jobs.where({ parentId: parent.id })[0]; -}); +moduleForJobWithClientStatus( + 'Acceptance | job detail with client status (sysbatch child)', + () => { + const parent = server.create('job', 'periodicSysbatch', { + childrenCount: 1, + shallow: true, + datacenters: ['dc1'], + }); + return server.db.jobs.where({ parentId: parent.id })[0]; + } +); moduleForJobWithClientStatus( 'Acceptance | job detail with client status (sysbatch child with namespace)', @@ -89,7 +99,7 @@ moduleForJob( 'children', () => server.create('job', 'periodic', { shallow: true }), { - 'the default sort is submitTime descending': async function(job, assert) { + 'the default sort is submitTime descending': async function (job, assert) { const mostRecentLaunch = server.db.jobs .where({ parentId: job.id }) .sortBy('submitTime') @@ -98,7 +108,9 @@ moduleForJob( assert.ok(JobDetail.jobsHeader.hasSubmitTime); assert.equal( JobDetail.jobs[0].submitTime, - moment(mostRecentLaunch.submitTime / 1000000).format('MMM DD HH:mm:ss ZZ') + moment(mostRecentLaunch.submitTime / 1000000).format( + 'MMM DD HH:mm:ss ZZ' + ) ); }, } @@ -116,7 +128,7 @@ moduleForJob( return parent; }, { - 'display namespace in children table': async function(job, assert) { + 'display namespace in children table': async function (job, assert) { assert.ok(JobDetail.jobsHeader.hasNamespace); assert.equal(JobDetail.jobs[0].namespace, job.namespace); }, @@ -137,7 +149,9 @@ moduleForJob( assert.ok(JobDetail.jobsHeader.hasSubmitTime); assert.equal( JobDetail.jobs[0].submitTime, - moment(mostRecentLaunch.submitTime / 1000000).format('MMM DD HH:mm:ss ZZ') + moment(mostRecentLaunch.submitTime / 1000000).format( + 'MMM DD HH:mm:ss ZZ' + ) ); }, } @@ -155,7 +169,7 @@ moduleForJob( return parent; }, { - 'display namespace in children table': async function(job, assert) { + 'display namespace in children table': async function (job, assert) { assert.ok(JobDetail.jobsHeader.hasNamespace); assert.equal(JobDetail.jobs[0].namespace, job.namespace); }, @@ -163,14 +177,24 @@ moduleForJob( ); moduleForJob('Acceptance | job detail (periodic child)', 'allocations', () => { - const parent = server.create('job', 'periodic', { childrenCount: 1, shallow: true }); + const parent = server.create('job', 'periodic', { + childrenCount: 1, + shallow: true, + }); return server.db.jobs.where({ parentId: parent.id })[0]; }); -moduleForJob('Acceptance | job detail (parameterized child)', 'allocations', () => { - const parent = server.create('job', 'parameterized', { childrenCount: 1, shallow: true }); - return server.db.jobs.where({ parentId: parent.id })[0]; -}); +moduleForJob( + 'Acceptance | job detail (parameterized child)', + 'allocations', + () => { + const parent = server.create('job', 'parameterized', { + childrenCount: 1, + shallow: true, + }); + return server.db.jobs.where({ parentId: parent.id })[0]; + } +); moduleForJob( 'Acceptance | job detail (service)', @@ -181,33 +205,35 @@ moduleForJob( await JobDetail.tabFor('deployments').visit(); assert.equal(currentURL(), `/jobs/${job.id}/deployments`); }, - 'when the job is not found, an error message is shown, but the URL persists': async ( - job, - assert - ) => { - await JobDetail.visit({ id: 'not-a-real-job' }); + 'when the job is not found, an error message is shown, but the URL persists': + async (job, assert) => { + await JobDetail.visit({ id: 'not-a-real-job' }); - assert.equal( - server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) - .findBy('status', 404).url, - '/v1/job/not-a-real-job', - 'A request to the nonexistent job is made' - ); - assert.equal(currentURL(), '/jobs/not-a-real-job', 'The URL persists'); - assert.ok(JobDetail.error.isPresent, 'Error message is shown'); - assert.equal(JobDetail.error.title, 'Not Found', 'Error message is for 404'); - }, + assert.equal( + server.pretender.handledRequests + .filter((request) => !request.url.includes('policy')) + .findBy('status', 404).url, + '/v1/job/not-a-real-job', + 'A request to the nonexistent job is made' + ); + assert.equal(currentURL(), '/jobs/not-a-real-job', 'The URL persists'); + assert.ok(JobDetail.error.isPresent, 'Error message is shown'); + assert.equal( + JobDetail.error.title, + 'Not Found', + 'Error message is for 404' + ); + }, } ); -module('Acceptance | job detail (with namespaces)', function(hooks) { +module('Acceptance | job detail (with namespaces)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); let job, managementToken, clientToken; - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.createList('namespace', 2); server.create('node'); job = server.create('job', { @@ -223,20 +249,23 @@ module('Acceptance | job detail (with namespaces)', function(hooks) { clientToken = server.create('token'); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { const namespace = server.db.namespaces.find(job.namespaceId); await JobDetail.visit({ id: job.id, namespace: namespace.name }); await a11yAudit(assert); }); - test('when there are namespaces, the job detail page states the namespace for the job', async function(assert) { + test('when there are namespaces, the job detail page states the namespace for the job', async function (assert) { const namespace = server.db.namespaces.find(job.namespaceId); await JobDetail.visit({ id: job.id, namespace: namespace.name }); - assert.ok(JobDetail.statFor('namespace').text, 'Namespace included in stats'); + assert.ok( + JobDetail.statFor('namespace').text, + 'Namespace included in stats' + ); }); - test('the exec button state can change between namespaces', async function(assert) { + test('the exec button state can change between namespaces', async function (assert) { const job1 = server.create('job', { status: 'running', namespaceId: server.db.namespaces[0].id, @@ -276,7 +305,7 @@ module('Acceptance | job detail (with namespaces)', function(hooks) { assert.ok(JobDetail.execButton.isDisabled); }); - test('the anonymous policy is fetched to check whether to show the exec button', async function(assert) { + test('the anonymous policy is fetched to check whether to show the exec button', async function (assert) { window.localStorage.removeItem('nomadTokenSecret'); server.create('policy', { @@ -292,11 +321,14 @@ module('Acceptance | job detail (with namespaces)', function(hooks) { }, }); - await JobDetail.visit({ id: job.id, namespace: server.db.namespaces[1].name }); + await JobDetail.visit({ + id: job.id, + namespace: server.db.namespaces[1].name, + }); assert.notOk(JobDetail.execButton.isDisabled); }); - test('meta table is displayed if job has meta attributes', async function(assert) { + test('meta table is displayed if job has meta attributes', async function (assert) { const jobWithMeta = server.create('job', { status: 'running', namespaceId: server.db.namespaces[1].id, @@ -305,14 +337,20 @@ module('Acceptance | job detail (with namespaces)', function(hooks) { }, }); - await JobDetail.visit({ id: job.id, namespace: server.db.namespaces[1].name }); + await JobDetail.visit({ + id: job.id, + namespace: server.db.namespaces[1].name, + }); assert.notOk(JobDetail.metaTable, 'Meta table not present'); - await JobDetail.visit({ id: jobWithMeta.id, namespace: server.db.namespaces[1].name }); + await JobDetail.visit({ + id: jobWithMeta.id, + namespace: server.db.namespaces[1].name, + }); assert.ok(JobDetail.metaTable, 'Meta table is present'); }); - test('pack details are displayed', async function(assert) { + test('pack details are displayed', async function (assert) { const namespace = server.db.namespaces[1].id; const jobFromPack = server.create('job', { status: 'running', @@ -337,7 +375,7 @@ module('Acceptance | job detail (with namespaces)', function(hooks) { ); }); - test('resource recommendations show when they exist and can be expanded, collapsed, and processed', async function(assert) { + test('resource recommendations show when they exist and can be expanded, collapsed, and processed', async function (assert) { server.create('feature', { name: 'Dynamic Application Sizing' }); job = server.create('job', { @@ -349,10 +387,13 @@ module('Acceptance | job detail (with namespaces)', function(hooks) { }); window.localStorage.nomadTokenSecret = managementToken.secretId; - await JobDetail.visit({ id: job.id, namespace: server.db.namespaces[1].name }); + await JobDetail.visit({ + id: job.id, + namespace: server.db.namespaces[1].name, + }); - const groupsWithRecommendations = job.taskGroups.filter(group => - group.tasks.models.any(task => task.recommendations.models.length) + const groupsWithRecommendations = job.taskGroups.filter((group) => + group.tasks.models.any((task) => task.recommendations.models.length) ); const jobRecommendationCount = groupsWithRecommendations.length; @@ -380,7 +421,10 @@ module('Acceptance | job detail (with namespaces)', function(hooks) { await toggle.click(); - assert.equal(recommendation.card.slug.groupName, firstRecommendationGroup.name); + assert.equal( + recommendation.card.slug.groupName, + firstRecommendationGroup.name + ); await recommendation.card.acceptButton.click(); @@ -392,26 +436,34 @@ module('Acceptance | job detail (with namespaces)', function(hooks) { assert.equal(JobDetail.recommendations.length, jobRecommendationCount - 1); }); - test('resource recommendations are not fetched when the feature doesn’t exist', async function(assert) { + test('resource recommendations are not fetched when the feature doesn’t exist', async function (assert) { window.localStorage.nomadTokenSecret = managementToken.secretId; - await JobDetail.visit({ id: job.id, namespace: server.db.namespaces[1].name }); + await JobDetail.visit({ + id: job.id, + namespace: server.db.namespaces[1].name, + }); assert.equal(JobDetail.recommendations.length, 0); assert.equal( - server.pretender.handledRequests.filter(request => request.url.includes('recommendations')) - .length, + server.pretender.handledRequests.filter((request) => + request.url.includes('recommendations') + ).length, 0 ); }); - test('when the dynamic autoscaler is applied, you can scale a task within the job detail page', async function(assert) { + test('when the dynamic autoscaler is applied, you can scale a task within the job detail page', async function (assert) { const SCALE_AND_WRITE_NAMESPACE = 'scale-and-write-namespace'; const READ_ONLY_NAMESPACE = 'read-only-namespace'; const clientToken = server.create('token'); - const namespace = server.create('namespace', { id: SCALE_AND_WRITE_NAMESPACE }); - const secondNamespace = server.create('namespace', { id: READ_ONLY_NAMESPACE }); + const namespace = server.create('namespace', { + id: SCALE_AND_WRITE_NAMESPACE, + }); + const secondNamespace = server.create('namespace', { + id: READ_ONLY_NAMESPACE, + }); job = server.create('job', { groupCount: 0, diff --git a/ui/tests/acceptance/job-dispatch-test.js b/ui/tests/acceptance/job-dispatch-test.js index 00d4aa888..ef4764310 100644 --- a/ui/tests/acceptance/job-dispatch-test.js +++ b/ui/tests/acceptance/job-dispatch-test.js @@ -1,4 +1,6 @@ /* eslint-disable ember/no-test-module-for */ +/* eslint-disable qunit/require-expect */ +/* eslint-disable qunit/no-conditional-assertions */ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; @@ -33,12 +35,12 @@ moduleForJobDispatch('Acceptance | job dispatch (with namespace)', () => { function moduleForJobDispatch(title, jobFactory) { let job, namespace, managementToken, clientToken; - module(title, function(hooks) { + module(title, function (hooks) { setupApplicationTest(hooks); setupCodeMirror(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { // Required for placing allocations (a result of dispatching jobs) server.create('node'); @@ -51,17 +53,17 @@ function moduleForJobDispatch(title, jobFactory) { window.localStorage.nomadTokenSecret = managementToken.secretId; }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await JobDispatch.visit({ id: job.id, namespace: namespace.name }); await a11yAudit(assert); }); - test('the dispatch button is displayed with management token', async function(assert) { + test('the dispatch button is displayed with management token', async function (assert) { await JobDetail.visit({ id: job.id, namespace: namespace.name }); assert.notOk(JobDetail.dispatchButton.isDisabled); }); - test('the dispatch button is displayed when allowed', async function(assert) { + test('the dispatch button is displayed when allowed', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; const policy = server.create('policy', { @@ -88,37 +90,43 @@ function moduleForJobDispatch(title, jobFactory) { clientToken.save(); }); - test('the dispatch button is disabled when not allowed', async function(assert) { + test('the dispatch button is disabled when not allowed', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; await JobDetail.visit({ id: job.id, namespace: namespace.name }); assert.ok(JobDetail.dispatchButton.isDisabled); }); - test('all meta fields are displayed', async function(assert) { + test('all meta fields are displayed', async function (assert) { await JobDispatch.visit({ id: job.id, namespace: namespace.name }); assert.equal( JobDispatch.metaFields.length, - job.parameterizedJob.MetaOptional.length + job.parameterizedJob.MetaRequired.length + job.parameterizedJob.MetaOptional.length + + job.parameterizedJob.MetaRequired.length ); }); - test('required meta fields are properly indicated', async function(assert) { + test('required meta fields are properly indicated', async function (assert) { await JobDispatch.visit({ id: job.id, namespace: namespace.name }); - JobDispatch.metaFields.forEach(f => { + JobDispatch.metaFields.forEach((f) => { const hasIndicator = f.label.includes(REQUIRED_INDICATOR); - const isRequired = job.parameterizedJob.MetaRequired.includes(f.field.id); + const isRequired = job.parameterizedJob.MetaRequired.includes( + f.field.id + ); if (isRequired) { assert.ok(hasIndicator, `${f.label} contains required indicator.`); } else { - assert.notOk(hasIndicator, `${f.label} doesn't contain required indicator.`); + assert.notOk( + hasIndicator, + `${f.label} doesn't contain required indicator.` + ); } }); }); - test('job without meta fields', async function(assert) { + test('job without meta fields', async function (assert) { const jobWithoutMeta = server.create('job', 'parameterized', { status: 'running', namespaceId: namespace.name, @@ -128,11 +136,14 @@ function moduleForJobDispatch(title, jobFactory) { }, }); - await JobDispatch.visit({ id: jobWithoutMeta.id, namespace: namespace.name }); + await JobDispatch.visit({ + id: jobWithoutMeta.id, + namespace: namespace.name, + }); assert.ok(JobDispatch.dispatchButton.isPresent); }); - test('payload text area is hidden when forbidden', async function(assert) { + test('payload text area is hidden when forbidden', async function (assert) { job.parameterizedJob.Payload = 'forbidden'; job.save(); @@ -142,7 +153,7 @@ function moduleForJobDispatch(title, jobFactory) { assert.notOk(JobDispatch.payload.editor.isPresent); }); - test('payload is indicated as required', async function(assert) { + test('payload is indicated as required', async function (assert) { const jobPayloadRequired = server.create('job', 'parameterized', { status: 'running', namespaceId: namespace.name, @@ -158,7 +169,10 @@ function moduleForJobDispatch(title, jobFactory) { }, }); - await JobDispatch.visit({ id: jobPayloadRequired.id, namespace: namespace.name }); + await JobDispatch.visit({ + id: jobPayloadRequired.id, + namespace: namespace.name, + }); let payloadTitle = JobDispatch.payload.title; assert.ok( @@ -166,7 +180,10 @@ function moduleForJobDispatch(title, jobFactory) { `${payloadTitle} contains required indicator.` ); - await JobDispatch.visit({ id: jobPayloadOptional.id, namespace: namespace.name }); + await JobDispatch.visit({ + id: jobPayloadOptional.id, + namespace: namespace.name, + }); payloadTitle = JobDispatch.payload.title; assert.notOk( @@ -175,15 +192,17 @@ function moduleForJobDispatch(title, jobFactory) { ); }); - test('dispatch a job', async function(assert) { + test('dispatch a job', async function (assert) { function countDispatchChildren() { - return server.db.jobs.where(j => j.id.startsWith(`${job.id}/`)).length; + return server.db.jobs.where((j) => + j.id.startsWith(`${job.id}/`) + ).length; } await JobDispatch.visit({ id: job.id, namespace: namespace.name }); // Fill form. - JobDispatch.metaFields.map(f => f.field.input('meta value')); + JobDispatch.metaFields.map((f) => f.field.input('meta value')); JobDispatch.payload.editor.fillIn('payload'); const childrenCountBefore = countDispatchChildren(); @@ -191,11 +210,13 @@ function moduleForJobDispatch(title, jobFactory) { const childrenCountAfter = countDispatchChildren(); assert.equal(childrenCountAfter, childrenCountBefore + 1); - assert.ok(currentURL().startsWith(`/jobs/${encodeURIComponent(`${job.id}/`)}`)); + assert.ok( + currentURL().startsWith(`/jobs/${encodeURIComponent(`${job.id}/`)}`) + ); assert.ok(JobDetail.jobName); }); - test('fail when required meta field is empty', async function(assert) { + test('fail when required meta field is empty', async function (assert) { // Make sure we have a required meta param. job.parameterizedJob.MetaRequired = ['required']; job.parameterizedJob.Payload = 'forbidden'; @@ -204,14 +225,14 @@ function moduleForJobDispatch(title, jobFactory) { await JobDispatch.visit({ id: job.id, namespace: namespace.name }); // Fill only optional meta params. - JobDispatch.optionalMetaFields.map(f => f.field.input('meta value')); + JobDispatch.optionalMetaFields.map((f) => f.field.input('meta value')); await JobDispatch.dispatchButton.click(); assert.ok(JobDispatch.hasError, 'Dispatch error message is shown'); }); - test('fail when required payload is empty', async function(assert) { + test('fail when required payload is empty', async function (assert) { job.parameterizedJob.MetaRequired = []; job.parameterizedJob.Payload = 'required'; job.save(); diff --git a/ui/tests/acceptance/job-evaluations-test.js b/ui/tests/acceptance/job-evaluations-test.js index 54e2c3dc8..d77eab4f8 100644 --- a/ui/tests/acceptance/job-evaluations-test.js +++ b/ui/tests/acceptance/job-evaluations-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -8,23 +9,30 @@ import Evaluations from 'nomad-ui/tests/pages/jobs/job/evaluations'; let job; let evaluations; -module('Acceptance | job evaluations', function(hooks) { +module('Acceptance | job evaluations', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { - job = server.create('job', { noFailedPlacements: true, createAllocations: false }); + hooks.beforeEach(async function () { + job = server.create('job', { + noFailedPlacements: true, + createAllocations: false, + }); evaluations = server.db.evaluations.where({ jobId: job.id }); await Evaluations.visit({ id: job.id }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await a11yAudit(assert); }); - test('lists all evaluations for the job', async function(assert) { - assert.equal(Evaluations.evaluations.length, evaluations.length, 'All evaluations are listed'); + test('lists all evaluations for the job', async function (assert) { + assert.equal( + Evaluations.evaluations.length, + evaluations.length, + 'All evaluations are listed' + ); const sortedEvaluations = evaluations.sortBy('modifyIndex').reverse(); @@ -36,7 +44,7 @@ module('Acceptance | job evaluations', function(hooks) { assert.equal(document.title, `Job ${job.name} evaluations - Nomad`); }); - test('evaluations table is sortable', async function(assert) { + test('evaluations table is sortable', async function (assert) { await Evaluations.sortBy('priority'); assert.equal( @@ -55,18 +63,26 @@ module('Acceptance | job evaluations', function(hooks) { }); }); - test('when the job for the evaluations is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the job for the evaluations is not found, an error message is shown, but the URL persists', async function (assert) { await Evaluations.visit({ id: 'not-a-real-job' }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); - assert.equal(currentURL(), '/jobs/not-a-real-job/evaluations', 'The URL persists'); + assert.equal( + currentURL(), + '/jobs/not-a-real-job/evaluations', + 'The URL persists' + ); assert.ok(Evaluations.error.isPresent, 'Error message is shown'); - assert.equal(Evaluations.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + Evaluations.error.title, + 'Not Found', + 'Error message is for 404' + ); }); }); diff --git a/ui/tests/acceptance/job-run-test.js b/ui/tests/acceptance/job-run-test.js index b8d723eda..619ae0da7 100644 --- a/ui/tests/acceptance/job-run-test.js +++ b/ui/tests/acceptance/job-run-test.js @@ -12,7 +12,7 @@ const newJobTaskGroupName = 'redis'; let managementToken, clientToken; -const jsonJob = overrides => { +const jsonJob = (overrides) => { return JSON.stringify( assign( {}, @@ -40,12 +40,12 @@ const jsonJob = overrides => { ); }; -module('Acceptance | job run', function(hooks) { +module('Acceptance | job run', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); setupCodeMirror(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { // Required for placing allocations (a result of creating jobs) server.create('node'); @@ -55,19 +55,21 @@ module('Acceptance | job run', function(hooks) { window.localStorage.nomadTokenSecret = managementToken.secretId; }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + await JobRun.visit(); await a11yAudit(assert); }); - test('visiting /jobs/run', async function(assert) { + test('visiting /jobs/run', async function (assert) { await JobRun.visit(); assert.equal(currentURL(), '/jobs/run'); assert.equal(document.title, 'Run a job - Nomad'); }); - test('when submitting a job, the site redirects to the new job overview page', async function(assert) { + test('when submitting a job, the site redirects to the new job overview page', async function (assert) { const spec = jsonJob(); await JobRun.visit(); @@ -82,7 +84,7 @@ module('Acceptance | job run', function(hooks) { ); }); - test('when submitting a job to a different namespace, the redirect to the job overview page takes namespace into account', async function(assert) { + test('when submitting a job to a different namespace, the redirect to the job overview page takes namespace into account', async function (assert) { const newNamespace = 'second-namespace'; server.create('namespace', { id: newNamespace }); @@ -100,14 +102,14 @@ module('Acceptance | job run', function(hooks) { ); }); - test('when the user doesn’t have permission to run a job, redirects to the job overview page', async function(assert) { + test('when the user doesn’t have permission to run a job, redirects to the job overview page', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; await JobRun.visit(); assert.equal(currentURL(), '/jobs'); }); - test('when using client token user can still go to job page if they have correct permissions', async function(assert) { + test('when using client token user can still go to job page if they have correct permissions', async function (assert) { const clientTokenWithPolicy = server.create('token'); const newNamespace = 'second-namespace'; diff --git a/ui/tests/acceptance/job-versions-test.js b/ui/tests/acceptance/job-versions-test.js index 902bd2a47..af43ded0a 100644 --- a/ui/tests/acceptance/job-versions-test.js +++ b/ui/tests/acceptance/job-versions-test.js @@ -1,3 +1,5 @@ +/* eslint-disable qunit/require-expect */ +/* eslint-disable qunit/no-conditional-assertions */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -11,15 +13,18 @@ let job; let namespace; let versions; -module('Acceptance | job versions', function(hooks) { +module('Acceptance | job versions', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('namespace'); namespace = server.create('namespace'); - job = server.create('job', { namespaceId: namespace.id, createAllocations: false }); + job = server.create('job', { + namespaceId: namespace.id, + createAllocations: false, + }); versions = server.db.jobVersions.where({ jobId: job.id }); const managementToken = server.create('token'); @@ -28,31 +33,38 @@ module('Acceptance | job versions', function(hooks) { await Versions.visit({ id: job.id, namespace: namespace.id }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await a11yAudit(assert); }); - test('/jobs/:id/versions should list all job versions', async function(assert) { - assert.ok(Versions.versions.length, versions.length, 'Each version gets a row in the timeline'); + test('/jobs/:id/versions should list all job versions', async function (assert) { + assert.equal( + Versions.versions.length, + versions.length, + 'Each version gets a row in the timeline' + ); assert.equal(document.title, `Job ${job.name} versions - Nomad`); }); - test('each version mentions the version number, the stability, and the submitted time', async function(assert) { + test('each version mentions the version number, the stability, and the submitted time', async function (assert) { const version = versions.sortBy('submitTime').reverse()[0]; const formattedSubmitTime = moment(version.submitTime / 1000000).format( "MMM DD, 'YY HH:mm:ss ZZ" ); const versionRow = Versions.versions.objectAt(0); - assert.ok(versionRow.text.includes(`Version #${version.version}`), 'Version #'); + assert.ok( + versionRow.text.includes(`Version #${version.version}`), + 'Version #' + ); assert.equal(versionRow.stability, version.stable.toString(), 'Stability'); assert.equal(versionRow.submitTime, formattedSubmitTime, 'Submit time'); }); - test('all versions but the current one have a button to revert to that version', async function(assert) { + test('all versions but the current one have a button to revert to that version', async function (assert) { let versionRowToRevertTo; - Versions.versions.forEach(versionRow => { + Versions.versions.forEach((versionRow) => { if (versionRow.number === job.version) { assert.ok(versionRow.revertToButton.isHidden); } else { @@ -67,11 +79,14 @@ module('Acceptance | job versions', function(hooks) { await versionRowToRevertTo.revertToButton.idle(); await versionRowToRevertTo.revertToButton.confirm(); - const revertRequest = this.server.pretender.handledRequests.find(request => - request.url.includes('revert') + const revertRequest = this.server.pretender.handledRequests.find( + (request) => request.url.includes('revert') ); - assert.equal(revertRequest.url, `/v1/job/${job.id}/revert?namespace=${namespace.id}`); + assert.equal( + revertRequest.url, + `/v1/job/${job.id}/revert?namespace=${namespace.id}` + ); assert.deepEqual(JSON.parse(revertRequest.requestBody), { JobID: job.id, @@ -82,9 +97,9 @@ module('Acceptance | job versions', function(hooks) { } }); - test('when reversion fails, the error message from the API is piped through to the alert', async function(assert) { + test('when reversion fails, the error message from the API is piped through to the alert', async function (assert) { const versionRowToRevertTo = Versions.versions.filter( - versionRow => versionRow.revertToButton.isPresent + (versionRow) => versionRow.revertToButton.isPresent )[0]; if (versionRowToRevertTo) { @@ -107,9 +122,9 @@ module('Acceptance | job versions', function(hooks) { } }); - test('when reversion has no effect, the error message explains', async function(assert) { + test('when reversion has no effect, the error message explains', async function (assert) { const versionRowToRevertTo = Versions.versions.filter( - versionRow => versionRow.revertToButton.isPresent + (versionRow) => versionRow.revertToButton.isPresent )[0]; if (versionRowToRevertTo) { @@ -131,27 +146,31 @@ module('Acceptance | job versions', function(hooks) { } }); - test('when the job for the versions is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the job for the versions is not found, an error message is shown, but the URL persists', async function (assert) { await Versions.visit({ id: 'not-a-real-job' }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); - assert.equal(currentURL(), '/jobs/not-a-real-job/versions', 'The URL persists'); + assert.equal( + currentURL(), + '/jobs/not-a-real-job/versions', + 'The URL persists' + ); assert.ok(Versions.error.isPresent, 'Error message is shown'); assert.equal(Versions.error.title, 'Not Found', 'Error message is for 404'); }); }); -module('Acceptance | job versions (with client token)', function(hooks) { +module('Acceptance | job versions (with client token)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { job = server.create('job', { createAllocations: false }); versions = server.db.jobVersions.where({ jobId: job.id }); @@ -162,9 +181,9 @@ module('Acceptance | job versions (with client token)', function(hooks) { await Versions.visit({ id: job.id }); }); - test('reversion buttons are disabled when the token lacks permissions', async function(assert) { + test('reversion buttons are disabled when the token lacks permissions', async function (assert) { const versionRowWithReversion = Versions.versions.filter( - versionRow => versionRow.revertToButton.isPresent + (versionRow) => versionRow.revertToButton.isPresent )[0]; if (versionRowWithReversion) { @@ -176,7 +195,7 @@ module('Acceptance | job versions (with client token)', function(hooks) { window.localStorage.clear(); }); - test('reversion buttons are available when the client token has permissions', async function(assert) { + test('reversion buttons are available when the client token has permissions', async function (assert) { const REVERT_NAMESPACE = 'revert-namespace'; window.localStorage.clear(); const clientToken = server.create('token'); @@ -212,7 +231,7 @@ module('Acceptance | job versions (with client token)', function(hooks) { versions = server.db.jobVersions.where({ jobId: job.id }); await Versions.visit({ id: job.id, namespace: REVERT_NAMESPACE }); const versionRowWithReversion = Versions.versions.filter( - versionRow => versionRow.revertToButton.isPresent + (versionRow) => versionRow.revertToButton.isPresent )[0]; if (versionRowWithReversion) { diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index 6a0038303..2a46b0649 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -9,11 +10,11 @@ import Layout from 'nomad-ui/tests/pages/layout'; let managementToken, clientToken; -module('Acceptance | jobs list', function(hooks) { +module('Acceptance | jobs list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { // Required for placing allocations (a result of creating jobs) server.create('node'); @@ -24,19 +25,19 @@ module('Acceptance | jobs list', function(hooks) { window.localStorage.nomadTokenSecret = managementToken.secretId; }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await JobsList.visit(); await a11yAudit(assert); }); - test('visiting /jobs', async function(assert) { + test('visiting /jobs', async function (assert) { await JobsList.visit(); assert.equal(currentURL(), '/jobs'); assert.equal(document.title, 'Jobs - Nomad'); }); - test('/jobs should list the first page of jobs sorted by modify index', async function(assert) { + test('/jobs should list the first page of jobs sorted by modify index', async function (assert) { const jobsCount = JobsList.pageSize + 1; server.createList('job', jobsCount, { createAllocations: false }); @@ -49,7 +50,7 @@ module('Acceptance | jobs list', function(hooks) { }); }); - test('each job row should contain information about the job', async function(assert) { + test('each job row should contain information about the job', async function (assert) { server.createList('job', 2); const job = server.db.jobs.sortBy('modifyIndex').reverse()[0]; const taskGroups = server.db.taskGroups.where({ jobId: job.id }); @@ -67,7 +68,7 @@ module('Acceptance | jobs list', function(hooks) { assert.equal(jobRow.taskGroups, taskGroups.length, '# Groups'); }); - test('each job row should link to the corresponding job', async function(assert) { + test('each job row should link to the corresponding job', async function (assert) { server.create('job'); const job = server.db.jobs[0]; @@ -77,14 +78,14 @@ module('Acceptance | jobs list', function(hooks) { assert.equal(currentURL(), `/jobs/${job.id}`); }); - test('the new job button transitions to the new job page', async function(assert) { + test('the new job button transitions to the new job page', async function (assert) { await JobsList.visit(); await JobsList.runJobButton.click(); assert.equal(currentURL(), '/jobs/run'); }); - test('the job run button is disabled when the token lacks permission', async function(assert) { + test('the job run button is disabled when the token lacks permission', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; await JobsList.visit(); @@ -94,7 +95,7 @@ module('Acceptance | jobs list', function(hooks) { assert.equal(currentURL(), '/jobs'); }); - test('the anonymous policy is fetched to check whether to show the job run button', async function(assert) { + test('the anonymous policy is fetched to check whether to show the job run button', async function (assert) { window.localStorage.removeItem('nomadTokenSecret'); server.create('policy', { @@ -114,14 +115,18 @@ module('Acceptance | jobs list', function(hooks) { assert.notOk(JobsList.runJobButton.isDisabled); }); - test('when there are no jobs, there is an empty message', async function(assert) { + test('when there are no jobs, there is an empty message', async function (assert) { await JobsList.visit(); assert.ok(JobsList.isEmpty, 'There is an empty message'); - assert.equal(JobsList.emptyState.headline, 'No Jobs', 'The message is appropriate'); + assert.equal( + JobsList.emptyState.headline, + 'No Jobs', + 'The message is appropriate' + ); }); - test('when there are jobs, but no matches for a search result, there is an empty message', async function(assert) { + test('when there are jobs, but no matches for a search result, there is an empty message', async function (assert) { server.create('job', { name: 'cat 1' }); server.create('job', { name: 'cat 2' }); @@ -129,23 +134,33 @@ module('Acceptance | jobs list', function(hooks) { await JobsList.search.fillIn('dog'); assert.ok(JobsList.isEmpty, 'The empty message is shown'); - assert.equal(JobsList.emptyState.headline, 'No Matches', 'The message is appropriate'); + assert.equal( + JobsList.emptyState.headline, + 'No Matches', + 'The message is appropriate' + ); }); - test('searching resets the current page', async function(assert) { - server.createList('job', JobsList.pageSize + 1, { createAllocations: false }); + test('searching resets the current page', async function (assert) { + server.createList('job', JobsList.pageSize + 1, { + createAllocations: false, + }); await JobsList.visit(); await JobsList.nextPage(); - assert.equal(currentURL(), '/jobs?page=2', 'Page query param captures page=2'); + assert.equal( + currentURL(), + '/jobs?page=2', + 'Page query param captures page=2' + ); await JobsList.search.fillIn('foobar'); assert.equal(currentURL(), '/jobs?search=foobar', 'No page query param'); }); - test('when a cluster has namespaces, each job row includes the job namespace', async function(assert) { + test('when a cluster has namespaces, each job row includes the job namespace', async function (assert) { server.createList('namespace', 2); server.createList('job', 2); const job = server.db.jobs.sortBy('modifyIndex').reverse()[0]; @@ -156,10 +171,14 @@ module('Acceptance | jobs list', function(hooks) { assert.equal(jobRow.namespace, job.namespaceId); }); - test('when the namespace query param is set, only matching jobs are shown', async function(assert) { + test('when the namespace query param is set, only matching jobs are shown', async function (assert) { server.createList('namespace', 2); - const job1 = server.create('job', { namespaceId: server.db.namespaces[0].id }); - const job2 = server.create('job', { namespaceId: server.db.namespaces[1].id }); + const job1 = server.create('job', { + namespaceId: server.db.namespaces[0].id, + }); + const job2 = server.create('job', { + namespaceId: server.db.namespaces[1].id, + }); await JobsList.visit(); assert.equal(JobsList.jobs.length, 2, 'All jobs by default'); @@ -167,16 +186,28 @@ module('Acceptance | jobs list', function(hooks) { const firstNamespace = server.db.namespaces[0]; await JobsList.visit({ namespace: firstNamespace.id }); assert.equal(JobsList.jobs.length, 1, 'One job in the default namespace'); - assert.equal(JobsList.jobs.objectAt(0).name, job1.name, 'The correct job is shown'); + assert.equal( + JobsList.jobs.objectAt(0).name, + job1.name, + 'The correct job is shown' + ); const secondNamespace = server.db.namespaces[1]; await JobsList.visit({ namespace: secondNamespace.id }); - assert.equal(JobsList.jobs.length, 1, `One job in the ${secondNamespace.name} namespace`); - assert.equal(JobsList.jobs.objectAt(0).name, job2.name, 'The correct job is shown'); + assert.equal( + JobsList.jobs.length, + 1, + `One job in the ${secondNamespace.name} namespace` + ); + assert.equal( + JobsList.jobs.objectAt(0).name, + job2.name, + 'The correct job is shown' + ); }); - test('when accessing jobs is forbidden, show a message with a link to the tokens page', async function(assert) { + test('when accessing jobs is forbidden, show a message with a link to the tokens page', async function (assert) { server.pretender.get('/v1/jobs', () => [403, {}, null]); await JobsList.visit(); @@ -187,13 +218,20 @@ module('Acceptance | jobs list', function(hooks) { }); function typeForJob(job) { - return job.periodic ? 'periodic' : job.parameterized ? 'parameterized' : job.type; + return job.periodic + ? 'periodic' + : job.parameterized + ? 'parameterized' + : job.type; } - test('the jobs list page has appropriate faceted search options', async function(assert) { + test('the jobs list page has appropriate faceted search options', async function (assert) { await JobsList.visit(); - assert.ok(JobsList.facets.namespace.isHidden, 'Namespace facet not found (no namespaces)'); + assert.ok( + JobsList.facets.namespace.isHidden, + 'Namespace facet not found (no namespaces)' + ); assert.ok(JobsList.facets.type.isPresent, 'Type facet found'); assert.ok(JobsList.facets.status.isPresent, 'Status facet found'); assert.ok(JobsList.facets.datacenter.isPresent, 'Datacenter facet found'); @@ -220,7 +258,14 @@ module('Acceptance | jobs list', function(hooks) { testFacet('Type', { facet: JobsList.facets.type, paramName: 'type', - expectedOptions: ['Batch', 'Parameterized', 'Periodic', 'Service', 'System', 'System Batch'], + expectedOptions: [ + 'Batch', + 'Parameterized', + 'Periodic', + 'Service', + 'System', + 'System Batch', + ], async beforeEach() { server.createList('job', 2, { createAllocations: false, type: 'batch' }); server.createList('job', 2, { @@ -235,7 +280,10 @@ module('Acceptance | jobs list', function(hooks) { parameterized: true, childrenCount: 0, }); - server.createList('job', 2, { createAllocations: false, type: 'service' }); + server.createList('job', 2, { + createAllocations: false, + type: 'service', + }); await JobsList.visit(); }, filter(job, selection) { @@ -261,7 +309,11 @@ module('Acceptance | jobs list', function(hooks) { createAllocations: false, childrenCount: 0, }); - server.createList('job', 2, { status: 'dead', createAllocations: false, childrenCount: 0 }); + server.createList('job', 2, { + status: 'dead', + createAllocations: false, + childrenCount: 0, + }); await JobsList.visit(); }, filter: (job, selection) => selection.includes(job.status), @@ -297,10 +349,15 @@ module('Acceptance | jobs list', function(hooks) { createAllocations: false, childrenCount: 0, }); - server.create('job', { datacenters: ['pdx'], createAllocations: false, childrenCount: 0 }); + server.create('job', { + datacenters: ['pdx'], + createAllocations: false, + childrenCount: 0, + }); await JobsList.visit(); }, - filter: (job, selection) => job.datacenters.find(dc => selection.includes(dc)), + filter: (job, selection) => + job.datacenters.find((dc) => selection.includes(dc)), }); testFacet('Prefix', { @@ -318,35 +375,52 @@ module('Acceptance | jobs list', function(hooks) { 'hashi-three', 'nmd_two', 'noprefix', - ].forEach(name => { - server.create('job', { name, createAllocations: false, childrenCount: 0 }); + ].forEach((name) => { + server.create('job', { + name, + createAllocations: false, + childrenCount: 0, + }); }); await JobsList.visit(); }, - filter: (job, selection) => selection.find(prefix => job.name.startsWith(prefix)), + filter: (job, selection) => + selection.find((prefix) => job.name.startsWith(prefix)), }); - test('when the facet selections result in no matches, the empty state states why', async function(assert) { - server.createList('job', 2, { status: 'pending', createAllocations: false, childrenCount: 0 }); + test('when the facet selections result in no matches, the empty state states why', async function (assert) { + server.createList('job', 2, { + status: 'pending', + createAllocations: false, + childrenCount: 0, + }); await JobsList.visit(); await JobsList.facets.status.toggle(); await JobsList.facets.status.options.objectAt(1).toggle(); assert.ok(JobsList.isEmpty, 'There is an empty message'); - assert.equal(JobsList.emptyState.headline, 'No Matches', 'The message is appropriate'); + assert.equal( + JobsList.emptyState.headline, + 'No Matches', + 'The message is appropriate' + ); }); - test('the jobs list is immediately filtered based on query params', async function(assert) { + test('the jobs list is immediately filtered based on query params', async function (assert) { server.create('job', { type: 'batch', createAllocations: false }); server.create('job', { type: 'service', createAllocations: false }); await JobsList.visit({ type: JSON.stringify(['batch']) }); - assert.equal(JobsList.jobs.length, 1, 'Only one job shown due to query param'); + assert.equal( + JobsList.jobs.length, + 1, + 'Only one job shown due to query param' + ); }); - test('the active namespace is carried over to the storage pages', async function(assert) { + test('the active namespace is carried over to the storage pages', async function (assert) { server.createList('namespace', 2); const namespace = server.db.namespaces[1]; @@ -359,7 +433,7 @@ module('Acceptance | jobs list', function(hooks) { assert.equal(currentURL(), `/csi/volumes?namespace=${namespace.id}`); }); - test('when the user has a client token that has a namespace with a policy to run a job', async function(assert) { + test('when the user has a client token that has a namespace with a policy to run a job', async function (assert) { const READ_AND_WRITE_NAMESPACE = 'read-and-write-namespace'; const READ_ONLY_NAMESPACE = 'read-only-namespace'; @@ -400,7 +474,10 @@ module('Acceptance | jobs list', function(hooks) { pageObject: JobsList, pageObjectList: JobsList.jobs, async setup() { - server.createList('job', JobsList.pageSize, { shallow: true, createAllocations: false }); + server.createList('job', JobsList.pageSize, { + shallow: true, + createAllocations: false, + }); await JobsList.visit(); }, }); @@ -417,7 +494,7 @@ module('Acceptance | jobs list', function(hooks) { } assert.deepEqual( - facet.options.map(option => option.label.trim()), + facet.options.map((option) => option.label.trim()), expectation, 'Options for facet are as expected' ); @@ -427,11 +504,11 @@ module('Acceptance | jobs list', function(hooks) { label, { facet, paramName, beforeEach, filter, expectedOptions, optionToSelect } ) { - test(`the ${label} facet has the correct options`, async function(assert) { + test(`the ${label} facet has the correct options`, async function (assert) { await facetOptions(assert, beforeEach, facet, expectedOptions); }); - test(`the ${label} facet filters the jobs list by ${label}`, async function(assert) { + test(`the ${label} facet filters the jobs list by ${label}`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -440,7 +517,7 @@ module('Acceptance | jobs list', function(hooks) { await option.select(); const expectedJobs = server.db.jobs - .filter(job => filter(job, selection)) + .filter((job) => filter(job, selection)) .sortBy('modifyIndex') .reverse(); @@ -453,7 +530,7 @@ module('Acceptance | jobs list', function(hooks) { }); }); - test(`selecting an option in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`selecting an option in the ${label} facet updates the ${paramName} query param`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -468,12 +545,15 @@ module('Acceptance | jobs list', function(hooks) { }); } - function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) { - test(`the ${label} facet has the correct options`, async function(assert) { + function testFacet( + label, + { facet, paramName, beforeEach, filter, expectedOptions } + ) { + test(`the ${label} facet has the correct options`, async function (assert) { await facetOptions(assert, beforeEach, facet, expectedOptions); }); - test(`the ${label} facet filters the jobs list by ${label}`, async function(assert) { + test(`the ${label} facet filters the jobs list by ${label}`, async function (assert) { let option; await beforeEach(); @@ -484,7 +564,7 @@ module('Acceptance | jobs list', function(hooks) { const selection = [option.key]; const expectedJobs = server.db.jobs - .filter(job => filter(job, selection)) + .filter((job) => filter(job, selection)) .sortBy('modifyIndex') .reverse(); @@ -497,7 +577,7 @@ module('Acceptance | jobs list', function(hooks) { }); }); - test(`selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { + test(`selecting multiple options in the ${label} facet results in a broader search`, async function (assert) { const selection = []; await beforeEach(); @@ -511,7 +591,7 @@ module('Acceptance | jobs list', function(hooks) { selection.push(option2.key); const expectedJobs = server.db.jobs - .filter(job => filter(job, selection)) + .filter((job) => filter(job, selection)) .sortBy('modifyIndex') .reverse(); @@ -524,7 +604,7 @@ module('Acceptance | jobs list', function(hooks) { }); }); - test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) { const selection = []; await beforeEach(); @@ -543,9 +623,13 @@ module('Acceptance | jobs list', function(hooks) { ); }); - test('the run job button works when filters are set', async function(assert) { - ['pre-one', 'pre-two', 'pre-three'].forEach(name => { - server.create('job', { name, createAllocations: false, childrenCount: 0 }); + test('the run job button works when filters are set', async function (assert) { + ['pre-one', 'pre-two', 'pre-three'].forEach((name) => { + server.create('job', { + name, + createAllocations: false, + childrenCount: 0, + }); }); await JobsList.visit(); diff --git a/ui/tests/acceptance/optimize-test.js b/ui/tests/acceptance/optimize-test.js index 0f459b8a7..ec1e291fd 100644 --- a/ui/tests/acceptance/optimize-test.js +++ b/ui/tests/acceptance/optimize-test.js @@ -1,3 +1,5 @@ +/* eslint-disable qunit/require-expect */ +/* eslint-disable qunit/no-conditional-assertions */ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { currentURL, visit } from '@ember/test-helpers'; @@ -18,17 +20,18 @@ function getLatestRecommendationSubmitTimeForJob(job) { .mapBy('tasks.models') .reduce((tasks, taskModels) => tasks.concat(taskModels), []); const recommendations = tasks.reduce( - (recommendations, task) => recommendations.concat(task.recommendations.models), + (recommendations, task) => + recommendations.concat(task.recommendations.models), [] ); return Math.max(...recommendations.mapBy('submitTime')); } -module('Acceptance | optimize', function(hooks) { +module('Acceptance | optimize', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('feature', { name: 'Dynamic Application Sizing' }); server.create('node'); @@ -58,19 +61,19 @@ module('Acceptance | optimize', function(hooks) { window.localStorage.nomadTokenSecret = managementToken.secretId; }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await Optimize.visit(); await a11yAudit(assert); }); - test('lets recommendations be toggled, reports the choices to the recommendations API, and displays task group recommendations serially', async function(assert) { + test('lets recommendations be toggled, reports the choices to the recommendations API, and displays task group recommendations serially', async function (assert) { const currentTaskGroup = this.job1.taskGroups.models[0]; const nextTaskGroup = this.job2.taskGroups.models[0]; const currentTaskGroupHasCPURecommendation = currentTaskGroup.tasks.models .mapBy('recommendations.models') .flat() - .find(r => r.resource === 'CPU'); + .find((r) => r.resource === 'CPU'); // If no CPU recommendation, will not be able to accept recommendation with all memory recommendations turned off @@ -96,7 +99,10 @@ module('Acceptance | optimize', function(hooks) { `${this.job1.name} / ${currentTaskGroup.name}` ); - assert.equal(Optimize.recommendationSummaries[0].namespace, this.job1.namespace); + assert.equal( + Optimize.recommendationSummaries[0].namespace, + this.job1.namespace + ); assert.equal( Optimize.recommendationSummaries[1].slug, @@ -104,15 +110,20 @@ module('Acceptance | optimize', function(hooks) { ); const currentRecommendations = currentTaskGroup.tasks.models.reduce( - (recommendations, task) => recommendations.concat(task.recommendations.models), + (recommendations, task) => + recommendations.concat(task.recommendations.models), [] ); - const latestSubmitTime = Math.max(...currentRecommendations.mapBy('submitTime')); + const latestSubmitTime = Math.max( + ...currentRecommendations.mapBy('submitTime') + ); - Optimize.recommendationSummaries[0].as(summary => { + Optimize.recommendationSummaries[0].as((summary) => { assert.equal( summary.date, - moment(new Date(latestSubmitTime / 1000000)).format('MMM DD HH:mm:ss ZZ') + moment(new Date(latestSubmitTime / 1000000)).format( + 'MMM DD HH:mm:ss ZZ' + ) ); const currentTaskGroupAllocations = server.schema.allocations.where({ @@ -154,23 +165,39 @@ module('Acceptance | optimize', function(hooks) { assert.equal( replaceMinus(summary.cpu), - cpuDiff ? `${cpuSign}${formatHertz(cpuDiff, 'MHz')} ${cpuSign}${cpuDiffPercent}%` : '' + cpuDiff + ? `${cpuSign}${formatHertz( + cpuDiff, + 'MHz' + )} ${cpuSign}${cpuDiffPercent}%` + : '' ); assert.equal( replaceMinus(summary.memory), - memDiff ? `${memSign}${formattedMemDiff(memDiff)} ${memSign}${memDiffPercent}%` : '' + memDiff + ? `${memSign}${formattedMemDiff( + memDiff + )} ${memSign}${memDiffPercent}%` + : '' ); assert.equal( replaceMinus(summary.aggregateCpu), cpuDiff - ? `${cpuSign}${formatHertz(cpuDiff * currentTaskGroupAllocations.length, 'MHz')}` + ? `${cpuSign}${formatHertz( + cpuDiff * currentTaskGroupAllocations.length, + 'MHz' + )}` : '' ); assert.equal( replaceMinus(summary.aggregateMemory), - memDiff ? `${memSign}${formattedMemDiff(memDiff * currentTaskGroupAllocations.length)}` : '' + memDiff + ? `${memSign}${formattedMemDiff( + memDiff * currentTaskGroupAllocations.length + )}` + : '' ); }); @@ -203,7 +230,7 @@ module('Acceptance | optimize', function(hooks) { ); const currentTaskIds = currentTaskGroup.tasks.models.mapBy('id'); - const taskIdFilter = task => currentTaskIds.includes(task.taskId); + const taskIdFilter = (task) => currentTaskIds.includes(task.taskId); const cpuRecommendationIds = server.schema.recommendations .where({ resource: 'CPU' }) @@ -215,12 +242,16 @@ module('Acceptance | optimize', function(hooks) { .models.filter(taskIdFilter) .mapBy('id'); - const appliedIds = toggledAnything ? cpuRecommendationIds : memoryRecommendationIds; + const appliedIds = toggledAnything + ? cpuRecommendationIds + : memoryRecommendationIds; const dismissedIds = toggledAnything ? memoryRecommendationIds : []; await Optimize.card.acceptButton.click(); - const request = server.pretender.handledRequests.filterBy('method', 'POST').pop(); + const request = server.pretender.handledRequests + .filterBy('method', 'POST') + .pop(); const { Apply, Dismiss } = JSON.parse(request.requestBody); assert.equal(request.url, '/v1/recommendations/apply'); @@ -234,7 +265,7 @@ module('Acceptance | optimize', function(hooks) { assert.ok(Optimize.recommendationSummaries[1].isActive); }); - test('can navigate between summaries via the table', async function(assert) { + test('can navigate between summaries via the table', async function (assert) { server.createList('job', 10, { createRecommendations: true, groupsCount: 1, @@ -252,7 +283,7 @@ module('Acceptance | optimize', function(hooks) { assert.ok(Optimize.recommendationSummaries[1].isActive); }); - test('can visit a summary directly via URL', async function(assert) { + test('can visit a summary directly via URL', async function (assert) { server.createList('job', 10, { createRecommendations: true, groupsCount: 1, @@ -263,29 +294,39 @@ module('Acceptance | optimize', function(hooks) { await Optimize.visit(); const lastSummary = - Optimize.recommendationSummaries[Optimize.recommendationSummaries.length - 1]; + Optimize.recommendationSummaries[ + Optimize.recommendationSummaries.length - 1 + ]; const collapsedSlug = lastSummary.slug.replace(' / ', '/'); // preferable to use page object’s visitable but it encodes the slash - await visit(`/optimize/${collapsedSlug}?namespace=${lastSummary.namespace}`); + await visit( + `/optimize/${collapsedSlug}?namespace=${lastSummary.namespace}` + ); assert.equal( `${Optimize.card.slug.jobName} / ${Optimize.card.slug.groupName}`, lastSummary.slug ); assert.ok(lastSummary.isActive); - assert.equal(currentURL(), `/optimize/${collapsedSlug}?namespace=${lastSummary.namespace}`); + assert.equal( + currentURL(), + `/optimize/${collapsedSlug}?namespace=${lastSummary.namespace}` + ); }); - test('when a summary is not found, an error message is shown, but the URL persists', async function(assert) { + test('when a summary is not found, an error message is shown, but the URL persists', async function (assert) { await visit('/optimize/nonexistent/summary?namespace=anamespace'); - assert.equal(currentURL(), '/optimize/nonexistent/summary?namespace=anamespace'); + assert.equal( + currentURL(), + '/optimize/nonexistent/summary?namespace=anamespace' + ); assert.ok(Optimize.applicationError.isPresent); assert.equal(Optimize.applicationError.title, 'Not Found'); }); - test('cannot return to already-processed summaries', async function(assert) { + test('cannot return to already-processed summaries', async function (assert) { await Optimize.visit(); await Optimize.card.acceptButton.click(); @@ -296,12 +337,12 @@ module('Acceptance | optimize', function(hooks) { assert.ok(Optimize.recommendationSummaries[1].isActive); }); - test('can dismiss a set of recommendations', async function(assert) { + test('can dismiss a set of recommendations', async function (assert) { await Optimize.visit(); const currentTaskGroup = this.job1.taskGroups.models[0]; const currentTaskIds = currentTaskGroup.tasks.models.mapBy('id'); - const taskIdFilter = task => currentTaskIds.includes(task.taskId); + const taskIdFilter = (task) => currentTaskIds.includes(task.taskId); const idsBeforeDismissal = server.schema.recommendations .all() @@ -310,7 +351,9 @@ module('Acceptance | optimize', function(hooks) { await Optimize.card.dismissButton.click(); - const request = server.pretender.handledRequests.filterBy('method', 'POST').pop(); + const request = server.pretender.handledRequests + .filterBy('method', 'POST') + .pop(); const { Apply, Dismiss } = JSON.parse(request.requestBody); assert.equal(request.url, '/v1/recommendations/apply'); @@ -319,8 +362,8 @@ module('Acceptance | optimize', function(hooks) { assert.deepEqual(Dismiss, idsBeforeDismissal); }); - test('it displays an error encountered trying to save and proceeds to the next summary when the error is dismissed', async function(assert) { - server.post('/recommendations/apply', function() { + test('it displays an error encountered trying to save and proceeds to the next summary when the error is dismissed', async function (assert) { + server.post('/recommendations/apply', function () { return new Response(500, {}, null); }); @@ -338,7 +381,7 @@ module('Acceptance | optimize', function(hooks) { assert.equal(Optimize.card.slug.jobName, this.job2.name); }); - test('it displays an empty message when there are no recommendations', async function(assert) { + test('it displays an empty message when there are no recommendations', async function (assert) { server.db.recommendations.remove(); await Optimize.visit(); @@ -346,7 +389,7 @@ module('Acceptance | optimize', function(hooks) { assert.equal(Optimize.empty.headline, 'No Recommendations'); }); - test('it displays an empty message after all recommendations have been processed', async function(assert) { + test('it displays an empty message after all recommendations have been processed', async function (assert) { await Optimize.visit(); await Optimize.card.acceptButton.click(); @@ -355,7 +398,7 @@ module('Acceptance | optimize', function(hooks) { assert.ok(Optimize.empty.isPresent); }); - test('it redirects to jobs and hides the gutter link when the token lacks permissions', async function(assert) { + test('it redirects to jobs and hides the gutter link when the token lacks permissions', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; await Optimize.visit(); @@ -363,7 +406,7 @@ module('Acceptance | optimize', function(hooks) { assert.ok(Layout.gutter.optimize.isHidden); }); - test('it reloads partially-loaded jobs', async function(assert) { + test('it reloads partially-loaded jobs', async function (assert) { await JobsList.visit(); await Optimize.visit(); @@ -371,11 +414,11 @@ module('Acceptance | optimize', function(hooks) { }); }); -module('Acceptance | optimize search and facets', function(hooks) { +module('Acceptance | optimize search and facets', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('feature', { name: 'Dynamic Application Sizing' }); server.create('node'); @@ -388,7 +431,7 @@ module('Acceptance | optimize search and facets', function(hooks) { window.localStorage.nomadTokenSecret = managementToken.secretId; }); - test('search field narrows summary table results, changes the active summary if it no longer matches, and displays a no matches message when there are none', async function(assert) { + test('search field narrows summary table results, changes the active summary if it no longer matches, and displays a no matches message when there are none', async function (assert) { server.create('job', { name: 'zzzzzz', createRecommendations: true, @@ -444,7 +487,7 @@ module('Acceptance | optimize search and facets', function(hooks) { assert.ok(Optimize.recommendationSummaries[0].isActive); }); - test('the namespaces toggle doesn’t show when there aren’t namespaces', async function(assert) { + test('the namespaces toggle doesn’t show when there aren’t namespaces', async function (assert) { server.db.namespaces.remove(); server.create('job', { @@ -458,7 +501,7 @@ module('Acceptance | optimize search and facets', function(hooks) { assert.ok(Optimize.facets.namespace.isHidden); }); - test('processing a summary moves to the next one in the sorted list', async function(assert) { + test('processing a summary moves to the next one in the sorted list', async function (assert) { server.create('job', { name: 'ooo111', createRecommendations: true, @@ -491,9 +534,10 @@ module('Acceptance | optimize search and facets', function(hooks) { ooo222: pastSubmitTime, }; - server.schema.recommendations.all().models.forEach(recommendation => { + server.schema.recommendations.all().models.forEach((recommendation) => { const parentJob = recommendation.task.taskGroup.job; - const submitTimeForJob = jobNameToRecommendationSubmitTime[parentJob.name]; + const submitTimeForJob = + jobNameToRecommendationSubmitTime[parentJob.name]; recommendation.submitTime = submitTimeForJob; recommendation.save(); }); @@ -505,7 +549,7 @@ module('Acceptance | optimize search and facets', function(hooks) { assert.equal(Optimize.card.slug.jobName, 'ooo222'); }); - test('the optimize page has appropriate faceted search options', async function(assert) { + test('the optimize page has appropriate faceted search options', async function (assert) { server.createList('job', 4, { status: 'running', createRecommendations: true, @@ -527,8 +571,14 @@ module('Acceptance | optimize search and facets', function(hooks) { expectedOptions: ['All (*)', 'default', 'namespace-1'], optionToSelect: 'namespace-1', async beforeEach() { - server.createList('job', 2, { namespaceId: 'default', createRecommendations: true }); - server.createList('job', 2, { namespaceId: 'namespace-1', createRecommendations: true }); + server.createList('job', 2, { + namespaceId: 'default', + createRecommendations: true, + }); + server.createList('job', 2, { + namespaceId: 'namespace-1', + createRecommendations: true, + }); await Optimize.visit(); }, filter(taskGroup, selection) { @@ -629,10 +679,15 @@ module('Acceptance | optimize search and facets', function(hooks) { groupTaskCount: 2, childrenCount: 0, }); - server.create('job', { datacenters: ['pdx'], createRecommendations: true, childrenCount: 0 }); + server.create('job', { + datacenters: ['pdx'], + createRecommendations: true, + childrenCount: 0, + }); await Optimize.visit(); }, - filter: (taskGroup, selection) => taskGroup.job.datacenters.find(dc => selection.includes(dc)), + filter: (taskGroup, selection) => + taskGroup.job.datacenters.find((dc) => selection.includes(dc)), }); testFacet('Prefix', { @@ -650,7 +705,7 @@ module('Acceptance | optimize search and facets', function(hooks) { 'hashi-three', 'nmd_two', 'noprefix', - ].forEach(name => { + ].forEach((name) => { server.create('job', { name, createRecommendations: true, @@ -663,7 +718,7 @@ module('Acceptance | optimize search and facets', function(hooks) { await Optimize.visit(); }, filter: (taskGroup, selection) => - selection.find(prefix => taskGroup.job.name.startsWith(prefix)), + selection.find((prefix) => taskGroup.job.name.startsWith(prefix)), }); async function facetOptions(assert, beforeEach, facet, expectedOptions) { @@ -678,7 +733,7 @@ module('Acceptance | optimize search and facets', function(hooks) { } assert.deepEqual( - facet.options.map(option => option.label.trim()), + facet.options.map((option) => option.label.trim()), expectation, 'Options for facet are as expected' ); @@ -688,11 +743,11 @@ module('Acceptance | optimize search and facets', function(hooks) { label, { facet, paramName, beforeEach, filter, expectedOptions, optionToSelect } ) { - test(`the ${label} facet has the correct options`, async function(assert) { + test(`the ${label} facet has the correct options`, async function (assert) { await facetOptions.call(this, assert, beforeEach, facet, expectedOptions); }); - test(`the ${label} facet filters the jobs list by ${label}`, async function(assert) { + test(`the ${label} facet filters the jobs list by ${label}`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -700,13 +755,15 @@ module('Acceptance | optimize search and facets', function(hooks) { const selection = option.key; await option.select(); - const sortedRecommendations = server.db.recommendations.sortBy('submitTime').reverse(); + const sortedRecommendations = server.db.recommendations + .sortBy('submitTime') + .reverse(); const recommendationTaskGroups = server.schema.tasks .find(sortedRecommendations.mapBy('taskId').uniq()) .models.mapBy('taskGroup') .uniqBy('id') - .filter(group => filter(group, selection)); + .filter((group) => filter(group, selection)); Optimize.recommendationSummaries.forEach((summary, index) => { const group = recommendationTaskGroups[index]; @@ -714,7 +771,7 @@ module('Acceptance | optimize search and facets', function(hooks) { }); }); - test(`selecting an option in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`selecting an option in the ${label} facet updates the ${paramName} query param`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -729,12 +786,15 @@ module('Acceptance | optimize search and facets', function(hooks) { }); } - function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) { - test(`the ${label} facet has the correct options`, async function(assert) { + function testFacet( + label, + { facet, paramName, beforeEach, filter, expectedOptions } + ) { + test(`the ${label} facet has the correct options`, async function (assert) { await facetOptions.call(this, assert, beforeEach, facet, expectedOptions); }); - test(`the ${label} facet filters the recommendation summaries by ${label}`, async function(assert) { + test(`the ${label} facet filters the recommendation summaries by ${label}`, async function (assert) { let option; await beforeEach(); @@ -745,13 +805,15 @@ module('Acceptance | optimize search and facets', function(hooks) { const selection = [option.key]; - const sortedRecommendations = server.db.recommendations.sortBy('submitTime').reverse(); + const sortedRecommendations = server.db.recommendations + .sortBy('submitTime') + .reverse(); const recommendationTaskGroups = server.schema.tasks .find(sortedRecommendations.mapBy('taskId').uniq()) .models.mapBy('taskGroup') .uniqBy('id') - .filter(group => filter(group, selection)); + .filter((group) => filter(group, selection)); Optimize.recommendationSummaries.forEach((summary, index) => { const group = recommendationTaskGroups[index]; @@ -759,7 +821,7 @@ module('Acceptance | optimize search and facets', function(hooks) { }); }); - test(`selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { + test(`selecting multiple options in the ${label} facet results in a broader search`, async function (assert) { const selection = []; await beforeEach(); @@ -772,13 +834,15 @@ module('Acceptance | optimize search and facets', function(hooks) { await option2.toggle(); selection.push(option2.key); - const sortedRecommendations = server.db.recommendations.sortBy('submitTime').reverse(); + const sortedRecommendations = server.db.recommendations + .sortBy('submitTime') + .reverse(); const recommendationTaskGroups = server.schema.tasks .find(sortedRecommendations.mapBy('taskId').uniq()) .models.mapBy('taskGroup') .uniqBy('id') - .filter(group => filter(group, selection)); + .filter((group) => filter(group, selection)); Optimize.recommendationSummaries.forEach((summary, index) => { const group = recommendationTaskGroups[index]; @@ -786,7 +850,7 @@ module('Acceptance | optimize search and facets', function(hooks) { }); }); - test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) { const selection = []; await beforeEach(); @@ -799,7 +863,9 @@ module('Acceptance | optimize search and facets', function(hooks) { await option2.toggle(); selection.push(option2.key); - assert.ok(currentURL().includes(encodeURIComponent(JSON.stringify(selection)))); + assert.ok( + currentURL().includes(encodeURIComponent(JSON.stringify(selection))) + ); }); } }); diff --git a/ui/tests/acceptance/plugin-allocations-test.js b/ui/tests/acceptance/plugin-allocations-test.js index 34a78f261..5f85c6203 100644 --- a/ui/tests/acceptance/plugin-allocations-test.js +++ b/ui/tests/acceptance/plugin-allocations-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { module, test } from 'qunit'; import { currentURL } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; @@ -6,18 +7,18 @@ import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; import pageSizeSelect from './behaviors/page-size-select'; import PluginAllocations from 'nomad-ui/tests/pages/storage/plugins/plugin/allocations'; -module('Acceptance | plugin allocations', function(hooks) { +module('Acceptance | plugin allocations', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); let plugin; - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('node'); window.localStorage.clear(); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { plugin = server.create('csi-plugin', { shallow: true, controllerRequired: true, @@ -29,7 +30,7 @@ module('Acceptance | plugin allocations', function(hooks) { await a11yAudit(assert); }); - test('/csi/plugins/:id/allocations shows all allocations in a single table', async function(assert) { + test('/csi/plugins/:id/allocations shows all allocations in a single table', async function (assert) { plugin = server.create('csi-plugin', { shallow: true, controllerRequired: true, @@ -71,7 +72,8 @@ module('Acceptance | plugin allocations', function(hooks) { await PluginAllocations.visit({ id: plugin.id }); }, - filter: (allocation, selection) => selection.includes(allocation.healthy.toString()), + filter: (allocation, selection) => + selection.includes(allocation.healthy.toString()), }); testFacet('Type', { @@ -89,13 +91,14 @@ module('Acceptance | plugin allocations', function(hooks) { }, filter: (allocation, selection) => { if (selection.length === 0 || selection.length === 2) return true; - if (selection[0] === 'controller') return plugin.controllers.models.includes(allocation); + if (selection[0] === 'controller') + return plugin.controllers.models.includes(allocation); return plugin.nodes.models.includes(allocation); }, }); function testFacet(label, { facet, paramName, beforeEach, filter }) { - test(`the ${label} facet filters the allocations list by ${label}`, async function(assert) { + test(`the ${label} facet filters the allocations list by ${label}`, async function (assert) { let option; await beforeEach(); @@ -105,9 +108,12 @@ module('Acceptance | plugin allocations', function(hooks) { await option.toggle(); const selection = [option.key]; - const allAllocations = [...plugin.controllers.models, ...plugin.nodes.models]; + const allAllocations = [ + ...plugin.controllers.models, + ...plugin.nodes.models, + ]; const expectedAllocations = allAllocations - .filter(allocation => filter(allocation, selection)) + .filter((allocation) => filter(allocation, selection)) .sortBy('updateTime'); PluginAllocations.allocations.forEach((allocation, index) => { @@ -115,7 +121,7 @@ module('Acceptance | plugin allocations', function(hooks) { }); }); - test(`selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { + test(`selecting multiple options in the ${label} facet results in a broader search`, async function (assert) { const selection = []; await beforeEach(); @@ -128,9 +134,12 @@ module('Acceptance | plugin allocations', function(hooks) { await option2.toggle(); selection.push(option2.key); - const allAllocations = [...plugin.controllers.models, ...plugin.nodes.models]; + const allAllocations = [ + ...plugin.controllers.models, + ...plugin.nodes.models, + ]; const expectedAllocations = allAllocations - .filter(allocation => filter(allocation, selection)) + .filter((allocation) => filter(allocation, selection)) .sortBy('updateTime'); PluginAllocations.allocations.forEach((allocation, index) => { @@ -138,7 +147,7 @@ module('Acceptance | plugin allocations', function(hooks) { }); }); - test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) { const selection = []; await beforeEach(); @@ -151,9 +160,14 @@ module('Acceptance | plugin allocations', function(hooks) { await option2.toggle(); selection.push(option2.key); - const queryString = `${paramName}=${window.encodeURIComponent(JSON.stringify(selection))}`; + const queryString = `${paramName}=${window.encodeURIComponent( + JSON.stringify(selection) + )}`; - assert.equal(currentURL(), `/csi/plugins/${plugin.id}/allocations?${queryString}`); + assert.equal( + currentURL(), + `/csi/plugins/${plugin.id}/allocations?${queryString}` + ); }); } }); diff --git a/ui/tests/acceptance/plugin-detail-test.js b/ui/tests/acceptance/plugin-detail-test.js index 4f7a82200..fe00f8419 100644 --- a/ui/tests/acceptance/plugin-detail-test.js +++ b/ui/tests/acceptance/plugin-detail-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { module, test } from 'qunit'; import { currentURL } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; @@ -8,23 +9,23 @@ import { formatBytes, formatHertz } from 'nomad-ui/utils/units'; import PluginDetail from 'nomad-ui/tests/pages/storage/plugins/detail'; import Layout from 'nomad-ui/tests/pages/layout'; -module('Acceptance | plugin detail', function(hooks) { +module('Acceptance | plugin detail', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); let plugin; - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('node'); plugin = server.create('csi-plugin', { controllerRequired: true }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await PluginDetail.visit({ id: plugin.id }); await a11yAudit(assert); }); - test('/csi/plugins/:id should have a breadcrumb trail linking back to Plugins and Storage', async function(assert) { + test('/csi/plugins/:id should have a breadcrumb trail linking back to Plugins and Storage', async function (assert) { await PluginDetail.visit({ id: plugin.id }); assert.equal(Layout.breadcrumbFor('csi.index').text, 'Storage'); @@ -32,19 +33,21 @@ module('Acceptance | plugin detail', function(hooks) { assert.equal(Layout.breadcrumbFor('csi.plugins.plugin').text, plugin.id); }); - test('/csi/plugins/:id should show the plugin name in the title', async function(assert) { + test('/csi/plugins/:id should show the plugin name in the title', async function (assert) { await PluginDetail.visit({ id: plugin.id }); assert.equal(document.title, `CSI Plugin ${plugin.id} - Nomad`); assert.equal(PluginDetail.title, plugin.id); }); - test('/csi/plugins/:id should list additional details for the plugin below the title', async function(assert) { + test('/csi/plugins/:id should list additional details for the plugin below the title', async function (assert) { await PluginDetail.visit({ id: plugin.id }); assert.ok( PluginDetail.controllerHealth.includes( - `${Math.round((plugin.controllersHealthy / plugin.controllersExpected) * 100)}%` + `${Math.round( + (plugin.controllersHealthy / plugin.controllersExpected) * 100 + )}%` ) ); assert.ok( @@ -57,23 +60,33 @@ module('Acceptance | plugin detail', function(hooks) { `${Math.round((plugin.nodesHealthy / plugin.nodesExpected) * 100)}%` ) ); - assert.ok(PluginDetail.nodeHealth.includes(`${plugin.nodesHealthy}/${plugin.nodesExpected}`)); + assert.ok( + PluginDetail.nodeHealth.includes( + `${plugin.nodesHealthy}/${plugin.nodesExpected}` + ) + ); assert.ok(PluginDetail.provider.includes(plugin.provider)); }); - test('/csi/plugins/:id should list all the controller plugin allocations for the plugin', async function(assert) { + test('/csi/plugins/:id should list all the controller plugin allocations for the plugin', async function (assert) { await PluginDetail.visit({ id: plugin.id }); - assert.equal(PluginDetail.controllerAllocations.length, plugin.controllers.length); + assert.equal( + PluginDetail.controllerAllocations.length, + plugin.controllers.length + ); plugin.controllers.models .sortBy('updateTime') .reverse() .forEach((allocation, idx) => { - assert.equal(PluginDetail.controllerAllocations.objectAt(idx).id, allocation.allocID); + assert.equal( + PluginDetail.controllerAllocations.objectAt(idx).id, + allocation.allocID + ); }); }); - test('/csi/plugins/:id should list all the node plugin allocations for the plugin', async function(assert) { + test('/csi/plugins/:id should list all the node plugin allocations for the plugin', async function (assert) { await PluginDetail.visit({ id: plugin.id }); assert.equal(PluginDetail.nodeAllocations.length, plugin.nodes.length); @@ -81,12 +94,17 @@ module('Acceptance | plugin detail', function(hooks) { .sortBy('updateTime') .reverse() .forEach((allocation, idx) => { - assert.equal(PluginDetail.nodeAllocations.objectAt(idx).id, allocation.allocID); + assert.equal( + PluginDetail.nodeAllocations.objectAt(idx).id, + allocation.allocID + ); }); }); - test('each allocation should have high-level details for the allocation', async function(assert) { - const controller = plugin.controllers.models.sortBy('updateTime').reverse()[0]; + test('each allocation should have high-level details for the allocation', async function (assert) { + const controller = plugin.controllers.models + .sortBy('updateTime') + .reverse()[0]; const allocation = server.db.allocations.find(controller.allocID); const allocStats = server.db.clientAllocationStats.find(allocation.id); const taskGroup = server.db.taskGroups.findBy({ @@ -94,14 +112,21 @@ module('Acceptance | plugin detail', function(hooks) { jobId: allocation.jobId, }); - const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); + const tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0); - const memoryUsed = tasks.reduce((sum, task) => sum + task.resources.MemoryMB, 0); + const memoryUsed = tasks.reduce( + (sum, task) => sum + task.resources.MemoryMB, + 0 + ); await PluginDetail.visit({ id: plugin.id }); - PluginDetail.controllerAllocations.objectAt(0).as(allocationRow => { - assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short ID'); + PluginDetail.controllerAllocations.objectAt(0).as((allocationRow) => { + assert.equal( + allocationRow.shortId, + allocation.id.split('-')[0], + 'Allocation short ID' + ); assert.equal( allocationRow.createTime, moment(allocation.createTime / 1000000).format('MMM D') @@ -110,8 +135,14 @@ module('Acceptance | plugin detail', function(hooks) { allocationRow.createTooltip, moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ') ); - assert.equal(allocationRow.modifyTime, moment(allocation.modifyTime / 1000000).fromNow()); - assert.equal(allocationRow.health, controller.healthy ? 'Healthy' : 'Unhealthy'); + assert.equal( + allocationRow.modifyTime, + moment(allocation.modifyTime / 1000000).fromNow() + ); + assert.equal( + allocationRow.health, + controller.healthy ? 'Healthy' : 'Unhealthy' + ); assert.equal( allocationRow.client, server.db.nodes.find(allocation.nodeId).id.split('-')[0], @@ -122,7 +153,11 @@ module('Acceptance | plugin detail', function(hooks) { server.db.nodes.find(allocation.nodeId).name.substr(0, 15), 'Node Name' ); - assert.equal(allocationRow.job, server.db.jobs.find(allocation.jobId).name, 'Job name'); + assert.equal( + allocationRow.job, + server.db.jobs.find(allocation.jobId).name, + 'Job name' + ); assert.ok(allocationRow.taskGroup, 'Task group name'); assert.ok(allocationRow.jobVersion, 'Job Version'); assert.equal( @@ -130,7 +165,9 @@ module('Acceptance | plugin detail', function(hooks) { Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, 'CPU %' ); - const roundedTicks = Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks); + const roundedTicks = Math.floor( + allocStats.resourceUsage.CpuStats.TotalTicks + ); assert.equal( allocationRow.cpuTooltip, `${formatHertz(roundedTicks, 'MHz')} / ${formatHertz(cpuUsed, 'MHz')}`, @@ -143,17 +180,18 @@ module('Acceptance | plugin detail', function(hooks) { ); assert.equal( allocationRow.memTooltip, - `${formatBytes(allocStats.resourceUsage.MemoryStats.RSS)} / ${formatBytes( - memoryUsed, - 'MiB' - )}`, + `${formatBytes( + allocStats.resourceUsage.MemoryStats.RSS + )} / ${formatBytes(memoryUsed, 'MiB')}`, 'Detailed memory information is in a tooltip' ); }); }); - test('each allocation should link to the allocation detail page', async function(assert) { - const controller = plugin.controllers.models.sortBy('updateTime').reverse()[0]; + test('each allocation should link to the allocation detail page', async function (assert) { + const controller = plugin.controllers.models + .sortBy('updateTime') + .reverse()[0]; await PluginDetail.visit({ id: plugin.id }); await PluginDetail.controllerAllocations.objectAt(0).visit(); @@ -161,7 +199,7 @@ module('Acceptance | plugin detail', function(hooks) { assert.equal(currentURL(), `/allocations/${controller.allocID}`); }); - test('when there are no plugin allocations, the tables present empty states', async function(assert) { + test('when there are no plugin allocations, the tables present empty states', async function (assert) { const emptyPlugin = server.create('csi-plugin', { controllerRequired: true, controllersHealthy: 0, @@ -173,14 +211,22 @@ module('Acceptance | plugin detail', function(hooks) { await PluginDetail.visit({ id: emptyPlugin.id }); assert.ok(PluginDetail.controllerTableIsEmpty); - assert.equal(PluginDetail.controllerEmptyState.headline, 'No Controller Plugin Allocations'); + assert.equal( + PluginDetail.controllerEmptyState.headline, + 'No Controller Plugin Allocations' + ); assert.ok(PluginDetail.nodeTableIsEmpty); - assert.equal(PluginDetail.nodeEmptyState.headline, 'No Node Plugin Allocations'); + assert.equal( + PluginDetail.nodeEmptyState.headline, + 'No Node Plugin Allocations' + ); }); - test('when the plugin is node-only, the controller information is omitted', async function(assert) { - const nodeOnlyPlugin = server.create('csi-plugin', { controllerRequired: false }); + test('when the plugin is node-only, the controller information is omitted', async function (assert) { + const nodeOnlyPlugin = server.create('csi-plugin', { + controllerRequired: false, + }); await PluginDetail.visit({ id: nodeOnlyPlugin.id }); @@ -191,7 +237,7 @@ module('Acceptance | plugin detail', function(hooks) { assert.notOk(PluginDetail.controllerTableIsPresent); }); - test('when there are more than 10 controller or node allocations, only 10 are shown', async function(assert) { + test('when there are more than 10 controller or node allocations, only 10 are shown', async function (assert) { const manyAllocationsPlugin = server.create('csi-plugin', { shallow: true, controllerRequired: false, @@ -203,12 +249,14 @@ module('Acceptance | plugin detail', function(hooks) { assert.equal(PluginDetail.nodeAllocations.length, 10); }); - test('the View All links under each allocation table link to a filtered view of the plugins allocation list', async function(assert) { - const serialize = arr => window.encodeURIComponent(JSON.stringify(arr)); + test('the View All links under each allocation table link to a filtered view of the plugins allocation list', async function (assert) { + const serialize = (arr) => window.encodeURIComponent(JSON.stringify(arr)); await PluginDetail.visit({ id: plugin.id }); assert.ok( - PluginDetail.goToControllerAllocationsText.includes(plugin.controllers.models.length) + PluginDetail.goToControllerAllocationsText.includes( + plugin.controllers.models.length + ) ); await PluginDetail.goToControllerAllocations(); assert.equal( @@ -217,8 +265,13 @@ module('Acceptance | plugin detail', function(hooks) { ); await PluginDetail.visit({ id: plugin.id }); - assert.ok(PluginDetail.goToNodeAllocationsText.includes(plugin.nodes.models.length)); + assert.ok( + PluginDetail.goToNodeAllocationsText.includes(plugin.nodes.models.length) + ); await PluginDetail.goToNodeAllocations(); - assert.equal(currentURL(), `/csi/plugins/${plugin.id}/allocations?type=${serialize(['node'])}`); + assert.equal( + currentURL(), + `/csi/plugins/${plugin.id}/allocations?type=${serialize(['node'])}` + ); }); }); diff --git a/ui/tests/acceptance/plugins-list-test.js b/ui/tests/acceptance/plugins-list-test.js index b378f0f2e..3660ec9db 100644 --- a/ui/tests/acceptance/plugins-list-test.js +++ b/ui/tests/acceptance/plugins-list-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -6,28 +7,28 @@ import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; import pageSizeSelect from './behaviors/page-size-select'; import PluginsList from 'nomad-ui/tests/pages/storage/plugins/list'; -module('Acceptance | plugins list', function(hooks) { +module('Acceptance | plugins list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('node'); window.localStorage.clear(); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await PluginsList.visit(); await a11yAudit(assert); }); - test('visiting /csi/plugins', async function(assert) { + test('visiting /csi/plugins', async function (assert) { await PluginsList.visit(); assert.equal(currentURL(), '/csi/plugins'); assert.equal(document.title, 'CSI Plugins - Nomad'); }); - test('/csi/plugins should list the first page of plugins sorted by id', async function(assert) { + test('/csi/plugins should list the first page of plugins sorted by id', async function (assert) { const pluginCount = PluginsList.pageSize + 1; server.createList('csi-plugin', pluginCount, { shallow: true }); @@ -40,13 +41,17 @@ module('Acceptance | plugins list', function(hooks) { }); }); - test('each plugin row should contain information about the plugin', async function(assert) { - const plugin = server.create('csi-plugin', { shallow: true, controllerRequired: true }); + test('each plugin row should contain information about the plugin', async function (assert) { + const plugin = server.create('csi-plugin', { + shallow: true, + controllerRequired: true, + }); await PluginsList.visit(); const pluginRow = PluginsList.plugins.objectAt(0); - const controllerHealthStr = plugin.controllersHealthy > 0 ? 'Healthy' : 'Unhealthy'; + const controllerHealthStr = + plugin.controllersHealthy > 0 ? 'Healthy' : 'Unhealthy'; const nodeHealthStr = plugin.nodesHealthy > 0 ? 'Healthy' : 'Unhealthy'; assert.equal(pluginRow.id, plugin.id); @@ -61,8 +66,11 @@ module('Acceptance | plugins list', function(hooks) { assert.equal(pluginRow.provider, plugin.provider); }); - test('node only plugins explain that there is no controller health for this plugin type', async function(assert) { - const plugin = server.create('csi-plugin', { shallow: true, controllerRequired: false }); + test('node only plugins explain that there is no controller health for this plugin type', async function (assert) { + const plugin = server.create('csi-plugin', { + shallow: true, + controllerRequired: false, + }); await PluginsList.visit(); @@ -78,7 +86,7 @@ module('Acceptance | plugins list', function(hooks) { assert.equal(pluginRow.provider, plugin.provider); }); - test('each plugin row should link to the corresponding plugin', async function(assert) { + test('each plugin row should link to the corresponding plugin', async function (assert) { const plugin = server.create('csi-plugin', { shallow: true }); await PluginsList.visit(); @@ -93,14 +101,14 @@ module('Acceptance | plugins list', function(hooks) { assert.equal(currentURL(), `/csi/plugins/${plugin.id}`); }); - test('when there are no plugins, there is an empty message', async function(assert) { + test('when there are no plugins, there is an empty message', async function (assert) { await PluginsList.visit(); assert.ok(PluginsList.isEmpty); assert.equal(PluginsList.emptyState.headline, 'No Plugins'); }); - test('when there are plugins, but no matches for a search, there is an empty message', async function(assert) { + test('when there are plugins, but no matches for a search, there is an empty message', async function (assert) { server.create('csi-plugin', { id: 'cat 1', shallow: true }); server.create('csi-plugin', { id: 'cat 2', shallow: true }); @@ -111,8 +119,10 @@ module('Acceptance | plugins list', function(hooks) { assert.equal(PluginsList.emptyState.headline, 'No Matches'); }); - test('search resets the current page', async function(assert) { - server.createList('csi-plugin', PluginsList.pageSize + 1, { shallow: true }); + test('search resets the current page', async function (assert) { + server.createList('csi-plugin', PluginsList.pageSize + 1, { + shallow: true, + }); await PluginsList.visit(); await PluginsList.nextPage(); @@ -124,7 +134,7 @@ module('Acceptance | plugins list', function(hooks) { assert.equal(currentURL(), '/csi/plugins?search=foobar'); }); - test('when accessing plugins is forbidden, a message is shown with a link to the tokens page', async function(assert) { + test('when accessing plugins is forbidden, a message is shown with a link to the tokens page', async function (assert) { server.pretender.get('/v1/plugins', () => [403, {}, null]); await PluginsList.visit(); diff --git a/ui/tests/acceptance/proxy-test.js b/ui/tests/acceptance/proxy-test.js index 582a57e76..7d7a4643e 100644 --- a/ui/tests/acceptance/proxy-test.js +++ b/ui/tests/acceptance/proxy-test.js @@ -6,11 +6,11 @@ import Jobs from 'nomad-ui/tests/pages/jobs/list'; let managementToken; -module('Acceptance | reverse proxy', function(hooks) { +module('Acceptance | reverse proxy', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { window.localStorage.clear(); window.sessionStorage.clear(); @@ -19,30 +19,34 @@ module('Acceptance | reverse proxy', function(hooks) { // Simulate a reverse proxy injecting X-Nomad-Token header for all requests this._originalXMLHttpRequestSend = XMLHttpRequest.prototype.send; - (function(send) { - XMLHttpRequest.prototype.send = function(data) { + (function (send) { + XMLHttpRequest.prototype.send = function (data) { this.setRequestHeader('X-Nomad-Token', managementToken.secretId); send.call(this, data); }; })(this._originalXMLHttpRequestSend); }); - hooks.afterEach(function() { + hooks.afterEach(function () { XMLHttpRequest.prototype.send = this._originalXMLHttpRequestSend; }); - test('when token is inserted by a reverse proxy, the UI is adjusted', async function(assert) { + test('when token is inserted by a reverse proxy, the UI is adjusted', async function (assert) { // when token is inserted by reserve proxy, the token is reverse proxy const { secretId } = managementToken; await Jobs.visit(); - assert.ok(window.localStorage.nomadTokenSecret == null, 'No token secret set'); + assert.equal( + window.localStorage.nomadTokenSecret, + null, + 'No token secret set' + ); // Make sure that server received the header assert.ok( server.pretender.handledRequests .mapBy('requestHeaders') - .every(headers => headers['X-Nomad-Token'] === secretId), + .every((headers) => headers['X-Nomad-Token'] === secretId), 'The token header is always present' ); diff --git a/ui/tests/acceptance/regions-test.js b/ui/tests/acceptance/regions-test.js index 0e48903ff..fc3ba7026 100644 --- a/ui/tests/acceptance/regions-test.js +++ b/ui/tests/acceptance/regions-test.js @@ -1,3 +1,5 @@ +/* eslint-disable qunit/require-expect */ +/* eslint-disable qunit/no-conditional-assertions */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -9,22 +11,25 @@ import ClientsList from 'nomad-ui/tests/pages/clients/list'; import Layout from 'nomad-ui/tests/pages/layout'; import Allocation from 'nomad-ui/tests/pages/allocations/detail'; -module('Acceptance | regions (only one)', function(hooks) { +module('Acceptance | regions (only one)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('agent'); server.create('node'); - server.createList('job', 2, { createAllocations: false, noDeployments: true }); + server.createList('job', 2, { + createAllocations: false, + noDeployments: true, + }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await JobsList.visit(); await a11yAudit(assert); }); - test('when there is only one region, the region switcher is not shown in the nav bar and the region is not in the page title', async function(assert) { + test('when there is only one region, the region switcher is not shown in the nav bar and the region is not in the page title', async function (assert) { server.create('region', { id: 'global' }); await JobsList.visit(); @@ -33,7 +38,7 @@ module('Acceptance | regions (only one)', function(hooks) { assert.equal(document.title, 'Jobs - Nomad'); }); - test('when the only region is not named "global", the region switcher still is not shown', async function(assert) { + test('when the only region is not named "global", the region switcher still is not shown', async function (assert) { server.create('region', { id: 'some-region' }); await JobsList.visit(); @@ -41,7 +46,7 @@ module('Acceptance | regions (only one)', function(hooks) { assert.notOk(Layout.navbar.regionSwitcher.isPresent, 'No region switcher'); }); - test('pages do not include the region query param', async function(assert) { + test('pages do not include the region query param', async function (assert) { server.create('region', { id: 'global' }); await JobsList.visit(); @@ -55,47 +60,57 @@ module('Acceptance | regions (only one)', function(hooks) { assert.equal(currentURL(), '/clients', 'No region query param'); }); - test('api requests do not include the region query param', async function(assert) { + test('api requests do not include the region query param', async function (assert) { server.create('region', { id: 'global' }); await JobsList.visit(); await JobsList.jobs.objectAt(0).clickRow(); await Layout.gutter.visitClients(); await Layout.gutter.visitServers(); - server.pretender.handledRequests.forEach(req => { + server.pretender.handledRequests.forEach((req) => { assert.notOk(req.url.includes('region='), req.url); }); }); }); -module('Acceptance | regions (many)', function(hooks) { +module('Acceptance | regions (many)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('agent'); server.create('node'); - server.createList('job', 2, { createAllocations: false, noDeployments: true }); + server.createList('job', 2, { + createAllocations: false, + noDeployments: true, + }); server.create('allocation'); server.create('region', { id: 'global' }); server.create('region', { id: 'region-2' }); }); - test('the region switcher is rendered in the nav bar and the region is in the page title', async function(assert) { + test('the region switcher is rendered in the nav bar and the region is in the page title', async function (assert) { await JobsList.visit(); - assert.ok(Layout.navbar.regionSwitcher.isPresent, 'Region switcher is shown'); + assert.ok( + Layout.navbar.regionSwitcher.isPresent, + 'Region switcher is shown' + ); assert.equal(document.title, 'Jobs - global - Nomad'); }); - test('when on the default region, pages do not include the region query param', async function(assert) { + test('when on the default region, pages do not include the region query param', async function (assert) { await JobsList.visit(); assert.equal(currentURL(), '/jobs', 'No region query param'); - assert.equal(window.localStorage.nomadActiveRegion, 'global', 'Region in localStorage'); + assert.equal( + window.localStorage.nomadActiveRegion, + 'global', + 'Region in localStorage' + ); }); - test('switching regions sets localStorage and the region query param', async function(assert) { + test('switching regions sets localStorage and the region query param', async function (assert) { const newRegion = server.db.regions[1].id; await JobsList.visit(); @@ -106,10 +121,14 @@ module('Acceptance | regions (many)', function(hooks) { currentURL().includes(`region=${newRegion}`), 'New region is the region query param value' ); - assert.equal(window.localStorage.nomadActiveRegion, newRegion, 'New region in localStorage'); + assert.equal( + window.localStorage.nomadActiveRegion, + newRegion, + 'New region in localStorage' + ); }); - test('switching regions to the default region, unsets the region query param', async function(assert) { + test('switching regions to the default region, unsets the region query param', async function (assert) { const startingRegion = server.db.regions[1].id; const defaultRegion = server.db.regions[0].id; @@ -117,7 +136,10 @@ module('Acceptance | regions (many)', function(hooks) { await selectChoose('[data-test-region-switcher-parent]', defaultRegion); - assert.notOk(currentURL().includes('region='), 'No region query param for the default region'); + assert.notOk( + currentURL().includes('region='), + 'No region query param for the default region' + ); assert.equal( window.localStorage.nomadActiveRegion, defaultRegion, @@ -125,7 +147,7 @@ module('Acceptance | regions (many)', function(hooks) { ); }); - test('switching regions on deep pages redirects to the application root', async function(assert) { + test('switching regions on deep pages redirects to the application root', async function (assert) { const newRegion = server.db.regions[1].id; await Allocation.visit({ id: server.db.allocations[0].id }); @@ -135,7 +157,7 @@ module('Acceptance | regions (many)', function(hooks) { assert.ok(currentURL().includes('/jobs?'), 'Back at the jobs page'); }); - test('navigating directly to a page with the region query param sets the application to that region', async function(assert) { + test('navigating directly to a page with the region query param sets the application to that region', async function (assert) { const allocation = server.db.allocations[0]; const region = server.db.regions[1].id; await Allocation.visit({ id: allocation.id, region }); @@ -152,7 +174,7 @@ module('Acceptance | regions (many)', function(hooks) { ); }); - test('when the region is not the default region, all api requests other than the agent/self request include the region query param', async function(assert) { + test('when the region is not the default region, all api requests other than the agent/self request include the region query param', async function (assert) { window.localStorage.removeItem('nomadTokenSecret'); const region = server.db.regions[1].id; @@ -182,7 +204,7 @@ module('Acceptance | regions (many)', function(hooks) { 'The default region request is made without a region qp' ); - appRequests.forEach(req => { + appRequests.forEach((req) => { if (req.url === '/v1/agent/self') { assert.notOk(req.url.includes('region='), `(no region) ${req.url}`); } else { diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 635aa0ee9..78a0fbd2b 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -1,4 +1,5 @@ -/* eslint-disable ember-a11y-testing/a11y-audit-called */ // TODO +/* eslint-disable ember-a11y-testing/a11y-audit-called */ +/* eslint-disable qunit/require-expect */ import { module, test } from 'qunit'; import { currentURL, triggerEvent, visit } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; @@ -8,11 +9,11 @@ import JobsList from 'nomad-ui/tests/pages/jobs/list'; import { selectSearch } from 'ember-power-select/test-support'; import Response from 'ember-cli-mirage/response'; -module('Acceptance | search', function(hooks) { +module('Acceptance | search', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - test('search exposes and navigates to results from the fuzzy search endpoint', async function(assert) { + test('search exposes and navigates to results from the fuzzy search endpoint', async function (assert) { server.create('node', { name: 'xyz' }); const otherNode = server.create('node', { name: 'ghi' }); @@ -29,7 +30,12 @@ module('Acceptance | search', function(hooks) { groupsCount: 1, groupTaskCount: 1, }); - server.create('job', { id: 'abc', namespaceId: 'default', groupsCount: 1, groupTaskCount: 1 }); + server.create('job', { + id: 'abc', + namespaceId: 'default', + groupsCount: 1, + groupTaskCount: 1, + }); const firstAllocation = server.schema.allocations.all().models[0]; const firstTaskGroup = server.schema.taskGroups.all().models[0]; @@ -40,33 +46,33 @@ module('Acceptance | search', function(hooks) { await selectSearch(Layout.navbar.search.scope, 'xy'); - Layout.navbar.search.as(search => { + Layout.navbar.search.as((search) => { assert.equal(search.groups.length, 5); - search.groups[0].as(jobs => { + search.groups[0].as((jobs) => { assert.equal(jobs.name, 'Jobs (2)'); assert.equal(jobs.options.length, 2); assert.equal(jobs.options[0].text, 'default > vwxyz'); assert.equal(jobs.options[1].text, 'default > xyz job'); }); - search.groups[1].as(clients => { + search.groups[1].as((clients) => { assert.equal(clients.name, 'Clients (1)'); assert.equal(clients.options.length, 1); assert.equal(clients.options[0].text, 'xyz'); }); - search.groups[2].as(allocs => { + search.groups[2].as((allocs) => { assert.equal(allocs.name, 'Allocations (0)'); assert.equal(allocs.options.length, 0); }); - search.groups[3].as(groups => { + search.groups[3].as((groups) => { assert.equal(groups.name, 'Task Groups (0)'); assert.equal(groups.options.length, 0); }); - search.groups[4].as(plugins => { + search.groups[4].as((plugins) => { assert.equal(plugins.name, 'CSI Plugins (1)'); assert.equal(plugins.options.length, 1); assert.equal(plugins.options[0].text, 'xyz-plugin'); @@ -100,13 +106,16 @@ module('Acceptance | search', function(hooks) { await Layout.navbar.search.groups[4].options[0].click(); assert.equal(currentURL(), '/csi/plugins/xyz-plugin'); - const fuzzySearchQueries = server.pretender.handledRequests.filterBy('url', '/v1/search/fuzzy'); + const fuzzySearchQueries = server.pretender.handledRequests.filterBy( + 'url', + '/v1/search/fuzzy' + ); - const featureDetectionQueries = fuzzySearchQueries.filter(request => + const featureDetectionQueries = fuzzySearchQueries.filter((request) => request.requestBody.includes('feature-detection-query') ); - assert.ok( + assert.equal( featureDetectionQueries.length, 1, 'expect the feature detection query to only run once' @@ -121,21 +130,22 @@ module('Acceptance | search', function(hooks) { }); }); - test('search does not perform a request when only one character has been entered', async function(assert) { + test('search does not perform a request when only one character has been entered', async function (assert) { await visit('/'); await selectSearch(Layout.navbar.search.scope, 'q'); assert.ok(Layout.navbar.search.noOptionsShown); assert.equal( - server.pretender.handledRequests.filterBy('url', '/v1/search/fuzzy').length, + server.pretender.handledRequests.filterBy('url', '/v1/search/fuzzy') + .length, 1, 'expect the feature detection query' ); }); - test('when fuzzy search is disabled on the server, the search control is hidden', async function(assert) { - server.post('/search/fuzzy', function() { + test('when fuzzy search is disabled on the server, the search control is hidden', async function (assert) { + server.post('/search/fuzzy', function () { return new Response(500, {}, ''); }); @@ -144,7 +154,7 @@ module('Acceptance | search', function(hooks) { assert.ok(Layout.navbar.search.isHidden); }); - test('results are truncated at 10 per group', async function(assert) { + test('results are truncated at 10 per group', async function (assert) { server.create('node', { name: 'xyz' }); for (let i = 0; i < 11; i++) { @@ -155,15 +165,15 @@ module('Acceptance | search', function(hooks) { await selectSearch(Layout.navbar.search.scope, 'job'); - Layout.navbar.search.as(search => { - search.groups[0].as(jobs => { + Layout.navbar.search.as((search) => { + search.groups[0].as((jobs) => { assert.equal(jobs.name, 'Jobs (showing 10 of 11)'); assert.equal(jobs.options.length, 10); }); }); }); - test('server-side truncation is indicated in the group label', async function(assert) { + test('server-side truncation is indicated in the group label', async function (assert) { server.create('node', { name: 'xyz' }); for (let i = 0; i < 21; i++) { @@ -174,14 +184,14 @@ module('Acceptance | search', function(hooks) { await selectSearch(Layout.navbar.search.scope, 'job'); - Layout.navbar.search.as(search => { - search.groups[0].as(jobs => { + Layout.navbar.search.as((search) => { + search.groups[0].as((jobs) => { assert.equal(jobs.name, 'Jobs (showing 10 of 20+)'); }); }); }); - test('clicking the search field starts search immediately', async function(assert) { + test('clicking the search field starts search immediately', async function (assert) { await visit('/'); assert.notOk(Layout.navbar.search.field.isPresent); @@ -191,7 +201,7 @@ module('Acceptance | search', function(hooks) { assert.ok(Layout.navbar.search.field.isPresent); }); - test('pressing slash starts a search', async function(assert) { + test('pressing slash starts a search', async function (assert) { await visit('/'); assert.notOk(Layout.navbar.search.field.isPresent); @@ -201,7 +211,7 @@ module('Acceptance | search', function(hooks) { assert.ok(Layout.navbar.search.field.isPresent); }); - test('pressing slash when an input element is focused does not start a search', async function(assert) { + test('pressing slash when an input element is focused does not start a search', async function (assert) { server.create('node'); server.create('job'); diff --git a/ui/tests/acceptance/server-detail-test.js b/ui/tests/acceptance/server-detail-test.js index 507d83fb8..ef8b761f5 100644 --- a/ui/tests/acceptance/server-detail-test.js +++ b/ui/tests/acceptance/server-detail-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -8,41 +9,43 @@ import formatHost from 'nomad-ui/utils/format-host'; let agent; -module('Acceptance | server detail', function(hooks) { +module('Acceptance | server detail', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.createList('agent', 3); agent = server.db.agents[0]; await ServerDetail.visit({ name: agent.name }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await a11yAudit(assert); }); - test('visiting /servers/:server_name', async function(assert) { + test('visiting /servers/:server_name', async function (assert) { assert.equal(currentURL(), `/servers/${encodeURIComponent(agent.name)}`); assert.equal(document.title, `Server ${agent.name} - Nomad`); }); - test('when the server is the leader, the title shows a leader badge', async function(assert) { + test('when the server is the leader, the title shows a leader badge', async function (assert) { assert.ok(ServerDetail.title.includes(agent.name)); assert.ok(ServerDetail.hasLeaderBadge); }); - test('the details ribbon displays basic information about the server', async function(assert) { + test('the details ribbon displays basic information about the server', async function (assert) { assert.ok(ServerDetail.serverStatus.includes(agent.member.Status)); assert.ok( - ServerDetail.address.includes(formatHost(agent.member.Address, agent.member.Tags.port)) + ServerDetail.address.includes( + formatHost(agent.member.Address, agent.member.Tags.port) + ) ); assert.ok(ServerDetail.datacenter.includes(agent.member.Tags.dc)); }); - test('the server detail page should list all tags for the server', async function(assert) { + test('the server detail page should list all tags for the server', async function (assert) { const tags = Object.keys(agent.member.Tags) - .map(name => ({ name, value: agent.member.Tags[name] })) + .map((name) => ({ name, value: agent.member.Tags[name] })) .sortBy('name'); assert.equal(ServerDetail.tags.length, tags.length, '# of tags'); @@ -53,15 +56,23 @@ module('Acceptance | server detail', function(hooks) { }); }); - test('when the server is not the leader, there is no leader badge', async function(assert) { + test('when the server is not the leader, there is no leader badge', async function (assert) { await ServerDetail.visit({ name: server.db.agents[1].name }); assert.notOk(ServerDetail.hasLeaderBadge); }); - test('when the server is not found, an error message is shown, but the URL persists', async function(assert) { + test('when the server is not found, an error message is shown, but the URL persists', async function (assert) { await ServerDetail.visit({ name: 'not-a-real-server' }); - assert.equal(currentURL(), '/servers/not-a-real-server', 'The URL persists'); - assert.equal(ServerDetail.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + currentURL(), + '/servers/not-a-real-server', + 'The URL persists' + ); + assert.equal( + ServerDetail.error.title, + 'Not Found', + 'Error message is for 404' + ); }); }); diff --git a/ui/tests/acceptance/server-monitor-test.js b/ui/tests/acceptance/server-monitor-test.js index 0f6aeed3e..dc3b755e7 100644 --- a/ui/tests/acceptance/server-monitor-test.js +++ b/ui/tests/acceptance/server-monitor-test.js @@ -11,11 +11,11 @@ let agent; let managementToken; let clientToken; -module('Acceptance | server monitor', function(hooks) { +module('Acceptance | server monitor', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { agent = server.create('agent'); managementToken = server.create('token'); @@ -26,28 +26,33 @@ module('Acceptance | server monitor', function(hooks) { run.later(run, run.cancelTimers, 500); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + await ServerMonitor.visit({ name: agent.name }); await a11yAudit(assert); }); - test('/servers/:id/monitor should have a breadcrumb trail linking back to servers', async function(assert) { + test('/servers/:id/monitor should have a breadcrumb trail linking back to servers', async function (assert) { await ServerMonitor.visit({ name: agent.name }); assert.equal( Layout.breadcrumbFor('servers.index').text, 'Servers', 'The page should read the breadcrumb Servers' ); - assert.equal(Layout.breadcrumbFor('servers.server').text, `Server ${agent.name}`); + assert.equal( + Layout.breadcrumbFor('servers.server').text, + `Server ${agent.name}` + ); await Layout.breadcrumbFor('servers.index').visit(); assert.equal(currentURL(), '/servers'); }); - test('the monitor page immediately streams agent monitor output at the info level', async function(assert) { + test('the monitor page immediately streams agent monitor output at the info level', async function (assert) { await ServerMonitor.visit({ name: agent.name }); - const logRequest = server.pretender.handledRequests.find(req => + const logRequest = server.pretender.handledRequests.find((req) => req.url.startsWith('/v1/agent/monitor') ); assert.ok(ServerMonitor.logsArePresent); @@ -55,13 +60,13 @@ module('Acceptance | server monitor', function(hooks) { assert.ok(logRequest.url.includes('log_level=info')); }); - test('switching the log level persists the new log level as a query param', async function(assert) { + test('switching the log level persists the new log level as a query param', async function (assert) { await ServerMonitor.visit({ name: agent.name }); await ServerMonitor.selectLogLevel('Debug'); assert.equal(currentURL(), `/servers/${agent.name}/monitor?level=debug`); }); - test('when the current access token does not include the agent:read rule, a descriptive error message is shown', async function(assert) { + test('when the current access token does not include the agent:read rule, a descriptive error message is shown', async function (assert) { window.localStorage.nomadTokenSecret = clientToken.secretId; await ServerMonitor.visit({ name: agent.name }); diff --git a/ui/tests/acceptance/servers-list-test.js b/ui/tests/acceptance/servers-list-test.js index 05ac3f327..6c6a12abb 100644 --- a/ui/tests/acceptance/servers-list-test.js +++ b/ui/tests/acceptance/servers-list-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -12,7 +13,7 @@ const minimumSetup = () => { server.createList('agent', 1); }; -const agentSort = leader => (a, b) => { +const agentSort = (leader) => (a, b) => { if (formatHost(a.member.Address, a.member.Tags.port) === leader) { return 1; } else if (formatHost(b.member.Address, b.member.Tags.port) === leader) { @@ -21,17 +22,17 @@ const agentSort = leader => (a, b) => { return 0; }; -module('Acceptance | servers list', function(hooks) { +module('Acceptance | servers list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { minimumSetup(); await ServersList.visit(); await a11yAudit(assert); }); - test('/servers should list all servers', async function(assert) { + test('/servers should list all servers', async function (assert) { server.createList('node', 1); server.createList('agent', 10); @@ -40,16 +41,24 @@ module('Acceptance | servers list', function(hooks) { await ServersList.visit(); - assert.equal(ServersList.servers.length, ServersList.pageSize, 'List is stopped at pageSize'); + assert.equal( + ServersList.servers.length, + ServersList.pageSize, + 'List is stopped at pageSize' + ); ServersList.servers.forEach((server, index) => { - assert.equal(server.name, sortedAgents[index].name, 'Servers are ordered'); + assert.equal( + server.name, + sortedAgents[index].name, + 'Servers are ordered' + ); }); assert.equal(document.title, 'Servers - Nomad'); }); - test('each server should show high-level info of the server', async function(assert) { + test('each server should show high-level info of the server', async function (assert) { minimumSetup(); const agent = server.db.agents[0]; @@ -66,17 +75,21 @@ module('Acceptance | servers list', function(hooks) { assert.equal(agentRow.version, agent.version, 'Version'); }); - test('each server should link to the server detail page', async function(assert) { + test('each server should link to the server detail page', async function (assert) { minimumSetup(); const agent = server.db.agents[0]; await ServersList.visit(); await ServersList.servers.objectAt(0).clickRow(); - assert.equal(currentURL(), `/servers/${agent.name}`, 'Now at the server detail page'); + assert.equal( + currentURL(), + `/servers/${agent.name}`, + 'Now at the server detail page' + ); }); - test('when accessing servers is forbidden, show a message with a link to the tokens page', async function(assert) { + test('when accessing servers is forbidden, show a message with a link to the tokens page', async function (assert) { server.create('agent'); server.pretender.get('/v1/agent/members', () => [403, {}, null]); diff --git a/ui/tests/acceptance/task-detail-test.js b/ui/tests/acceptance/task-detail-test.js index becc9c212..b9adbb3d0 100644 --- a/ui/tests/acceptance/task-detail-test.js +++ b/ui/tests/acceptance/task-detail-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL, waitFor } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -10,38 +11,49 @@ import moment from 'moment'; let allocation; let task; -module('Acceptance | task detail', function(hooks) { +module('Acceptance | task detail', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node'); server.create('job', { createAllocations: false }); - allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); + allocation = server.create('allocation', 'withTaskWithPorts', { + clientStatus: 'running', + }); task = server.db.taskStates.where({ allocationId: allocation.id })[0]; await Task.visit({ id: allocation.id, name: task.name }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + await a11yAudit(assert); }); - test('/allocation/:id/:task_name should name the task and list high-level task information', async function(assert) { + test('/allocation/:id/:task_name should name the task and list high-level task information', async function (assert) { assert.ok(Task.title.text.includes(task.name), 'Task name'); assert.ok(Task.state.includes(task.state), 'Task state'); assert.ok( - Task.startedAt.includes(moment(task.startedAt).format("MMM DD, 'YY HH:mm:ss ZZ")), + Task.startedAt.includes( + moment(task.startedAt).format("MMM DD, 'YY HH:mm:ss ZZ") + ), 'Task started at' ); const lifecycle = server.db.tasks.where({ name: task.name })[0].Lifecycle; let lifecycleName = 'main'; - if (lifecycle && (lifecycle.Hook === 'prestart' || lifecycle.Hook === 'poststart')) { - lifecycleName = `${lifecycle.Hook}-${lifecycle.Sidecar ? 'sidecar' : 'ephemeral'}`; + if ( + lifecycle && + (lifecycle.Hook === 'prestart' || lifecycle.Hook === 'poststart') + ) { + lifecycleName = `${lifecycle.Hook}-${ + lifecycle.Sidecar ? 'sidecar' : 'ephemeral' + }`; } if (lifecycle && lifecycle.Hook === 'poststop') { lifecycleName = 'poststop'; @@ -52,12 +64,16 @@ module('Acceptance | task detail', function(hooks) { assert.equal(document.title, `Task ${task.name} - Nomad`); }); - test('breadcrumbs match jobs / job / task group / allocation / task', async function(assert) { + test('breadcrumbs match jobs / job / task group / allocation / task', async function (assert) { const { jobId, taskGroup } = allocation; const job = server.db.jobs.find(jobId); const shortId = allocation.id.split('-')[0]; - assert.equal(Layout.breadcrumbFor('jobs.index').text, 'Jobs', 'Jobs is the first breadcrumb'); + assert.equal( + Layout.breadcrumbFor('jobs.index').text, + 'Jobs', + 'Jobs is the first breadcrumb' + ); await waitFor('[data-test-job-breadcrumb]'); assert.equal( @@ -86,7 +102,11 @@ module('Acceptance | task detail', function(hooks) { await Task.visit({ id: allocation.id, name: task.name }); await Layout.breadcrumbFor('jobs.job.index').visit(); - assert.equal(currentURL(), `/jobs/${job.id}`, 'Job breadcrumb links correctly'); + assert.equal( + currentURL(), + `/jobs/${job.id}`, + 'Job breadcrumb links correctly' + ); await Task.visit({ id: allocation.id, name: task.name }); await Layout.breadcrumbFor('jobs.job.task-group').visit(); @@ -105,57 +125,85 @@ module('Acceptance | task detail', function(hooks) { ); }); - test('/allocation/:id/:task_name should include resource utilization graphs', async function(assert) { - assert.equal(Task.resourceCharts.length, 2, 'Two resource utilization graphs'); - assert.equal(Task.resourceCharts.objectAt(0).name, 'CPU', 'First chart is CPU'); - assert.equal(Task.resourceCharts.objectAt(1).name, 'Memory', 'Second chart is Memory'); + test('/allocation/:id/:task_name should include resource utilization graphs', async function (assert) { + assert.equal( + Task.resourceCharts.length, + 2, + 'Two resource utilization graphs' + ); + assert.equal( + Task.resourceCharts.objectAt(0).name, + 'CPU', + 'First chart is CPU' + ); + assert.equal( + Task.resourceCharts.objectAt(1).name, + 'Memory', + 'Second chart is Memory' + ); }); - test('the events table lists all recent events', async function(assert) { + test('the events table lists all recent events', async function (assert) { const events = server.db.taskEvents.where({ taskStateId: task.id }); - assert.equal(Task.events.length, events.length, `Lists ${events.length} events`); + assert.equal( + Task.events.length, + events.length, + `Lists ${events.length} events` + ); }); - test('when a task has volumes, the volumes table is shown', async function(assert) { + test('when a task has volumes, the volumes table is shown', async function (assert) { const taskGroup = server.schema.taskGroups.where({ jobId: allocation.jobId, name: allocation.taskGroup, }).models[0]; - const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); + const jobTask = taskGroup.tasks.models.find((m) => m.name === task.name); assert.ok(Task.hasVolumes); assert.equal(Task.volumes.length, jobTask.volumeMounts.length); }); - test('when a task does not have volumes, the volumes table is not shown', async function(assert) { - const job = server.create('job', { createAllocations: false, noHostVolumes: true }); - allocation = server.create('allocation', { jobId: job.id, clientStatus: 'running' }); + test('when a task does not have volumes, the volumes table is not shown', async function (assert) { + const job = server.create('job', { + createAllocations: false, + noHostVolumes: true, + }); + allocation = server.create('allocation', { + jobId: job.id, + clientStatus: 'running', + }); task = server.db.taskStates.where({ allocationId: allocation.id })[0]; await Task.visit({ id: allocation.id, name: task.name }); assert.notOk(Task.hasVolumes); }); - test('each volume in the volumes table shows information about the volume', async function(assert) { + test('each volume in the volumes table shows information about the volume', async function (assert) { const taskGroup = server.schema.taskGroups.where({ jobId: allocation.jobId, name: allocation.taskGroup, }).models[0]; - const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); + const jobTask = taskGroup.tasks.models.find((m) => m.name === task.name); const volume = jobTask.volumeMounts[0]; - Task.volumes[0].as(volumeRow => { + Task.volumes[0].as((volumeRow) => { assert.equal(volumeRow.name, volume.Volume); assert.equal(volumeRow.destination, volume.Destination); - assert.equal(volumeRow.permissions, volume.ReadOnly ? 'Read' : 'Read/Write'); - assert.equal(volumeRow.clientSource, taskGroup.volumes[volume.Volume].Source); + assert.equal( + volumeRow.permissions, + volume.ReadOnly ? 'Read' : 'Read/Write' + ); + assert.equal( + volumeRow.clientSource, + taskGroup.volumes[volume.Volume].Source + ); }); }); - test('each recent event should list the time, type, and description of the event', async function(assert) { + test('each recent event should list the time, type, and description of the event', async function (assert) { const event = server.db.taskEvents.where({ taskStateId: task.id })[0]; const recentEvent = Task.events.objectAt(Task.events.length - 1); @@ -168,12 +216,12 @@ module('Acceptance | task detail', function(hooks) { assert.equal(recentEvent.message, event.displayMessage, 'Event message'); }); - test('when the allocation is not found, the application errors', async function(assert) { + test('when the allocation is not found, the application errors', async function (assert) { await Task.visit({ id: 'not-a-real-allocation', name: task.name }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/allocation/not-a-real-allocation', 'A request to the nonexistent allocation is made' @@ -187,7 +235,7 @@ module('Acceptance | task detail', function(hooks) { assert.equal(Task.error.title, 'Not Found', 'Error message is for 404'); }); - test('when the allocation is found but the task is not, the application errors', async function(assert) { + test('when the allocation is found but the task is not, the application errors', async function (assert) { await Task.visit({ id: allocation.id, name: 'not-a-real-task-name' }); assert.ok( @@ -206,7 +254,7 @@ module('Acceptance | task detail', function(hooks) { assert.equal(Task.error.title, 'Not Found', 'Error message is for 404'); }); - test('task can be restarted', async function(assert) { + test('task can be restarted', async function (assert) { await Task.restart.idle(); await Task.restart.confirm(); @@ -224,14 +272,21 @@ module('Acceptance | task detail', function(hooks) { ); }); - test('when task restart fails (403), an ACL permissions error message is shown', async function(assert) { - server.pretender.put('/v1/client/allocation/:id/restart', () => [403, {}, '']); + test('when task restart fails (403), an ACL permissions error message is shown', async function (assert) { + server.pretender.put('/v1/client/allocation/:id/restart', () => [ + 403, + {}, + '', + ]); await Task.restart.idle(); await Task.restart.confirm(); assert.ok(Task.inlineError.isShown, 'Inline error is shown'); - assert.ok(Task.inlineError.title.includes('Could Not Restart Task'), 'Title is descriptive'); + assert.ok( + Task.inlineError.title.includes('Could Not Restart Task'), + 'Title is descriptive' + ); assert.ok( /ACL token.+?allocation lifecycle/.test(Task.inlineError.message), 'Message mentions ACLs and the appropriate permission' @@ -242,9 +297,13 @@ module('Acceptance | task detail', function(hooks) { assert.notOk(Task.inlineError.isShown, 'Inline error is no longer shown'); }); - test('when task restart fails (500), the error message from the API is piped through to the alert', async function(assert) { + test('when task restart fails (500), the error message from the API is piped through to the alert', async function (assert) { const message = 'A plaintext error message'; - server.pretender.put('/v1/client/allocation/:id/restart', () => [500, {}, message]); + server.pretender.put('/v1/client/allocation/:id/restart', () => [ + 500, + {}, + message, + ]); await Task.restart.idle(); await Task.restart.confirm(); @@ -258,48 +317,59 @@ module('Acceptance | task detail', function(hooks) { assert.notOk(Task.inlineError.isShown); }); - test('exec button is present', async function(assert) { + test('exec button is present', async function (assert) { assert.ok(Task.execButton.isPresent); }); }); -module('Acceptance | task detail (no addresses)', function(hooks) { +module('Acceptance | task detail (no addresses)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node'); server.create('job'); - allocation = server.create('allocation', 'withoutTaskWithPorts', { clientStatus: 'running' }); + allocation = server.create('allocation', 'withoutTaskWithPorts', { + clientStatus: 'running', + }); task = server.db.taskStates.where({ allocationId: allocation.id })[0]; await Task.visit({ id: allocation.id, name: task.name }); }); }); -module('Acceptance | task detail (different namespace)', function(hooks) { +module('Acceptance | task detail (different namespace)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node'); server.create('namespace'); server.create('namespace', { id: 'other-namespace' }); - server.create('job', { createAllocations: false, namespaceId: 'other-namespace' }); - allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); + server.create('job', { + createAllocations: false, + namespaceId: 'other-namespace', + }); + allocation = server.create('allocation', 'withTaskWithPorts', { + clientStatus: 'running', + }); task = server.db.taskStates.where({ allocationId: allocation.id })[0]; await Task.visit({ id: allocation.id, name: task.name }); }); - test('breadcrumbs match jobs / job / task group / allocation / task', async function(assert) { + test('breadcrumbs match jobs / job / task group / allocation / task', async function (assert) { const { jobId, taskGroup } = allocation; const job = server.db.jobs.find(jobId); await Layout.breadcrumbFor('jobs.index').visit(); - assert.equal(currentURL(), '/jobs?namespace=*', 'Jobs breadcrumb links correctly'); + assert.equal( + currentURL(), + '/jobs?namespace=*', + 'Jobs breadcrumb links correctly' + ); await Task.visit({ id: allocation.id, name: task.name }); await Layout.breadcrumbFor('jobs.job.index').visit(); @@ -327,41 +397,52 @@ module('Acceptance | task detail (different namespace)', function(hooks) { }); }); -module('Acceptance | task detail (not running)', function(hooks) { +module('Acceptance | task detail (not running)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node'); server.create('namespace'); server.create('namespace', { id: 'other-namespace' }); - server.create('job', { createAllocations: false, namespaceId: 'other-namespace' }); - allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'complete' }); + server.create('job', { + createAllocations: false, + namespaceId: 'other-namespace', + }); + allocation = server.create('allocation', 'withTaskWithPorts', { + clientStatus: 'complete', + }); task = server.db.taskStates.where({ allocationId: allocation.id })[0]; await Task.visit({ id: allocation.id, name: task.name }); }); - test('when the allocation for a task is not running, the resource utilization graphs are replaced by an empty message', async function(assert) { + test('when the allocation for a task is not running, the resource utilization graphs are replaced by an empty message', async function (assert) { assert.equal(Task.resourceCharts.length, 0, 'No resource charts'); - assert.equal(Task.resourceEmptyMessage, "Task isn't running", 'Empty message is appropriate'); + assert.equal( + Task.resourceEmptyMessage, + "Task isn't running", + 'Empty message is appropriate' + ); }); - test('exec button is absent', async function(assert) { + test('exec button is absent', async function (assert) { assert.notOk(Task.execButton.isPresent); }); }); -module('Acceptance | proxy task detail', function(hooks) { +module('Acceptance | proxy task detail', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node'); server.create('job', { createAllocations: false }); - allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); + allocation = server.create('allocation', 'withTaskWithPorts', { + clientStatus: 'running', + }); const taskState = allocation.taskStates.models[0]; const task = server.schema.tasks.findBy({ name: taskState.name }); @@ -371,7 +452,7 @@ module('Acceptance | proxy task detail', function(hooks) { await Task.visit({ id: allocation.id, name: taskState.name }); }); - test('a proxy tag is shown', async function(assert) { + test('a proxy tag is shown', async function (assert) { assert.ok(Task.title.proxyTag.isPresent); }); }); diff --git a/ui/tests/acceptance/task-fs-test.js b/ui/tests/acceptance/task-fs-test.js index 9737e8060..4af319090 100644 --- a/ui/tests/acceptance/task-fs-test.js +++ b/ui/tests/acceptance/task-fs-test.js @@ -10,17 +10,21 @@ let allocation; let task; let files, taskDirectory, directory, nestedDirectory; -module('Acceptance | task fs', function(hooks) { +module('Acceptance | task fs', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node', 'forceIPv4'); const job = server.create('job', { createAllocations: false }); - allocation = server.create('allocation', { jobId: job.id, clientStatus: 'running' }); - task = server.schema.taskStates.where({ allocationId: allocation.id }).models[0]; + allocation = server.create('allocation', { + jobId: job.id, + clientStatus: 'running', + }); + task = server.schema.taskStates.where({ allocationId: allocation.id }) + .models[0]; task.name = 'task-name'; task.save(); @@ -30,7 +34,10 @@ module('Acceptance | task fs', function(hooks) { // Reset files files = []; - taskDirectory = server.create('allocFile', { isDir: true, name: task.name }); + taskDirectory = server.create('allocFile', { + isDir: true, + name: task.name, + }); files.push(taskDirectory); // Nested files @@ -57,10 +64,24 @@ module('Acceptance | task fs', function(hooks) { ); files.push( - server.create('allocFile', { isDir: true, name: 'empty-directory', parent: taskDirectory }) + server.create('allocFile', { + isDir: true, + name: 'empty-directory', + parent: taskDirectory, + }) + ); + files.push( + server.create('allocFile', 'file', { + fileType: 'txt', + parent: taskDirectory, + }) + ); + files.push( + server.create('allocFile', 'file', { + fileType: 'txt', + parent: taskDirectory, + }) ); - files.push(server.create('allocFile', 'file', { fileType: 'txt', parent: taskDirectory })); - files.push(server.create('allocFile', 'file', { fileType: 'txt', parent: taskDirectory })); this.files = files; this.directory = directory; @@ -68,8 +89,12 @@ module('Acceptance | task fs', function(hooks) { }); browseFilesystem({ - visitSegments: ({ allocation, task }) => ({ id: allocation.id, name: task.name }), - getExpectedPathBase: ({ allocation, task }) => `/allocations/${allocation.id}/${task.name}/fs/`, + visitSegments: ({ allocation, task }) => ({ + id: allocation.id, + name: task.name, + }), + getExpectedPathBase: ({ allocation, task }) => + `/allocations/${allocation.id}/${task.name}/fs/`, getTitleComponent: ({ task }) => `Task ${task.name} filesystem`, getBreadcrumbComponent: ({ task }) => task.name, getFilesystemRoot: ({ task }) => task.name, diff --git a/ui/tests/acceptance/task-group-detail-test.js b/ui/tests/acceptance/task-group-detail-test.js index 1b76705fe..c07e0c477 100644 --- a/ui/tests/acceptance/task-group-detail-test.js +++ b/ui/tests/acceptance/task-group-detail-test.js @@ -1,3 +1,5 @@ +/* eslint-disable qunit/require-expect */ +/* eslint-disable qunit/no-conditional-assertions */ import { currentURL, settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -22,11 +24,11 @@ let managementToken; const sum = (total, n) => total + n; -module('Acceptance | task group detail', function(hooks) { +module('Acceptance | task group detail', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node', 'forceIPv4'); @@ -38,7 +40,7 @@ module('Acceptance | task group detail', function(hooks) { const taskGroups = server.db.taskGroups.where({ jobId: job.id }); taskGroup = taskGroups[0]; - tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); + tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); server.create('node', 'forceIPv4'); @@ -57,7 +59,7 @@ module('Acceptance | task group detail', function(hooks) { }); // Set a static name to make the search test deterministic - server.db.allocations.forEach(alloc => { + server.db.allocations.forEach((alloc) => { alloc.name = 'aaaaa'; }); @@ -74,16 +76,16 @@ module('Acceptance | task group detail', function(hooks) { window.localStorage.clear(); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await a11yAudit(assert); }); - test('/jobs/:id/:task-group should list high-level metrics for the allocation', async function(assert) { + test('/jobs/:id/:task-group should list high-level metrics for the allocation', async function (assert) { const totalCPU = tasks.mapBy('resources.CPU').reduce(sum, 0); const totalMemory = tasks.mapBy('resources.MemoryMB').reduce(sum, 0); const totalMemoryMax = tasks - .map(t => t.resources.MemoryMaxMB || t.resources.MemoryMB) + .map((t) => t.resources.MemoryMaxMB || t.resources.MemoryMB) .reduce(sum, 0); const totalDisk = taskGroup.ephemeralDisk.SizeMB; @@ -99,12 +101,18 @@ module('Acceptance | task group detail', function(hooks) { let totalMemoryMaxAddendum = ''; if (totalMemoryMax > totalMemory) { - totalMemoryMaxAddendum = ` (${formatScheduledBytes(totalMemoryMax, 'MiB')} Max)`; + totalMemoryMaxAddendum = ` (${formatScheduledBytes( + totalMemoryMax, + 'MiB' + )} Max)`; } assert.equal( TaskGroup.mem, - `Reserved Memory ${formatScheduledBytes(totalMemory, 'MiB')}${totalMemoryMaxAddendum}`, + `Reserved Memory ${formatScheduledBytes( + totalMemory, + 'MiB' + )}${totalMemoryMaxAddendum}`, 'Aggregated Memory reservation for all tasks' ); assert.equal( @@ -113,13 +121,20 @@ module('Acceptance | task group detail', function(hooks) { 'Aggregated Disk reservation for all tasks' ); - assert.equal(document.title, `Task group ${taskGroup.name} - Job ${job.name} - Nomad`); + assert.equal( + document.title, + `Task group ${taskGroup.name} - Job ${job.name} - Nomad` + ); }); - test('/jobs/:id/:task-group should have breadcrumbs for job and jobs', async function(assert) { + test('/jobs/:id/:task-group should have breadcrumbs for job and jobs', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); - assert.equal(Layout.breadcrumbFor('jobs.index').text, 'Jobs', 'First breadcrumb says jobs'); + assert.equal( + Layout.breadcrumbFor('jobs.index').text, + 'Jobs', + 'First breadcrumb says jobs' + ); assert.equal( Layout.breadcrumbFor('jobs.job.index').text, `Job ${job.name}`, @@ -132,14 +147,14 @@ module('Acceptance | task group detail', function(hooks) { ); }); - test('/jobs/:id/:task-group first breadcrumb should link to jobs', async function(assert) { + test('/jobs/:id/:task-group first breadcrumb should link to jobs', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await Layout.breadcrumbFor('jobs.index').visit(); assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs'); }); - test('/jobs/:id/:task-group second breadcrumb should link to the job for the task group', async function(assert) { + test('/jobs/:id/:task-group second breadcrumb should link to the job for the task group', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await Layout.breadcrumbFor('jobs.job.index').visit(); @@ -150,7 +165,7 @@ module('Acceptance | task group detail', function(hooks) { ); }); - test('when the user has a client token that has a namespace with a policy to run and scale a job the autoscaler options should be available', async function(assert) { + test('when the user has a client token that has a namespace with a policy to run and scale a job the autoscaler options should be available', async function (assert) { window.localStorage.clear(); const SCALE_AND_WRITE_NAMESPACE = 'scale-and-write-namespace'; @@ -158,7 +173,9 @@ module('Acceptance | task group detail', function(hooks) { const clientToken = server.create('token'); server.create('namespace', { id: SCALE_AND_WRITE_NAMESPACE }); - const secondNamespace = server.create('namespace', { id: READ_ONLY_NAMESPACE }); + const secondNamespace = server.create('namespace', { + id: READ_ONLY_NAMESPACE, + }); job = server.create('job', { groupCount: 0, @@ -220,7 +237,10 @@ module('Acceptance | task group detail', function(hooks) { namespace: SCALE_AND_WRITE_NAMESPACE, }); - assert.equal(currentURL(), `/jobs/${job.id}/scaling?namespace=${SCALE_AND_WRITE_NAMESPACE}`); + assert.equal( + currentURL(), + `/jobs/${job.id}/scaling?namespace=${SCALE_AND_WRITE_NAMESPACE}` + ); assert.notOk(TaskGroup.countStepper.increment.isDisabled); await TaskGroup.visit({ @@ -228,11 +248,14 @@ module('Acceptance | task group detail', function(hooks) { name: scalingGroup2.name, namespace: secondNamespace.name, }); - assert.equal(currentURL(), `/jobs/${job2.id}/scaling?namespace=${READ_ONLY_NAMESPACE}`); + assert.equal( + currentURL(), + `/jobs/${job2.id}/scaling?namespace=${READ_ONLY_NAMESPACE}` + ); assert.ok(TaskGroup.countStepper.increment.isDisabled); }); - test('/jobs/:id/:task-group should list one page of allocations for the task group', async function(assert) { + test('/jobs/:id/:task-group should list one page of allocations for the task group', async function (assert) { server.createList('allocation', TaskGroup.pageSize, { jobId: job.id, taskGroup: taskGroup.name, @@ -242,7 +265,8 @@ module('Acceptance | task group detail', function(hooks) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); assert.ok( - server.db.allocations.where({ jobId: job.id }).length > TaskGroup.pageSize, + server.db.allocations.where({ jobId: job.id }).length > + TaskGroup.pageSize, 'There are enough allocations to invoke pagination' ); @@ -253,13 +277,17 @@ module('Acceptance | task group detail', function(hooks) { ); }); - test('each allocation should show basic information about the allocation', async function(assert) { + test('each allocation should show basic information about the allocation', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); const allocation = allocations.sortBy('modifyIndex').reverse()[0]; const allocationRow = TaskGroup.allocations.objectAt(0); - assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short id'); + assert.equal( + allocationRow.shortId, + allocation.id.split('-')[0], + 'Allocation short id' + ); assert.equal( allocationRow.createTime, moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'), @@ -270,8 +298,16 @@ module('Acceptance | task group detail', function(hooks) { moment(allocation.modifyTime / 1000000).fromNow(), 'Allocation modify time' ); - assert.equal(allocationRow.status, allocation.clientStatus, 'Client status'); - assert.equal(allocationRow.jobVersion, allocation.jobVersion, 'Job Version'); + assert.equal( + allocationRow.status, + allocation.clientStatus, + 'Client status' + ); + assert.equal( + allocationRow.jobVersion, + allocation.jobVersion, + 'Job Version' + ); assert.equal( allocationRow.client, server.db.nodes.find(allocation.nodeId).id.split('-')[0], @@ -284,7 +320,7 @@ module('Acceptance | task group detail', function(hooks) { ); }); - test('clicking the client ID in the allocation row naviates to the client page', async function(assert) { + test('clicking the client ID in the allocation row naviates to the client page', async function (assert) { // Navigating to the client page requires node:read permission. const policy = server.create('policy', { id: 'node-read', @@ -306,20 +342,27 @@ module('Acceptance | task group detail', function(hooks) { const allocationRow = TaskGroup.allocations.objectAt(0); await allocationRow.visitClient(); - assert.equal(currentURL(), `/clients/${allocation.nodeId}`, 'Node links to node page'); + assert.equal( + currentURL(), + `/clients/${allocation.nodeId}`, + 'Node links to node page' + ); }); - test('each allocation should show stats about the allocation', async function(assert) { + test('each allocation should show stats about the allocation', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); const allocation = allocations.sortBy('name')[0]; const allocationRow = TaskGroup.allocations.objectAt(0); const allocStats = server.db.clientAllocationStats.find(allocation.id); - const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); + const tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0); - const memoryUsed = tasks.reduce((sum, task) => sum + task.resources.MemoryMB, 0); + const memoryUsed = tasks.reduce( + (sum, task) => sum + task.resources.MemoryMB, + 0 + ); assert.equal( allocationRow.cpu, @@ -327,7 +370,9 @@ module('Acceptance | task group detail', function(hooks) { 'CPU %' ); - const roundedTicks = Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks); + const roundedTicks = Math.floor( + allocStats.resourceUsage.CpuStats.TotalTicks + ); assert.equal( allocationRow.cpuTooltip, `${formatHertz(roundedTicks, 'MHz')} / ${formatHertz(cpuUsed, 'MHz')}`, @@ -350,7 +395,7 @@ module('Acceptance | task group detail', function(hooks) { ); }); - test('when the allocation search has no matches, there is an empty message', async function(assert) { + test('when the allocation search has no matches, there is an empty message', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.search('zzzzzz'); @@ -363,17 +408,20 @@ module('Acceptance | task group detail', function(hooks) { ); }); - test('when the allocation has reschedule events, the allocation row is denoted with an icon', async function(assert) { + test('when the allocation has reschedule events, the allocation row is denoted with an icon', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); const rescheduleRow = TaskGroup.allocationFor(allocations[0].id); const normalRow = TaskGroup.allocationFor(allocations[1].id); - assert.ok(rescheduleRow.rescheduled, 'Reschedule row has a reschedule icon'); + assert.ok( + rescheduleRow.rescheduled, + 'Reschedule row has a reschedule icon' + ); assert.notOk(normalRow.rescheduled, 'Normal row has no reschedule icon'); }); - test('/jobs/:id/:task-group should present task lifecycles', async function(assert) { + test('/jobs/:id/:task-group should present task lifecycles', async function (assert) { job = server.create('job', { groupsCount: 2, groupTaskCount: 3, @@ -385,30 +433,36 @@ module('Acceptance | task group detail', function(hooks) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); assert.ok(TaskGroup.lifecycleChart.isPresent); - assert.equal(TaskGroup.lifecycleChart.title, 'Task Lifecycle Configuration'); + assert.equal( + TaskGroup.lifecycleChart.title, + 'Task Lifecycle Configuration' + ); - tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); + tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); const taskNames = tasks.mapBy('name'); // This is thoroughly tested in allocation detail tests, so this mostly checks what’s different assert.equal(TaskGroup.lifecycleChart.tasks.length, 3); - TaskGroup.lifecycleChart.tasks.forEach(Task => { + TaskGroup.lifecycleChart.tasks.forEach((Task) => { assert.ok(taskNames.includes(Task.name)); assert.notOk(Task.isActive); assert.notOk(Task.isFinished); }); }); - test('when the task group depends on volumes, the volumes table is shown', async function(assert) { + test('when the task group depends on volumes, the volumes table is shown', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); assert.ok(TaskGroup.hasVolumes); - assert.equal(TaskGroup.volumes.length, Object.keys(taskGroup.volumes).length); + assert.equal( + TaskGroup.volumes.length, + Object.keys(taskGroup.volumes).length + ); }); - test('when the task group does not depend on volumes, the volumes table is not shown', async function(assert) { + test('when the task group does not depend on volumes, the volumes table is not shown', async function (assert) { job = server.create('job', { noHostVolumes: true, shallow: true }); taskGroup = server.db.taskGroups.where({ jobId: job.id })[0]; @@ -417,19 +471,22 @@ module('Acceptance | task group detail', function(hooks) { assert.notOk(TaskGroup.hasVolumes); }); - test('each row in the volumes table lists information about the volume', async function(assert) { + test('each row in the volumes table lists information about the volume', async function (assert) { await TaskGroup.visit({ id: job.id, name: taskGroup.name }); - TaskGroup.volumes[0].as(volumeRow => { + TaskGroup.volumes[0].as((volumeRow) => { const volume = taskGroup.volumes[volumeRow.name]; assert.equal(volumeRow.name, volume.Name); assert.equal(volumeRow.type, volume.Type); assert.equal(volumeRow.source, volume.Source); - assert.equal(volumeRow.permissions, volume.ReadOnly ? 'Read' : 'Read/Write'); + assert.equal( + volumeRow.permissions, + volume.ReadOnly ? 'Read' : 'Read/Write' + ); }); }); - test('the count stepper sends the appropriate POST request', async function(assert) { + test('the count stepper sends the appropriate POST request', async function (assert) { window.localStorage.nomadTokenSecret = managementToken.secretId; job = server.create('job', { @@ -452,14 +509,14 @@ module('Acceptance | task group detail', function(hooks) { await settled(); const scaleRequest = server.pretender.handledRequests.find( - req => req.method === 'POST' && req.url.endsWith('/scale') + (req) => req.method === 'POST' && req.url.endsWith('/scale') ); const requestBody = JSON.parse(scaleRequest.requestBody); assert.equal(requestBody.Target.Group, scalingGroup.name); assert.equal(requestBody.Count, scalingGroup.count + 1); }); - test('the count stepper is disabled when a deployment is running', async function(assert) { + test('the count stepper is disabled when a deployment is running', async function (assert) { window.localStorage.nomadTokenSecret = managementToken.secretId; job = server.create('job', { @@ -484,22 +541,33 @@ module('Acceptance | task group detail', function(hooks) { assert.ok(TaskGroup.countStepper.decrement.isDisabled); }); - test('when the job for the task group is not found, an error message is shown, but the URL persists', async function(assert) { - await TaskGroup.visit({ id: 'not-a-real-job', name: 'not-a-real-task-group' }); + test('when the job for the task group is not found, an error message is shown, but the URL persists', async function (assert) { + await TaskGroup.visit({ + id: 'not-a-real-job', + name: 'not-a-real-task-group', + }); assert.equal( server.pretender.handledRequests - .filter(request => !request.url.includes('policy')) + .filter((request) => !request.url.includes('policy')) .findBy('status', 404).url, '/v1/job/not-a-real-job', 'A request to the nonexistent job is made' ); - assert.equal(currentURL(), '/jobs/not-a-real-job/not-a-real-task-group', 'The URL persists'); + assert.equal( + currentURL(), + '/jobs/not-a-real-job/not-a-real-task-group', + 'The URL persists' + ); assert.ok(TaskGroup.error.isPresent, 'Error message is shown'); - assert.equal(TaskGroup.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + TaskGroup.error.title, + 'Not Found', + 'Error message is for 404' + ); }); - test('when the task group is not found on the job, an error message is shown, but the URL persists', async function(assert) { + test('when the task group is not found on the job, an error message is shown, but the URL persists', async function (assert) { await TaskGroup.visit({ id: job.id, name: 'not-a-real-task-group' }); assert.ok( @@ -509,9 +577,17 @@ module('Acceptance | task group detail', function(hooks) { .includes(`/v1/job/${job.id}`), 'A request to the job is made and succeeds' ); - assert.equal(currentURL(), `/jobs/${job.id}/not-a-real-task-group`, 'The URL persists'); + assert.equal( + currentURL(), + `/jobs/${job.id}/not-a-real-task-group`, + 'The URL persists' + ); assert.ok(TaskGroup.error.isPresent, 'Error message is shown'); - assert.equal(TaskGroup.error.title, 'Not Found', 'Error message is for 404'); + assert.equal( + TaskGroup.error.title, + 'Not Found', + 'Error message is for 404' + ); }); pageSizeSelect({ @@ -529,8 +605,10 @@ module('Acceptance | task group detail', function(hooks) { }, }); - test('when a task group has no scaling events, there is no recent scaling events section', async function(assert) { - const taskGroupScale = job.jobScale.taskGroupScales.models.find(m => m.name === taskGroup.name); + test('when a task group has no scaling events, there is no recent scaling events section', async function (assert) { + const taskGroupScale = job.jobScale.taskGroupScales.models.find( + (m) => m.name === taskGroup.name + ); taskGroupScale.update({ events: [] }); await TaskGroup.visit({ id: job.id, name: taskGroup.name }); @@ -538,8 +616,10 @@ module('Acceptance | task group detail', function(hooks) { assert.notOk(TaskGroup.hasScaleEvents); }); - test('the recent scaling events section shows all recent scaling events in reverse chronological order', async function(assert) { - const taskGroupScale = job.jobScale.taskGroupScales.models.find(m => m.name === taskGroup.name); + test('the recent scaling events section shows all recent scaling events in reverse chronological order', async function (assert) { + const taskGroupScale = job.jobScale.taskGroupScales.models.find( + (m) => m.name === taskGroup.name + ); taskGroupScale.update({ events: [ server.create('scale-event', { error: true }), @@ -558,7 +638,10 @@ module('Acceptance | task group detail', function(hooks) { scaleEvents.forEach((scaleEvent, idx) => { const ScaleEvent = TaskGroup.scaleEvents[idx]; - assert.equal(ScaleEvent.time, moment(scaleEvent.time / 1000000).format('MMM DD HH:mm:ss ZZ')); + assert.equal( + ScaleEvent.time, + moment(scaleEvent.time / 1000000).format('MMM DD HH:mm:ss ZZ') + ); assert.equal(ScaleEvent.message, scaleEvent.message); if (scaleEvent.count != null) { @@ -577,8 +660,10 @@ module('Acceptance | task group detail', function(hooks) { }); }); - test('when a task group has at least two count scaling events and the count scaling events outnumber the non-count scaling events, a timeline is shown in addition to the accordion', async function(assert) { - const taskGroupScale = job.jobScale.taskGroupScales.models.find(m => m.name === taskGroup.name); + test('when a task group has at least two count scaling events and the count scaling events outnumber the non-count scaling events, a timeline is shown in addition to the accordion', async function (assert) { + const taskGroupScale = job.jobScale.taskGroupScales.models.find( + (m) => m.name === taskGroup.name + ); taskGroupScale.update({ events: [ server.create('scale-event', { error: true }), @@ -600,7 +685,7 @@ module('Acceptance | task group detail', function(hooks) { assert.equal( TaskGroup.scalingAnnotations.length, - scaleEvents.filter(ev => ev.count == null).length + scaleEvents.filter((ev) => ev.count == null).length ); }); @@ -609,7 +694,7 @@ module('Acceptance | task group detail', function(hooks) { paramName: 'status', expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'], async beforeEach() { - ['pending', 'running', 'complete', 'failed', 'lost'].forEach(s => { + ['pending', 'running', 'complete', 'failed', 'lost'].forEach((s) => { server.createList('allocation', 5, { clientStatus: s }); }); await TaskGroup.visit({ id: job.id, name: taskGroup.name }); @@ -627,15 +712,18 @@ module('Acceptance | task group detail', function(hooks) { return Array.from( new Set( allocs - .filter(alloc => alloc.jobId == job.id && alloc.taskGroup == taskGroup.name) + .filter( + (alloc) => + alloc.jobId == job.id && alloc.taskGroup == taskGroup.name + ) .mapBy('nodeId') - .map(id => id.split('-')[0]) + .map((id) => id.split('-')[0]) ) ).sort(); }, async beforeEach() { const nodes = server.createList('node', 3, 'forceIPv4'); - nodes.forEach(node => + nodes.forEach((node) => server.createList('allocation', 5, { nodeId: node.id, jobId: job.id, @@ -651,8 +739,11 @@ module('Acceptance | task group detail', function(hooks) { }); }); -function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) { - test(`facet ${label} | the ${label} facet has the correct options`, async function(assert) { +function testFacet( + label, + { facet, paramName, beforeEach, filter, expectedOptions } +) { + test(`facet ${label} | the ${label} facet has the correct options`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -664,13 +755,13 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption } assert.deepEqual( - facet.options.map(option => option.label.trim()), + facet.options.map((option) => option.label.trim()), expectation, 'Options for facet are as expected' ); }); - test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function(assert) { + test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function (assert) { let option; await beforeEach(); @@ -681,7 +772,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption const selection = [option.key]; const expectedAllocs = server.db.allocations - .filter(alloc => filter(alloc, selection)) + .filter((alloc) => filter(alloc, selection)) .sortBy('modifyIndex') .reverse(); @@ -694,7 +785,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption }); }); - test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { + test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function (assert) { const selection = []; await beforeEach(); @@ -708,7 +799,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption selection.push(option2.key); const expectedAllocs = server.db.allocations - .filter(alloc => filter(alloc, selection)) + .filter((alloc) => filter(alloc, selection)) .sortBy('modifyIndex') .reverse(); @@ -721,7 +812,7 @@ function testFacet(label, { facet, paramName, beforeEach, filter, expectedOption }); }); - test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) { const selection = []; await beforeEach(); diff --git a/ui/tests/acceptance/task-logs-test.js b/ui/tests/acceptance/task-logs-test.js index 9887e9892..90ca1e9b3 100644 --- a/ui/tests/acceptance/task-logs-test.js +++ b/ui/tests/acceptance/task-logs-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL } from '@ember/test-helpers'; import { run } from '@ember/runloop'; import { module, test } from 'qunit'; @@ -9,37 +10,48 @@ import TaskLogs from 'nomad-ui/tests/pages/allocations/task/logs'; let allocation; let task; -module('Acceptance | task logs', function(hooks) { +module('Acceptance | task logs', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('agent'); server.create('node', 'forceIPv4'); const job = server.create('job', { createAllocations: false }); - allocation = server.create('allocation', { jobId: job.id, clientStatus: 'running' }); + allocation = server.create('allocation', { + jobId: job.id, + clientStatus: 'running', + }); task = server.db.taskStates.where({ allocationId: allocation.id })[0]; run.later(run, run.cancelTimers, 1000); await TaskLogs.visit({ id: allocation.id, name: task.name }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await a11yAudit(assert); }); - test('/allocation/:id/:task_name/logs should have a log component', async function(assert) { - assert.equal(currentURL(), `/allocations/${allocation.id}/${task.name}/logs`, 'No redirect'); + test('/allocation/:id/:task_name/logs should have a log component', async function (assert) { + assert.equal( + currentURL(), + `/allocations/${allocation.id}/${task.name}/logs`, + 'No redirect' + ); assert.ok(TaskLogs.hasTaskLog, 'Task log component found'); assert.equal(document.title, `Task ${task.name} logs - Nomad`); }); - test('the stdout log immediately starts streaming', async function(assert) { + test('the stdout log immediately starts streaming', async function (assert) { const node = server.db.nodes.find(allocation.nodeId); - const logUrlRegex = new RegExp(`${node.httpAddr}/v1/client/fs/logs/${allocation.id}`); + const logUrlRegex = new RegExp( + `${node.httpAddr}/v1/client/fs/logs/${allocation.id}` + ); assert.ok( - server.pretender.handledRequests.filter(req => logUrlRegex.test(req.url)).length, + server.pretender.handledRequests.filter((req) => + logUrlRegex.test(req.url) + ).length, 'Log requests were made' ); }); diff --git a/ui/tests/acceptance/token-test.js b/ui/tests/acceptance/token-test.js index 9a1f3545c..96a1275e1 100644 --- a/ui/tests/acceptance/token-test.js +++ b/ui/tests/acceptance/token-test.js @@ -14,11 +14,11 @@ let node; let managementToken; let clientToken; -module('Acceptance | tokens', function(hooks) { +module('Acceptance | tokens', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { window.localStorage.clear(); window.sessionStorage.clear(); @@ -29,32 +29,45 @@ module('Acceptance | tokens', function(hooks) { clientToken = server.create('token'); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + await Tokens.visit(); await a11yAudit(assert); }); - test('the token form sets the token in local storage', async function(assert) { + test('the token form sets the token in local storage', async function (assert) { const { secretId } = managementToken; await Tokens.visit(); - assert.ok(window.localStorage.nomadTokenSecret == null, 'No token secret set'); + assert.equal( + window.localStorage.nomadTokenSecret, + null, + 'No token secret set' + ); assert.equal(document.title, 'Tokens - Nomad'); await Tokens.secret(secretId).submit(); - assert.equal(window.localStorage.nomadTokenSecret, secretId, 'Token secret was set'); + assert.equal( + window.localStorage.nomadTokenSecret, + secretId, + 'Token secret was set' + ); }); // TODO: unskip once store.unloadAll reliably waits for in-flight requests to settle - skip('the x-nomad-token header gets sent with requests once it is set', async function(assert) { + skip('the x-nomad-token header gets sent with requests once it is set', async function (assert) { const { secretId } = managementToken; await JobDetail.visit({ id: job.id }); await ClientDetail.visit({ id: node.id }); - assert.ok(server.pretender.handledRequests.length > 1, 'Requests have been made'); + assert.ok( + server.pretender.handledRequests.length > 1, + 'Requests have been made' + ); - server.pretender.handledRequests.forEach(req => { + server.pretender.handledRequests.forEach((req) => { assert.notOk(getHeader(req, 'x-nomad-token'), `No token for ${req.url}`); }); @@ -70,12 +83,16 @@ module('Acceptance | tokens', function(hooks) { assert.ok(newRequests.length > 1, 'New requests have been made'); // Cross-origin requests can't have a token - newRequests.forEach(req => { - assert.equal(getHeader(req, 'x-nomad-token'), secretId, `Token set for ${req.url}`); + newRequests.forEach((req) => { + assert.equal( + getHeader(req, 'x-nomad-token'), + secretId, + `Token set for ${req.url}` + ); }); }); - test('an error message is shown when authenticating a token fails', async function(assert) { + test('an error message is shown when authenticating a token fails', async function (assert) { const { secretId } = managementToken; const bogusSecret = 'this-is-not-the-secret'; assert.notEqual( @@ -87,13 +104,17 @@ module('Acceptance | tokens', function(hooks) { await Tokens.visit(); await Tokens.secret(bogusSecret).submit(); - assert.ok(window.localStorage.nomadTokenSecret == null, 'Token secret is discarded on failure'); + assert.equal( + window.localStorage.nomadTokenSecret, + null, + 'Token secret is discarded on failure' + ); assert.ok(Tokens.errorMessage, 'Token error message is shown'); assert.notOk(Tokens.successMessage, 'Token success message is not shown'); assert.equal(Tokens.policies.length, 0, 'No token policies are shown'); }); - test('a success message and a special management token message are shown when authenticating succeeds', async function(assert) { + test('a success message and a special management token message are shown when authenticating succeeds', async function (assert) { const { secretId } = managementToken; await Tokens.visit(); @@ -105,7 +126,7 @@ module('Acceptance | tokens', function(hooks) { assert.equal(Tokens.policies.length, 0, 'No token policies are shown'); }); - test('a success message and associated policies are shown when authenticating succeeds', async function(assert) { + test('a success message and associated policies are shown when authenticating succeeds', async function (assert) { const { secretId } = clientToken; const policy = clientToken.policies.models[0]; policy.update('description', 'Make sure there is a description'); @@ -115,7 +136,10 @@ module('Acceptance | tokens', function(hooks) { assert.ok(Tokens.successMessage, 'Token success message is shown'); assert.notOk(Tokens.errorMessage, 'Token error message is not shown'); - assert.notOk(Tokens.managementMessage, 'Token management message is not shown'); + assert.notOk( + Tokens.managementMessage, + 'Token management message is not shown' + ); assert.equal( Tokens.policies.length, clientToken.policies.length, @@ -125,11 +149,15 @@ module('Acceptance | tokens', function(hooks) { const policyElement = Tokens.policies.objectAt(0); assert.equal(policyElement.name, policy.name, 'Policy Name'); - assert.equal(policyElement.description, policy.description, 'Policy Description'); + assert.equal( + policyElement.description, + policy.description, + 'Policy Description' + ); assert.equal(policyElement.rules, policy.rules, 'Policy Rules'); }); - test('setting a token clears the store', async function(assert) { + test('setting a token clears the store', async function (assert) { const { secretId } = clientToken; await Jobs.visit(); @@ -138,7 +166,7 @@ module('Acceptance | tokens', function(hooks) { await Tokens.visit(); await Tokens.secret(secretId).submit(); - server.pretender.get('/v1/jobs', function() { + server.pretender.get('/v1/jobs', function () { return [200, {}, '[]']; }); @@ -148,24 +176,34 @@ module('Acceptance | tokens', function(hooks) { assert.notOk(find('[data-test-job-row]'), 'No jobs found'); }); - test('when the ott query parameter is present upon application load it’s exchanged for a token', async function(assert) { + test('when the ott query parameter is present upon application load it’s exchanged for a token', async function (assert) { const { oneTimeSecret, secretId } = managementToken; await JobDetail.visit({ id: job.id, ott: oneTimeSecret }); - assert.notOk(currentURL().includes(oneTimeSecret), 'OTT is cleared from the URL after loading'); + assert.notOk( + currentURL().includes(oneTimeSecret), + 'OTT is cleared from the URL after loading' + ); await Tokens.visit(); - assert.equal(window.localStorage.nomadTokenSecret, secretId, 'Token secret was set'); + assert.equal( + window.localStorage.nomadTokenSecret, + secretId, + 'Token secret was set' + ); }); - test('when the ott exchange fails an error is shown', async function(assert) { + test('when the ott exchange fails an error is shown', async function (assert) { await visit('/?ott=fake'); assert.ok(Layout.error.isPresent); assert.equal(Layout.error.title, 'Token Exchange Error'); - assert.equal(Layout.error.message, 'Failed to exchange the one-time token.'); + assert.equal( + Layout.error.message, + 'Failed to exchange the one-time token.' + ); }); function getHeader({ requestHeaders }, name) { diff --git a/ui/tests/acceptance/topology-test.js b/ui/tests/acceptance/topology-test.js index 3685fc2cf..a12cb8880 100644 --- a/ui/tests/acceptance/topology-test.js +++ b/ui/tests/acceptance/topology-test.js @@ -16,15 +16,17 @@ import queryString from 'query-string'; const sumResources = (list, dimension) => list.reduce((agg, val) => agg + (get(val, dimension) || 0), 0); -module('Acceptance | topology', function(hooks) { +module('Acceptance | topology', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('job', { createAllocations: false }); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { + assert.expect(1); + server.createList('node', 3); server.createList('allocation', 5); @@ -32,7 +34,7 @@ module('Acceptance | topology', function(hooks) { await a11yAudit(assert); }); - test('by default the info panel shows cluster aggregate stats', async function(assert) { + test('by default the info panel shows cluster aggregate stats', async function (assert) { server.createList('node', 3); server.createList('allocation', 5); @@ -46,12 +48,17 @@ module('Acceptance | topology', function(hooks) { ); const allocs = server.schema.allocations.all().models; - const scheduledAllocs = allocs.filter(alloc => + const scheduledAllocs = allocs.filter((alloc) => ['pending', 'running'].includes(alloc.clientStatus) ); - assert.equal(Topology.clusterInfoPanel.allocCount, `${scheduledAllocs.length} Allocations`); + assert.equal( + Topology.clusterInfoPanel.allocCount, + `${scheduledAllocs.length} Allocations` + ); - const nodeResources = server.schema.nodes.all().models.mapBy('nodeResources'); + const nodeResources = server.schema.nodes + .all() + .models.mapBy('nodeResources'); const taskResources = scheduledAllocs .mapBy('taskResources.models') .flat() @@ -62,21 +69,32 @@ module('Acceptance | topology', function(hooks) { const reservedMem = sumResources(taskResources, 'Memory.MemoryMB'); const reservedCPU = sumResources(taskResources, 'Cpu.CpuShares'); - assert.equal(Topology.clusterInfoPanel.memoryProgressValue, reservedMem / totalMem); - assert.equal(Topology.clusterInfoPanel.cpuProgressValue, reservedCPU / totalCPU); + assert.equal( + Topology.clusterInfoPanel.memoryProgressValue, + reservedMem / totalMem + ); + assert.equal( + Topology.clusterInfoPanel.cpuProgressValue, + reservedCPU / totalCPU + ); assert.equal( Topology.clusterInfoPanel.memoryAbsoluteValue, - `${formatBytes(reservedMem * 1024 * 1024)} / ${formatBytes(totalMem * 1024 * 1024)} reserved` + `${formatBytes(reservedMem * 1024 * 1024)} / ${formatBytes( + totalMem * 1024 * 1024 + )} reserved` ); assert.equal( Topology.clusterInfoPanel.cpuAbsoluteValue, - `${formatHertz(reservedCPU, 'MHz')} / ${formatHertz(totalCPU, 'MHz')} reserved` + `${formatHertz(reservedCPU, 'MHz')} / ${formatHertz( + totalCPU, + 'MHz' + )} reserved` ); }); - test('all allocations for all namespaces and all clients are queried on load', async function(assert) { + test('all allocations for all namespaces and all clients are queried on load', async function (assert) { server.createList('node', 3); server.createList('allocation', 5); @@ -84,10 +102,14 @@ module('Acceptance | topology', function(hooks) { const requests = this.server.pretender.handledRequests; assert.ok(requests.findBy('url', '/v1/nodes?resources=true')); - const allocationsRequest = requests.find(req => req.url.startsWith('/v1/allocations')); + const allocationsRequest = requests.find((req) => + req.url.startsWith('/v1/allocations') + ); assert.ok(allocationsRequest); - const allocationRequestParams = queryString.parse(allocationsRequest.url.split('?')[1]); + const allocationRequestParams = queryString.parse( + allocationsRequest.url.split('?')[1] + ); assert.deepEqual(allocationRequestParams, { namespace: '*', task_states: 'false', @@ -95,7 +117,7 @@ module('Acceptance | topology', function(hooks) { }); }); - test('when an allocation is selected, the info panel shows information on the allocation', async function(assert) { + test('when an allocation is selected, the info panel shows information on the allocation', async function (assert) { const nodes = server.createList('node', 5); const job = server.create('job', { createAllocations: false }); const taskGroup = server.schema.find('taskGroup', job.taskGroupIds[0]).name; @@ -109,7 +131,7 @@ module('Acceptance | topology', function(hooks) { const sortedNodes = nodes.sortBy('datacenter'); let node, alloc; for (let n of sortedNodes) { - alloc = allocs.find(a => a.nodeId === n.id); + alloc = allocs.find((a) => a.nodeId === n.id); if (alloc) { node = n; break; @@ -121,11 +143,15 @@ module('Acceptance | topology', function(hooks) { .uniq() .sort() .indexOf(node.datacenter); - const nodeIndex = nodes.filterBy('datacenter', node.datacenter).indexOf(node); + const nodeIndex = nodes + .filterBy('datacenter', node.datacenter) + .indexOf(node); const reset = async () => { await Topology.visit(); - await Topology.viz.datacenters[dcIndex].nodes[nodeIndex].memoryRects[0].select(); + await Topology.viz.datacenters[dcIndex].nodes[ + nodeIndex + ].memoryRects[0].select(); }; await reset(); @@ -134,7 +160,10 @@ module('Acceptance | topology', function(hooks) { assert.equal(Topology.allocInfoPanel.id, alloc.id.split('-')[0]); const uniqueClients = allocs.mapBy('nodeId').uniq(); - assert.equal(Topology.allocInfoPanel.siblingAllocs, `Sibling Allocations: ${allocs.length}`); + assert.equal( + Topology.allocInfoPanel.siblingAllocs, + `Sibling Allocations: ${allocs.length}` + ); assert.equal( Topology.allocInfoPanel.uniquePlacements, `Unique Client Placements: ${uniqueClients.length}` @@ -158,10 +187,13 @@ module('Acceptance | topology', function(hooks) { assert.equal(currentURL(), `/clients/${node.id}`); }); - test('changing which allocation is selected changes the metric charts', async function(assert) { + test('changing which allocation is selected changes the metric charts', async function (assert) { server.create('node'); const job1 = server.create('job', { createAllocations: false }); - const taskGroup1 = server.schema.find('taskGroup', job1.taskGroupIds[0]).name; + const taskGroup1 = server.schema.find( + 'taskGroup', + job1.taskGroupIds[0] + ).name; server.create('allocation', { forceRunningClientStatus: true, jobId: job1.id, @@ -169,7 +201,10 @@ module('Acceptance | topology', function(hooks) { }); const job2 = server.create('job', { createAllocations: false }); - const taskGroup2 = server.schema.find('taskGroup', job2.taskGroupIds[0]).name; + const taskGroup2 = server.schema.find( + 'taskGroup', + job2.taskGroupIds[0] + ).name; server.create('allocation', { forceRunningClientStatus: true, jobId: job2.id, @@ -178,15 +213,17 @@ module('Acceptance | topology', function(hooks) { await Topology.visit(); await Topology.viz.datacenters[0].nodes[0].memoryRects[0].select(); - const firstAllocationTaskNames = Topology.allocInfoPanel.charts[0].areas.mapBy('taskName'); + const firstAllocationTaskNames = + Topology.allocInfoPanel.charts[0].areas.mapBy('taskName'); await Topology.viz.datacenters[0].nodes[0].memoryRects[1].select(); - const secondAllocationTaskNames = Topology.allocInfoPanel.charts[0].areas.mapBy('taskName'); + const secondAllocationTaskNames = + Topology.allocInfoPanel.charts[0].areas.mapBy('taskName'); assert.notDeepEqual(firstAllocationTaskNames, secondAllocationTaskNames); }); - test('when a node is selected, the info panel shows information on the node', async function(assert) { + test('when a node is selected, the info panel shows information on the node', async function (assert) { // A high node count is required for node selection const nodes = server.createList('node', 51); const node = nodes.sortBy('datacenter')[0]; @@ -204,7 +241,10 @@ module('Acceptance | topology', function(hooks) { assert.equal(Topology.nodeInfoPanel.address, `Address: ${node.httpAddr}`); assert.equal(Topology.nodeInfoPanel.status, `Status: ${node.status}`); - assert.equal(Topology.nodeInfoPanel.drainingLabel, node.drain ? 'Yes' : 'No'); + assert.equal( + Topology.nodeInfoPanel.drainingLabel, + node.drain ? 'Yes' : 'No' + ); assert.equal( Topology.nodeInfoPanel.eligibleLabel, node.schedulingEligibility === 'eligible' ? 'Yes' : 'No' @@ -226,15 +266,20 @@ module('Acceptance | topology', function(hooks) { const totalMem = node.nodeResources.Memory.MemoryMB; const totalCPU = node.nodeResources.Cpu.CpuShares; - assert.equal(Topology.nodeInfoPanel.memoryProgressValue, reservedMem / totalMem); - assert.equal(Topology.nodeInfoPanel.cpuProgressValue, reservedCPU / totalCPU); + assert.equal( + Topology.nodeInfoPanel.memoryProgressValue, + reservedMem / totalMem + ); + assert.equal( + Topology.nodeInfoPanel.cpuProgressValue, + reservedCPU / totalCPU + ); assert.equal( Topology.nodeInfoPanel.memoryAbsoluteValue, - `${formatScheduledBytes(reservedMem * 1024 * 1024)} / ${formatScheduledBytes( - totalMem, - 'MiB' - )} reserved` + `${formatScheduledBytes( + reservedMem * 1024 * 1024 + )} / ${formatScheduledBytes(totalMem, 'MiB')} reserved` ); assert.equal( @@ -249,7 +294,7 @@ module('Acceptance | topology', function(hooks) { assert.equal(currentURL(), `/clients/${node.id}`); }); - test('when one or more nodes lack the NodeResources property, a warning message is shown', async function(assert) { + test('when one or more nodes lack the NodeResources property, a warning message is shown', async function (assert) { server.createList('node', 3); server.createList('allocation', 5); diff --git a/ui/tests/acceptance/volume-detail-test.js b/ui/tests/acceptance/volume-detail-test.js index d43d2bb31..fb264110a 100644 --- a/ui/tests/acceptance/volume-detail-test.js +++ b/ui/tests/acceptance/volume-detail-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { module, test } from 'qunit'; import { currentURL } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; @@ -20,24 +21,24 @@ const assignReadAlloc = (volume, alloc) => { volume.save(); }; -module('Acceptance | volume detail', function(hooks) { +module('Acceptance | volume detail', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); let volume; - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('node'); server.create('csi-plugin', { createVolumes: false }); volume = server.create('csi-volume'); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await VolumeDetail.visit({ id: volume.id }); await a11yAudit(assert); }); - test('/csi/volumes/:id should have a breadcrumb trail linking back to Volumes and Storage', async function(assert) { + test('/csi/volumes/:id should have a breadcrumb trail linking back to Volumes and Storage', async function (assert) { await VolumeDetail.visit({ id: volume.id }); assert.equal(Layout.breadcrumbFor('csi.index').text, 'Storage'); @@ -45,17 +46,21 @@ module('Acceptance | volume detail', function(hooks) { assert.equal(Layout.breadcrumbFor('csi.volumes.volume').text, volume.name); }); - test('/csi/volumes/:id should show the volume name in the title', async function(assert) { + test('/csi/volumes/:id should show the volume name in the title', async function (assert) { await VolumeDetail.visit({ id: volume.id }); assert.equal(document.title, `CSI Volume ${volume.name} - Nomad`); assert.equal(VolumeDetail.title, volume.name); }); - test('/csi/volumes/:id should list additional details for the volume below the title', async function(assert) { + test('/csi/volumes/:id should list additional details for the volume below the title', async function (assert) { await VolumeDetail.visit({ id: volume.id }); - assert.ok(VolumeDetail.health.includes(volume.schedulable ? 'Schedulable' : 'Unschedulable')); + assert.ok( + VolumeDetail.health.includes( + volume.schedulable ? 'Schedulable' : 'Unschedulable' + ) + ); assert.ok(VolumeDetail.provider.includes(volume.provider)); assert.ok(VolumeDetail.externalId.includes(volume.externalId)); assert.notOk( @@ -64,11 +69,11 @@ module('Acceptance | volume detail', function(hooks) { ); }); - test('/csi/volumes/:id should list all write allocations the volume is attached to', async function(assert) { + test('/csi/volumes/:id should list all write allocations the volume is attached to', async function (assert) { const writeAllocations = server.createList('allocation', 2); const readAllocations = server.createList('allocation', 3); - writeAllocations.forEach(alloc => assignWriteAlloc(volume, alloc)); - readAllocations.forEach(alloc => assignReadAlloc(volume, alloc)); + writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc)); + readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc)); await VolumeDetail.visit({ id: volume.id }); @@ -77,15 +82,18 @@ module('Acceptance | volume detail', function(hooks) { .sortBy('modifyIndex') .reverse() .forEach((allocation, idx) => { - assert.equal(allocation.id, VolumeDetail.writeAllocations.objectAt(idx).id); + assert.equal( + allocation.id, + VolumeDetail.writeAllocations.objectAt(idx).id + ); }); }); - test('/csi/volumes/:id should list all read allocations the volume is attached to', async function(assert) { + test('/csi/volumes/:id should list all read allocations the volume is attached to', async function (assert) { const writeAllocations = server.createList('allocation', 2); const readAllocations = server.createList('allocation', 3); - writeAllocations.forEach(alloc => assignWriteAlloc(volume, alloc)); - readAllocations.forEach(alloc => assignReadAlloc(volume, alloc)); + writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc)); + readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc)); await VolumeDetail.visit({ id: volume.id }); @@ -94,11 +102,14 @@ module('Acceptance | volume detail', function(hooks) { .sortBy('modifyIndex') .reverse() .forEach((allocation, idx) => { - assert.equal(allocation.id, VolumeDetail.readAllocations.objectAt(idx).id); + assert.equal( + allocation.id, + VolumeDetail.readAllocations.objectAt(idx).id + ); }); }); - test('each allocation should have high-level details for the allocation', async function(assert) { + test('each allocation should have high-level details for the allocation', async function (assert) { const allocation = server.create('allocation', { clientStatus: 'running' }); assignWriteAlloc(volume, allocation); @@ -108,14 +119,21 @@ module('Acceptance | volume detail', function(hooks) { jobId: allocation.jobId, }); - const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); + const tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0); - const memoryUsed = tasks.reduce((sum, task) => sum + task.resources.MemoryMB, 0); + const memoryUsed = tasks.reduce( + (sum, task) => sum + task.resources.MemoryMB, + 0 + ); await VolumeDetail.visit({ id: volume.id }); - VolumeDetail.writeAllocations.objectAt(0).as(allocationRow => { - assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short ID'); + VolumeDetail.writeAllocations.objectAt(0).as((allocationRow) => { + assert.equal( + allocationRow.shortId, + allocation.id.split('-')[0], + 'Allocation short ID' + ); assert.equal( allocationRow.createTime, moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'), @@ -126,8 +144,16 @@ module('Acceptance | volume detail', function(hooks) { moment(allocation.modifyTime / 1000000).fromNow(), 'Allocation modify time' ); - assert.equal(allocationRow.status, allocation.clientStatus, 'Client status'); - assert.equal(allocationRow.job, server.db.jobs.find(allocation.jobId).name, 'Job name'); + assert.equal( + allocationRow.status, + allocation.clientStatus, + 'Client status' + ); + assert.equal( + allocationRow.job, + server.db.jobs.find(allocation.jobId).name, + 'Job name' + ); assert.ok(allocationRow.taskGroup, 'Task group name'); assert.ok(allocationRow.jobVersion, 'Job Version'); assert.equal( @@ -145,7 +171,9 @@ module('Acceptance | volume detail', function(hooks) { Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, 'CPU %' ); - const roundedTicks = Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks); + const roundedTicks = Math.floor( + allocStats.resourceUsage.CpuStats.TotalTicks + ); assert.equal( allocationRow.cpuTooltip, `${formatHertz(roundedTicks, 'MHz')} / ${formatHertz(cpuUsed, 'MHz')}`, @@ -158,16 +186,15 @@ module('Acceptance | volume detail', function(hooks) { ); assert.equal( allocationRow.memTooltip, - `${formatBytes(allocStats.resourceUsage.MemoryStats.RSS)} / ${formatBytes( - memoryUsed, - 'MiB' - )}`, + `${formatBytes( + allocStats.resourceUsage.MemoryStats.RSS + )} / ${formatBytes(memoryUsed, 'MiB')}`, 'Detailed memory information is in a tooltip' ); }); }); - test('each allocation should link to the allocation detail page', async function(assert) { + test('each allocation should link to the allocation detail page', async function (assert) { const allocation = server.create('allocation'); assignWriteAlloc(volume, allocation); @@ -177,43 +204,46 @@ module('Acceptance | volume detail', function(hooks) { assert.equal(currentURL(), `/allocations/${allocation.id}`); }); - test('when there are no write allocations, the table presents an empty state', async function(assert) { + test('when there are no write allocations, the table presents an empty state', async function (assert) { await VolumeDetail.visit({ id: volume.id }); assert.ok(VolumeDetail.writeTableIsEmpty); assert.equal(VolumeDetail.writeEmptyState.headline, 'No Write Allocations'); }); - test('when there are no read allocations, the table presents an empty state', async function(assert) { + test('when there are no read allocations, the table presents an empty state', async function (assert) { await VolumeDetail.visit({ id: volume.id }); assert.ok(VolumeDetail.readTableIsEmpty); assert.equal(VolumeDetail.readEmptyState.headline, 'No Read Allocations'); }); - test('the constraints table shows access mode and attachment mode', async function(assert) { + test('the constraints table shows access mode and attachment mode', async function (assert) { await VolumeDetail.visit({ id: volume.id }); assert.equal(VolumeDetail.constraints.accessMode, volume.accessMode); - assert.equal(VolumeDetail.constraints.attachmentMode, volume.attachmentMode); + assert.equal( + VolumeDetail.constraints.attachmentMode, + volume.attachmentMode + ); }); }); // Namespace test: details shows the namespace -module('Acceptance | volume detail (with namespaces)', function(hooks) { +module('Acceptance | volume detail (with namespaces)', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); let volume; - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.createList('namespace', 2); server.create('node'); server.create('csi-plugin', { createVolumes: false }); volume = server.create('csi-volume'); }); - test('/csi/volumes/:id detail ribbon includes the namespace of the volume', async function(assert) { + test('/csi/volumes/:id detail ribbon includes the namespace of the volume', async function (assert) { await VolumeDetail.visit({ id: volume.id, namespace: volume.namespaceId }); assert.ok(VolumeDetail.hasNamespace); diff --git a/ui/tests/acceptance/volumes-list-test.js b/ui/tests/acceptance/volumes-list-test.js index bec92893f..0876aa36a 100644 --- a/ui/tests/acceptance/volumes-list-test.js +++ b/ui/tests/acceptance/volumes-list-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/require-expect */ import { currentURL, visit } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -19,35 +20,35 @@ const assignReadAlloc = (volume, alloc) => { volume.save(); }; -module('Acceptance | volumes list', function(hooks) { +module('Acceptance | volumes list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { server.create('node'); server.create('csi-plugin', { createVolumes: false }); window.localStorage.clear(); }); - test('it passes an accessibility audit', async function(assert) { + test('it passes an accessibility audit', async function (assert) { await VolumesList.visit(); await a11yAudit(assert); }); - test('visiting /csi redirects to /csi/volumes', async function(assert) { + test('visiting /csi redirects to /csi/volumes', async function (assert) { await visit('/csi'); assert.equal(currentURL(), '/csi/volumes'); }); - test('visiting /csi/volumes', async function(assert) { + test('visiting /csi/volumes', async function (assert) { await VolumesList.visit(); assert.equal(currentURL(), '/csi/volumes'); assert.equal(document.title, 'CSI Volumes - Nomad'); }); - test('/csi/volumes should list the first page of volumes sorted by name', async function(assert) { + test('/csi/volumes should list the first page of volumes sorted by name', async function (assert) { const volumeCount = VolumesList.pageSize + 1; server.createList('csi-volume', volumeCount); @@ -60,12 +61,12 @@ module('Acceptance | volumes list', function(hooks) { }); }); - test('each volume row should contain information about the volume', async function(assert) { + test('each volume row should contain information about the volume', async function (assert) { const volume = server.create('csi-volume'); const readAllocs = server.createList('allocation', 2, { shallow: true }); const writeAllocs = server.createList('allocation', 3, { shallow: true }); - readAllocs.forEach(alloc => assignReadAlloc(volume, alloc)); - writeAllocs.forEach(alloc => assignWriteAlloc(volume, alloc)); + readAllocs.forEach((alloc) => assignReadAlloc(volume, alloc)); + writeAllocs.forEach((alloc) => assignWriteAlloc(volume, alloc)); await VolumesList.visit(); @@ -76,14 +77,19 @@ module('Acceptance | volumes list', function(hooks) { const healthy = volume.controllersHealthy; const expected = volume.controllersExpected; const isHealthy = healthy > 0; - controllerHealthStr = `${isHealthy ? 'Healthy' : 'Unhealthy'} (${healthy}/${expected})`; + controllerHealthStr = `${ + isHealthy ? 'Healthy' : 'Unhealthy' + } (${healthy}/${expected})`; } const nodeHealthStr = volume.nodesHealthy > 0 ? 'Healthy' : 'Unhealthy'; assert.equal(volumeRow.name, volume.id); assert.notOk(volumeRow.hasNamespace); - assert.equal(volumeRow.schedulable, volume.schedulable ? 'Schedulable' : 'Unschedulable'); + assert.equal( + volumeRow.schedulable, + volume.schedulable ? 'Schedulable' : 'Unschedulable' + ); assert.equal(volumeRow.controllerHealth, controllerHealthStr); assert.equal( volumeRow.nodeHealth, @@ -93,30 +99,38 @@ module('Acceptance | volumes list', function(hooks) { assert.equal(volumeRow.allocations, readAllocs.length + writeAllocs.length); }); - test('each volume row should link to the corresponding volume', async function(assert) { + test('each volume row should link to the corresponding volume', async function (assert) { const [, secondNamespace] = server.createList('namespace', 2); - const volume = server.create('csi-volume', { namespaceId: secondNamespace.id }); + const volume = server.create('csi-volume', { + namespaceId: secondNamespace.id, + }); await VolumesList.visit({ namespace: '*' }); await VolumesList.volumes.objectAt(0).clickName(); - assert.equal(currentURL(), `/csi/volumes/${volume.id}?namespace=${secondNamespace.id}`); + assert.equal( + currentURL(), + `/csi/volumes/${volume.id}?namespace=${secondNamespace.id}` + ); await VolumesList.visit({ namespace: '*' }); assert.equal(currentURL(), '/csi/volumes?namespace=*'); await VolumesList.volumes.objectAt(0).clickRow(); - assert.equal(currentURL(), `/csi/volumes/${volume.id}?namespace=${secondNamespace.id}`); + assert.equal( + currentURL(), + `/csi/volumes/${volume.id}?namespace=${secondNamespace.id}` + ); }); - test('when there are no volumes, there is an empty message', async function(assert) { + test('when there are no volumes, there is an empty message', async function (assert) { await VolumesList.visit(); assert.ok(VolumesList.isEmpty); assert.equal(VolumesList.emptyState.headline, 'No Volumes'); }); - test('when there are volumes, but no matches for a search, there is an empty message', async function(assert) { + test('when there are volumes, but no matches for a search, there is an empty message', async function (assert) { server.create('csi-volume', { id: 'cat 1' }); server.create('csi-volume', { id: 'cat 2' }); @@ -127,7 +141,7 @@ module('Acceptance | volumes list', function(hooks) { assert.equal(VolumesList.emptyState.headline, 'No Matches'); }); - test('searching resets the current page', async function(assert) { + test('searching resets the current page', async function (assert) { server.createList('csi-volume', VolumesList.pageSize + 1); await VolumesList.visit(); @@ -140,7 +154,7 @@ module('Acceptance | volumes list', function(hooks) { assert.equal(currentURL(), '/csi/volumes?search=foobar'); }); - test('when the cluster has namespaces, each volume row includes the volume namespace', async function(assert) { + test('when the cluster has namespaces, each volume row includes the volume namespace', async function (assert) { server.createList('namespace', 2); const volume = server.create('csi-volume'); @@ -150,10 +164,14 @@ module('Acceptance | volumes list', function(hooks) { assert.equal(volumeRow.namespace, volume.namespaceId); }); - test('when the namespace query param is set, only matching volumes are shown and the namespace value is forwarded to app state', async function(assert) { + test('when the namespace query param is set, only matching volumes are shown and the namespace value is forwarded to app state', async function (assert) { server.createList('namespace', 2); - const volume1 = server.create('csi-volume', { namespaceId: server.db.namespaces[0].id }); - const volume2 = server.create('csi-volume', { namespaceId: server.db.namespaces[1].id }); + const volume1 = server.create('csi-volume', { + namespaceId: server.db.namespaces[0].id, + }); + const volume2 = server.create('csi-volume', { + namespaceId: server.db.namespaces[1].id, + }); await VolumesList.visit(); assert.equal(VolumesList.volumes.length, 2); @@ -170,7 +188,7 @@ module('Acceptance | volumes list', function(hooks) { assert.equal(VolumesList.volumes.objectAt(0).name, volume2.id); }); - test('the active namespace is carried over to the jobs pages', async function(assert) { + test('the active namespace is carried over to the jobs pages', async function (assert) { server.createList('namespace', 2); const namespace = server.db.namespaces[1]; @@ -183,7 +201,7 @@ module('Acceptance | volumes list', function(hooks) { assert.equal(currentURL(), `/jobs?namespace=${namespace.id}`); }); - test('when accessing volumes is forbidden, a message is shown with a link to the tokens page', async function(assert) { + test('when accessing volumes is forbidden, a message is shown with a link to the tokens page', async function (assert) { server.pretender.get('/v1/volumes', () => [403, {}, null]); await VolumesList.visit(); @@ -224,7 +242,7 @@ module('Acceptance | volumes list', function(hooks) { label, { facet, paramName, beforeEach, filter, expectedOptions, optionToSelect } ) { - test(`the ${label} facet has the correct options`, async function(assert) { + test(`the ${label} facet has the correct options`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -236,13 +254,13 @@ module('Acceptance | volumes list', function(hooks) { } assert.deepEqual( - facet.options.map(option => option.label.trim()), + facet.options.map((option) => option.label.trim()), expectation, 'Options for facet are as expected' ); }); - test(`the ${label} facet filters the volumes list by ${label}`, async function(assert) { + test(`the ${label} facet filters the volumes list by ${label}`, async function (assert) { await beforeEach(); await facet.toggle(); @@ -251,7 +269,7 @@ module('Acceptance | volumes list', function(hooks) { await option.select(); const expectedVolumes = server.db.csiVolumes - .filter(volume => filter(volume, selection)) + .filter((volume) => filter(volume, selection)) .sortBy('id'); VolumesList.volumes.forEach((volume, index) => { @@ -263,7 +281,7 @@ module('Acceptance | volumes list', function(hooks) { }); }); - test(`selecting an option in the ${label} facet updates the ${paramName} query param`, async function(assert) { + test(`selecting an option in the ${label} facet updates the ${paramName} query param`, async function (assert) { await beforeEach(); await facet.toggle(); diff --git a/ui/tests/helpers/a11y-audit.js b/ui/tests/helpers/a11y-audit.js index 6ab951a66..a866784f2 100644 --- a/ui/tests/helpers/a11y-audit.js +++ b/ui/tests/helpers/a11y-audit.js @@ -10,7 +10,7 @@ function appendRuleOverrides(overriddenRules) { }, }; - overriddenRules.forEach(rule => (rules[rule] = { enabled: false })); + overriddenRules.forEach((rule) => (rules[rule] = { enabled: false })); return rules; } diff --git a/ui/tests/helpers/codemirror.js b/ui/tests/helpers/codemirror.js index 0cc9674fe..923db4563 100644 --- a/ui/tests/helpers/codemirror.js +++ b/ui/tests/helpers/codemirror.js @@ -3,7 +3,7 @@ const invariant = (truthy, error) => { }; export function getCodeMirrorInstance(container) { - return function(selector) { + return function (selector) { const cmService = container.lookup('service:code-mirror'); const element = document.querySelector(selector); @@ -17,14 +17,14 @@ export function getCodeMirrorInstance(container) { } export default function setupCodeMirror(hooks) { - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.getCodeMirrorInstance = getCodeMirrorInstance(this.owner); // Expose to window for access from page objects window.getCodeMirrorInstance = this.getCodeMirrorInstance; }); - hooks.afterEach(function() { + hooks.afterEach(function () { delete window.getCodeMirrorInstance; delete this.getCodeMirrorInstance; }); diff --git a/ui/tests/helpers/glimmer-factory.js b/ui/tests/helpers/glimmer-factory.js index 5525eee7b..3326155dc 100644 --- a/ui/tests/helpers/glimmer-factory.js +++ b/ui/tests/helpers/glimmer-factory.js @@ -1,22 +1,31 @@ -// Used in glimmer component unit tests. Glimmer components should typically -// be tested with integration tests, but occasionally individual methods or -// properties have logic that isn't coupled to rendering or the DOM and can -// be better tested in a unit fashion. -// -// Use like -// -// setupGlimmerComponentFactory(hooks, 'my-component') -// -// test('testing my component', function(assert) { -// const component = this.createComponent({ hello: 'world' }); -// assert.equal(component.args.hello, 'world'); -// }); +/* eslint-disable qunit/no-commented-tests */ +// We comment test to show an example of how to use the factory function + +/* + Used in glimmer component unit tests. Glimmer components should typically + be tested with integration tests, but occasionally individual methods or + properties have logic that isn't coupled to rendering or the DOM and can + be better tested in a unit fashion. + + Use like + + setupGlimmerComponentFactory(hooks, 'my-component') + + test('testing my component', function(assert) { + const component = this.createComponent({ hello: 'world' }); + assert.equal(component.args.hello, 'world'); + }); +*/ + export default function setupGlimmerComponentFactory(hooks, componentKey) { - hooks.beforeEach(function() { - this.createComponent = glimmerComponentInstantiator(this.owner, componentKey); + hooks.beforeEach(function () { + this.createComponent = glimmerComponentInstantiator( + this.owner, + componentKey + ); }); - hooks.afterEach(function() { + hooks.afterEach(function () { delete this.createComponent; }); } diff --git a/ui/tests/helpers/module-for-job.js b/ui/tests/helpers/module-for-job.js index 87462b063..5fb740a01 100644 --- a/ui/tests/helpers/module-for-job.js +++ b/ui/tests/helpers/module-for-job.js @@ -1,3 +1,5 @@ +/* eslint-disable qunit/require-expect */ +/* eslint-disable qunit/no-conditional-assertions */ import { currentURL } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -6,13 +8,18 @@ import JobDetail from 'nomad-ui/tests/pages/jobs/detail'; import Tokens from 'nomad-ui/tests/pages/settings/tokens'; // eslint-disable-next-line ember/no-test-module-for -export default function moduleForJob(title, context, jobFactory, additionalTests) { +export default function moduleForJob( + title, + context, + jobFactory, + additionalTests +) { let job; - module(title, function(hooks) { + module(title, function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.before(function() { + hooks.before(function () { if (context !== 'allocations' && context !== 'children') { throw new Error( `Invalid context provided to moduleForJob, expected either "allocations" or "children", got ${context}` @@ -20,7 +27,7 @@ export default function moduleForJob(title, context, jobFactory, additionalTests } }); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { server.create('node'); job = jobFactory(); if (!job.namespace || job.namespace === 'default') { @@ -30,7 +37,7 @@ export default function moduleForJob(title, context, jobFactory, additionalTests } }); - test('visiting /jobs/:job_id', async function(assert) { + test('visiting /jobs/:job_id', async function (assert) { const expectedURL = new URL( urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace), window.location @@ -42,7 +49,7 @@ export default function moduleForJob(title, context, jobFactory, additionalTests assert.equal(document.title, `Job ${job.name} - Nomad`); }); - test('the subnav links to overview', async function(assert) { + test('the subnav links to overview', async function (assert) { await JobDetail.tabFor('overview').visit(); const expectedURL = new URL( @@ -55,31 +62,40 @@ export default function moduleForJob(title, context, jobFactory, additionalTests assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); }); - test('the subnav links to definition', async function(assert) { + test('the subnav links to definition', async function (assert) { await JobDetail.tabFor('definition').visit(); assert.equal( currentURL(), - urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}/definition`, job.namespace) + urlWithNamespace( + `/jobs/${encodeURIComponent(job.id)}/definition`, + job.namespace + ) ); }); - test('the subnav links to versions', async function(assert) { + test('the subnav links to versions', async function (assert) { await JobDetail.tabFor('versions').visit(); assert.equal( currentURL(), - urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}/versions`, job.namespace) + urlWithNamespace( + `/jobs/${encodeURIComponent(job.id)}/versions`, + job.namespace + ) ); }); - test('the subnav links to evaluations', async function(assert) { + test('the subnav links to evaluations', async function (assert) { await JobDetail.tabFor('evaluations').visit(); assert.equal( currentURL(), - urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}/evaluations`, job.namespace) + urlWithNamespace( + `/jobs/${encodeURIComponent(job.id)}/evaluations`, + job.namespace + ) ); }); - test('the title buttons are dependent on job status', async function(assert) { + test('the title buttons are dependent on job status', async function (assert) { if (job.status === 'dead') { assert.ok(JobDetail.start.isPresent); assert.notOk(JobDetail.stop.isPresent); @@ -92,7 +108,7 @@ export default function moduleForJob(title, context, jobFactory, additionalTests }); if (context === 'allocations') { - test('allocations for the job are shown in the overview', async function(assert) { + test('allocations for the job are shown in the overview', async function (assert) { assert.ok( JobDetail.allocationsSummary.isPresent, 'Allocations are shown in the summary section' @@ -103,7 +119,7 @@ export default function moduleForJob(title, context, jobFactory, additionalTests ); }); - test('clicking in an allocation row navigates to that allocation', async function(assert) { + test('clicking in an allocation row navigates to that allocation', async function (assert) { const allocationRow = JobDetail.allocations[0]; const allocationId = allocationRow.id; @@ -116,14 +132,18 @@ export default function moduleForJob(title, context, jobFactory, additionalTests ); }); - test('clicking legend item navigates to a pre-filtered allocations table', async function(assert) { - const legendItem = JobDetail.allocationsSummary.legend.clickableItems[1]; + test('clicking legend item navigates to a pre-filtered allocations table', async function (assert) { + const legendItem = + JobDetail.allocationsSummary.legend.clickableItems[1]; const status = legendItem.label; await legendItem.click(); const encodedStatus = encodeURIComponent(JSON.stringify([status])); const expectedURL = new URL( - urlWithNamespace(`/jobs/${job.name}/clients?status=${encodedStatus}`, job.namespace), + urlWithNamespace( + `/jobs/${job.name}/clients?status=${encodedStatus}`, + job.namespace + ), window.location ); const gotURL = new URL(currentURL(), window.location); @@ -131,7 +151,7 @@ export default function moduleForJob(title, context, jobFactory, additionalTests assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); }); - test('clicking in a slice takes you to a pre-filtered allocations table', async function(assert) { + test('clicking in a slice takes you to a pre-filtered allocations table', async function (assert) { const slice = JobDetail.allocationsSummary.slices[1]; const status = slice.label; await slice.click(); @@ -139,7 +159,9 @@ export default function moduleForJob(title, context, jobFactory, additionalTests const encodedStatus = encodeURIComponent(JSON.stringify([status])); const expectedURL = new URL( urlWithNamespace( - `/jobs/${encodeURIComponent(job.name)}/allocations?status=${encodedStatus}`, + `/jobs/${encodeURIComponent( + job.name + )}/allocations?status=${encodedStatus}`, job.namespace ), window.location @@ -150,13 +172,19 @@ export default function moduleForJob(title, context, jobFactory, additionalTests // Sort and compare URL query params. gotURL.searchParams.sort(); expectedURL.searchParams.sort(); - assert.equal(gotURL.searchParams.toString(), expectedURL.searchParams.toString()); + assert.equal( + gotURL.searchParams.toString(), + expectedURL.searchParams.toString() + ); }); } if (context === 'children') { - test('children for the job are shown in the overview', async function(assert) { - assert.ok(JobDetail.childrenSummary.isPresent, 'Children are shown in the summary section'); + test('children for the job are shown in the overview', async function (assert) { + assert.ok( + JobDetail.childrenSummary.isPresent, + 'Children are shown in the summary section' + ); assert.ok( JobDetail.allocationsSummary.isHidden, 'Allocations are not shown in the summary section' @@ -165,7 +193,7 @@ export default function moduleForJob(title, context, jobFactory, additionalTests } for (var testName in additionalTests) { - test(testName, async function(assert) { + test(testName, async function (assert) { await additionalTests[testName].call(this, job, assert); }); } @@ -173,14 +201,18 @@ export default function moduleForJob(title, context, jobFactory, additionalTests } // eslint-disable-next-line ember/no-test-module-for -export function moduleForJobWithClientStatus(title, jobFactory, additionalTests) { +export function moduleForJobWithClientStatus( + title, + jobFactory, + additionalTests +) { let job; - module(title, function(hooks) { + module(title, function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { // Displaying the job status in client requires node:read permission. const policy = server.create('policy', { id: 'node-read', @@ -203,7 +235,7 @@ export function moduleForJobWithClientStatus(title, jobFactory, additionalTests) status: 'ready', }); job = jobFactory(); - clients.forEach(c => { + clients.forEach((c) => { server.create('allocation', { jobId: job.id, nodeId: c.id }); }); if (!job.namespace || job.namespace === 'default') { @@ -213,7 +245,7 @@ export function moduleForJobWithClientStatus(title, jobFactory, additionalTests) } }); - test('job status summary is collapsed when not authorized', async function(assert) { + test('job status summary is collapsed when not authorized', async function (assert) { const clientToken = server.create('token', { type: 'client' }); await Tokens.visit(); await Tokens.secret(clientToken.secretId).submit(); @@ -230,29 +262,36 @@ export function moduleForJobWithClientStatus(title, jobFactory, additionalTests) ); }); - test('the subnav links to clients', async function(assert) { + test('the subnav links to clients', async function (assert) { await JobDetail.tabFor('clients').visit(); assert.equal( currentURL(), - urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}/clients`, job.namespace) + urlWithNamespace( + `/jobs/${encodeURIComponent(job.id)}/clients`, + job.namespace + ) ); }); - test('job status summary is shown in the overview', async function(assert) { + test('job status summary is shown in the overview', async function (assert) { assert.ok( JobDetail.jobClientStatusSummary.statusBar.isPresent, 'Summary bar is displayed in the Job Status in Client summary section' ); }); - test('clicking legend item navigates to a pre-filtered clients table', async function(assert) { - const legendItem = JobDetail.jobClientStatusSummary.statusBar.legend.clickableItems[0]; + test('clicking legend item navigates to a pre-filtered clients table', async function (assert) { + const legendItem = + JobDetail.jobClientStatusSummary.statusBar.legend.clickableItems[0]; const status = legendItem.label; await legendItem.click(); const encodedStatus = encodeURIComponent(JSON.stringify([status])); const expectedURL = new URL( - urlWithNamespace(`/jobs/${job.name}/clients?status=${encodedStatus}`, job.namespace), + urlWithNamespace( + `/jobs/${job.name}/clients?status=${encodedStatus}`, + job.namespace + ), window.location ); const gotURL = new URL(currentURL(), window.location); @@ -260,14 +299,17 @@ export function moduleForJobWithClientStatus(title, jobFactory, additionalTests) assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); }); - test('clicking in a slice takes you to a pre-filtered clients table', async function(assert) { + test('clicking in a slice takes you to a pre-filtered clients table', async function (assert) { const slice = JobDetail.jobClientStatusSummary.statusBar.slices[0]; const status = slice.label; await slice.click(); const encodedStatus = encodeURIComponent(JSON.stringify([status])); const expectedURL = new URL( - urlWithNamespace(`/jobs/${job.name}/clients?status=${encodedStatus}`, job.namespace), + urlWithNamespace( + `/jobs/${job.name}/clients?status=${encodedStatus}`, + job.namespace + ), window.location ); const gotURL = new URL(currentURL(), window.location); @@ -276,11 +318,14 @@ export function moduleForJobWithClientStatus(title, jobFactory, additionalTests) // Sort and compare URL query params. gotURL.searchParams.sort(); expectedURL.searchParams.sort(); - assert.equal(gotURL.searchParams.toString(), expectedURL.searchParams.toString()); + assert.equal( + gotURL.searchParams.toString(), + expectedURL.searchParams.toString() + ); }); for (var testName in additionalTests) { - test(testName, async function(assert) { + test(testName, async function (assert) { await additionalTests[testName].call(this, job, assert); }); } diff --git a/ui/tests/helpers/setup-ability.js b/ui/tests/helpers/setup-ability.js index c2edd14b7..1d8f49982 100644 --- a/ui/tests/helpers/setup-ability.js +++ b/ui/tests/helpers/setup-ability.js @@ -1,10 +1,10 @@ -export default ability => hooks => { - hooks.beforeEach(function() { +export default (ability) => (hooks) => { + hooks.beforeEach(function () { this.ability = this.owner.lookup(`ability:${ability}`); this.can = this.owner.lookup('service:can'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { delete this.ability; delete this.can; }); diff --git a/ui/tests/integration/components/agent-monitor-test.js b/ui/tests/integration/components/agent-monitor-test.js index 2dc4570ee..13947a385 100644 --- a/ui/tests/integration/components/agent-monitor-test.js +++ b/ui/tests/integration/components/agent-monitor-test.js @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-string-prototype-extensions */ import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; @@ -6,27 +7,37 @@ import hbs from 'htmlbars-inline-precompile'; import Pretender from 'pretender'; import sinon from 'sinon'; import { logEncode } from '../../../mirage/data/logs'; -import { selectOpen, selectOpenChoose } from '../../utils/ember-power-select-extensions'; +import { + selectOpen, + selectOpenChoose, +} from '../../utils/ember-power-select-extensions'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | agent-monitor', function(hooks) { +module('Integration | Component | agent-monitor', function (hooks) { setupRenderingTest(hooks); const LOG_MESSAGE = 'log message goes here'; - hooks.beforeEach(function() { + hooks.beforeEach(function () { // Normally this would be called server, but server is a prop of this component. - this.pretender = new Pretender(function() { + this.pretender = new Pretender(function () { this.get('/v1/regions', () => [200, {}, '[]']); this.get('/v1/agent/monitor', ({ queryParams }) => [ 200, {}, - logEncode([`[${(queryParams.log_level || 'info').toUpperCase()}] ${LOG_MESSAGE}\n`], 0), + logEncode( + [ + `[${( + queryParams.log_level || 'info' + ).toUpperCase()}] ${LOG_MESSAGE}\n`, + ], + 0 + ), ]); }); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.pretender.shutdown(); }); @@ -40,7 +51,9 @@ module('Integration | Component | agent-monitor', function(hooks) { @onLevelChange={{this.onLevelChange}} /> `; - test('basic appearance', async function(assert) { + test('basic appearance', async function (assert) { + assert.expect(5); + this.setProperties({ level: 'info', client: { id: 'client1' }, @@ -58,7 +71,7 @@ module('Integration | Component | agent-monitor', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when provided with a client, AgentMonitor streams logs for the client', async function(assert) { + test('when provided with a client, AgentMonitor streams logs for the client', async function (assert) { this.setProperties({ level: 'info', client: { id: 'client1', region: 'us-west-1' }, @@ -76,7 +89,7 @@ module('Integration | Component | agent-monitor', function(hooks) { assert.notOk(logRequest.url.includes('region=')); }); - test('when provided with a server, AgentMonitor streams logs for the server', async function(assert) { + test('when provided with a server, AgentMonitor streams logs for the server', async function (assert) { this.setProperties({ level: 'warn', server: { id: 'server1', region: 'us-west-1' }, @@ -94,7 +107,7 @@ module('Integration | Component | agent-monitor', function(hooks) { assert.notOk(logRequest.url.includes('client_id')); }); - test('switching levels calls onLevelChange and restarts the logger', async function(assert) { + test('switching levels calls onLevelChange and restarts the logger', async function (assert) { const onLevelChange = sinon.spy(); const newLevel = 'trace'; @@ -107,7 +120,6 @@ module('Integration | Component | agent-monitor', function(hooks) { run.later(run, run.cancelTimers, INTERVAL); await render(commonTemplate); - await settled(); const contentId = await selectOpen('[data-test-level-switcher-parent]'); run.later(run, run.cancelTimers, INTERVAL); @@ -121,7 +133,7 @@ module('Integration | Component | agent-monitor', function(hooks) { assert.ok(secondLogRequest.url.includes(`log_level=${newLevel}`)); }); - test('when switching levels, the scrollback is preserved and annotated with a switch message', async function(assert) { + test('when switching levels, the scrollback is preserved and annotated with a switch message', async function (assert) { const newLevel = 'trace'; const onLevelChange = sinon.spy(); @@ -134,8 +146,11 @@ module('Integration | Component | agent-monitor', function(hooks) { run.later(run, run.cancelTimers, INTERVAL); await render(commonTemplate); - await settled(); - assert.equal(find('[data-test-log-cli]').textContent, `[INFO] ${LOG_MESSAGE}\n`); + + assert.equal( + find('[data-test-log-cli]').textContent, + `[INFO] ${LOG_MESSAGE}\n` + ); const contentId = await selectOpen('[data-test-level-switcher-parent]'); run.later(run, run.cancelTimers, INTERVAL); @@ -148,7 +163,7 @@ module('Integration | Component | agent-monitor', function(hooks) { ); }); - test('when switching levels and there is no scrollback, there is no appended switch message', async function(assert) { + test('when switching levels and there is no scrollback, there is no appended switch message', async function (assert) { const newLevel = 'trace'; const onLevelChange = sinon.spy(); @@ -158,7 +173,14 @@ module('Integration | Component | agent-monitor', function(hooks) { {}, queryParams.log_level === 'info' ? logEncode([''], 0) - : logEncode([`[${(queryParams.log_level || 'info').toUpperCase()}] ${LOG_MESSAGE}\n`], 0), + : logEncode( + [ + `[${( + queryParams.log_level || 'info' + ).toUpperCase()}] ${LOG_MESSAGE}\n`, + ], + 0 + ), ]); this.setProperties({ @@ -170,7 +192,7 @@ module('Integration | Component | agent-monitor', function(hooks) { run.later(run, run.cancelTimers, INTERVAL); await render(commonTemplate); - await settled(); + assert.equal(find('[data-test-log-cli]').textContent, ''); const contentId = await selectOpen('[data-test-level-switcher-parent]'); @@ -178,6 +200,9 @@ module('Integration | Component | agent-monitor', function(hooks) { await selectOpenChoose(contentId, newLevel.capitalize()); await settled(); - assert.equal(find('[data-test-log-cli]').textContent, `[TRACE] ${LOG_MESSAGE}\n`); + assert.equal( + find('[data-test-log-cli]').textContent, + `[TRACE] ${LOG_MESSAGE}\n` + ); }); }); diff --git a/ui/tests/integration/components/allocation-row-test.js b/ui/tests/integration/components/allocation-row-test.js index e363f7454..864629e79 100644 --- a/ui/tests/integration/components/allocation-row-test.js +++ b/ui/tests/integration/components/allocation-row-test.js @@ -8,10 +8,10 @@ import Response from 'ember-cli-mirage/response'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | allocation row', function(hooks) { +module('Integration | Component | allocation row', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { fragmentSerializerInitializer(this.owner); this.store = this.owner.lookup('service:store'); this.token = this.owner.lookup('service:token'); @@ -22,11 +22,11 @@ module('Integration | Component | allocation row', function(hooks) { window.localStorage.clear(); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); - test('Allocation row polls for stats, even when it errors or has an invalid response', async function(assert) { + test('Allocation row polls for stats, even when it errors or has an invalid response', async function (assert) { const component = this; let currentFrame = 0; @@ -38,7 +38,7 @@ module('Integration | Component | allocation row', function(hooks) { JSON.stringify({ ResourceUsage: generateResources() }), ]; - this.server.get('/client/allocation/:id/stats', function() { + this.server.get('/client/allocation/:id/stats', function () { const response = frames[++currentFrame]; // Disable polling to stop the EC task in the component @@ -80,7 +80,9 @@ module('Integration | Component | allocation row', function(hooks) { ); }); - test('Allocation row shows warning when it requires drivers that are unhealthy on the node it is running on', async function(assert) { + test('Allocation row shows warning when it requires drivers that are unhealthy on the node it is running on', async function (assert) { + assert.expect(2); + // Driver health status require node:read permission. const policy = server.create('policy', { id: 'node-read', @@ -101,7 +103,7 @@ module('Integration | Component | allocation row', function(hooks) { const node = this.server.schema.nodes.first(); const drivers = node.drivers; - Object.values(drivers).forEach(driver => { + Object.values(drivers).forEach((driver) => { driver.Healthy = false; driver.Detected = true; }); @@ -125,11 +127,16 @@ module('Integration | Component | allocation row', function(hooks) { @context={{context}} /> `); - assert.ok(find('[data-test-icon="unhealthy-driver"]'), 'Unhealthy driver icon is shown'); + assert.ok( + find('[data-test-icon="unhealthy-driver"]'), + 'Unhealthy driver icon is shown' + ); await componentA11yAudit(this.element, assert); }); - test('Allocation row shows an icon indicator when it was preempted', async function(assert) { + test('Allocation row shows an icon indicator when it was preempted', async function (assert) { + assert.expect(2); + const allocId = this.server.create('allocation', 'preempted').id; const allocation = await this.store.findRecord('allocation', allocId); @@ -144,14 +151,16 @@ module('Integration | Component | allocation row', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when an allocation is not running, the utilization graphs are omitted', async function(assert) { + test('when an allocation is not running, the utilization graphs are omitted', async function (assert) { + assert.expect(8); + this.setProperties({ context: 'job', enablePolling: false, }); // All non-running statuses need to be tested - ['pending', 'complete', 'failed', 'lost'].forEach(clientStatus => + ['pending', 'complete', 'failed', 'lost'].forEach((clientStatus) => this.server.create('allocation', { clientStatus }) ); @@ -161,7 +170,7 @@ module('Integration | Component | allocation row', function(hooks) { for (const allocation of allocations.toArray()) { this.set('allocation', allocation); - await this.render(hbs` + await render(hbs` @@ -25,7 +26,9 @@ module('Integration | Component | app breadcrumbs', function(hooks) { assert .dom('[data-test-breadcrumb-default]') - .exists('We register the default breadcrumb component if no type is specified on the crumb'); + .exists( + 'We register the default breadcrumb component if no type is specified on the crumb' + ); const renderedCrumbs = findAll('[data-test-breadcrumb]'); @@ -38,21 +41,20 @@ module('Integration | Component | app breadcrumbs', function(hooks) { }); }); - test('when we register a crumb with a type property, a dedicated breadcrumb/ component renders', async function(assert) { + test('when we register a crumb with a type property, a dedicated breadcrumb/ component renders', async function (assert) { const crumbs = [ { label: 'Jobs', args: ['jobs.index'] }, { type: 'special', label: 'Job', args: ['jobs.job.index'] }, ]; this.set('crumbs', crumbs); - class MockComponent extends Component {} this.owner.register( 'component:breadcrumbs/special', setComponentTemplate( hbs`
Test
`, - MockComponent + templateOnlyComponent() ) ); diff --git a/ui/tests/integration/components/attributes-table-test.js b/ui/tests/integration/components/attributes-table-test.js index 8ea493a9f..ac7873838 100644 --- a/ui/tests/integration/components/attributes-table-test.js +++ b/ui/tests/integration/components/attributes-table-test.js @@ -7,7 +7,7 @@ import flat from 'flat'; const { flatten } = flat; -module('Integration | Component | attributes table', function(hooks) { +module('Integration | Component | attributes table', function (hooks) { setupRenderingTest(hooks); const commonAttributes = { @@ -27,13 +27,17 @@ module('Integration | Component | attributes table', function(hooks) { }, }; - test('should render a row for each key/value pair in a deep object', async function(assert) { + test('should render a row for each key/value pair in a deep object', async function (assert) { + assert.expect(2); + this.set('attributes', commonAttributes); await render(hbs``); const rowsCount = Object.keys(flatten(commonAttributes)).length; assert.equal( - this.element.querySelectorAll('[data-test-attributes-section] [data-test-value]').length, + this.element.querySelectorAll( + '[data-test-attributes-section] [data-test-value]' + ).length, rowsCount, `Table has ${rowsCount} rows with values` ); @@ -41,12 +45,20 @@ module('Integration | Component | attributes table', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('should render the full path of key/value pair from the root of the object', async function(assert) { + test('should render the full path of key/value pair from the root of the object', async function (assert) { this.set('attributes', commonAttributes); await render(hbs``); - assert.equal(find('[data-test-key]').textContent.trim(), 'key', 'Row renders the key'); - assert.equal(find('[data-test-value]').textContent.trim(), 'value', 'Row renders the value'); + assert.equal( + find('[data-test-key]').textContent.trim(), + 'key', + 'Row renders the key' + ); + assert.equal( + find('[data-test-value]').textContent.trim(), + 'value', + 'Row renders the value' + ); const deepRow = findAll('[data-test-attributes-section]')[8]; assert.equal( @@ -59,10 +71,13 @@ module('Integration | Component | attributes table', function(hooks) { 'so.are.deeply.', 'The prefix is faded to put emphasis on the attribute' ); - assert.equal(deepRow.querySelector('[data-test-value]').textContent.trim(), 'properties'); + assert.equal( + deepRow.querySelector('[data-test-value]').textContent.trim(), + 'properties' + ); }); - test('should render a row for key/value pairs even when the value is another object', async function(assert) { + test('should render a row for key/value pairs even when the value is another object', async function (assert) { this.set('attributes', commonAttributes); await render(hbs``); diff --git a/ui/tests/integration/components/breadcrumbs-test.js b/ui/tests/integration/components/breadcrumbs-test.js index 5d24329ff..b8fe92503 100644 --- a/ui/tests/integration/components/breadcrumbs-test.js +++ b/ui/tests/integration/components/breadcrumbs-test.js @@ -4,10 +4,10 @@ import { setupRenderingTest } from 'ember-qunit'; import { click, findAll, render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -module('Integration | Component | breadcrumbs', function(hooks) { +module('Integration | Component | breadcrumbs', function (hooks) { setupRenderingTest(hooks); - test('it declaratively renders a list of registered crumbs', async function(assert) { + test('it declaratively renders a list of registered crumbs', async function (assert) { this.set('isRegistered', false); this.set('toggleCrumb', () => this.set('isRegistered', !this.isRegistered)); await render(hbs` @@ -25,8 +25,12 @@ module('Integration | Component | breadcrumbs', function(hooks) { {{/if}} `); - assert.dom('[data-test-crumb]').exists({ count: 1 }, 'We register one crumb'); - assert.dom('[data-test-crumb]').hasText('Zoey', 'The first registered crumb is Zoey'); + assert + .dom('[data-test-crumb]') + .exists({ count: 1 }, 'We register one crumb'); + assert + .dom('[data-test-crumb]') + .hasText('Zoey', 'The first registered crumb is Zoey'); await click('[data-test-button]'); const crumbs = await findAll('[data-test-crumb]'); @@ -36,19 +40,30 @@ module('Integration | Component | breadcrumbs', function(hooks) { .exists({ count: 2 }, 'The second crumb registered successfully'); assert .dom(crumbs[0]) - .hasText('Zoey', 'Breadcrumbs maintain the order in which they are declared'); + .hasText( + 'Zoey', + 'Breadcrumbs maintain the order in which they are declared' + ); assert .dom(crumbs[1]) - .hasText('Tomster', 'Breadcrumbs maintain the order in which they are declared'); + .hasText( + 'Tomster', + 'Breadcrumbs maintain the order in which they are declared' + ); await click('[data-test-button]'); - assert.dom('[data-test-crumb]').exists({ count: 1 }, 'We deregister one crumb'); assert .dom('[data-test-crumb]') - .hasText('Zoey', 'Zoey remains in the template after Tomster deregisters'); + .exists({ count: 1 }, 'We deregister one crumb'); + assert + .dom('[data-test-crumb]') + .hasText( + 'Zoey', + 'Zoey remains in the template after Tomster deregisters' + ); }); - test('it can register complex crumb objects', async function(assert) { + test('it can register complex crumb objects', async function (assert) { await render(hbs`
    @@ -62,6 +77,9 @@ module('Integration | Component | breadcrumbs', function(hooks) { assert .dom('[data-test-crumb]') - .hasText('Tomster', 'We can access the registered breadcrumbs in the template'); + .hasText( + 'Tomster', + 'We can access the registered breadcrumbs in the template' + ); }); }); diff --git a/ui/tests/integration/components/copy-button-test.js b/ui/tests/integration/components/copy-button-test.js index 734294f37..acc4579cf 100644 --- a/ui/tests/integration/components/copy-button-test.js +++ b/ui/tests/integration/components/copy-button-test.js @@ -6,19 +6,26 @@ import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; import sinon from 'sinon'; -import { triggerCopyError, triggerCopySuccess } from 'ember-cli-clipboard/test-support'; +import { + triggerCopyError, + triggerCopySuccess, +} from 'ember-cli-clipboard/test-support'; -module('Integration | Component | copy-button', function(hooks) { +module('Integration | Component | copy-button', function (hooks) { setupRenderingTest(hooks); - test('it shows the copy icon by default', async function(assert) { + test('it shows the copy icon by default', async function (assert) { + assert.expect(2); + await render(hbs``); assert.dom('.copy-button .icon-is-copy-action').exists(); await componentA11yAudit(this.element, assert); }); - test('it shows the success icon on success and resets afterward', async function(assert) { + test('it shows the success icon on success and resets afterward', async function (assert) { + assert.expect(4); + const clock = sinon.useFakeTimers({ shouldAdvanceTime: true }); await render(hbs``); @@ -37,7 +44,9 @@ module('Integration | Component | copy-button', function(hooks) { clock.restore(); }); - test('it shows the error icon on error', async function(assert) { + test('it shows the error icon on error', async function (assert) { + assert.expect(2); + await render(hbs``); await click('.copy-button button'); diff --git a/ui/tests/integration/components/das/dismissed-test.js b/ui/tests/integration/components/das/dismissed-test.js index c990e964f..c3b4ed741 100644 --- a/ui/tests/integration/components/das/dismissed-test.js +++ b/ui/tests/integration/components/das/dismissed-test.js @@ -5,14 +5,16 @@ import { hbs } from 'ember-cli-htmlbars'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; import sinon from 'sinon'; -module('Integration | Component | das/dismissed', function(hooks) { +module('Integration | Component | das/dismissed', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { window.localStorage.clear(); }); - test('it renders the dismissal interstitial with a button to proceed and an option to never show again and proceeds manually', async function(assert) { + test('it renders the dismissal interstitial with a button to proceed and an option to never show again and proceeds manually', async function (assert) { + assert.expect(3); + const proceedSpy = sinon.spy(); this.set('proceedSpy', proceedSpy); @@ -24,10 +26,15 @@ module('Integration | Component | das/dismissed', function(hooks) { await click('[data-test-understood]'); assert.ok(proceedSpy.calledWith({ manuallyDismissed: true })); - assert.equal(window.localStorage.getItem('nomadRecommendationDismssalUnderstood'), 'true'); + assert.equal( + window.localStorage.getItem('nomadRecommendationDismssalUnderstood'), + 'true' + ); }); - test('it renders the dismissal interstitial with no button when the option to never show again has been chosen and proceeds automatically', async function(assert) { + test('it renders the dismissal interstitial with no button when the option to never show again has been chosen and proceeds automatically', async function (assert) { + assert.expect(3); + window.localStorage.setItem('nomadRecommendationDismssalUnderstood', true); const proceedSpy = sinon.spy(); diff --git a/ui/tests/integration/components/das/recommendation-card-test.js b/ui/tests/integration/components/das/recommendation-card-test.js index c0d0f956f..73f229bbe 100644 --- a/ui/tests/integration/components/das/recommendation-card-test.js +++ b/ui/tests/integration/components/das/recommendation-card-test.js @@ -13,10 +13,10 @@ import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { set } from '@ember/object'; -module('Integration | Component | das/recommendation-card', function(hooks) { +module('Integration | Component | das/recommendation-card', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { const mockRouter = Service.extend({ init() { this._super(...arguments); @@ -30,7 +30,9 @@ module('Integration | Component | das/recommendation-card', function(hooks) { this.owner.register('service:router', mockRouter); }); - test('it renders a recommendation card', async function(assert) { + test('it renders a recommendation card', async function (assert) { + assert.expect(49); + const task1 = { name: 'jortle', reservedCPU: 150, @@ -103,15 +105,17 @@ module('Integration | Component | das/recommendation-card', function(hooks) { assert.equal(RecommendationCard.totalsTable.current.cpu.text, '275 MHz'); assert.equal(RecommendationCard.totalsTable.current.memory.text, '384 MiB'); - RecommendationCard.totalsTable.recommended.cpu.as(RecommendedCpu => { + RecommendationCard.totalsTable.recommended.cpu.as((RecommendedCpu) => { assert.equal(RecommendedCpu.text, '200 MHz'); assert.ok(RecommendedCpu.isDecrease); }); - RecommendationCard.totalsTable.recommended.memory.as(RecommendedMemory => { - assert.equal(RecommendedMemory.text, '512 MiB'); - assert.ok(RecommendedMemory.isIncrease); - }); + RecommendationCard.totalsTable.recommended.memory.as( + (RecommendedMemory) => { + assert.equal(RecommendedMemory.text, '512 MiB'); + assert.ok(RecommendedMemory.isIncrease); + } + ); assert.equal(RecommendationCard.totalsTable.unitDiff.cpu, '-75 MHz'); assert.equal(RecommendationCard.totalsTable.unitDiff.memory, '+128 MiB'); @@ -127,18 +131,28 @@ module('Integration | Component | das/recommendation-card', function(hooks) { ) ); - assert.equal(RecommendationCard.activeTask.totalsTable.current.cpu.text, '150 MHz'); - assert.equal(RecommendationCard.activeTask.totalsTable.current.memory.text, '128 MiB'); + assert.equal( + RecommendationCard.activeTask.totalsTable.current.cpu.text, + '150 MHz' + ); + assert.equal( + RecommendationCard.activeTask.totalsTable.current.memory.text, + '128 MiB' + ); - RecommendationCard.activeTask.totalsTable.recommended.cpu.as(RecommendedCpu => { - assert.equal(RecommendedCpu.text, '50 MHz'); - assert.ok(RecommendedCpu.isDecrease); - }); + RecommendationCard.activeTask.totalsTable.recommended.cpu.as( + (RecommendedCpu) => { + assert.equal(RecommendedCpu.text, '50 MHz'); + assert.ok(RecommendedCpu.isDecrease); + } + ); - RecommendationCard.activeTask.totalsTable.recommended.memory.as(RecommendedMemory => { - assert.equal(RecommendedMemory.text, '192 MiB'); - assert.ok(RecommendedMemory.isIncrease); - }); + RecommendationCard.activeTask.totalsTable.recommended.memory.as( + (RecommendedMemory) => { + assert.equal(RecommendedMemory.text, '192 MiB'); + assert.ok(RecommendedMemory.isIncrease); + } + ); assert.equal(RecommendationCard.activeTask.charts.length, 2); assert.equal( @@ -152,7 +166,7 @@ module('Integration | Component | das/recommendation-card', function(hooks) { assert.equal(RecommendationCard.togglesTable.tasks.length, 2); - await RecommendationCard.togglesTable.tasks[0].as(async FirstTask => { + await RecommendationCard.togglesTable.tasks[0].as(async (FirstTask) => { assert.equal(FirstTask.name, 'jortle'); assert.ok(FirstTask.isActive); @@ -172,25 +186,29 @@ module('Integration | Component | das/recommendation-card', function(hooks) { assert.equal(RecommendationCard.activeTask.name, 'jortle task'); - RecommendationCard.totalsTable.recommended.cpu.as(RecommendedCpu => { + RecommendationCard.totalsTable.recommended.cpu.as((RecommendedCpu) => { assert.equal(RecommendedCpu.text, '300 MHz'); assert.ok(RecommendedCpu.isIncrease); }); - RecommendationCard.activeTask.totalsTable.recommended.cpu.as(RecommendedCpu => { - assert.equal(RecommendedCpu.text, '150 MHz'); - assert.ok(RecommendedCpu.isNeutral); - }); + RecommendationCard.activeTask.totalsTable.recommended.cpu.as( + (RecommendedCpu) => { + assert.equal(RecommendedCpu.text, '150 MHz'); + assert.ok(RecommendedCpu.isNeutral); + } + ); await RecommendationCard.togglesTable.toggleAllMemory.toggle(); assert.notOk(RecommendationCard.togglesTable.tasks[0].memory.isActive); assert.notOk(RecommendationCard.togglesTable.tasks[1].memory.isActive); - RecommendationCard.totalsTable.recommended.memory.as(RecommendedMemory => { - assert.equal(RecommendedMemory.text, '384 MiB'); - assert.ok(RecommendedMemory.isNeutral); - }); + RecommendationCard.totalsTable.recommended.memory.as( + (RecommendedMemory) => { + assert.equal(RecommendedMemory.text, '384 MiB'); + assert.ok(RecommendedMemory.isNeutral); + } + ); await RecommendationCard.togglesTable.tasks[1].click(); @@ -198,12 +216,15 @@ module('Integration | Component | das/recommendation-card', function(hooks) { assert.ok(RecommendationCard.togglesTable.tasks[1].isActive); assert.equal(RecommendationCard.activeTask.name, 'tortle task'); - assert.equal(RecommendationCard.activeTask.totalsTable.current.cpu.text, '125 MHz'); + assert.equal( + RecommendationCard.activeTask.totalsTable.current.cpu.text, + '125 MHz' + ); await componentA11yAudit(this.element, assert); }); - test('it doesn’t have header toggles when there’s only one task', async function(assert) { + test('it doesn’t have header toggles when there’s only one task', async function (assert) { const task1 = { name: 'jortle', reservedCPU: 150, @@ -243,7 +264,7 @@ module('Integration | Component | das/recommendation-card', function(hooks) { assert.notOk(RecommendationCard.togglesTable.toggleAllMemory.isPresent); }); - test('it disables the accept button when all recommendations are disabled', async function(assert) { + test('it disables the accept button when all recommendations are disabled', async function (assert) { const task1 = { name: 'jortle', reservedCPU: 150, @@ -284,7 +305,7 @@ module('Integration | Component | das/recommendation-card', function(hooks) { assert.ok(RecommendationCard.acceptButton.isDisabled); }); - test('it doesn’t show a toggle or chart when there’s no recommendation for that resource', async function(assert) { + test('it doesn’t show a toggle or chart when there’s no recommendation for that resource', async function (assert) { const task1 = { name: 'jortle', reservedCPU: 150, @@ -317,7 +338,10 @@ module('Integration | Component | das/recommendation-card', function(hooks) { await render(hbs``); - assert.equal(RecommendationCard.totalsTable.recommended.memory.text, '128 MiB'); + assert.equal( + RecommendationCard.totalsTable.recommended.memory.text, + '128 MiB' + ); assert.equal(RecommendationCard.totalsTable.unitDiff.memory, '0 MiB'); assert.equal(RecommendationCard.totalsTable.percentDiff.memory, '+0%'); @@ -330,7 +354,7 @@ module('Integration | Component | das/recommendation-card', function(hooks) { assert.notOk(RecommendationCard.activeTask.memoryChart.isPresent); }); - test('it disables a resource’s toggle all toggle when there are no recommendations for it', async function(assert) { + test('it disables a resource’s toggle all toggle when there are no recommendations for it', async function (assert) { const task1 = { name: 'jortle', reservedCPU: 150, @@ -380,7 +404,7 @@ module('Integration | Component | das/recommendation-card', function(hooks) { assert.notOk(RecommendationCard.activeTask.memoryChart.isPresent); }); - test('it renders diff calculations in a sentence', async function(assert) { + test('it renders diff calculations in a sentence', async function (assert) { const task1 = { name: 'jortle', reservedCPU: 150, @@ -507,7 +531,7 @@ module('Integration | Component | das/recommendation-card', function(hooks) { ); }); - test('it renders diff calculations in a sentence with no aggregation for one allocatio', async function(assert) { + test('it renders diff calculations in a sentence with no aggregation for one allocatio', async function (assert) { const task1 = { name: 'jortle', reservedCPU: 150, @@ -601,9 +625,14 @@ class MockRecommendationSummary { @action toggleAllRecommendationsForResource(resource, enabled) { if (enabled) { - this.excludedRecommendations = this.excludedRecommendations.rejectBy('resource', resource); + this.excludedRecommendations = this.excludedRecommendations.rejectBy( + 'resource', + resource + ); } else { - this.excludedRecommendations.pushObjects(this.recommendations.filterBy('resource', resource)); + this.excludedRecommendations.pushObjects( + this.recommendations.filterBy('resource', resource) + ); } } } diff --git a/ui/tests/integration/components/das/recommendation-chart-test.js b/ui/tests/integration/components/das/recommendation-chart-test.js index 3df2cb6fc..53b7d6c9e 100644 --- a/ui/tests/integration/components/das/recommendation-chart-test.js +++ b/ui/tests/integration/components/das/recommendation-chart-test.js @@ -4,10 +4,12 @@ import { render, triggerEvent } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | das/recommendation-chart', function(hooks) { +module('Integration | Component | das/recommendation-chart', function (hooks) { setupRenderingTest(hooks); - test('it renders a chart for a recommended CPU increase', async function(assert) { + test('it renders a chart for a recommended CPU increase', async function (assert) { + assert.expect(5); + this.set('resource', 'CPU'); this.set('current', 1312); this.set('recommended', 1919); @@ -29,7 +31,9 @@ module('Integration | Component | das/recommendation-chart', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('it renders a chart for a recommended memory decrease', async function(assert) { + test('it renders a chart for a recommended memory decrease', async function (assert) { + assert.expect(5); + this.set('resource', 'MemoryMB'); this.set('current', 1919); this.set('recommended', 1312); @@ -51,7 +55,7 @@ module('Integration | Component | das/recommendation-chart', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('it handles the maximum being far beyond the recommended', async function(assert) { + test('it handles the maximum being far beyond the recommended', async function (assert) { this.set('resource', 'CPU'); this.set('current', 1312); this.set('recommended', 1919); @@ -74,7 +78,9 @@ module('Integration | Component | das/recommendation-chart', function(hooks) { assert.ok(maxLine.getAttribute('x1') < chartSvg.clientWidth); }); - test('it can be disabled and will show no delta', async function(assert) { + test('it can be disabled and will show no delta', async function (assert) { + assert.expect(6); + this.set('resource', 'CPU'); this.set('current', 1312); this.set('recommended', 1919); @@ -99,7 +105,7 @@ module('Integration | Component | das/recommendation-chart', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('the stats labels shift aligment and disappear to account for space', async function(assert) { + test('the stats labels shift aligment and disappear to account for space', async function (assert) { this.set('resource', 'CPU'); this.set('current', 50); this.set('recommended', 100); @@ -140,7 +146,7 @@ module('Integration | Component | das/recommendation-chart', function(hooks) { assert.dom('[data-test-label=p99]').hasClass('hidden'); }); - test('a legend tooltip shows the sorted stats values on hover', async function(assert) { + test('a legend tooltip shows the sorted stats values on hover', async function (assert) { this.set('resource', 'CPU'); this.set('current', 50); this.set('recommended', 101); diff --git a/ui/tests/integration/components/flex-masonry-test.js b/ui/tests/integration/components/flex-masonry-test.js index 7134f9397..a27b64d1e 100644 --- a/ui/tests/integration/components/flex-masonry-test.js +++ b/ui/tests/integration/components/flex-masonry-test.js @@ -1,22 +1,24 @@ import { htmlSafe } from '@ember/template'; -import { click, find, findAll, settled } from '@ember/test-helpers'; +import { click, find, findAll, render, settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; // Used to prevent XSS warnings in console -const h = height => htmlSafe(`height:${height}px`); +const h = (height) => htmlSafe(`height:${height}px`); -module('Integration | Component | FlexMasonry', function(hooks) { +module('Integration | Component | FlexMasonry', function (hooks) { setupRenderingTest(hooks); - test('presents as a single div when @items is empty', async function(assert) { + test('presents as a single div when @items is empty', async function (assert) { + assert.expect(4); + this.setProperties({ items: [], }); - await this.render(hbs` + await render(hbs` @@ -31,13 +33,13 @@ module('Integration | Component | FlexMasonry', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('each item in @items gets wrapped in a flex-masonry-item wrapper', async function(assert) { + test('each item in @items gets wrapped in a flex-masonry-item wrapper', async function (assert) { this.setProperties({ items: ['one', 'two', 'three'], columns: 2, }); - await this.render(hbs` + await render(hbs` @@ -45,11 +47,14 @@ module('Integration | Component | FlexMasonry', function(hooks) { `); - assert.equal(findAll('[data-test-flex-masonry-item]').length, this.items.length); + assert.equal( + findAll('[data-test-flex-masonry-item]').length, + this.items.length + ); }); - test('the @withSpacing arg adds the with-spacing class', async function(assert) { - await this.render(hbs` + test('the @withSpacing arg adds the with-spacing class', async function (assert) { + await render(hbs` `); - assert.ok(find('[data-test-flex-masonry]').classList.contains('with-spacing')); + assert.ok( + find('[data-test-flex-masonry]').classList.contains('with-spacing') + ); }); - test('individual items along with the reflow action are yielded', async function(assert) { + test('individual items along with the reflow action are yielded', async function (assert) { this.setProperties({ items: ['one', 'two'], columns: 2, height: h(50), }); - await this.render(hbs` + await render(hbs` @@ -86,11 +93,13 @@ module('Integration | Component | FlexMasonry', function(hooks) { // The height of the div changes when reflow is called await click('[data-test-flex-masonry-item]:first-child div'); - await settled(); + assert.equal(div.style.maxHeight, '501px'); }); - test('items are rendered to the DOM in the order they were passed into the component', async function(assert) { + test('items are rendered to the DOM in the order they were passed into the component', async function (assert) { + assert.expect(4); + this.setProperties({ items: [ { text: 'One', height: h(20) }, @@ -101,7 +110,7 @@ module('Integration | Component | FlexMasonry', function(hooks) { columns: 2, }); - await this.render(hbs` + await render(hbs` @@ -114,7 +123,9 @@ module('Integration | Component | FlexMasonry', function(hooks) { }); }); - test('each item gets an order property', async function(assert) { + test('each item gets an order property', async function (assert) { + assert.expect(4); + this.setProperties({ items: [ { text: 'One', height: h(20), expectedOrder: 0 }, @@ -125,7 +136,7 @@ module('Integration | Component | FlexMasonry', function(hooks) { columns: 2, }); - await this.render(hbs` + await render(hbs` @@ -138,7 +149,9 @@ module('Integration | Component | FlexMasonry', function(hooks) { }); }); - test('the last item in each column gets a specific flex-basis value', async function(assert) { + test('the last item in each column gets a specific flex-basis value', async function (assert) { + assert.expect(4); + this.setProperties({ items: [ { text: 'One', height: h(20) }, @@ -151,7 +164,7 @@ module('Integration | Component | FlexMasonry', function(hooks) { columns: 4, }); - await this.render(hbs` + await render(hbs` @@ -161,12 +174,15 @@ module('Integration | Component | FlexMasonry', function(hooks) { findAll('[data-test-flex-masonry-item]').forEach((el, index) => { if (el.style.flexBasis) { + /* eslint-disable-next-line qunit/no-conditional-assertions */ assert.equal(el.style.flexBasis, this.items[index].flexBasis); } }); }); - test('when a multi-column layout becomes a single column layout, all inline-styles are reset', async function(assert) { + test('when a multi-column layout becomes a single column layout, all inline-styles are reset', async function (assert) { + assert.expect(14); + this.setProperties({ items: [ { text: 'One', height: h(20) }, @@ -179,7 +195,7 @@ module('Integration | Component | FlexMasonry', function(hooks) { columns: 4, }); - await this.render(hbs` + await render(hbs` @@ -192,7 +208,7 @@ module('Integration | Component | FlexMasonry', function(hooks) { this.set('columns', 1); await settled(); - findAll('[data-test-flex-masonry-item]').forEach(el => { + findAll('[data-test-flex-masonry-item]').forEach((el) => { assert.equal(el.style.flexBasis, ''); assert.equal(el.style.order, ''); }); diff --git a/ui/tests/integration/components/fs/file-test.js b/ui/tests/integration/components/fs/file-test.js index 79d844637..29ad9c703 100644 --- a/ui/tests/integration/components/fs/file-test.js +++ b/ui/tests/integration/components/fs/file-test.js @@ -9,25 +9,37 @@ import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; const { assign } = Object; const HOST = '1.1.1.1:1111'; -module('Integration | Component | fs/file', function(hooks) { +module('Integration | Component | fs/file', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { - this.server = new Pretender(function() { + hooks.beforeEach(function () { + this.server = new Pretender(function () { this.get('/v1/agent/members', () => [ 200, {}, JSON.stringify({ ServerRegion: 'default', Members: [] }), ]); - this.get('/v1/regions', () => [200, {}, JSON.stringify(['default', 'region-2'])]); - this.get('/v1/client/fs/stream/:alloc_id', () => [200, {}, logEncode(['Hello World'], 0)]); + this.get('/v1/regions', () => [ + 200, + {}, + JSON.stringify(['default', 'region-2']), + ]); + this.get('/v1/client/fs/stream/:alloc_id', () => [ + 200, + {}, + logEncode(['Hello World'], 0), + ]); this.get('/v1/client/fs/cat/:alloc_id', () => [200, {}, 'Hello World']); - this.get('/v1/client/fs/readat/:alloc_id', () => [200, {}, 'Hello World']); + this.get('/v1/client/fs/readat/:alloc_id', () => [ + 200, + {}, + 'Hello World', + ]); }); this.system = this.owner.lookup('service:system'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); window.localStorage.clear(); }); @@ -64,7 +76,9 @@ module('Integration | Component | fs/file', function(hooks) { props ); - test('When a file is text-based, the file mode is streaming', async function(assert) { + test('When a file is text-based, the file mode is streaming', async function (assert) { + assert.expect(3); + const props = makeProps(fileStat('text/plain', 500)); this.setProperties(props); @@ -82,7 +96,9 @@ module('Integration | Component | fs/file', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('When a file is an image, the file mode is image', async function(assert) { + test('When a file is an image, the file mode is image', async function (assert) { + assert.expect(3); + const props = makeProps(fileStat('image/png', 1234)); this.setProperties(props); @@ -100,7 +116,9 @@ module('Integration | Component | fs/file', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('When the file is neither text-based or an image, the unsupported file type empty state is shown', async function(assert) { + test('When the file is neither text-based or an image, the unsupported file type empty state is shown', async function (assert) { + assert.expect(4); + const props = makeProps(fileStat('wat/ohno', 1234)); this.setProperties(props); @@ -114,11 +132,14 @@ module('Integration | Component | fs/file', function(hooks) { find('[data-test-file-box] [data-test-log-cli]'), 'The streaming file component was not rendered' ); - assert.ok(find('[data-test-unsupported-type]'), 'Unsupported file type message is shown'); + assert.ok( + find('[data-test-unsupported-type]'), + 'Unsupported file type message is shown' + ); await componentA11yAudit(this.element, assert); }); - test('The unsupported file type empty state includes a link to the raw file', async function(assert) { + test('The unsupported file type empty state includes a link to the raw file', async function (assert) { const props = makeProps(fileStat('wat/ohno', 1234)); this.setProperties(props); @@ -135,7 +156,7 @@ module('Integration | Component | fs/file', function(hooks) { ); }); - test('The view raw button goes to the correct API url', async function(assert) { + test('The view raw button goes to the correct API url', async function (assert) { const props = makeProps(fileStat('image/png', 1234)); this.setProperties(props); @@ -154,7 +175,7 @@ module('Integration | Component | fs/file', function(hooks) { ); }); - test('The view raw button respects the active region', async function(assert) { + test('The view raw button respects the active region', async function (assert) { const region = 'region-2'; window.localStorage.nomadActiveRegion = region; @@ -178,7 +199,7 @@ module('Integration | Component | fs/file', function(hooks) { ); }); - test('The head and tail buttons are not shown when the file is small', async function(assert) { + test('The head and tail buttons are not shown when the file is small', async function (assert) { const props = makeProps(fileStat('application/json', 5000)); this.setProperties(props); @@ -195,7 +216,7 @@ module('Integration | Component | fs/file', function(hooks) { assert.ok(find('[data-test-log-action="tail"]'), 'Tail button is shown'); }); - test('The head and tail buttons are not shown for an image file', async function(assert) { + test('The head and tail buttons are not shown for an image file', async function (assert) { const props = makeProps(fileStat('image/svg', 5000)); this.setProperties(props); @@ -212,7 +233,9 @@ module('Integration | Component | fs/file', function(hooks) { assert.notOk(find('[data-test-log-action="tail"]'), 'Still no tail button'); }); - test('Yielded content goes in the top-left header area', async function(assert) { + test('Yielded content goes in the top-left header area', async function (assert) { + assert.expect(2); + const props = makeProps(fileStat('image/svg', 5000)); this.setProperties(props); @@ -230,7 +253,7 @@ module('Integration | Component | fs/file', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('The body is full-bleed and dark when the file is streaming', async function(assert) { + test('The body is full-bleed and dark when the file is streaming', async function (assert) { const props = makeProps(fileStat('application/json', 5000)); this.setProperties(props); @@ -241,7 +264,7 @@ module('Integration | Component | fs/file', function(hooks) { assert.ok(classes.includes('is-full-bleed'), 'Body is full-bleed'); }); - test('The body has padding and a light background when the file is not streaming', async function(assert) { + test('The body has padding and a light background when the file is not streaming', async function (assert) { const props = makeProps(fileStat('image/jpeg', 5000)); this.setProperties(props); @@ -257,6 +280,9 @@ module('Integration | Component | fs/file', function(hooks) { classes = Array.from(find('[data-test-file-box]').classList); assert.notOk(classes.includes('is-dark'), 'Body is still not dark'); - assert.notOk(classes.includes('is-full-bleed'), 'Body is still not full-bleed'); + assert.notOk( + classes.includes('is-full-bleed'), + 'Body is still not full-bleed' + ); }); }); diff --git a/ui/tests/integration/components/gauge-chart-test.js b/ui/tests/integration/components/gauge-chart-test.js index 910c31922..fb10f5f7a 100644 --- a/ui/tests/integration/components/gauge-chart-test.js +++ b/ui/tests/integration/components/gauge-chart-test.js @@ -8,7 +8,7 @@ import gaugeChart from 'nomad-ui/tests/pages/components/gauge-chart'; const GaugeChart = create(gaugeChart()); -module('Integration | Component | gauge chart', function(hooks) { +module('Integration | Component | gauge chart', function (hooks) { setupRenderingTest(hooks); const commonProperties = () => ({ @@ -17,7 +17,9 @@ module('Integration | Component | gauge chart', function(hooks) { label: 'Gauge', }); - test('presents as an svg, a formatted percentage, and a label', async function(assert) { + test('presents as an svg, a formatted percentage, and a label', async function (assert) { + assert.expect(4); + const props = commonProperties(); this.setProperties(props); @@ -35,7 +37,7 @@ module('Integration | Component | gauge chart', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('the width of the chart is determined based on the container and the height is a function of the width', async function(assert) { + test('the width of the chart is determined based on the container and the height is a function of the width', async function (assert) { const props = commonProperties(); this.setProperties(props); diff --git a/ui/tests/integration/components/image-file-test.js b/ui/tests/integration/components/image-file-test.js index 80299e815..60f0e6813 100644 --- a/ui/tests/integration/components/image-file-test.js +++ b/ui/tests/integration/components/image-file-test.js @@ -1,4 +1,4 @@ -import { find, settled } from '@ember/test-helpers'; +import { find, render } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; @@ -7,7 +7,7 @@ import sinon from 'sinon'; import RSVP from 'rsvp'; import { formatBytes } from 'nomad-ui/utils/units'; -module('Integration | Component | image file', function(hooks) { +module('Integration | Component | image file', function (hooks) { setupRenderingTest(hooks); const commonTemplate = hbs` @@ -20,10 +20,12 @@ module('Integration | Component | image file', function(hooks) { size: 123456, }; - test('component displays the image', async function(assert) { + test('component displays the image', async function (assert) { + assert.expect(3); + this.setProperties(commonProperties); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(find('img'), 'Image is in the DOM'); assert.equal( @@ -35,10 +37,10 @@ module('Integration | Component | image file', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('the image is wrapped in an anchor that links directly to the image', async function(assert) { + test('the image is wrapped in an anchor that links directly to the image', async function (assert) { this.setProperties(commonProperties); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(find('a'), 'Anchor'); assert.ok(find('a > img'), 'Image in anchor'); @@ -47,7 +49,11 @@ module('Integration | Component | image file', function(hooks) { commonProperties.src, `href is ${commonProperties.src}` ); - assert.equal(find('a').getAttribute('target'), '_blank', 'Anchor opens to a new tab'); + assert.equal( + find('a').getAttribute('target'), + '_blank', + 'Anchor opens to a new tab' + ); assert.equal( find('a').getAttribute('rel'), 'noopener noreferrer', @@ -55,13 +61,13 @@ module('Integration | Component | image file', function(hooks) { ); }); - test('component updates image meta when the image loads', async function(assert) { + test('component updates image meta when the image loads', async function (assert) { const { spy, wrapper, notifier } = notifyingSpy(); this.setProperties(commonProperties); this.set('spy', wrapper); - this.render(hbs` + render(hbs` `); @@ -69,11 +75,10 @@ module('Integration | Component | image file', function(hooks) { assert.ok(spy.calledOnce); }); - test('component shows the width, height, and size of the image', async function(assert) { + test('component shows the width, height, and size of the image', async function (assert) { this.setProperties(commonProperties); - await this.render(commonTemplate); - await settled(); + await render(commonTemplate); const statsEl = find('[data-test-file-stats]'); assert.ok( @@ -81,7 +86,9 @@ module('Integration | Component | image file', function(hooks) { 'Width and height are formatted correctly' ); assert.ok( - statsEl.textContent.trim().endsWith(formatBytes(commonProperties.size) + ')'), + statsEl.textContent + .trim() + .endsWith(formatBytes(commonProperties.size) + ')'), 'Human-formatted size is included' ); }); @@ -90,7 +97,7 @@ module('Integration | Component | image file', function(hooks) { function notifyingSpy() { // The notifier must resolve when the spy wrapper is called let dispatch; - const notifier = new RSVP.Promise(resolve => { + const notifier = new RSVP.Promise((resolve) => { dispatch = resolve; }); diff --git a/ui/tests/integration/components/job-client-status-bar-test.js b/ui/tests/integration/components/job-client-status-bar-test.js index 827c033c7..b4adac86d 100644 --- a/ui/tests/integration/components/job-client-status-bar-test.js +++ b/ui/tests/integration/components/job-client-status-bar-test.js @@ -9,7 +9,7 @@ import jobClientStatusBar from 'nomad-ui/tests/pages/components/job-client-statu const JobClientStatusBar = create(jobClientStatusBar()); -module('Integration | Component | job-client-status-bar', function(hooks) { +module('Integration | Component | job-client-status-bar', function (hooks) { setupRenderingTest(hooks); const commonProperties = () => ({ @@ -42,7 +42,9 @@ module('Integration | Component | job-client-status-bar', function(hooks) { @isNarrow={{isNarrow}} />`; - test('it renders', async function(assert) { + test('it renders', async function (assert) { + assert.expect(2); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -51,7 +53,7 @@ module('Integration | Component | job-client-status-bar', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('it fires the onBarClick handler method when clicking a bar in the chart', async function(assert) { + test('it fires the onBarClick handler method when clicking a bar in the chart', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -59,7 +61,7 @@ module('Integration | Component | job-client-status-bar', function(hooks) { assert.ok(props.onSliceClick.calledOnce); }); - test('it handles an update to client status property', async function(assert) { + test('it handles an update to client status property', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -67,7 +69,11 @@ module('Integration | Component | job-client-status-bar', function(hooks) { ...props, jobClientStatus: { ...props.jobClientStatus, - byStatus: { ...props.jobClientStatus.byStatus, starting: [], running: ['someNodeId'] }, + byStatus: { + ...props.jobClientStatus.byStatus, + starting: [], + running: ['someNodeId'], + }, }, }; this.setProperties(newProps); diff --git a/ui/tests/integration/components/job-diff-test.js b/ui/tests/integration/components/job-diff-test.js index 4661e6a64..dfc1fa403 100644 --- a/ui/tests/integration/components/job-diff-test.js +++ b/ui/tests/integration/components/job-diff-test.js @@ -5,7 +5,7 @@ import hbs from 'htmlbars-inline-precompile'; import cleanWhitespace from '../../utils/clean-whitespace'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | job diff', function(hooks) { +module('Integration | Component | job diff', function (hooks) { setupRenderingTest(hooks); const commonTemplate = hbs` @@ -16,7 +16,9 @@ module('Integration | Component | job diff', function(hooks) { `; - test('job field diffs', async function(assert) { + test('job field diffs', async function (assert) { + assert.expect(5); + this.set('diff', { ID: 'test-case-1', Type: 'Edited', @@ -37,21 +39,27 @@ module('Integration | Component | job diff', function(hooks) { ); assert.equal( cleanWhitespace( - find('[data-test-diff-section-label="field"][data-test-diff-field="added"]').textContent + find( + '[data-test-diff-section-label="field"][data-test-diff-field="added"]' + ).textContent ), '+ Added Field: "Foobar"', 'Added field is rendered correctly' ); assert.equal( cleanWhitespace( - find('[data-test-diff-section-label="field"][data-test-diff-field="edited"]').textContent + find( + '[data-test-diff-section-label="field"][data-test-diff-field="edited"]' + ).textContent ), '+/- Edited Field: "256" => "512"', 'Edited field is rendered correctly' ); assert.equal( cleanWhitespace( - find('[data-test-diff-section-label="field"][data-test-diff-field="deleted"]').textContent + find( + '[data-test-diff-section-label="field"][data-test-diff-field="deleted"]' + ).textContent ), '- Removed Field: "12"', 'Removed field is rendered correctly' @@ -60,7 +68,9 @@ module('Integration | Component | job diff', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('job object diffs', async function(assert) { + test('job object diffs', async function (assert) { + assert.expect(9); + this.set('diff', { ID: 'test-case-2', Type: 'Edited', @@ -108,38 +118,50 @@ module('Integration | Component | job diff', function(hooks) { assert.ok( cleanWhitespace( - find('[data-test-diff-section-label="object"][data-test-diff-field="added"]').textContent + find( + '[data-test-diff-section-label="object"][data-test-diff-field="added"]' + ).textContent ).startsWith('+ DeepConfiguration {'), 'Added object starts with a JSON block' ); assert.ok( cleanWhitespace( - find('[data-test-diff-section-label="object"][data-test-diff-field="edited"]').textContent + find( + '[data-test-diff-section-label="object"][data-test-diff-field="edited"]' + ).textContent ).startsWith('+/- ComplexProperty {'), 'Edited object starts with a JSON block' ); assert.ok( cleanWhitespace( - find('[data-test-diff-section-label="object"][data-test-diff-field="deleted"]').textContent + find( + '[data-test-diff-section-label="object"][data-test-diff-field="deleted"]' + ).textContent ).startsWith('- DatedStuff {'), 'Removed object starts with a JSON block' ); assert.ok( cleanWhitespace( - find('[data-test-diff-section-label="object"][data-test-diff-field="added"]').textContent + find( + '[data-test-diff-section-label="object"][data-test-diff-field="added"]' + ).textContent ).endsWith('}'), 'Added object ends the JSON block' ); assert.ok( cleanWhitespace( - find('[data-test-diff-section-label="object"][data-test-diff-field="edited"]').textContent + find( + '[data-test-diff-section-label="object"][data-test-diff-field="edited"]' + ).textContent ).endsWith('}'), 'Edited object starts with a JSON block' ); assert.ok( cleanWhitespace( - find('[data-test-diff-section-label="object"][data-test-diff-field="deleted"]').textContent + find( + '[data-test-diff-section-label="object"][data-test-diff-field="deleted"]' + ).textContent ).endsWith('}'), 'Removed object ends the JSON block' ); diff --git a/ui/tests/integration/components/job-editor-test.js b/ui/tests/integration/components/job-editor-test.js index d3818ca4b..aab1e7973 100644 --- a/ui/tests/integration/components/job-editor-test.js +++ b/ui/tests/integration/components/job-editor-test.js @@ -12,11 +12,11 @@ import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; const Editor = create(jobEditor()); -module('Integration | Component | job-editor', function(hooks) { +module('Integration | Component | job-editor', function (hooks) { setupRenderingTest(hooks); setupCodeMirror(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { window.localStorage.clear(); fragmentSerializerInitializer(this.owner); @@ -28,13 +28,13 @@ module('Integration | Component | job-editor', function(hooks) { this.server.create('node'); }); - hooks.afterEach(async function() { + hooks.afterEach(async function () { this.server.shutdown(); }); const newJobName = 'new-job'; const newJobTaskGroupName = 'redis'; - const jsonJob = overrides => { + const jsonJob = (overrides) => { return JSON.stringify( assign( {}, @@ -95,31 +95,44 @@ module('Integration | Component | job-editor', function(hooks) { }; const renderEditJob = async (component, job) => { - component.setProperties({ job, onSubmit: sinon.spy(), onCancel: sinon.spy(), context: 'edit' }); + component.setProperties({ + job, + onSubmit: sinon.spy(), + onCancel: sinon.spy(), + context: 'edit', + }); await component.render(cancelableTemplate); }; - const planJob = async spec => { + const planJob = async (spec) => { await Editor.editor.fillIn(spec); await Editor.plan(); }; - test('the default state is an editor with an explanation popup', async function(assert) { + test('the default state is an editor with an explanation popup', async function (assert) { + assert.expect(3); + const job = await this.store.createRecord('job'); await renderNewJob(this, job); - assert.ok(Editor.editorHelp.isPresent, 'Editor explanation popup is present'); + assert.ok( + Editor.editorHelp.isPresent, + 'Editor explanation popup is present' + ); assert.ok(Editor.editor.isPresent, 'Editor is present'); await componentA11yAudit(this.element, assert); }); - test('the explanation popup can be dismissed', async function(assert) { + test('the explanation popup can be dismissed', async function (assert) { const job = await this.store.createRecord('job'); await renderNewJob(this, job); await Editor.editorHelp.dismiss(); - assert.notOk(Editor.editorHelp.isPresent, 'Editor explanation popup is gone'); + assert.notOk( + Editor.editorHelp.isPresent, + 'Editor explanation popup is gone' + ); assert.equal( window.localStorage.nomadMessageJobEditor, 'false', @@ -127,55 +140,76 @@ module('Integration | Component | job-editor', function(hooks) { ); }); - test('the explanation popup is not shown once the dismissal state is set in localStorage', async function(assert) { + test('the explanation popup is not shown once the dismissal state is set in localStorage', async function (assert) { window.localStorage.nomadMessageJobEditor = 'false'; const job = await this.store.createRecord('job'); await renderNewJob(this, job); - assert.notOk(Editor.editorHelp.isPresent, 'Editor explanation popup is gone'); + assert.notOk( + Editor.editorHelp.isPresent, + 'Editor explanation popup is gone' + ); }); - test('submitting a json job skips the parse endpoint', async function(assert) { + test('submitting a json job skips the parse endpoint', async function (assert) { const spec = jsonJob(); const job = await this.store.createRecord('job'); await renderNewJob(this, job); await planJob(spec); const requests = this.server.pretender.handledRequests.mapBy('url'); - assert.notOk(requests.includes('/v1/jobs/parse'), 'JSON job spec is not parsed'); - assert.ok(requests.includes(`/v1/job/${newJobName}/plan`), 'JSON job spec is still planned'); + assert.notOk( + requests.includes('/v1/jobs/parse'), + 'JSON job spec is not parsed' + ); + assert.ok( + requests.includes(`/v1/job/${newJobName}/plan`), + 'JSON job spec is still planned' + ); }); - test('submitting an hcl job requires the parse endpoint', async function(assert) { + test('submitting an hcl job requires the parse endpoint', async function (assert) { const spec = hclJob(); const job = await this.store.createRecord('job'); await renderNewJob(this, job); await planJob(spec); const requests = this.server.pretender.handledRequests.mapBy('url'); - assert.ok(requests.includes('/v1/jobs/parse'), 'HCL job spec is parsed first'); - assert.ok(requests.includes(`/v1/job/${newJobName}/plan`), 'HCL job spec is planned'); assert.ok( - requests.indexOf('/v1/jobs/parse') < requests.indexOf(`/v1/job/${newJobName}/plan`), + requests.includes('/v1/jobs/parse'), + 'HCL job spec is parsed first' + ); + assert.ok( + requests.includes(`/v1/job/${newJobName}/plan`), + 'HCL job spec is planned' + ); + assert.ok( + requests.indexOf('/v1/jobs/parse') < + requests.indexOf(`/v1/job/${newJobName}/plan`), 'Parse comes before Plan' ); }); - test('when a job is successfully parsed and planned, the plan is shown to the user', async function(assert) { + test('when a job is successfully parsed and planned, the plan is shown to the user', async function (assert) { + assert.expect(4); + const spec = hclJob(); const job = await this.store.createRecord('job'); await renderNewJob(this, job); await planJob(spec); assert.ok(Editor.planOutput, 'The plan is outputted'); - assert.notOk(Editor.editor.isPresent, 'The editor is replaced with the plan output'); + assert.notOk( + Editor.editor.isPresent, + 'The editor is replaced with the plan output' + ); assert.ok(Editor.planHelp.isPresent, 'The plan explanation popup is shown'); await componentA11yAudit(this.element, assert); }); - test('from the plan screen, the cancel button goes back to the editor with the job still in tact', async function(assert) { + test('from the plan screen, the cancel button goes back to the editor with the job still in tact', async function (assert) { const spec = hclJob(); const job = await this.store.createRecord('job'); @@ -183,10 +217,16 @@ module('Integration | Component | job-editor', function(hooks) { await planJob(spec); await Editor.cancel(); assert.ok(Editor.editor.isPresent, 'The editor is shown again'); - assert.equal(Editor.editor.contents, spec, 'The spec that was planned is still in the editor'); + assert.equal( + Editor.editor.contents, + spec, + 'The spec that was planned is still in the editor' + ); }); - test('when parse fails, the parse error message is shown', async function(assert) { + test('when parse fails, the parse error message is shown', async function (assert) { + assert.expect(5); + const spec = hclJob(); const errorMessage = 'Parse Failed!! :o'; const job = await this.store.createRecord('job'); @@ -208,12 +248,18 @@ module('Integration | Component | job-editor', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when plan fails, the plan error message is shown', async function(assert) { + test('when plan fails, the plan error message is shown', async function (assert) { + assert.expect(5); + const spec = hclJob(); const errorMessage = 'Plan Failed!! :o'; const job = await this.store.createRecord('job'); - this.server.pretender.post(`/v1/job/${newJobName}/plan`, () => [400, {}, errorMessage]); + this.server.pretender.post(`/v1/job/${newJobName}/plan`, () => [ + 400, + {}, + errorMessage, + ]); await renderNewJob(this, job); await planJob(spec); @@ -230,7 +276,9 @@ module('Integration | Component | job-editor', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when run fails, the run error message is shown', async function(assert) { + test('when run fails, the run error message is shown', async function (assert) { + assert.expect(5); + const spec = hclJob(); const errorMessage = 'Run Failed!! :o'; const job = await this.store.createRecord('job'); @@ -253,7 +301,9 @@ module('Integration | Component | job-editor', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when the scheduler dry-run has warnings, the warnings are shown to the user', async function(assert) { + test('when the scheduler dry-run has warnings, the warnings are shown to the user', async function (assert) { + assert.expect(4); + const spec = jsonJob({ Unschedulable: true }); const job = await this.store.createRecord('job'); @@ -275,7 +325,9 @@ module('Integration | Component | job-editor', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when the scheduler dry-run has no warnings, a success message is shown to the user', async function(assert) { + test('when the scheduler dry-run has no warnings, a success message is shown to the user', async function (assert) { + assert.expect(3); + const spec = hclJob(); const job = await this.store.createRecord('job'); @@ -293,34 +345,47 @@ module('Integration | Component | job-editor', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when a job is submitted in the edit context, a POST request is made to the update job endpoint', async function(assert) { + test('when a job is submitted in the edit context, a POST request is made to the update job endpoint', async function (assert) { const spec = hclJob(); const job = await this.store.createRecord('job'); await renderEditJob(this, job); await planJob(spec); await Editor.run(); - const requests = this.server.pretender.handledRequests.filterBy('method', 'POST').mapBy('url'); - assert.ok(requests.includes(`/v1/job/${newJobName}`), 'A request was made to job update'); - assert.notOk(requests.includes('/v1/jobs'), 'A request was not made to job create'); + const requests = this.server.pretender.handledRequests + .filterBy('method', 'POST') + .mapBy('url'); + assert.ok( + requests.includes(`/v1/job/${newJobName}`), + 'A request was made to job update' + ); + assert.notOk( + requests.includes('/v1/jobs'), + 'A request was not made to job create' + ); }); - test('when a job is submitted in the new context, a POST request is made to the create job endpoint', async function(assert) { + test('when a job is submitted in the new context, a POST request is made to the create job endpoint', async function (assert) { const spec = hclJob(); const job = await this.store.createRecord('job'); await renderNewJob(this, job); await planJob(spec); await Editor.run(); - const requests = this.server.pretender.handledRequests.filterBy('method', 'POST').mapBy('url'); - assert.ok(requests.includes('/v1/jobs'), 'A request was made to job create'); + const requests = this.server.pretender.handledRequests + .filterBy('method', 'POST') + .mapBy('url'); + assert.ok( + requests.includes('/v1/jobs'), + 'A request was made to job create' + ); assert.notOk( requests.includes(`/v1/job/${newJobName}`), 'A request was not made to job update' ); }); - test('when a job is successfully submitted, the onSubmit hook is called', async function(assert) { + test('when a job is successfully submitted, the onSubmit hook is called', async function (assert) { const spec = hclJob(); const job = await this.store.createRecord('job'); @@ -333,14 +398,16 @@ module('Integration | Component | job-editor', function(hooks) { ); }); - test('when the job-editor cancelable flag is false, there is no cancel button in the header', async function(assert) { + test('when the job-editor cancelable flag is false, there is no cancel button in the header', async function (assert) { const job = await this.store.createRecord('job'); await renderNewJob(this, job); assert.notOk(Editor.cancelEditingIsAvailable, 'No way to cancel editing'); }); - test('when the job-editor cancelable flag is true, there is a cancel button in the header', async function(assert) { + test('when the job-editor cancelable flag is true, there is a cancel button in the header', async function (assert) { + assert.expect(2); + const job = await this.store.createRecord('job'); await renderEditJob(this, job); @@ -349,7 +416,7 @@ module('Integration | Component | job-editor', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when the job-editor cancel button is clicked, the onCancel hook is called', async function(assert) { + test('when the job-editor cancel button is clicked, the onCancel hook is called', async function (assert) { const job = await this.store.createRecord('job'); await renderEditJob(this, job); diff --git a/ui/tests/integration/components/job-page/helpers.js b/ui/tests/integration/components/job-page/helpers.js index ddefc9c28..7b986816d 100644 --- a/ui/tests/integration/components/job-page/helpers.js +++ b/ui/tests/integration/components/job-page/helpers.js @@ -24,12 +24,15 @@ export function expectStartRequest(assert, server, job) { const expectedURL = jobURL(job); const request = server.pretender.handledRequests .filterBy('method', 'POST') - .find(req => req.url === expectedURL); + .find((req) => req.url === expectedURL); const requestPayload = JSON.parse(request.requestBody).Job; assert.ok(request, 'POST URL was made correctly'); - assert.ok(requestPayload.Stop == null, 'The Stop signal is not sent in the POST request'); + assert.ok( + requestPayload.Stop == null, + 'The Stop signal is not sent in the POST request' + ); } export async function expectError(assert, title) { @@ -44,7 +47,10 @@ export async function expectError(assert, title) { ); await click('[data-test-job-error-close]'); - assert.notOk(find('[data-test-job-error-title]'), 'Error message is dismissable'); + assert.notOk( + find('[data-test-job-error-title]'), + 'Error message is dismissable' + ); } export function expectDeleteRequest(assert, server, job) { @@ -53,7 +59,7 @@ export function expectDeleteRequest(assert, server, job) { assert.ok( server.pretender.handledRequests .filterBy('method', 'DELETE') - .find(req => req.url === expectedURL), + .find((req) => req.url === expectedURL), 'DELETE URL was made correctly' ); } diff --git a/ui/tests/integration/components/job-page/parts/body-test.js b/ui/tests/integration/components/job-page/parts/body-test.js index 7a5aab931..f61abc69c 100644 --- a/ui/tests/integration/components/job-page/parts/body-test.js +++ b/ui/tests/integration/components/job-page/parts/body-test.js @@ -5,21 +5,21 @@ import hbs from 'htmlbars-inline-precompile'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | job-page/parts/body', function(hooks) { +module('Integration | Component | job-page/parts/body', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { window.localStorage.clear(); this.server = startMirage(); this.server.createList('namespace', 3); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); window.localStorage.clear(); }); - test('includes a subnav for the job', async function(assert) { + test('includes a subnav for the job', async function (assert) { this.set('job', {}); await render(hbs` @@ -31,7 +31,9 @@ module('Integration | Component | job-page/parts/body', function(hooks) { assert.ok(find('[data-test-subnav="job"]'), 'Job subnav is rendered'); }); - test('the subnav includes the deployments link when the job is a service', async function(assert) { + test('the subnav includes the deployments link when the job is a service', async function (assert) { + assert.expect(4); + const store = this.owner.lookup('service:store'); const job = await store.createRecord('job', { id: 'service-job', @@ -46,15 +48,26 @@ module('Integration | Component | job-page/parts/body', function(hooks) { `); - const subnavLabels = findAll('[data-test-tab]').map(anchor => anchor.textContent); - assert.ok(subnavLabels.some(label => label === 'Definition'), 'Definition link'); - assert.ok(subnavLabels.some(label => label === 'Versions'), 'Versions link'); - assert.ok(subnavLabels.some(label => label === 'Deployments'), 'Deployments link'); + const subnavLabels = findAll('[data-test-tab]').map( + (anchor) => anchor.textContent + ); + assert.ok( + subnavLabels.some((label) => label === 'Definition'), + 'Definition link' + ); + assert.ok( + subnavLabels.some((label) => label === 'Versions'), + 'Versions link' + ); + assert.ok( + subnavLabels.some((label) => label === 'Deployments'), + 'Deployments link' + ); await componentA11yAudit(this.element, assert); }); - test('the subnav does not include the deployments link when the job is not a service', async function(assert) { + test('the subnav does not include the deployments link when the job is not a service', async function (assert) { const store = this.owner.lookup('service:store'); const job = await store.createRecord('job', { id: 'batch-job', @@ -69,13 +82,24 @@ module('Integration | Component | job-page/parts/body', function(hooks) { `); - const subnavLabels = findAll('[data-test-tab]').map(anchor => anchor.textContent); - assert.ok(subnavLabels.some(label => label === 'Definition'), 'Definition link'); - assert.ok(subnavLabels.some(label => label === 'Versions'), 'Versions link'); - assert.notOk(subnavLabels.some(label => label === 'Deployments'), 'Deployments link'); + const subnavLabels = findAll('[data-test-tab]').map( + (anchor) => anchor.textContent + ); + assert.ok( + subnavLabels.some((label) => label === 'Definition'), + 'Definition link' + ); + assert.ok( + subnavLabels.some((label) => label === 'Versions'), + 'Versions link' + ); + assert.notOk( + subnavLabels.some((label) => label === 'Deployments'), + 'Deployments link' + ); }); - test('body yields content to a section after the subnav', async function(assert) { + test('body yields content to a section after the subnav', async function (assert) { this.set('job', {}); await render(hbs` diff --git a/ui/tests/integration/components/job-page/parts/children-test.js b/ui/tests/integration/components/job-page/parts/children-test.js index 29b42eac3..3c7bb98c9 100644 --- a/ui/tests/integration/components/job-page/parts/children-test.js +++ b/ui/tests/integration/components/job-page/parts/children-test.js @@ -7,17 +7,17 @@ import { setupRenderingTest } from 'ember-qunit'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | job-page/parts/children', function(hooks) { +module('Integration | Component | job-page/parts/children', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { window.localStorage.clear(); this.store = this.owner.lookup('service:store'); this.server = startMirage(); this.server.create('namespace'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); window.localStorage.clear(); }); @@ -34,7 +34,7 @@ module('Integration | Component | job-page/parts/children', function(hooks) { options ); - test('lists each child', async function(assert) { + test('lists each child', async function (assert) { this.server.create('job', 'periodic', { id: 'parent', childrenCount: 3, @@ -63,7 +63,9 @@ module('Integration | Component | job-page/parts/children', function(hooks) { ); }); - test('eventually paginates', async function(assert) { + test('eventually paginates', async function (assert) { + assert.expect(5); + const pageSize = 10; window.localStorage.nomadPageSize = pageSize; @@ -89,18 +91,29 @@ module('Integration | Component | job-page/parts/children', function(hooks) { `); const childrenCount = parent.get('children.length'); - assert.ok(childrenCount > pageSize, 'Parent has more children than one page size'); - assert.equal(findAll('[data-test-job-name]').length, pageSize, 'Table length maxes out at 10'); + assert.ok( + childrenCount > pageSize, + 'Parent has more children than one page size' + ); + assert.equal( + findAll('[data-test-job-name]').length, + pageSize, + 'Table length maxes out at 10' + ); assert.ok(find('.pagination-next'), 'Next button is rendered'); assert.ok( - new RegExp(`1.10.+?${childrenCount}`).test(find('.pagination-numbers').textContent.trim()) + new RegExp(`1.10.+?${childrenCount}`).test( + find('.pagination-numbers').textContent.trim() + ) ); await componentA11yAudit(this.element, assert); }); - test('is sorted based on the sortProperty and sortDescending properties', async function(assert) { + test('is sorted based on the sortProperty and sortDescending properties', async function (assert) { + assert.expect(6); + this.server.create('job', 'periodic', { id: 'parent', childrenCount: 3, @@ -144,7 +157,7 @@ module('Integration | Component | job-page/parts/children', function(hooks) { }); }); - test('gotoJob is called when a job row is clicked', async function(assert) { + test('gotoJob is called when a job row is clicked', async function (assert) { const gotoJobSpy = sinon.spy(); this.server.create('job', 'periodic', { diff --git a/ui/tests/integration/components/job-page/parts/latest-deployment-test.js b/ui/tests/integration/components/job-page/parts/latest-deployment-test.js index d51c430cb..873ed200e 100644 --- a/ui/tests/integration/components/job-page/parts/latest-deployment-test.js +++ b/ui/tests/integration/components/job-page/parts/latest-deployment-test.js @@ -7,176 +7,206 @@ import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | job-page/parts/latest-deployment', function(hooks) { - setupRenderingTest(hooks); +module( + 'Integration | Component | job-page/parts/latest-deployment', + function (hooks) { + setupRenderingTest(hooks); - hooks.beforeEach(function() { - fragmentSerializerInitializer(this.owner); - window.localStorage.clear(); - this.store = this.owner.lookup('service:store'); - this.server = startMirage(); - this.server.create('namespace'); - }); - - hooks.afterEach(function() { - this.server.shutdown(); - window.localStorage.clear(); - }); - - test('there is no latest deployment section when the job has no deployments', async function(assert) { - this.server.create('job', { - type: 'service', - noDeployments: true, - createAllocations: false, + hooks.beforeEach(function () { + fragmentSerializerInitializer(this.owner); + window.localStorage.clear(); + this.store = this.owner.lookup('service:store'); + this.server = startMirage(); + this.server.create('namespace'); }); - await this.store.findAll('job'); + hooks.afterEach(function () { + this.server.shutdown(); + window.localStorage.clear(); + }); - this.set('job', this.store.peekAll('job').get('firstObject')); - await render(hbs` + test('there is no latest deployment section when the job has no deployments', async function (assert) { + this.server.create('job', { + type: 'service', + noDeployments: true, + createAllocations: false, + }); + + await this.store.findAll('job'); + + this.set('job', this.store.peekAll('job').get('firstObject')); + await render(hbs` ) `); - assert.notOk(find('[data-test-active-deployment]'), 'No active deployment'); - }); - - test('the latest deployment section shows up for the currently running deployment', async function(assert) { - this.server.create('job', { - type: 'service', - createAllocations: false, - activeDeployment: true, + assert.notOk( + find('[data-test-active-deployment]'), + 'No active deployment' + ); }); - await this.store.findAll('job'); + test('the latest deployment section shows up for the currently running deployment', async function (assert) { + assert.expect(11); - this.set('job', this.store.peekAll('job').get('firstObject')); - await render(hbs` + this.server.create('job', { + type: 'service', + createAllocations: false, + activeDeployment: true, + }); + + await this.store.findAll('job'); + + this.set('job', this.store.peekAll('job').get('firstObject')); + await render(hbs` `); - const deployment = await this.get('job.latestDeployment'); - const version = await deployment.get('version'); + const deployment = await this.get('job.latestDeployment'); + const version = await deployment.get('version'); - assert.ok(find('[data-test-active-deployment]'), 'Active deployment'); - assert.ok( - find('[data-test-active-deployment]').classList.contains('is-info'), - 'Running deployment gets the is-info class' - ); - assert.equal( - find('[data-test-active-deployment-stat="id"]').textContent.trim(), - deployment.get('shortId'), - 'The active deployment is the most recent running deployment' - ); + assert.ok(find('[data-test-active-deployment]'), 'Active deployment'); + assert.ok( + find('[data-test-active-deployment]').classList.contains('is-info'), + 'Running deployment gets the is-info class' + ); + assert.equal( + find('[data-test-active-deployment-stat="id"]').textContent.trim(), + deployment.get('shortId'), + 'The active deployment is the most recent running deployment' + ); - assert.equal( - find('[data-test-active-deployment-stat="submit-time"]').textContent.trim(), - moment(version.get('submitTime')).fromNow(), - 'Time since the job was submitted is in the active deployment header' - ); + assert.equal( + find( + '[data-test-active-deployment-stat="submit-time"]' + ).textContent.trim(), + moment(version.get('submitTime')).fromNow(), + 'Time since the job was submitted is in the active deployment header' + ); - assert.equal( - find('[data-test-deployment-metric="canaries"]').textContent.trim(), - `${deployment.get('placedCanaries')} / ${deployment.get('desiredCanaries')}`, - 'Canaries, both places and desired, are in the metrics' - ); + assert.equal( + find('[data-test-deployment-metric="canaries"]').textContent.trim(), + `${deployment.get('placedCanaries')} / ${deployment.get( + 'desiredCanaries' + )}`, + 'Canaries, both places and desired, are in the metrics' + ); - assert.equal( - find('[data-test-deployment-metric="placed"]').textContent.trim(), - deployment.get('placedAllocs'), - 'Placed allocs aggregates across task groups' - ); + assert.equal( + find('[data-test-deployment-metric="placed"]').textContent.trim(), + deployment.get('placedAllocs'), + 'Placed allocs aggregates across task groups' + ); - assert.equal( - find('[data-test-deployment-metric="desired"]').textContent.trim(), - deployment.get('desiredTotal'), - 'Desired allocs aggregates across task groups' - ); + assert.equal( + find('[data-test-deployment-metric="desired"]').textContent.trim(), + deployment.get('desiredTotal'), + 'Desired allocs aggregates across task groups' + ); - assert.equal( - find('[data-test-deployment-metric="healthy"]').textContent.trim(), - deployment.get('healthyAllocs'), - 'Healthy allocs aggregates across task groups' - ); + assert.equal( + find('[data-test-deployment-metric="healthy"]').textContent.trim(), + deployment.get('healthyAllocs'), + 'Healthy allocs aggregates across task groups' + ); - assert.equal( - find('[data-test-deployment-metric="unhealthy"]').textContent.trim(), - deployment.get('unhealthyAllocs'), - 'Unhealthy allocs aggregates across task groups' - ); + assert.equal( + find('[data-test-deployment-metric="unhealthy"]').textContent.trim(), + deployment.get('unhealthyAllocs'), + 'Unhealthy allocs aggregates across task groups' + ); - assert.equal( - find('[data-test-deployment-notification]').textContent.trim(), - deployment.get('statusDescription'), - 'Status description is in the metrics block' - ); + assert.equal( + find('[data-test-deployment-notification]').textContent.trim(), + deployment.get('statusDescription'), + 'Status description is in the metrics block' + ); - await componentA11yAudit(this.element, assert); - }); - - test('when there is no running deployment, the latest deployment section shows up for the last deployment', async function(assert) { - this.server.create('job', { - type: 'service', - createAllocations: false, - noActiveDeployment: true, + await componentA11yAudit(this.element, assert); }); - await this.store.findAll('job'); + test('when there is no running deployment, the latest deployment section shows up for the last deployment', async function (assert) { + this.server.create('job', { + type: 'service', + createAllocations: false, + noActiveDeployment: true, + }); - this.set('job', this.store.peekAll('job').get('firstObject')); - await render(hbs` + await this.store.findAll('job'); + + this.set('job', this.store.peekAll('job').get('firstObject')); + await render(hbs` `); - assert.ok(find('[data-test-active-deployment]'), 'Active deployment'); - assert.notOk( - find('[data-test-active-deployment]').classList.contains('is-info'), - 'Non-running deployment does not get the is-info class' - ); - }); + assert.ok(find('[data-test-active-deployment]'), 'Active deployment'); + assert.notOk( + find('[data-test-active-deployment]').classList.contains('is-info'), + 'Non-running deployment does not get the is-info class' + ); + }); - test('the latest deployment section can be expanded to show task groups and allocations', async function(assert) { - this.server.create('node'); - this.server.create('job', { type: 'service', activeDeployment: true }); + test('the latest deployment section can be expanded to show task groups and allocations', async function (assert) { + assert.expect(5); - await this.store.findAll('job'); + this.server.create('node'); + this.server.create('job', { type: 'service', activeDeployment: true }); - this.set('job', this.store.peekAll('job').get('firstObject')); - await render(hbs` + await this.store.findAll('job'); + + this.set('job', this.store.peekAll('job').get('firstObject')); + await render(hbs` `); - assert.notOk(find('[data-test-deployment-task-groups]'), 'Task groups not found'); - assert.notOk(find('[data-test-deployment-allocations]'), 'Allocations not found'); + assert.notOk( + find('[data-test-deployment-task-groups]'), + 'Task groups not found' + ); + assert.notOk( + find('[data-test-deployment-allocations]'), + 'Allocations not found' + ); - await click('[data-test-deployment-toggle-details]'); + await click('[data-test-deployment-toggle-details]'); - assert.ok(find('[data-test-deployment-task-groups]'), 'Task groups found'); - assert.ok(find('[data-test-deployment-allocations]'), 'Allocations found'); + assert.ok( + find('[data-test-deployment-task-groups]'), + 'Task groups found' + ); + assert.ok( + find('[data-test-deployment-allocations]'), + 'Allocations found' + ); - await componentA11yAudit(this.element, assert); - }); + await componentA11yAudit(this.element, assert); + }); - test('each task group in the expanded task group section shows task group details', async function(assert) { - this.server.create('node'); - this.server.create('job', { type: 'service', activeDeployment: true }); + test('each task group in the expanded task group section shows task group details', async function (assert) { + this.server.create('node'); + this.server.create('job', { type: 'service', activeDeployment: true }); - await this.store.findAll('job'); + await this.store.findAll('job'); - const job = this.store.peekAll('job').get('firstObject'); + const job = this.store.peekAll('job').get('firstObject'); - this.set('job', job); - await render(hbs` + this.set('job', job); + await render(hbs` `); - await click('[data-test-deployment-toggle-details]'); + await click('[data-test-deployment-toggle-details]'); - const task = job.get('runningDeployment.taskGroupSummaries.firstObject'); - const findForTaskGroup = selector => find(`[data-test-deployment-task-group-${selector}]`); - assert.equal(findForTaskGroup('name').textContent.trim(), task.get('name')); - assert.equal( - findForTaskGroup('progress-deadline').textContent.trim(), - moment(task.get('requireProgressBy')).format("MMM DD, 'YY HH:mm:ss ZZ") - ); - }); -}); + const task = job.get('runningDeployment.taskGroupSummaries.firstObject'); + const findForTaskGroup = (selector) => + find(`[data-test-deployment-task-group-${selector}]`); + assert.equal( + findForTaskGroup('name').textContent.trim(), + task.get('name') + ); + assert.equal( + findForTaskGroup('progress-deadline').textContent.trim(), + moment(task.get('requireProgressBy')).format("MMM DD, 'YY HH:mm:ss ZZ") + ); + }); + } +); diff --git a/ui/tests/integration/components/job-page/parts/placement-failures-test.js b/ui/tests/integration/components/job-page/parts/placement-failures-test.js index 80b409dad..7e27425f2 100644 --- a/ui/tests/integration/components/job-page/parts/placement-failures-test.js +++ b/ui/tests/integration/components/job-page/parts/placement-failures-test.js @@ -1,3 +1,5 @@ +/* eslint-disable qunit/require-expect */ +/* Mirage fixtures are random so we can't expect a set number of assertions */ import hbs from 'htmlbars-inline-precompile'; import { findAll, find, render } from '@ember/test-helpers'; import { module, test } from 'qunit'; @@ -6,76 +8,93 @@ import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | job-page/parts/placement-failures', function(hooks) { - setupRenderingTest(hooks); +module( + 'Integration | Component | job-page/parts/placement-failures', + function (hooks) { + setupRenderingTest(hooks); - hooks.beforeEach(function() { - fragmentSerializerInitializer(this.owner); - window.localStorage.clear(); - this.store = this.owner.lookup('service:store'); - this.server = startMirage(); - this.server.create('namespace'); - }); - - hooks.afterEach(function() { - this.server.shutdown(); - window.localStorage.clear(); - }); - - test('when the job has placement failures, they are called out', async function(assert) { - this.server.create('job', { failedPlacements: true, createAllocations: false }); - await this.store.findAll('job'); - - const job = this.store.peekAll('job').get('firstObject'); - await job.reload(); - - this.set('job', job); - - await render(hbs` - ) - `); - - const failedEvaluation = this.get('job.evaluations') - .filterBy('hasPlacementFailures') - .sortBy('modifyIndex') - .reverse() - .get('firstObject'); - const failedTGAllocs = failedEvaluation.get('failedTGAllocs'); - - assert.ok(find('[data-test-placement-failures]'), 'Placement failures section found'); - - const taskGroupLabels = findAll('[data-test-placement-failure-task-group]').map(title => - title.textContent.trim() - ); - - failedTGAllocs.forEach(alloc => { - const name = alloc.get('name'); - assert.ok( - taskGroupLabels.find(label => label.includes(name)), - `${name} included in placement failures list` - ); - assert.ok( - taskGroupLabels.find(label => label.includes(alloc.get('coalescedFailures') + 1)), - 'The number of unplaced allocs = CoalescedFailures + 1' - ); + hooks.beforeEach(function () { + fragmentSerializerInitializer(this.owner); + window.localStorage.clear(); + this.store = this.owner.lookup('service:store'); + this.server = startMirage(); + this.server.create('namespace'); }); - await componentA11yAudit(this.element, assert); - }); + hooks.afterEach(function () { + this.server.shutdown(); + window.localStorage.clear(); + }); - test('when the job has no placement failures, the placement failures section is gone', async function(assert) { - this.server.create('job', { noFailedPlacements: true, createAllocations: false }); - await this.store.findAll('job'); + test('when the job has placement failures, they are called out', async function (assert) { + this.server.create('job', { + failedPlacements: true, + createAllocations: false, + }); + await this.store.findAll('job'); - const job = this.store.peekAll('job').get('firstObject'); - await job.reload(); + const job = this.store.peekAll('job').get('firstObject'); + await job.reload(); - this.set('job', job); + this.set('job', job); - await render(hbs` + await render(hbs` ) `); - assert.notOk(find('[data-test-placement-failures]'), 'Placement failures section not found'); - }); -}); + const failedEvaluation = this.get('job.evaluations') + .filterBy('hasPlacementFailures') + .sortBy('modifyIndex') + .reverse() + .get('firstObject'); + const failedTGAllocs = failedEvaluation.get('failedTGAllocs'); + + assert.ok( + find('[data-test-placement-failures]'), + 'Placement failures section found' + ); + + const taskGroupLabels = findAll( + '[data-test-placement-failure-task-group]' + ).map((title) => title.textContent.trim()); + + failedTGAllocs.forEach((alloc) => { + const name = alloc.get('name'); + assert.ok( + taskGroupLabels.find((label) => label.includes(name)), + `${name} included in placement failures list` + ); + assert.ok( + taskGroupLabels.find((label) => + label.includes(alloc.get('coalescedFailures') + 1) + ), + 'The number of unplaced allocs = CoalescedFailures + 1' + ); + }); + + await componentA11yAudit(this.element, assert); + }); + + test('when the job has no placement failures, the placement failures section is gone', async function (assert) { + this.server.create('job', { + noFailedPlacements: true, + createAllocations: false, + }); + await this.store.findAll('job'); + + const job = this.store.peekAll('job').get('firstObject'); + await job.reload(); + + this.set('job', job); + + await render(hbs` + ) + `); + + assert.notOk( + find('[data-test-placement-failures]'), + 'Placement failures section not found' + ); + }); + } +); diff --git a/ui/tests/integration/components/job-page/parts/summary-test.js b/ui/tests/integration/components/job-page/parts/summary-test.js index 975a1b892..dc18a8c33 100644 --- a/ui/tests/integration/components/job-page/parts/summary-test.js +++ b/ui/tests/integration/components/job-page/parts/summary-test.js @@ -6,10 +6,10 @@ import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | job-page/parts/summary', function(hooks) { +module('Integration | Component | job-page/parts/summary', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { fragmentSerializerInitializer(this.owner); window.localStorage.clear(); this.store = this.owner.lookup('service:store'); @@ -17,12 +17,14 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { this.server.create('namespace'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); window.localStorage.clear(); }); - test('jobs with children use the children diagram', async function(assert) { + test('jobs with children use the children diagram', async function (assert) { + assert.expect(3); + this.server.create('job', 'periodic', { createAllocations: false, }); @@ -35,13 +37,21 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { `); - assert.ok(find('[data-test-children-status-bar]'), 'Children status bar found'); - assert.notOk(find('[data-test-allocation-status-bar]'), 'Allocation status bar not found'); + assert.ok( + find('[data-test-children-status-bar]'), + 'Children status bar found' + ); + assert.notOk( + find('[data-test-allocation-status-bar]'), + 'Allocation status bar not found' + ); await componentA11yAudit(this.element, assert); }); - test('jobs without children use the allocations diagram', async function(assert) { + test('jobs without children use the allocations diagram', async function (assert) { + assert.expect(3); + this.server.create('job', { createAllocations: false, }); @@ -54,13 +64,19 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { `); - assert.ok(find('[data-test-allocation-status-bar]'), 'Allocation status bar found'); - assert.notOk(find('[data-test-children-status-bar]'), 'Children status bar not found'); + assert.ok( + find('[data-test-allocation-status-bar]'), + 'Allocation status bar found' + ); + assert.notOk( + find('[data-test-children-status-bar]'), + 'Children status bar not found' + ); await componentA11yAudit(this.element, assert); }); - test('the allocations diagram lists all allocation status figures', async function(assert) { + test('the allocations diagram lists all allocation status figures', async function (assert) { this.server.create('job', { createAllocations: false, }); @@ -110,7 +126,7 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { ); }); - test('the children diagram lists all children status figures', async function(assert) { + test('the children diagram lists all children status figures', async function (assert) { this.server.create('job', 'periodic', { createAllocations: false, }); @@ -142,7 +158,7 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { ); }); - test('the summary block can be collapsed', async function(assert) { + test('the summary block can be collapsed', async function (assert) { this.server.create('job', { createAllocations: false, }); @@ -161,7 +177,9 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { assert.notOk(find('[data-test-legend]'), 'No legend'); }); - test('when collapsed, the summary block includes an inline version of the chart', async function(assert) { + test('when collapsed, the summary block includes an inline version of the chart', async function (assert) { + assert.expect(3); + this.server.create('job', { createAllocations: false, }); @@ -176,7 +194,10 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { await click('[data-test-accordion-toggle]'); - assert.ok(find('[data-test-allocation-status-bar]'), 'Allocation bar still existed'); + assert.ok( + find('[data-test-allocation-status-bar]'), + 'Allocation bar still existed' + ); assert.ok( find('.inline-chart [data-test-allocation-status-bar]'), 'Allocation bar is rendered in an inline-chart container' @@ -185,7 +206,7 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('the collapsed/expanded state is persisted to localStorage', async function(assert) { + test('the collapsed/expanded state is persisted to localStorage', async function (assert) { this.server.create('job', { createAllocations: false, }); @@ -198,7 +219,10 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { `); - assert.notOk(window.localStorage.nomadExpandJobSummary, 'No value in localStorage yet'); + assert.notOk( + window.localStorage.nomadExpandJobSummary, + 'No value in localStorage yet' + ); await click('[data-test-accordion-toggle]'); assert.equal( @@ -208,7 +232,7 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { ); }); - test('the collapsed/expanded state from localStorage is used for the initial state when available', async function(assert) { + test('the collapsed/expanded state from localStorage is used for the initial state when available', async function (assert) { this.server.create('job', { createAllocations: false, }); @@ -223,7 +247,10 @@ module('Integration | Component | job-page/parts/summary', function(hooks) { `); - assert.ok(find('[data-test-allocation-status-bar]'), 'Allocation bar still existed'); + assert.ok( + find('[data-test-allocation-status-bar]'), + 'Allocation bar still existed' + ); assert.ok( find('.inline-chart [data-test-allocation-status-bar]'), 'Allocation bar is rendered in an inline-chart container' diff --git a/ui/tests/integration/components/job-page/parts/task-groups-test.js b/ui/tests/integration/components/job-page/parts/task-groups-test.js index 86722b026..0fa42ec1f 100644 --- a/ui/tests/integration/components/job-page/parts/task-groups-test.js +++ b/ui/tests/integration/components/job-page/parts/task-groups-test.js @@ -1,51 +1,58 @@ import { assign } from '@ember/polyfills'; import hbs from 'htmlbars-inline-precompile'; -import { click, findAll, find } from '@ember/test-helpers'; +import { click, findAll, find, render } from '@ember/test-helpers'; import { module, test } from 'qunit'; import sinon from 'sinon'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { setupRenderingTest } from 'ember-qunit'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -import { formatScheduledHertz, formatScheduledBytes } from 'nomad-ui/utils/units'; +import { + formatScheduledHertz, + formatScheduledBytes, +} from 'nomad-ui/utils/units'; -module('Integration | Component | job-page/parts/task-groups', function(hooks) { - setupRenderingTest(hooks); +module( + 'Integration | Component | job-page/parts/task-groups', + function (hooks) { + setupRenderingTest(hooks); - hooks.beforeEach(function() { - window.localStorage.clear(); - this.store = this.owner.lookup('service:store'); - this.server = startMirage(); - this.server.create('namespace'); - }); - - hooks.afterEach(function() { - this.server.shutdown(); - }); - - const props = (job, options = {}) => - assign( - { - job, - sortProperty: 'name', - sortDescending: true, - gotoTaskGroup: () => {}, - }, - options - ); - - test('the job detail page should list all task groups', async function(assert) { - this.server.create('job', { - createAllocations: false, + hooks.beforeEach(function () { + window.localStorage.clear(); + this.store = this.owner.lookup('service:store'); + this.server = startMirage(); + this.server.create('namespace'); }); - await this.store.findAll('job').then(jobs => { - jobs.forEach(job => job.reload()); + hooks.afterEach(function () { + this.server.shutdown(); }); - const job = this.store.peekAll('job').get('firstObject'); - this.setProperties(props(job)); + const props = (job, options = {}) => + assign( + { + job, + sortProperty: 'name', + sortDescending: true, + gotoTaskGroup: () => {}, + }, + options + ); - await this.render(hbs` + test('the job detail page should list all task groups', async function (assert) { + assert.expect(2); + + this.server.create('job', { + createAllocations: false, + }); + + await this.store.findAll('job').then((jobs) => { + jobs.forEach((job) => job.reload()); + }); + + const job = this.store.peekAll('job').get('firstObject'); + this.setProperties(props(job)); + + await render(hbs` `); - assert.equal( - findAll('[data-test-task-group]').length, - job.get('taskGroups.length'), - 'One row per task group' - ); + assert.equal( + findAll('[data-test-task-group]').length, + job.get('taskGroups.length'), + 'One row per task group' + ); - await componentA11yAudit(this.element, assert); - }); - - test('each row in the task group table should show basic information about the task group', async function(assert) { - this.server.create('job', { - createAllocations: false, + await componentA11yAudit(this.element, assert); }); - const job = await this.store.findAll('job').then(async jobs => { - return await jobs.get('firstObject').reload(); - }); + test('each row in the task group table should show basic information about the task group', async function (assert) { + this.server.create('job', { + createAllocations: false, + }); - const taskGroups = await job.get('taskGroups'); - const taskGroup = taskGroups - .sortBy('name') - .reverse() - .get('firstObject'); + const job = await this.store.findAll('job').then(async (jobs) => { + return await jobs.get('firstObject').reload(); + }); - this.setProperties(props(job)); + const taskGroups = await job.get('taskGroups'); + const taskGroup = taskGroups.sortBy('name').reverse().get('firstObject'); - await this.render(hbs` + this.setProperties(props(job)); + + await render(hbs` `); - const taskGroupRow = find('[data-test-task-group]'); + const taskGroupRow = find('[data-test-task-group]'); - assert.equal( - taskGroupRow.querySelector('[data-test-task-group-name]').textContent.trim(), - taskGroup.get('name'), - 'Name' - ); - assert.equal( - taskGroupRow.querySelector('[data-test-task-group-count]').textContent.trim(), - taskGroup.get('count'), - 'Count' - ); - assert.equal( - taskGroupRow.querySelector('[data-test-task-group-volume]').textContent.trim(), - taskGroup.get('volumes.length') ? 'Yes' : '', - 'Volumes' - ); - assert.equal( - taskGroupRow.querySelector('[data-test-task-group-cpu]').textContent.trim(), - `${formatScheduledHertz(taskGroup.get('reservedCPU'), 'MHz')}`, - 'Reserved CPU' - ); - assert.equal( - taskGroupRow.querySelector('[data-test-task-group-mem]').textContent.trim(), - `${formatScheduledBytes(taskGroup.get('reservedMemory'), 'MiB')}`, - 'Reserved Memory' - ); - assert.equal( - taskGroupRow.querySelector('[data-test-task-group-disk]').textContent.trim(), - `${formatScheduledBytes(taskGroup.get('reservedEphemeralDisk'), 'MiB')}`, - 'Reserved Disk' - ); - }); - - test('gotoTaskGroup is called when task group rows are clicked', async function(assert) { - this.server.create('job', { - createAllocations: false, + assert.equal( + taskGroupRow + .querySelector('[data-test-task-group-name]') + .textContent.trim(), + taskGroup.get('name'), + 'Name' + ); + assert.equal( + taskGroupRow + .querySelector('[data-test-task-group-count]') + .textContent.trim(), + taskGroup.get('count'), + 'Count' + ); + assert.equal( + taskGroupRow + .querySelector('[data-test-task-group-volume]') + .textContent.trim(), + taskGroup.get('volumes.length') ? 'Yes' : '', + 'Volumes' + ); + assert.equal( + taskGroupRow + .querySelector('[data-test-task-group-cpu]') + .textContent.trim(), + `${formatScheduledHertz(taskGroup.get('reservedCPU'), 'MHz')}`, + 'Reserved CPU' + ); + assert.equal( + taskGroupRow + .querySelector('[data-test-task-group-mem]') + .textContent.trim(), + `${formatScheduledBytes(taskGroup.get('reservedMemory'), 'MiB')}`, + 'Reserved Memory' + ); + assert.equal( + taskGroupRow + .querySelector('[data-test-task-group-disk]') + .textContent.trim(), + `${formatScheduledBytes( + taskGroup.get('reservedEphemeralDisk'), + 'MiB' + )}`, + 'Reserved Disk' + ); }); - const job = await this.store.findAll('job').then(async jobs => { - return await jobs.get('firstObject').reload(); - }); + test('gotoTaskGroup is called when task group rows are clicked', async function (assert) { + this.server.create('job', { + createAllocations: false, + }); - const taskGroupSpy = sinon.spy(); + const job = await this.store.findAll('job').then(async (jobs) => { + return await jobs.get('firstObject').reload(); + }); - const taskGroups = await job.get('taskGroups'); - const taskGroup = taskGroups - .sortBy('name') - .reverse() - .get('firstObject'); + const taskGroupSpy = sinon.spy(); - this.setProperties( - props(job, { - gotoTaskGroup: taskGroupSpy, - }) - ); + const taskGroups = await job.get('taskGroups'); + const taskGroup = taskGroups.sortBy('name').reverse().get('firstObject'); - await this.render(hbs` + this.setProperties( + props(job, { + gotoTaskGroup: taskGroupSpy, + }) + ); + + await render(hbs` `); - await click('[data-test-task-group]'); + await click('[data-test-task-group]'); - assert.ok( - taskGroupSpy.withArgs(taskGroup).calledOnce, - 'Clicking the task group row calls the gotoTaskGroup action' - ); - }); -}); + assert.ok( + taskGroupSpy.withArgs(taskGroup).calledOnce, + 'Clicking the task group row calls the gotoTaskGroup action' + ); + }); + } +); diff --git a/ui/tests/integration/components/job-page/periodic-test.js b/ui/tests/integration/components/job-page/periodic-test.js index edcec6b54..54df6cd05 100644 --- a/ui/tests/integration/components/job-page/periodic-test.js +++ b/ui/tests/integration/components/job-page/periodic-test.js @@ -24,17 +24,17 @@ const PeriodicJobPage = create({ pageSizeSelect: pageSizeSelectPageObject(), }); -module('Integration | Component | job-page/periodic', function(hooks) { +module('Integration | Component | job-page/periodic', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { window.localStorage.clear(); this.store = this.owner.lookup('service:store'); this.server = startMirage(); this.server.create('namespace'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); window.localStorage.clear(); }); @@ -48,7 +48,7 @@ module('Integration | Component | job-page/periodic', function(hooks) { @gotoJob={{gotoJob}} /> `; - const commonProperties = job => ({ + const commonProperties = (job) => ({ job, sortProperty: 'name', sortDescending: true, @@ -56,7 +56,7 @@ module('Integration | Component | job-page/periodic', function(hooks) { gotoJob: () => {}, }); - test('Clicking Force Launch launches a new periodic child job', async function(assert) { + test('Clicking Force Launch launches a new periodic child job', async function (assert) { const childrenCount = 3; this.server.create('job', 'periodic', { @@ -70,7 +70,7 @@ module('Integration | Component | job-page/periodic', function(hooks) { const job = this.store.peekAll('job').findBy('plainId', 'parent'); this.setProperties(commonProperties(job)); - await this.render(commonTemplate); + await render(commonTemplate); const currentJobCount = server.db.jobs.length; @@ -87,15 +87,23 @@ module('Integration | Component | job-page/periodic', function(hooks) { assert.ok( this.server.pretender.handledRequests .filterBy('method', 'POST') - .find(req => req.url === expectedURL), + .find((req) => req.url === expectedURL), 'POST URL was correct' ); - assert.equal(server.db.jobs.length, currentJobCount + 1, 'POST request was made'); + assert.equal( + server.db.jobs.length, + currentJobCount + 1, + 'POST request was made' + ); }); - test('Clicking force launch without proper permissions shows an error message', async function(assert) { - this.server.pretender.post('/v1/job/:id/periodic/force', () => [403, {}, '']); + test('Clicking force launch without proper permissions shows an error message', async function (assert) { + this.server.pretender.post('/v1/job/:id/periodic/force', () => [ + 403, + {}, + '', + ]); this.server.create('job', 'periodic', { id: 'parent', @@ -109,7 +117,7 @@ module('Integration | Component | job-page/periodic', function(hooks) { const job = this.store.peekAll('job').findBy('plainId', 'parent'); this.setProperties(commonProperties(job)); - await this.render(commonTemplate); + await render(commonTemplate); assert.notOk(find('[data-test-job-error-title]'), 'No error message yet'); @@ -127,10 +135,15 @@ module('Integration | Component | job-page/periodic', function(hooks) { await click('[data-test-job-error-close]'); - assert.notOk(find('[data-test-job-error-title]'), 'Error message is dismissable'); + assert.notOk( + find('[data-test-job-error-title]'), + 'Error message is dismissable' + ); }); - test('Stopping a job sends a delete request for the job', async function(assert) { + test('Stopping a job sends a delete request for the job', async function (assert) { + assert.expect(1); + const mirageJob = this.server.create('job', 'periodic', { childrenCount: 0, createAllocations: false, @@ -149,7 +162,9 @@ module('Integration | Component | job-page/periodic', function(hooks) { expectDeleteRequest(assert, this.server, job); }); - test('Stopping a job without proper permissions shows an error message', async function(assert) { + test('Stopping a job without proper permissions shows an error message', async function (assert) { + assert.expect(4); + this.server.pretender.delete('/v1/job/:id', () => [403, {}, '']); const mirageJob = this.server.create('job', 'periodic', { @@ -171,7 +186,9 @@ module('Integration | Component | job-page/periodic', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('Starting a job sends a post request for the job using the current definition', async function(assert) { + test('Starting a job sends a post request for the job using the current definition', async function (assert) { + assert.expect(2); + const mirageJob = this.server.create('job', 'periodic', { childrenCount: 0, createAllocations: false, @@ -188,7 +205,9 @@ module('Integration | Component | job-page/periodic', function(hooks) { expectStartRequest(assert, this.server, job); }); - test('Starting a job without proper permissions shows an error message', async function(assert) { + test('Starting a job without proper permissions shows an error message', async function (assert) { + assert.expect(3); + this.server.pretender.post('/v1/job/:id', () => [403, {}, '']); const mirageJob = this.server.create('job', 'periodic', { @@ -207,7 +226,7 @@ module('Integration | Component | job-page/periodic', function(hooks) { expectError(assert, 'Could Not Start Job'); }); - test('Each job row includes the submitted time', async function(assert) { + test('Each job row includes the submitted time', async function (assert) { this.server.create('job', 'periodic', { id: 'parent', childrenCount: 1, @@ -219,11 +238,13 @@ module('Integration | Component | job-page/periodic', function(hooks) { const job = this.store.peekAll('job').findBy('plainId', 'parent'); this.setProperties(commonProperties(job)); - await this.render(commonTemplate); + await render(commonTemplate); assert.equal( find('[data-test-job-submit-time]').textContent, - moment(job.get('children.firstObject.submitTime')).format('MMM DD HH:mm:ss ZZ'), + moment(job.get('children.firstObject.submitTime')).format( + 'MMM DD HH:mm:ss ZZ' + ), 'The new periodic job launch is in the children list' ); }); @@ -244,7 +265,7 @@ module('Integration | Component | job-page/periodic', function(hooks) { const job = this.store.peekAll('job').findBy('plainId', 'parent'); this.setProperties(commonProperties(job)); - await this.render(commonTemplate); + await render(commonTemplate); }, }); }); diff --git a/ui/tests/integration/components/job-page/service-test.js b/ui/tests/integration/components/job-page/service-test.js index 075e001f1..79160c790 100644 --- a/ui/tests/integration/components/job-page/service-test.js +++ b/ui/tests/integration/components/job-page/service-test.js @@ -4,15 +4,21 @@ import { setupRenderingTest } from 'ember-qunit'; import { click, find, render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; -import { startJob, stopJob, expectError, expectDeleteRequest, expectStartRequest } from './helpers'; +import { + startJob, + stopJob, + expectError, + expectDeleteRequest, + expectStartRequest, +} from './helpers'; import Job from 'nomad-ui/tests/pages/jobs/detail'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | job-page/service', function(hooks) { +module('Integration | Component | job-page/service', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { fragmentSerializerInitializer(this.owner); window.localStorage.clear(); this.store = this.owner.lookup('service:store'); @@ -20,7 +26,7 @@ module('Integration | Component | job-page/service', function(hooks) { this.server.create('namespace'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { Job.removeContext(); this.server.shutdown(); window.localStorage.clear(); @@ -35,7 +41,7 @@ module('Integration | Component | job-page/service', function(hooks) { @gotoJob={{gotoJob}} /> `; - const commonProperties = job => ({ + const commonProperties = (job) => ({ job, sortProperty: 'name', sortDescending: true, @@ -56,7 +62,9 @@ module('Integration | Component | job-page/service', function(hooks) { ) ); - test('Stopping a job sends a delete request for the job', async function(assert) { + test('Stopping a job sends a delete request for the job', async function (assert) { + assert.expect(1); + const mirageJob = makeMirageJob(this.server); await this.store.findAll('job'); @@ -69,7 +77,9 @@ module('Integration | Component | job-page/service', function(hooks) { expectDeleteRequest(assert, this.server, job); }); - test('Stopping a job without proper permissions shows an error message', async function(assert) { + test('Stopping a job without proper permissions shows an error message', async function (assert) { + assert.expect(4); + this.server.pretender.delete('/v1/job/:id', () => [403, {}, '']); const mirageJob = makeMirageJob(this.server); @@ -86,7 +96,9 @@ module('Integration | Component | job-page/service', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('Starting a job sends a post request for the job using the current definition', async function(assert) { + test('Starting a job sends a post request for the job using the current definition', async function (assert) { + assert.expect(2); + const mirageJob = makeMirageJob(this.server, { status: 'dead' }); await this.store.findAll('job'); @@ -99,7 +111,9 @@ module('Integration | Component | job-page/service', function(hooks) { expectStartRequest(assert, this.server, job); }); - test('Starting a job without proper permissions shows an error message', async function(assert) { + test('Starting a job without proper permissions shows an error message', async function (assert) { + assert.expect(3); + this.server.pretender.post('/v1/job/:id', () => [403, {}, '']); const mirageJob = makeMirageJob(this.server, { status: 'dead' }); @@ -114,7 +128,9 @@ module('Integration | Component | job-page/service', function(hooks) { expectError(assert, 'Could Not Start Job'); }); - test('Recent allocations shows allocations in the job context', async function(assert) { + test('Recent allocations shows allocations in the job context', async function (assert) { + assert.expect(3); + this.server.create('node'); const mirageJob = makeMirageJob(this.server, { createAllocations: true }); await this.store.findAll('job'); @@ -124,16 +140,22 @@ module('Integration | Component | job-page/service', function(hooks) { this.setProperties(commonProperties(job)); await render(commonTemplate); - const allocation = this.server.db.allocations.sortBy('modifyIndex').reverse()[0]; + const allocation = this.server.db.allocations + .sortBy('modifyIndex') + .reverse()[0]; const allocationRow = Job.allocations.objectAt(0); assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'ID'); - assert.equal(allocationRow.taskGroup, allocation.taskGroup, 'Task Group name'); + assert.equal( + allocationRow.taskGroup, + allocation.taskGroup, + 'Task Group name' + ); await componentA11yAudit(this.element, assert); }); - test('Recent allocations caps out at five', async function(assert) { + test('Recent allocations caps out at five', async function (assert) { this.server.create('node'); const mirageJob = makeMirageJob(this.server); this.server.createList('allocation', 10); @@ -152,7 +174,9 @@ module('Integration | Component | job-page/service', function(hooks) { ); }); - test('Recent allocations shows an empty message when the job has no allocations', async function(assert) { + test('Recent allocations shows an empty message when the job has no allocations', async function (assert) { + assert.expect(2); + this.server.create('node'); const mirageJob = makeMirageJob(this.server); @@ -171,7 +195,7 @@ module('Integration | Component | job-page/service', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('Active deployment can be promoted', async function(assert) { + test('Active deployment can be promoted', async function (assert) { this.server.create('node'); const mirageJob = makeMirageJob(this.server, { activeDeployment: true }); @@ -195,8 +219,14 @@ module('Integration | Component | job-page/service', function(hooks) { ); }); - test('When promoting the active deployment fails, an error is shown', async function(assert) { - this.server.pretender.post('/v1/deployment/promote/:id', () => [403, {}, '']); + test('When promoting the active deployment fails, an error is shown', async function (assert) { + assert.expect(4); + + this.server.pretender.post('/v1/deployment/promote/:id', () => [ + 403, + {}, + '', + ]); this.server.create('node'); const mirageJob = makeMirageJob(this.server, { activeDeployment: true }); @@ -224,10 +254,13 @@ module('Integration | Component | job-page/service', function(hooks) { await click('[data-test-job-error-close]'); - assert.notOk(find('[data-test-job-error-title]'), 'Error message is dismissable'); + assert.notOk( + find('[data-test-job-error-title]'), + 'Error message is dismissable' + ); }); - test('Active deployment can be failed', async function(assert) { + test('Active deployment can be failed', async function (assert) { this.server.create('node'); const mirageJob = makeMirageJob(this.server, { activeDeployment: true }); @@ -252,7 +285,9 @@ module('Integration | Component | job-page/service', function(hooks) { ); }); - test('When failing the active deployment fails, an error is shown', async function(assert) { + test('When failing the active deployment fails, an error is shown', async function (assert) { + assert.expect(4); + this.server.pretender.post('/v1/deployment/fail/:id', () => [403, {}, '']); this.server.create('node'); @@ -282,6 +317,9 @@ module('Integration | Component | job-page/service', function(hooks) { await click('[data-test-job-error-close]'); - assert.notOk(find('[data-test-job-error-title]'), 'Error message is dismissable'); + assert.notOk( + find('[data-test-job-error-title]'), + 'Error message is dismissable' + ); }); }); diff --git a/ui/tests/integration/components/lifecycle-chart-test.js b/ui/tests/integration/components/lifecycle-chart-test.js index 9dd541946..d9ab57a27 100644 --- a/ui/tests/integration/components/lifecycle-chart-test.js +++ b/ui/tests/integration/components/lifecycle-chart-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/no-conditional-assertions */ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render, settled } from '@ember/test-helpers'; @@ -40,10 +41,12 @@ const tasks = [ }, ]; -module('Integration | Component | lifecycle-chart', function(hooks) { +module('Integration | Component | lifecycle-chart', function (hooks) { setupRenderingTest(hooks); - test('it renders stateless phases and lifecycle- and name-sorted tasks', async function(assert) { + test('it renders stateless phases and lifecycle- and name-sorted tasks', async function (assert) { + assert.expect(32); + this.set('tasks', tasks); await render(hbs``); @@ -54,7 +57,7 @@ module('Integration | Component | lifecycle-chart', function(hooks) { assert.equal(Chart.phases[2].name, 'Poststart'); assert.equal(Chart.phases[3].name, 'Poststop'); - Chart.phases.forEach(phase => assert.notOk(phase.isActive)); + Chart.phases.forEach((phase) => assert.notOk(phase.isActive)); assert.deepEqual(Chart.tasks.mapBy('name'), [ 'prestart ephemeral: 0', @@ -82,7 +85,7 @@ module('Integration | Component | lifecycle-chart', function(hooks) { assert.ok(Chart.tasks[5].isPoststartEphemeral); assert.ok(Chart.tasks[6].isPoststop); - Chart.tasks.forEach(task => { + Chart.tasks.forEach((task) => { assert.notOk(task.isActive); assert.notOk(task.isFinished); }); @@ -90,7 +93,7 @@ module('Integration | Component | lifecycle-chart', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('it doesn’t render when there’s only one phase', async function(assert) { + test('it doesn’t render when there’s only one phase', async function (assert) { this.set('tasks', [ { lifecycleName: 'main', @@ -101,17 +104,19 @@ module('Integration | Component | lifecycle-chart', function(hooks) { assert.notOk(Chart.isPresent); }); - test('it renders all phases when there are any non-main tasks', async function(assert) { + test('it renders all phases when there are any non-main tasks', async function (assert) { this.set('tasks', [tasks[0], tasks[6]]); await render(hbs``); assert.equal(Chart.phases.length, 4); }); - test('it reflects phase and task states when states are passed in', async function(assert) { + test('it reflects phase and task states when states are passed in', async function (assert) { + assert.expect(24); + this.set( 'taskStates', - tasks.map(task => { + tasks.map((task) => { return { task }; }) ); @@ -119,9 +124,9 @@ module('Integration | Component | lifecycle-chart', function(hooks) { await render(hbs``); assert.ok(Chart.isPresent); - Chart.phases.forEach(phase => assert.notOk(phase.isActive)); + Chart.phases.forEach((phase) => assert.notOk(phase.isActive)); - Chart.tasks.forEach(task => { + Chart.tasks.forEach((task) => { assert.notOk(task.isActive); assert.notOk(task.isFinished); }); @@ -158,18 +163,24 @@ module('Integration | Component | lifecycle-chart', function(hooks) { activePhaseNames: [], }, { - testName: 'poststart ephemeral task states affect main phase active state', + testName: + 'poststart ephemeral task states affect main phase active state', runningTaskNames: ['poststart ephemeral'], activePhaseNames: ['Main'], }, ].forEach(async ({ testName, runningTaskNames, activePhaseNames }) => { - test(testName, async function(assert) { - this.set('taskStates', tasks.map(task => ({ task }))); + test(testName, async function (assert) { + assert.expect(4); + + this.set( + 'taskStates', + tasks.map((task) => ({ task })) + ); await render(hbs``); - runningTaskNames.forEach(taskName => { - const taskState = this.taskStates.find(taskState => + runningTaskNames.forEach((taskName) => { + const taskState = this.taskStates.find((taskState) => taskState.task.name.includes(taskName) ); set(taskState, 'state', 'running'); @@ -177,11 +188,14 @@ module('Integration | Component | lifecycle-chart', function(hooks) { await settled(); - Chart.phases.forEach(Phase => { + Chart.phases.forEach((Phase) => { if (activePhaseNames.includes(Phase.name)) { assert.ok(Phase.isActive, `expected ${Phase.name} not to be active`); } else { - assert.notOk(Phase.isActive, `expected ${Phase.name} phase not to be active`); + assert.notOk( + Phase.isActive, + `expected ${Phase.name} phase not to be active` + ); } }); }); diff --git a/ui/tests/integration/components/line-chart-test.js b/ui/tests/integration/components/line-chart-test.js index 16d9fa77e..3e53c0362 100644 --- a/ui/tests/integration/components/line-chart-test.js +++ b/ui/tests/integration/components/line-chart-test.js @@ -1,4 +1,10 @@ -import { find, findAll, click, render, triggerEvent } from '@ember/test-helpers'; +import { + find, + findAll, + click, + render, + triggerEvent, +} from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; @@ -8,10 +14,12 @@ import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; const REF_DATE = new Date(); -module('Integration | Component | line-chart', function(hooks) { +module('Integration | Component | line-chart', function (hooks) { setupRenderingTest(hooks); - test('when a chart has annotations, they are rendered in order', async function(assert) { + test('when a chart has annotations, they are rendered in order', async function (assert) { + assert.expect(4); + const annotations = [ { x: 2, type: 'info' }, { x: 1, type: 'error' }, @@ -48,24 +56,20 @@ module('Integration | Component | line-chart', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when a chart has annotations and is timeseries, annotations are sorted reverse-chronologically', async function(assert) { + test('when a chart has annotations and is timeseries, annotations are sorted reverse-chronologically', async function (assert) { + assert.expect(3); + const annotations = [ { - x: moment(REF_DATE) - .add(2, 'd') - .toDate(), + x: moment(REF_DATE).add(2, 'd').toDate(), type: 'info', }, { - x: moment(REF_DATE) - .add(1, 'd') - .toDate(), + x: moment(REF_DATE).add(1, 'd').toDate(), type: 'error', }, { - x: moment(REF_DATE) - .add(3, 'd') - .toDate(), + x: moment(REF_DATE).add(3, 'd').toDate(), type: 'info', }, ]; @@ -99,7 +103,7 @@ module('Integration | Component | line-chart', function(hooks) { }); }); - test('clicking annotations calls the onAnnotationClick action with the annotation as an argument', async function(assert) { + test('clicking annotations calls the onAnnotationClick action with the annotation as an argument', async function (assert) { const annotations = [{ x: 2, type: 'info', meta: { data: 'here' } }]; this.setProperties({ annotations, @@ -125,7 +129,9 @@ module('Integration | Component | line-chart', function(hooks) { assert.ok(this.click.calledWith(annotations[0])); }); - test('annotations will have staggered heights when too close to be positioned side-by-side', async function(assert) { + test('annotations will have staggered heights when too close to be positioned side-by-side', async function (assert) { + assert.expect(4); + const annotations = [ { x: 2, type: 'info' }, { x: 2.4, type: 'error' }, @@ -161,7 +167,9 @@ module('Integration | Component | line-chart', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('horizontal annotations render in order', async function(assert) { + test('horizontal annotations render in order', async function (assert) { + assert.expect(3); + const annotations = [ { y: 2, label: 'label one' }, { y: 9, label: 'label three' }, @@ -195,7 +203,9 @@ module('Integration | Component | line-chart', function(hooks) { }); }); - test('the tooltip includes information on the data closest to the mouse', async function(assert) { + test('the tooltip includes information on the data closest to the mouse', async function (assert) { + assert.expect(8); + const series1 = [ { x: 1, y: 2 }, { x: 3, y: 3 }, @@ -251,12 +261,17 @@ module('Integration | Component | line-chart', function(hooks) { // MouseEnter triggers the tooltip visibility await triggerEvent(hoverTarget, 'mouseenter'); // MouseMove positions the tooltip and updates the active datum - await triggerEvent(hoverTarget, 'mousemove', { clientX: xOffset + interval * 1 + 5 }); + await triggerEvent(hoverTarget, 'mousemove', { + clientX: xOffset + interval * 1 + 5, + }); assert.equal(findAll('[data-test-chart-tooltip] li').length, 1); - assert.equal(find('[data-test-chart-tooltip] .label').textContent.trim(), this.data[1].series); + assert.equal( + find('[data-test-chart-tooltip] .label').textContent.trim(), + this.data[1].series + ); assert.equal( find('[data-test-chart-tooltip] .value').textContent.trim(), - series2.find(d => d.x === 2).y + series2.find((d) => d.x === 2).y ); // When the mouse falls between points and each series has points with different x values, @@ -264,14 +279,22 @@ module('Integration | Component | line-chart', function(hooks) { // to the cursor. // This event is intentionally between points such that both points are within proximity. const expected = [ - { label: this.data[0].series, value: series1.find(d => d.x === 3).y }, - { label: this.data[1].series, value: series2.find(d => d.x === 2).y }, + { label: this.data[0].series, value: series1.find((d) => d.x === 3).y }, + { label: this.data[1].series, value: series2.find((d) => d.x === 2).y }, ]; - await triggerEvent(hoverTarget, 'mousemove', { clientX: xOffset + interval * 1.5 + 5 }); + await triggerEvent(hoverTarget, 'mousemove', { + clientX: xOffset + interval * 1.5 + 5, + }); assert.equal(findAll('[data-test-chart-tooltip] li').length, 2); findAll('[data-test-chart-tooltip] li').forEach((tooltipEntry, index) => { - assert.equal(tooltipEntry.querySelector('.label').textContent.trim(), expected[index].label); - assert.equal(tooltipEntry.querySelector('.value').textContent.trim(), expected[index].value); + assert.equal( + tooltipEntry.querySelector('.label').textContent.trim(), + expected[index].label + ); + assert.equal( + tooltipEntry.querySelector('.value').textContent.trim(), + expected[index].value + ); }); }); }); diff --git a/ui/tests/integration/components/list-pagination-test.js b/ui/tests/integration/components/list-pagination-test.js index 9731f4902..23d47f8c9 100644 --- a/ui/tests/integration/components/list-pagination-test.js +++ b/ui/tests/integration/components/list-pagination-test.js @@ -4,7 +4,7 @@ import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | list pagination', function(hooks) { +module('Integration | Component | list pagination', function (hooks) { setupRenderingTest(hooks); const defaults = { @@ -18,7 +18,9 @@ module('Integration | Component | list pagination', function(hooks) { .fill(null) .map((_, i) => i); - test('the source property', async function(assert) { + test('the source property', async function (assert) { + assert.expect(36); + this.set('source', list100); await render(hbs` @@ -37,8 +39,14 @@ module('Integration | Component | list pagination', function(hooks) { `); - assert.ok(!findAll('.first').length, 'On the first page, there is no first link'); - assert.ok(!findAll('.prev').length, 'On the first page, there is no prev link'); + assert.notOk( + findAll('.first').length, + 'On the first page, there is no first link' + ); + assert.notOk( + findAll('.prev').length, + 'On the first page, there is no prev link' + ); await componentA11yAudit(this.element, assert); assert.equal( @@ -48,14 +56,23 @@ module('Integration | Component | list pagination', function(hooks) { ); for (var pageNumber = 1; pageNumber <= defaults.spread + 1; pageNumber++) { - assert.ok(findAll(`.link.page-${pageNumber}`).length, `Page link includes ${pageNumber}`); + assert.ok( + findAll(`.link.page-${pageNumber}`).length, + `Page link includes ${pageNumber}` + ); } - assert.ok(findAll('.next').length, 'While not on the last page, there is a next link'); - assert.ok(findAll('.last').length, 'While not on the last page, there is a last link'); + assert.ok( + findAll('.next').length, + 'While not on the last page, there is a next link' + ); + assert.ok( + findAll('.last').length, + 'While not on the last page, there is a last link' + ); await componentA11yAudit(this.element, assert); - assert.ok( + assert.equal( findAll('.item').length, defaults.size, `Only ${defaults.size} (the default) number of items are rendered` @@ -70,7 +87,7 @@ module('Integration | Component | list pagination', function(hooks) { } }); - test('the size property', async function(assert) { + test('the size property', async function (assert) { this.setProperties({ size: 5, source: list100, @@ -82,10 +99,16 @@ module('Integration | Component | list pagination', function(hooks) { `); const totalPages = Math.ceil(this.source.length / this.size); - assert.equal(find('.page-info').textContent, `1 of ${totalPages}`, `${totalPages} total pages`); + assert.equal( + find('.page-info').textContent, + `1 of ${totalPages}`, + `${totalPages} total pages` + ); }); - test('the spread property', async function(assert) { + test('the spread property', async function (assert) { + assert.expect(12); + this.setProperties({ source: list100, spread: 1, @@ -106,7 +129,9 @@ module('Integration | Component | list pagination', function(hooks) { testSpread.call(this, assert); }); - test('page property', async function(assert) { + test('page property', async function (assert) { + assert.expect(10); + this.setProperties({ source: list100, size: 5, @@ -129,9 +154,9 @@ module('Integration | Component | list pagination', function(hooks) { // Ember doesn't support query params (or controllers or routes) in integration tests, // so links can only be tested in acceptance tests. // Leaving this test here for posterity. - skip('pagination links link with query params', function() {}); + skip('pagination links link with query params', function () {}); - test('there are no pagination links when source is less than page size', async function(assert) { + test('there are no pagination links when source is less than page size', async function (assert) { this.set('source', list100.slice(0, 10)); await render(hbs` @@ -150,10 +175,10 @@ module('Integration | Component | list pagination', function(hooks) { `); - assert.ok(!findAll('.first').length, 'No first link'); - assert.ok(!findAll('.prev').length, 'No prev link'); - assert.ok(!findAll('.next').length, 'No next link'); - assert.ok(!findAll('.last').length, 'No last link'); + assert.notOk(findAll('.first').length, 'No first link'); + assert.notOk(findAll('.prev').length, 'No prev link'); + assert.notOk(findAll('.next').length, 'No next link'); + assert.notOk(findAll('.last').length, 'No last link'); assert.equal(find('.page-info').textContent, '1 of 1', 'Only one page'); assert.equal( @@ -164,7 +189,9 @@ module('Integration | Component | list pagination', function(hooks) { }); // when there is less pages than the total spread amount - test('when there is less pages than the total spread amount', async function(assert) { + test('when there is less pages than the total spread amount', async function (assert) { + assert.expect(9); + this.setProperties({ source: list100, spread: 4, @@ -191,15 +218,26 @@ module('Integration | Component | list pagination', function(hooks) { assert.ok(findAll('.prev').length, 'Prev page still exists'); assert.ok(findAll('.next').length, 'Next page still exists'); assert.ok(findAll('.last').length, 'Last page still exists'); - assert.equal(findAll('.link').length, totalPages, 'Every page gets a page link'); + assert.equal( + findAll('.link').length, + totalPages, + 'Every page gets a page link' + ); for (var pageNumber = 1; pageNumber < totalPages; pageNumber++) { - assert.ok(findAll(`.link.page-${pageNumber}`).length, `Page link for ${pageNumber} exists`); + assert.ok( + findAll(`.link.page-${pageNumber}`).length, + `Page link for ${pageNumber} exists` + ); } }); function testSpread(assert) { const { spread, currentPage } = this.getProperties('spread', 'currentPage'); - for (var pageNumber = currentPage - spread; pageNumber <= currentPage + spread; pageNumber++) { + for ( + var pageNumber = currentPage - spread; + pageNumber <= currentPage + spread; + pageNumber++ + ) { assert.ok( findAll(`.link.page-${pageNumber}`).length, `Page links for currentPage (${currentPage}) +/- spread of ${spread} (${pageNumber})` @@ -213,8 +251,9 @@ module('Integration | Component | list pagination', function(hooks) { assert.equal( findAll('.item')[item].textContent, item + (currentPage - 1) * size, - `Rendered items are in the current page, ${currentPage} (${item + - (currentPage - 1) * size})` + `Rendered items are in the current page, ${currentPage} (${ + item + (currentPage - 1) * size + })` ); } } diff --git a/ui/tests/integration/components/list-table-test.js b/ui/tests/integration/components/list-table-test.js index 385fadac6..134f4f7d5 100644 --- a/ui/tests/integration/components/list-table-test.js +++ b/ui/tests/integration/components/list-table-test.js @@ -5,7 +5,7 @@ import faker from 'nomad-ui/mirage/faker'; import hbs from 'htmlbars-inline-precompile'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | list table', function(hooks) { +module('Integration | Component | list table', function (hooks) { setupRenderingTest(hooks); const commonTable = Array(10) @@ -17,7 +17,7 @@ module('Integration | Component | list table', function(hooks) { })); // thead - test('component exposes a thead contextual component', async function(assert) { + test('component exposes a thead contextual component', async function (assert) { this.set('source', commonTable); await render(hbs` @@ -30,11 +30,17 @@ module('Integration | Component | list table', function(hooks) { `); assert.ok(findAll('.head').length, 'Table head is rendered'); - assert.equal(find('.head').tagName.toLowerCase(), 'thead', 'Table head is a thead element'); + assert.equal( + find('.head').tagName.toLowerCase(), + 'thead', + 'Table head is a thead element' + ); }); // tbody - test('component exposes a tbody contextual component', async function(assert) { + test('component exposes a tbody contextual component', async function (assert) { + assert.expect(44); + this.setProperties({ source: commonTable, sortProperty: 'firstName', @@ -54,18 +60,42 @@ module('Integration | Component | list table', function(hooks) { `); assert.ok(findAll('.body').length, 'Table body is rendered'); - assert.equal(find('.body').tagName.toLowerCase(), 'tbody', 'Table body is a tbody element'); + assert.equal( + find('.body').tagName.toLowerCase(), + 'tbody', + 'Table body is a tbody element' + ); - assert.equal(findAll('.item').length, this.get('source.length'), 'Each item gets its own row'); + assert.equal( + findAll('.item').length, + this.get('source.length'), + 'Each item gets its own row' + ); // list-table is not responsible for sorting, only dispatching sort events. The table is still // rendered in index-order. this.source.forEach((item, index) => { const $item = this.element.querySelectorAll('.item')[index]; - assert.equal($item.querySelectorAll('td')[0].innerHTML.trim(), item.firstName, 'First name'); - assert.equal($item.querySelectorAll('td')[1].innerHTML.trim(), item.lastName, 'Last name'); - assert.equal($item.querySelectorAll('td')[2].innerHTML.trim(), item.age, 'Age'); - assert.equal($item.querySelectorAll('td')[3].innerHTML.trim(), index, 'Index'); + assert.equal( + $item.querySelectorAll('td')[0].innerHTML.trim(), + item.firstName, + 'First name' + ); + assert.equal( + $item.querySelectorAll('td')[1].innerHTML.trim(), + item.lastName, + 'Last name' + ); + assert.equal( + $item.querySelectorAll('td')[2].innerHTML.trim(), + item.age, + 'Age' + ); + assert.equal( + $item.querySelectorAll('td')[3].innerHTML.trim(), + index, + 'Index' + ); }); await componentA11yAudit(this.element, assert); @@ -74,5 +104,5 @@ module('Integration | Component | list table', function(hooks) { // Ember doesn't support query params (or controllers or routes) in integration tests, // so sorting links can only be tested in acceptance tests. // Leaving this test here for posterity. - skip('sort-by creates links using the appropriate links given sort property and sort descending', function() {}); + skip('sort-by creates links using the appropriate links given sort property and sort descending', function () {}); }); diff --git a/ui/tests/integration/components/multi-select-dropdown-test.js b/ui/tests/integration/components/multi-select-dropdown-test.js index 1592395f0..1d2f931be 100644 --- a/ui/tests/integration/components/multi-select-dropdown-test.js +++ b/ui/tests/integration/components/multi-select-dropdown-test.js @@ -1,4 +1,11 @@ -import { findAll, find, click, focus, render, triggerKeyEvent } from '@ember/test-helpers'; +import { + findAll, + find, + click, + focus, + render, + triggerKeyEvent, +} from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import sinon from 'sinon'; @@ -11,7 +18,7 @@ const SPACE = 32; const ARROW_UP = 38; const ARROW_DOWN = 40; -module('Integration | Component | multi-select dropdown', function(hooks) { +module('Integration | Component | multi-select dropdown', function (hooks) { setupRenderingTest(hooks); const commonProperties = () => ({ @@ -36,7 +43,9 @@ module('Integration | Component | multi-select dropdown', function(hooks) { @onSelect={{this.onSelect}} /> `; - test('component is initially closed', async function(assert) { + test('component is initially closed', async function (assert) { + assert.expect(4); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -47,27 +56,40 @@ module('Integration | Component | multi-select dropdown', function(hooks) { props.label, 'Trigger is appropriately labeled' ); - assert.notOk(find('[data-test-dropdown-options]'), 'Options are not rendered'); + assert.notOk( + find('[data-test-dropdown-options]'), + 'Options are not rendered' + ); await componentA11yAudit(this.element, assert); }); - test('component opens the options dropdown when clicked', async function(assert) { + test('component opens the options dropdown when clicked', async function (assert) { + assert.expect(3); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); await click('[data-test-dropdown-trigger]'); - await assert.ok(find('[data-test-dropdown-options]'), 'Options are shown now'); + await assert.ok( + find('[data-test-dropdown-options]'), + 'Options are shown now' + ); await componentA11yAudit(this.element, assert); await click('[data-test-dropdown-trigger]'); - assert.notOk(find('[data-test-dropdown-options]'), 'Options are hidden after clicking again'); + assert.notOk( + find('[data-test-dropdown-options]'), + 'Options are hidden after clicking again' + ); }); - test('all options are shown in the options dropdown, each with a checkbox input', async function(assert) { + test('all options are shown in the options dropdown, each with a checkbox input', async function (assert) { + assert.expect(13); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -81,12 +103,19 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); findAll('[data-test-dropdown-option]').forEach((optionEl, index) => { const label = props.options[index].label; - assert.equal(optionEl.textContent.trim(), label, `Correct label for ${label}`); - assert.ok(optionEl.querySelector('input[type="checkbox"]'), 'Option contains a checkbox'); + assert.equal( + optionEl.textContent.trim(), + label, + `Correct label for ${label}` + ); + assert.ok( + optionEl.querySelector('input[type="checkbox"]'), + 'Option contains a checkbox' + ); }); }); - test('onSelect gets called when an option is clicked', async function(assert) { + test('onSelect gets called when an option is clicked', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -103,7 +132,9 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('the component trigger shows the selection count when there is a selection', async function(assert) { + test('the component trigger shows the selection count when there is a selection', async function (assert) { + assert.expect(4); + const props = commonProperties(); props.selection = [props.options[0].key, props.options[1].key]; this.setProperties(props); @@ -114,7 +145,8 @@ module('Integration | Component | multi-select dropdown', function(hooks) { 'The count is shown' ); assert.equal( - find('[data-test-dropdown-trigger] [data-test-dropdown-count]').textContent, + find('[data-test-dropdown-trigger] [data-test-dropdown-count]') + .textContent, props.selection.length, 'The count is accurate' ); @@ -129,14 +161,21 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('pressing DOWN when the trigger has focus opens the options list', async function(assert) { + test('pressing DOWN when the trigger has focus opens the options list', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); await focus('[data-test-dropdown-trigger]'); - assert.notOk(find('[data-test-dropdown-options]'), 'Options are not shown on focus'); - await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); + assert.notOk( + find('[data-test-dropdown-options]'), + 'Options are not shown on focus' + ); + await triggerKeyEvent( + '[data-test-dropdown-trigger]', + 'keydown', + ARROW_DOWN + ); assert.ok(find('[data-test-dropdown-options]'), 'Options are now shown'); assert.equal( document.activeElement, @@ -145,14 +184,22 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('pressing DOWN when the trigger has focus and the options list is open focuses the first option', async function(assert) { + test('pressing DOWN when the trigger has focus and the options list is open focuses the first option', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); await focus('[data-test-dropdown-trigger]'); - await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); - await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); + await triggerKeyEvent( + '[data-test-dropdown-trigger]', + 'keydown', + ARROW_DOWN + ); + await triggerKeyEvent( + '[data-test-dropdown-trigger]', + 'keydown', + ARROW_DOWN + ); assert.equal( document.activeElement, find('[data-test-dropdown-option]'), @@ -160,13 +207,17 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('pressing TAB when the trigger has focus and the options list is open focuses the first option', async function(assert) { + test('pressing TAB when the trigger has focus and the options list is open focuses the first option', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); await focus('[data-test-dropdown-trigger]'); - await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); + await triggerKeyEvent( + '[data-test-dropdown-trigger]', + 'keydown', + ARROW_DOWN + ); await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', TAB); assert.equal( document.activeElement, @@ -175,7 +226,7 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('pressing UP when the first list option is focused does nothing', async function(assert) { + test('pressing UP when the first list option is focused does nothing', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -191,7 +242,7 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('pressing DOWN when the a list option is focused moves focus to the next list option', async function(assert) { + test('pressing DOWN when the a list option is focused moves focus to the next list option', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -207,7 +258,9 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('pressing DOWN when the last list option has focus does nothing', async function(assert) { + test('pressing DOWN when the last list option has focus does nothing', async function (assert) { + assert.expect(6); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -222,7 +275,12 @@ module('Integration | Component | multi-select dropdown', function(hooks) { await triggerKeyEvent(option, 'keydown', ARROW_DOWN); if (index < lastIndex) { - assert.equal(document.activeElement, optionEls[index + 1], `Option ${index + 1} has focus`); + /* eslint-disable-next-line qunit/no-conditional-assertions */ + assert.equal( + document.activeElement, + optionEls[index + 1], + `Option ${index + 1} has focus` + ); } } @@ -234,7 +292,7 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('onSelect gets called when pressing SPACE when a list option is focused', async function(assert) { + test('onSelect gets called when pressing SPACE when a list option is focused', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -253,44 +311,68 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('list options have a zero tabindex and are therefore sequentially navigable', async function(assert) { + test('list options have a zero tabindex and are therefore sequentially navigable', async function (assert) { + assert.expect(6); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); await click('[data-test-dropdown-trigger]'); - findAll('[data-test-dropdown-option]').forEach(option => { - assert.equal(parseInt(option.getAttribute('tabindex'), 10), 0, 'tabindex is zero'); + findAll('[data-test-dropdown-option]').forEach((option) => { + assert.equal( + parseInt(option.getAttribute('tabindex'), 10), + 0, + 'tabindex is zero' + ); }); }); - test('the checkboxes inside list options have a negative tabindex and are therefore not sequentially navigable', async function(assert) { + test('the checkboxes inside list options have a negative tabindex and are therefore not sequentially navigable', async function (assert) { + assert.expect(6); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); await click('[data-test-dropdown-trigger]'); - findAll('[data-test-dropdown-option]').forEach(option => { + findAll('[data-test-dropdown-option]').forEach((option) => { assert.ok( - parseInt(option.querySelector('input[type="checkbox"]').getAttribute('tabindex'), 10) < 0, + parseInt( + option + .querySelector('input[type="checkbox"]') + .getAttribute('tabindex'), + 10 + ) < 0, 'tabindex is a negative value' ); }); }); - test('pressing ESC when the options list is open closes the list and returns focus to the dropdown trigger', async function(assert) { + test('pressing ESC when the options list is open closes the list and returns focus to the dropdown trigger', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); await focus('[data-test-dropdown-trigger]'); - await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); - await triggerKeyEvent('[data-test-dropdown-trigger]', 'keydown', ARROW_DOWN); + await triggerKeyEvent( + '[data-test-dropdown-trigger]', + 'keydown', + ARROW_DOWN + ); + await triggerKeyEvent( + '[data-test-dropdown-trigger]', + 'keydown', + ARROW_DOWN + ); await triggerKeyEvent('[data-test-dropdown-option]', 'keydown', ESC); - assert.notOk(find('[data-test-dropdown-options]'), 'The options list is hidden once more'); + assert.notOk( + find('[data-test-dropdown-options]'), + 'The options list is hidden once more' + ); assert.equal( document.activeElement, find('[data-test-dropdown-trigger]'), @@ -298,14 +380,19 @@ module('Integration | Component | multi-select dropdown', function(hooks) { ); }); - test('when there are no list options, an empty message is shown', async function(assert) { + test('when there are no list options, an empty message is shown', async function (assert) { + assert.expect(4); + const props = commonProperties(); props.options = []; this.setProperties(props); await render(commonTemplate); await click('[data-test-dropdown-trigger]'); - assert.ok(find('[data-test-dropdown-options]'), 'The dropdown is still shown'); + assert.ok( + find('[data-test-dropdown-options]'), + 'The dropdown is still shown' + ); assert.ok(find('[data-test-dropdown-empty]'), 'The empty state is shown'); assert.notOk(find('[data-test-dropdown-option]'), 'No options are shown'); await componentA11yAudit(this.element, assert); diff --git a/ui/tests/integration/components/page-layout-test.js b/ui/tests/integration/components/page-layout-test.js index f2437dae7..59611fd63 100644 --- a/ui/tests/integration/components/page-layout-test.js +++ b/ui/tests/integration/components/page-layout-test.js @@ -5,18 +5,20 @@ import hbs from 'htmlbars-inline-precompile'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | page layout', function(hooks) { +module('Integration | Component | page layout', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.server = startMirage(); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); - test('the global-header hamburger menu opens the gutter menu', async function(assert) { + test('the global-header hamburger menu opens the gutter menu', async function (assert) { + assert.expect(3); + await render(hbs``); assert.notOk( @@ -25,16 +27,22 @@ module('Integration | Component | page layout', function(hooks) { ); await click('[data-test-header-gutter-toggle]'); - assert.ok(find('[data-test-gutter-menu]').classList.contains('is-open'), 'Gutter menu is open'); + assert.ok( + find('[data-test-gutter-menu]').classList.contains('is-open'), + 'Gutter menu is open' + ); await componentA11yAudit(this.element, assert); }); - test('the gutter-menu hamburger menu closes the gutter menu', async function(assert) { + test('the gutter-menu hamburger menu closes the gutter menu', async function (assert) { await render(hbs``); await click('[data-test-header-gutter-toggle]'); - assert.ok(find('[data-test-gutter-menu]').classList.contains('is-open'), 'Gutter menu is open'); + assert.ok( + find('[data-test-gutter-menu]').classList.contains('is-open'), + 'Gutter menu is open' + ); await click('[data-test-gutter-gutter-toggle]'); assert.notOk( @@ -43,12 +51,15 @@ module('Integration | Component | page layout', function(hooks) { ); }); - test('the gutter-menu backdrop closes the gutter menu', async function(assert) { + test('the gutter-menu backdrop closes the gutter menu', async function (assert) { await render(hbs``); await click('[data-test-header-gutter-toggle]'); - assert.ok(find('[data-test-gutter-menu]').classList.contains('is-open'), 'Gutter menu is open'); + assert.ok( + find('[data-test-gutter-menu]').classList.contains('is-open'), + 'Gutter menu is open' + ); await click('[data-test-gutter-backdrop]'); assert.notOk( diff --git a/ui/tests/integration/components/placement-failure-test.js b/ui/tests/integration/components/placement-failure-test.js index 78653426a..0801956f8 100644 --- a/ui/tests/integration/components/placement-failure-test.js +++ b/ui/tests/integration/components/placement-failure-test.js @@ -6,14 +6,16 @@ import hbs from 'htmlbars-inline-precompile'; import cleanWhitespace from '../../utils/clean-whitespace'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | placement failures', function(hooks) { +module('Integration | Component | placement failures', function (hooks) { setupRenderingTest(hooks); const commonTemplate = hbs` `; - test('should render the placement failure (basic render)', async function(assert) { + test('should render the placement failure (basic render)', async function (assert) { + assert.expect(12); + const name = 'Placement Failure'; const failures = 11; this.set( @@ -29,12 +31,16 @@ module('Integration | Component | placement failures', function(hooks) { await render(commonTemplate); assert.equal( - cleanWhitespace(find('[data-test-placement-failure-task-group]').firstChild.wholeText), + cleanWhitespace( + find('[data-test-placement-failure-task-group]').firstChild.wholeText + ), name, 'Title is rendered with the name of the placement failure' ); assert.equal( - parseInt(find('[data-test-placement-failure-coalesced-failures]').textContent), + parseInt( + find('[data-test-placement-failure-coalesced-failures]').textContent + ), failures, 'Title is rendered correctly with a count of unplaced' ); @@ -78,12 +84,18 @@ module('Integration | Component | placement failures', function(hooks) { 1, 'Quota exhausted message shown' ); - assert.equal(findAll('[data-test-placement-failure-scores]').length, 1, 'Scores message shown'); + assert.equal( + findAll('[data-test-placement-failure-scores]').length, + 1, + 'Scores message shown' + ); await componentA11yAudit(this.element, assert); }); - test('should render correctly when a node is not evaluated', async function(assert) { + test('should render correctly when a node is not evaluated', async function (assert) { + assert.expect(3); + this.set( 'taskGroup', createFixture({ diff --git a/ui/tests/integration/components/plugin-allocation-row-test.js b/ui/tests/integration/components/plugin-allocation-row-test.js index 243679a6b..073dff281 100644 --- a/ui/tests/integration/components/plugin-allocation-row-test.js +++ b/ui/tests/integration/components/plugin-allocation-row-test.js @@ -6,22 +6,27 @@ import { render, settled } from '@ember/test-helpers'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | plugin allocation row', function(hooks) { +module('Integration | Component | plugin allocation row', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { fragmentSerializerInitializer(this.owner); this.store = this.owner.lookup('service:store'); this.server = startMirage(); this.server.create('node'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); - test('Plugin allocation row immediately fetches the plugin allocation', async function(assert) { - const plugin = this.server.create('csi-plugin', { id: 'plugin', controllerRequired: true }); + test('Plugin allocation row immediately fetches the plugin allocation', async function (assert) { + assert.expect(2); + + const plugin = this.server.create('csi-plugin', { + id: 'plugin', + controllerRequired: true, + }); const storageController = plugin.controllers.models[0]; const pluginRecord = await this.store.find('plugin', 'csi/plugin'); @@ -34,17 +39,21 @@ module('Integration | Component | plugin allocation row', function(hooks) { `); - await settled(); - - const allocationRequest = this.server.pretender.handledRequests.find(req => - req.url.startsWith('/v1/allocation') + const allocationRequest = this.server.pretender.handledRequests.find( + (req) => req.url.startsWith('/v1/allocation') + ); + assert.equal( + allocationRequest.url, + `/v1/allocation/${storageController.allocID}` ); - assert.equal(allocationRequest.url, `/v1/allocation/${storageController.allocID}`); await componentA11yAudit(this.element, assert); }); - test('After the plugin allocation row fetches the plugin allocation, allocation stats are fetched', async function(assert) { - const plugin = this.server.create('csi-plugin', { id: 'plugin', controllerRequired: true }); + test('After the plugin allocation row fetches the plugin allocation, allocation stats are fetched', async function (assert) { + const plugin = this.server.create('csi-plugin', { + id: 'plugin', + controllerRequired: true, + }); const storageController = plugin.controllers.models[0]; const pluginRecord = await this.store.find('plugin', 'csi/plugin'); @@ -57,14 +66,15 @@ module('Integration | Component | plugin allocation row', function(hooks) { `); - await settled(); - const [statsRequest] = this.server.pretender.handledRequests.slice(-1); - assert.equal(statsRequest.url, `/v1/client/allocation/${storageController.allocID}/stats`); + assert.equal( + statsRequest.url, + `/v1/client/allocation/${storageController.allocID}/stats` + ); }); - test('Setting a new plugin fetches the new plugin allocation', async function(assert) { + test('Setting a new plugin fetches the new plugin allocation', async function (assert) { const plugin = this.server.create('csi-plugin', { id: 'plugin', isMonolith: false, @@ -84,21 +94,25 @@ module('Integration | Component | plugin allocation row', function(hooks) { `); - await settled(); - - const allocationRequest = this.server.pretender.handledRequests.find(req => - req.url.startsWith('/v1/allocation') + const allocationRequest = this.server.pretender.handledRequests.find( + (req) => req.url.startsWith('/v1/allocation') ); - assert.equal(allocationRequest.url, `/v1/allocation/${storageController.allocID}`); + assert.equal( + allocationRequest.url, + `/v1/allocation/${storageController.allocID}` + ); this.set('plugin', pluginRecord.get('controllers').toArray()[1]); await settled(); const latestAllocationRequest = this.server.pretender.handledRequests - .filter(req => req.url.startsWith('/v1/allocation')) + .filter((req) => req.url.startsWith('/v1/allocation')) .reverse()[0]; - assert.equal(latestAllocationRequest.url, `/v1/allocation/${storageController2.allocID}`); + assert.equal( + latestAllocationRequest.url, + `/v1/allocation/${storageController2.allocID}` + ); }); }); diff --git a/ui/tests/integration/components/popover-menu-test.js b/ui/tests/integration/components/popover-menu-test.js index 4f0184b37..511765cd7 100644 --- a/ui/tests/integration/components/popover-menu-test.js +++ b/ui/tests/integration/components/popover-menu-test.js @@ -1,4 +1,4 @@ -import { click } from '@ember/test-helpers'; +import { click, render } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; @@ -8,10 +8,10 @@ import popoverMenuPageObject from 'nomad-ui/tests/pages/components/popover-menu' const PopoverMenu = create(popoverMenuPageObject()); -module('Integration | Component | popover-menu', function(hooks) { +module('Integration | Component | popover-menu', function (hooks) { setupRenderingTest(hooks); - const commonProperties = overrides => + const commonProperties = (overrides) => Object.assign( { triggerClass: '', @@ -31,10 +31,12 @@ module('Integration | Component | popover-menu', function(hooks) { `; - test('presents as a button with a chevron-down icon', async function(assert) { + test('presents as a button with a chevron-down icon', async function (assert) { + assert.expect(5); + const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(PopoverMenu.isPresent); assert.ok(PopoverMenu.labelHasIcon); @@ -43,10 +45,12 @@ module('Integration | Component | popover-menu', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('clicking the trigger button toggles the popover menu', async function(assert) { + test('clicking the trigger button toggles the popover menu', async function (assert) { + assert.expect(3); + const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); assert.notOk(PopoverMenu.menu.isOpen); await PopoverMenu.toggle(); @@ -55,19 +59,19 @@ module('Integration | Component | popover-menu', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('the trigger gets the triggerClass prop assigned as a class', async function(assert) { + test('the trigger gets the triggerClass prop assigned as a class', async function (assert) { const specialClass = 'is-special'; const props = commonProperties({ triggerClass: specialClass }); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); assert.dom('[data-test-popover-trigger]').hasClass('is-special'); }); - test('pressing DOWN ARROW when the trigger is focused opens the popover menu', async function(assert) { + test('pressing DOWN ARROW when the trigger is focused opens the popover menu', async function (assert) { const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); assert.notOk(PopoverMenu.menu.isOpen); await PopoverMenu.focus(); @@ -76,10 +80,10 @@ module('Integration | Component | popover-menu', function(hooks) { assert.ok(PopoverMenu.menu.isOpen); }); - test('pressing TAB when the trigger button is focused and the menu is open focuses the first focusable element in the popover menu', async function(assert) { + test('pressing TAB when the trigger button is focused and the menu is open focuses the first focusable element in the popover menu', async function (assert) { const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); await PopoverMenu.focus(); await PopoverMenu.downArrow(); @@ -91,10 +95,10 @@ module('Integration | Component | popover-menu', function(hooks) { assert.dom('#mock-input-for-test').isFocused(); }); - test('pressing ESC when the popover menu is open closes the menu and returns focus to the trigger button', async function(assert) { + test('pressing ESC when the popover menu is open closes the menu and returns focus to the trigger button', async function (assert) { const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); await PopoverMenu.toggle(); assert.ok(PopoverMenu.menu.isOpen); @@ -104,10 +108,10 @@ module('Integration | Component | popover-menu', function(hooks) { assert.notOk(PopoverMenu.menu.isOpen); }); - test('the ember-basic-dropdown object is yielded as context, including the close action', async function(assert) { + test('the ember-basic-dropdown object is yielded as context, including the close action', async function (assert) { const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); await PopoverMenu.toggle(); assert.ok(PopoverMenu.menu.isOpen); diff --git a/ui/tests/integration/components/primary-metric/allocation-test.js b/ui/tests/integration/components/primary-metric/allocation-test.js index edc52a457..1a50f6f41 100644 --- a/ui/tests/integration/components/primary-metric/allocation-test.js +++ b/ui/tests/integration/components/primary-metric/allocation-test.js @@ -13,21 +13,25 @@ const mockTasks = [ { task: 'Three', reservedCPU: 300, reservedMemory: 100, cpu: [], memory: [] }, ]; -module('Integration | Component | PrimaryMetric::Allocation', function(hooks) { +module('Integration | Component | PrimaryMetric::Allocation', function (hooks) { setupRenderingTest(hooks); setupPrimaryMetricMocks(hooks, [...mockTasks]); - hooks.beforeEach(function() { + hooks.beforeEach(function () { fragmentSerializerInitializer(this.owner); this.store = this.owner.lookup('service:store'); this.server = startMirage(); this.server.create('namespace'); this.server.create('node'); - this.server.create('job', { groupsCount: 1, groupTaskCount: 3, createAllocations: false }); + this.server.create('job', { + groupsCount: 1, + groupTaskCount: 3, + createAllocations: false, + }); this.server.create('allocation'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); @@ -37,13 +41,16 @@ module('Integration | Component | PrimaryMetric::Allocation', function(hooks) { @metric={{this.metric}} /> `; - const preload = async store => { + const preload = async (store) => { await store.findAll('allocation'); }; - const findResource = store => store.peekAll('allocation').get('firstObject'); + const findResource = (store) => + store.peekAll('allocation').get('firstObject'); + + test('Must pass an accessibility audit', async function (assert) { + assert.expect(1); - test('Must pass an accessibility audit', async function(assert) { await preload(this.store); const resource = findResource(this.store); @@ -53,7 +60,7 @@ module('Integration | Component | PrimaryMetric::Allocation', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('Each task for the allocation gets its own line', async function(assert) { + test('Each task for the allocation gets its own line', async function (assert) { await preload(this.store); const resource = findResource(this.store); diff --git a/ui/tests/integration/components/primary-metric/node-test.js b/ui/tests/integration/components/primary-metric/node-test.js index e444c79ac..a6d944c3b 100644 --- a/ui/tests/integration/components/primary-metric/node-test.js +++ b/ui/tests/integration/components/primary-metric/node-test.js @@ -8,11 +8,11 @@ import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { formatScheduledHertz } from 'nomad-ui/utils/units'; -module('Integration | Component | PrimaryMetric::Node', function(hooks) { +module('Integration | Component | PrimaryMetric::Node', function (hooks) { setupRenderingTest(hooks); setupPrimaryMetricMocks(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { fragmentSerializerInitializer(this.owner); this.store = this.owner.lookup('service:store'); this.server = startMirage(); @@ -20,7 +20,7 @@ module('Integration | Component | PrimaryMetric::Node', function(hooks) { this.server.create('node'); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); @@ -30,13 +30,15 @@ module('Integration | Component | PrimaryMetric::Node', function(hooks) { @metric={{this.metric}} /> `; - const preload = async store => { + const preload = async (store) => { await store.findAll('node'); }; - const findResource = store => store.peekAll('node').get('firstObject'); + const findResource = (store) => store.peekAll('node').get('firstObject'); + + test('Must pass an accessibility audit', async function (assert) { + assert.expect(1); - test('Must pass an accessibility audit', async function(assert) { await preload(this.store); const resource = findResource(this.store); @@ -46,7 +48,7 @@ module('Integration | Component | PrimaryMetric::Node', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('When the node has a reserved amount for the metric, a horizontal annotation is shown', async function(assert) { + test('When the node has a reserved amount for the metric, a horizontal annotation is shown', async function (assert) { this.server.create('node', 'reserved', { id: 'withAnnotation' }); await preload(this.store); diff --git a/ui/tests/integration/components/primary-metric/primary-metric.js b/ui/tests/integration/components/primary-metric/primary-metric.js index a4e8f7f70..fb91cb20b 100644 --- a/ui/tests/integration/components/primary-metric/primary-metric.js +++ b/ui/tests/integration/components/primary-metric/primary-metric.js @@ -6,23 +6,23 @@ import { task } from 'ember-concurrency'; import sinon from 'sinon'; export function setupPrimaryMetricMocks(hooks, tasks = []) { - hooks.beforeEach(function() { + hooks.beforeEach(function () { const getTrackerSpy = (this.getTrackerSpy = sinon.spy()); const trackerPollSpy = (this.trackerPollSpy = sinon.spy()); const trackerSignalPauseSpy = (this.trackerSignalPauseSpy = sinon.spy()); const MockTracker = EmberObject.extend({ - poll: task(function*() { + poll: task(function* () { yield trackerPollSpy(); }), - signalPause: task(function*() { + signalPause: task(function* () { yield trackerSignalPauseSpy(); }), - cpu: computed(function() { + cpu: computed(function () { return []; }), - memory: computed(function() { + memory: computed(function () { return []; }), tasks, @@ -35,13 +35,18 @@ export function setupPrimaryMetricMocks(hooks, tasks = []) { }, }); - this.owner.register('service:stats-trackers-registry', mockStatsTrackersRegistry); - this.statsTrackersRegistry = this.owner.lookup('service:stats-trackers-registry'); + this.owner.register( + 'service:stats-trackers-registry', + mockStatsTrackersRegistry + ); + this.statsTrackersRegistry = this.owner.lookup( + 'service:stats-trackers-registry' + ); }); } export function primaryMetric({ template, findResource, preload }) { - test('Contains a line chart, a percentage bar, a percentage figure, and an absolute usage figure', async function(assert) { + test('Contains a line chart, a percentage bar, a percentage figure, and an absolute usage figure', async function (assert) { const metric = 'cpu'; await preload(this.store); @@ -57,7 +62,7 @@ export function primaryMetric({ template, findResource, preload }) { assert.ok(find('[data-test-absolute-value]'), 'Absolute usage figure'); }); - test('The CPU metric maps to is-info', async function(assert) { + test('The CPU metric maps to is-info', async function (assert) { const metric = 'cpu'; await preload(this.store); @@ -73,7 +78,7 @@ export function primaryMetric({ template, findResource, preload }) { ); }); - test('The Memory metric maps to is-danger', async function(assert) { + test('The Memory metric maps to is-danger', async function (assert) { const metric = 'memory'; await preload(this.store); @@ -89,7 +94,27 @@ export function primaryMetric({ template, findResource, preload }) { ); }); - test('Gets the tracker from the tracker registry', async function(assert) { + test('Gets the tracker from the tracker registry', async function (assert) { + const metric = 'cpu'; + + await preload(this.store); + + const resource = findResource(this.store); + this.setProperties({ resource, metric }); + + await render(template); + + const spy = + this.getTrackerSpy.calledWith(resource) || + this.getTrackerSpy.calledWith(resource.allocation); + + assert.ok( + spy, + 'Uses the tracker registry to get the tracker for the provided resource' + ); + }); + + test('Immediately polls the tracker', async function (assert) { const metric = 'cpu'; await preload(this.store); @@ -100,25 +125,12 @@ export function primaryMetric({ template, findResource, preload }) { await render(template); assert.ok( - this.getTrackerSpy.calledWith(resource) || this.getTrackerSpy.calledWith(resource.allocation), - 'Uses the tracker registry to get the tracker for the provided resource' + this.trackerPollSpy.calledOnce, + 'The tracker is polled immediately' ); }); - test('Immediately polls the tracker', async function(assert) { - const metric = 'cpu'; - - await preload(this.store); - - const resource = findResource(this.store); - this.setProperties({ resource, metric }); - - await render(template); - - assert.ok(this.trackerPollSpy.calledOnce, 'The tracker is polled immediately'); - }); - - test('A pause signal is sent to the tracker when the component is destroyed', async function(assert) { + test('A pause signal is sent to the tracker when the component is destroyed', async function (assert) { const metric = 'cpu'; // Capture a reference to the spy before the component is destroyed @@ -130,9 +142,15 @@ export function primaryMetric({ template, findResource, preload }) { this.setProperties({ resource, metric }); await render(template); - assert.notOk(trackerSignalPauseSpy.called, 'No pause signal has been sent yet'); + assert.notOk( + trackerSignalPauseSpy.called, + 'No pause signal has been sent yet' + ); await clearRender(); - assert.ok(trackerSignalPauseSpy.calledOnce, 'A pause signal is sent to the tracker'); + assert.ok( + trackerSignalPauseSpy.calledOnce, + 'A pause signal is sent to the tracker' + ); }); } diff --git a/ui/tests/integration/components/primary-metric/task-test.js b/ui/tests/integration/components/primary-metric/task-test.js index 71ac4ed25..d77638fac 100644 --- a/ui/tests/integration/components/primary-metric/task-test.js +++ b/ui/tests/integration/components/primary-metric/task-test.js @@ -13,11 +13,11 @@ const mockTasks = [ { task: 'Three', reservedCPU: 300, reservedMemory: 100, cpu: [], memory: [] }, ]; -module('Integration | Component | PrimaryMetric::Task', function(hooks) { +module('Integration | Component | PrimaryMetric::Task', function (hooks) { setupRenderingTest(hooks); setupPrimaryMetricMocks(hooks, [...mockTasks]); - hooks.beforeEach(function() { + hooks.beforeEach(function () { fragmentSerializerInitializer(this.owner); this.store = this.owner.lookup('service:store'); this.server = startMirage(); @@ -37,7 +37,7 @@ module('Integration | Component | PrimaryMetric::Task', function(hooks) { this.server.create('allocation', { forceRunningClientStatus: true }); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); @@ -47,13 +47,16 @@ module('Integration | Component | PrimaryMetric::Task', function(hooks) { @metric={{this.metric}} /> `; - const preload = async store => { + const preload = async (store) => { await store.findAll('allocation'); }; - const findResource = store => store.peekAll('allocation').get('firstObject.states.firstObject'); + const findResource = (store) => + store.peekAll('allocation').get('firstObject.states.firstObject'); + + test('Must pass an accessibility audit', async function (assert) { + assert.expect(1); - test('Must pass an accessibility audit', async function(assert) { await preload(this.store); const resource = findResource(this.store); diff --git a/ui/tests/integration/components/reschedule-event-timeline-test.js b/ui/tests/integration/components/reschedule-event-timeline-test.js index 80ca60bf3..34b8d6b08 100644 --- a/ui/tests/integration/components/reschedule-event-timeline-test.js +++ b/ui/tests/integration/components/reschedule-event-timeline-test.js @@ -6,10 +6,10 @@ import hbs from 'htmlbars-inline-precompile'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; import moment from 'moment'; -module('Integration | Component | reschedule event timeline', function(hooks) { +module('Integration | Component | reschedule event timeline', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.server = startMirage(); this.server.create('namespace'); @@ -17,7 +17,7 @@ module('Integration | Component | reschedule event timeline', function(hooks) { this.server.create('job', { createAllocations: false }); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); @@ -25,7 +25,9 @@ module('Integration | Component | reschedule event timeline', function(hooks) { `; - test('when the allocation is running, the timeline shows past allocations', async function(assert) { + test('when the allocation is running, the timeline shows past allocations', async function (assert) { + assert.expect(7); + const attempts = 2; this.server.create('allocation', 'rescheduled', { @@ -37,7 +39,7 @@ module('Integration | Component | reschedule event timeline', function(hooks) { const allocation = this.store .peekAll('allocation') - .find(alloc => !alloc.get('nextAllocation.content')); + .find((alloc) => !alloc.get('nextAllocation.content')); this.set('allocation', allocation); await render(commonTemplate); @@ -74,7 +76,9 @@ module('Integration | Component | reschedule event timeline', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when the allocation has failed and there is a follow up evaluation, a note with a time is shown', async function(assert) { + test('when the allocation has failed and there is a follow up evaluation, a note with a time is shown', async function (assert) { + assert.expect(3); + const attempts = 2; this.server.create('allocation', 'rescheduled', { @@ -86,7 +90,7 @@ module('Integration | Component | reschedule event timeline', function(hooks) { const allocation = this.store .peekAll('allocation') - .find(alloc => !alloc.get('nextAllocation.content')); + .find((alloc) => !alloc.get('nextAllocation.content')); this.set('allocation', allocation); await render(commonTemplate); @@ -95,12 +99,17 @@ module('Integration | Component | reschedule event timeline', function(hooks) { find('[data-test-stop-warning]'), 'Stop warning is shown since the last allocation failed' ); - assert.notOk(find('[data-test-attempt-notice]'), 'Reschdule attempt notice is not shown'); + assert.notOk( + find('[data-test-attempt-notice]'), + 'Reschdule attempt notice is not shown' + ); await componentA11yAudit(this.element, assert); }); - test('when the allocation has failed and there is no follow up evaluation, a warning is shown', async function(assert) { + test('when the allocation has failed and there is no follow up evaluation, a warning is shown', async function (assert) { + assert.expect(3); + const attempts = 2; this.server.create('allocation', 'rescheduled', { @@ -108,12 +117,12 @@ module('Integration | Component | reschedule event timeline', function(hooks) { rescheduleSuccess: false, }); - const lastAllocation = server.schema.allocations.findBy({ nextAllocation: undefined }); + const lastAllocation = server.schema.allocations.findBy({ + nextAllocation: undefined, + }); lastAllocation.update({ followupEvalId: server.create('evaluation', { - waitUntil: moment() - .add(2, 'hours') - .toDate(), + waitUntil: moment().add(2, 'hours').toDate(), }).id, }); @@ -121,7 +130,7 @@ module('Integration | Component | reschedule event timeline', function(hooks) { let allocation = this.store .peekAll('allocation') - .find(alloc => !alloc.get('nextAllocation.content')); + .find((alloc) => !alloc.get('nextAllocation.content')); this.set('allocation', allocation); await render(commonTemplate); @@ -135,7 +144,7 @@ module('Integration | Component | reschedule event timeline', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when the allocation has a next allocation already, it is shown in the timeline', async function(assert) { + test('when the allocation has a next allocation already, it is shown in the timeline', async function (assert) { const attempts = 2; const originalAllocation = this.server.create('allocation', 'rescheduled', { @@ -145,19 +154,23 @@ module('Integration | Component | reschedule event timeline', function(hooks) { await this.store.findAll('allocation'); - const allocation = this.store.peekAll('allocation').findBy('id', originalAllocation.id); + const allocation = this.store + .peekAll('allocation') + .findBy('id', originalAllocation.id); this.set('allocation', allocation); await render(commonTemplate); - assert.ok( + assert.equal( find('[data-test-reschedule-label]').textContent.trim(), 'Next Allocation', 'The first allocation is the next allocation and labeled as such' ); assert.equal( - find('[data-test-allocation] [data-test-allocation-link]').textContent.trim(), + find( + '[data-test-allocation] [data-test-allocation-link]' + ).textContent.trim(), allocation.get('nextAllocation.shortId'), 'The next allocation item is for the correct allocation' ); diff --git a/ui/tests/integration/components/scale-events-accordion-test.js b/ui/tests/integration/components/scale-events-accordion-test.js index 5b4af356b..0f3623dd0 100644 --- a/ui/tests/integration/components/scale-events-accordion-test.js +++ b/ui/tests/integration/components/scale-events-accordion-test.js @@ -7,44 +7,58 @@ import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | scale-events-accordion', function(hooks) { +module('Integration | Component | scale-events-accordion', function (hooks) { setupRenderingTest(hooks); setupCodeMirror(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { fragmentSerializerInitializer(this.owner); this.store = this.owner.lookup('service:store'); this.server = startMirage(); this.server.create('node'); - this.taskGroupWithEvents = async function(events) { + this.taskGroupWithEvents = async function (events) { const job = this.server.create('job', { createAllocations: false }); const group = job.taskGroups.models[0]; - job.jobScale.taskGroupScales.models.findBy('name', group.name).update({ events }); + job.jobScale.taskGroupScales.models + .findBy('name', group.name) + .update({ events }); - const jobModel = await this.store.find('job', JSON.stringify([job.id, 'default'])); + const jobModel = await this.store.find( + 'job', + JSON.stringify([job.id, 'default']) + ); await jobModel.get('scaleState'); return jobModel.taskGroups.findBy('name', group.name); }; }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); const commonTemplate = hbs``; - test('it shows an accordion with an entry for each event', async function(assert) { + test('it shows an accordion with an entry for each event', async function (assert) { + assert.expect(2); + const eventCount = 5; - const taskGroup = await this.taskGroupWithEvents(server.createList('scale-event', eventCount)); + const taskGroup = await this.taskGroupWithEvents( + server.createList('scale-event', eventCount) + ); this.set('events', taskGroup.scaleState.events); await render(commonTemplate); - assert.equal(findAll('[data-test-scale-events] [data-test-accordion-head]').length, eventCount); + assert.equal( + findAll('[data-test-scale-events] [data-test-accordion-head]').length, + eventCount + ); await componentA11yAudit(this.element, assert); }); - test('when an event is an error, an error icon is shown', async function(assert) { + test('when an event is an error, an error icon is shown', async function (assert) { + assert.expect(2); + const taskGroup = await this.taskGroupWithEvents( server.createList('scale-event', 1, { error: true }) ); @@ -56,10 +70,16 @@ module('Integration | Component | scale-events-accordion', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when an event has a count higher than previous count, a danger up arrow is shown', async function(assert) { + test('when an event has a count higher than previous count, a danger up arrow is shown', async function (assert) { + assert.expect(4); + const count = 5; const taskGroup = await this.taskGroupWithEvents( - server.createList('scale-event', 1, { count, previousCount: count - 1, error: false }) + server.createList('scale-event', 1, { + count, + previousCount: count - 1, + error: false, + }) ); this.set('events', taskGroup.scaleState.events); @@ -75,10 +95,14 @@ module('Integration | Component | scale-events-accordion', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when an event has a count lower than previous count, a primary down arrow is shown', async function(assert) { + test('when an event has a count lower than previous count, a primary down arrow is shown', async function (assert) { const count = 5; const taskGroup = await this.taskGroupWithEvents( - server.createList('scale-event', 1, { count, previousCount: count + 1, error: false }) + server.createList('scale-event', 1, { + count, + previousCount: count + 1, + error: false, + }) ); this.set('events', taskGroup.scaleState.events); @@ -93,7 +117,7 @@ module('Integration | Component | scale-events-accordion', function(hooks) { ); }); - test('when an event has no count, the count is omitted', async function(assert) { + test('when an event has no count, the count is omitted', async function (assert) { const taskGroup = await this.taskGroupWithEvents( server.createList('scale-event', 1, { count: null }) ); @@ -105,7 +129,9 @@ module('Integration | Component | scale-events-accordion', function(hooks) { assert.notOk(find('[data-test-count-icon]')); }); - test('when an event has no meta properties, the accordion entry is not expandable', async function(assert) { + test('when an event has no meta properties, the accordion entry is not expandable', async function (assert) { + assert.expect(2); + const taskGroup = await this.taskGroupWithEvents( server.createList('scale-event', 1, { meta: {} }) ); @@ -113,11 +139,15 @@ module('Integration | Component | scale-events-accordion', function(hooks) { await render(commonTemplate); - assert.ok(find('[data-test-accordion-toggle]').classList.contains('is-invisible')); + assert.ok( + find('[data-test-accordion-toggle]').classList.contains('is-invisible') + ); await componentA11yAudit(this.element, assert); }); - test('when an event has meta properties, the accordion entry is expanding, presenting the meta properties in a json viewer', async function(assert) { + test('when an event has meta properties, the accordion entry is expanding, presenting the meta properties in a json viewer', async function (assert) { + assert.expect(4); + const meta = { prop: 'one', prop2: 'two', @@ -126,7 +156,9 @@ module('Integration | Component | scale-events-accordion', function(hooks) { 'dot.separate.prop': 12, }, }; - const taskGroup = await this.taskGroupWithEvents(server.createList('scale-event', 1, { meta })); + const taskGroup = await this.taskGroupWithEvents( + server.createList('scale-event', 1, { meta }) + ); this.set('events', taskGroup.scaleState.events); await render(commonTemplate); diff --git a/ui/tests/integration/components/scale-events-chart-test.js b/ui/tests/integration/components/scale-events-chart-test.js index 87994e806..f0c048eca 100644 --- a/ui/tests/integration/components/scale-events-chart-test.js +++ b/ui/tests/integration/components/scale-events-chart-test.js @@ -6,7 +6,7 @@ import moment from 'moment'; import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | scale-events-chart', function(hooks) { +module('Integration | Component | scale-events-chart', function (hooks) { setupRenderingTest(hooks); setupCodeMirror(hooks); @@ -51,22 +51,23 @@ module('Integration | Component | scale-events-chart', function(hooks) { }, ]; - test('each event is rendered as an annotation', async function(assert) { + test('each event is rendered as an annotation', async function (assert) { + assert.expect(2); + this.set('events', events); await render(hbs``); assert.equal( findAll('[data-test-annotation]').length, - events.filter(ev => ev.count == null).length + events.filter((ev) => ev.count == null).length ); await componentA11yAudit(this.element, assert); }); - test('clicking an annotation presents details for the event', async function(assert) { - const annotation = events - .rejectBy('hasCount') - .sortBy('time') - .reverse()[0]; + test('clicking an annotation presents details for the event', async function (assert) { + assert.expect(6); + + const annotation = events.rejectBy('hasCount').sortBy('time').reverse()[0]; this.set('events', events); await render(hbs``); @@ -88,7 +89,7 @@ module('Integration | Component | scale-events-chart', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('clicking an active annotation closes event details', async function(assert) { + test('clicking an active annotation closes event details', async function (assert) { this.set('events', events); await render(hbs``); diff --git a/ui/tests/integration/components/single-select-dropdown-test.js b/ui/tests/integration/components/single-select-dropdown-test.js index 3d509de75..cc6d0b37b 100644 --- a/ui/tests/integration/components/single-select-dropdown-test.js +++ b/ui/tests/integration/components/single-select-dropdown-test.js @@ -1,12 +1,15 @@ import { findAll, find, render } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { selectChoose, clickTrigger } from 'ember-power-select/test-support/helpers'; +import { + selectChoose, + clickTrigger, +} from 'ember-power-select/test-support/helpers'; import sinon from 'sinon'; import hbs from 'htmlbars-inline-precompile'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; -module('Integration | Component | single-select dropdown', function(hooks) { +module('Integration | Component | single-select dropdown', function (hooks) { setupRenderingTest(hooks); const commonProperties = () => ({ @@ -31,12 +34,16 @@ module('Integration | Component | single-select dropdown', function(hooks) { @onSelect={{this.onSelect}} /> `; - test('component shows label and selection in the trigger', async function(assert) { + test('component shows label and selection in the trigger', async function (assert) { + assert.expect(4); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); - assert.ok(find('.ember-power-select-trigger').textContent.includes(props.label)); + assert.ok( + find('.ember-power-select-trigger').textContent.includes(props.label) + ); assert.ok( find('.ember-power-select-trigger').textContent.includes( props.options.findBy('key', props.selection).label @@ -47,7 +54,9 @@ module('Integration | Component | single-select dropdown', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('all options are shown in the dropdown', async function(assert) { + test('all options are shown in the dropdown', async function (assert) { + assert.expect(7); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -67,7 +76,7 @@ module('Integration | Component | single-select dropdown', function(hooks) { }); }); - test('selecting an option calls `onSelect` with the key for the selected option', async function(assert) { + test('selecting an option calls `onSelect` with the key for the selected option', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); diff --git a/ui/tests/integration/components/stepper-input-test.js b/ui/tests/integration/components/stepper-input-test.js index 9e22ad9b3..fa5c3e144 100644 --- a/ui/tests/integration/components/stepper-input-test.js +++ b/ui/tests/integration/components/stepper-input-test.js @@ -1,4 +1,10 @@ -import { find, render, settled, triggerEvent, waitUntil } from '@ember/test-helpers'; +import { + find, + render, + settled, + triggerEvent, + waitUntil, +} from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; @@ -13,7 +19,7 @@ const valueChange = () => { return () => StepperInput.input.value !== initial; }; -module('Integration | Component | stepper input', function(hooks) { +module('Integration | Component | stepper input', function (hooks) { setupRenderingTest(hooks); const commonProperties = () => ({ @@ -39,7 +45,9 @@ module('Integration | Component | stepper input', function(hooks) { `; - test('basic appearance includes a label, an input, and two buttons', async function(assert) { + test('basic appearance includes a label, an input, and two buttons', async function (assert) { + assert.expect(7); + this.setProperties(commonProperties()); await render(commonTemplate); @@ -48,13 +56,17 @@ module('Integration | Component | stepper input', function(hooks) { assert.equal(StepperInput.input.value, this.value); assert.ok(StepperInput.decrement.isPresent); assert.ok(StepperInput.increment.isPresent); - assert.ok(StepperInput.decrement.classNames.split(' ').includes(this.classVariant)); - assert.ok(StepperInput.increment.classNames.split(' ').includes(this.classVariant)); + assert.ok( + StepperInput.decrement.classNames.split(' ').includes(this.classVariant) + ); + assert.ok( + StepperInput.increment.classNames.split(' ').includes(this.classVariant) + ); await componentA11yAudit(this.element, assert); }); - test('clicking the increment and decrement buttons immediately changes the shown value in the input but debounces the onUpdate call', async function(assert) { + test('clicking the increment and decrement buttons immediately changes the shown value in the input but debounces the onUpdate call', async function (assert) { this.setProperties(commonProperties()); const baseValue = this.value; @@ -80,7 +92,7 @@ module('Integration | Component | stepper input', function(hooks) { assert.ok(this.onChange.calledWith(baseValue - 1)); }); - test('the increment button is disabled when the internal value is the max value', async function(assert) { + test('the increment button is disabled when the internal value is the max value', async function (assert) { this.setProperties(commonProperties()); this.set('value', this.max); @@ -89,7 +101,7 @@ module('Integration | Component | stepper input', function(hooks) { assert.ok(StepperInput.increment.isDisabled); }); - test('the decrement button is disabled when the internal value is the min value', async function(assert) { + test('the decrement button is disabled when the internal value is the min value', async function (assert) { this.setProperties(commonProperties()); this.set('value', this.min); @@ -98,7 +110,7 @@ module('Integration | Component | stepper input', function(hooks) { assert.ok(StepperInput.decrement.isDisabled); }); - test('the text input does not call the onUpdate function on oninput', async function(assert) { + test('the text input does not call the onUpdate function on oninput', async function (assert) { this.setProperties(commonProperties()); const newValue = 8; @@ -119,7 +131,7 @@ module('Integration | Component | stepper input', function(hooks) { assert.ok(this.onChange.calledWith(newValue)); }); - test('the text input does call the onUpdate function on onchange', async function(assert) { + test('the text input does call the onUpdate function on onchange', async function (assert) { this.setProperties(commonProperties()); const newValue = 8; @@ -132,7 +144,7 @@ module('Integration | Component | stepper input', function(hooks) { assert.ok(this.onChange.calledWith(newValue)); }); - test('text input limits input to the bounds of the min/max range', async function(assert) { + test('text input limits input to the bounds of the min/max range', async function (assert) { this.setProperties(commonProperties()); let newValue = this.max + 1; @@ -153,7 +165,7 @@ module('Integration | Component | stepper input', function(hooks) { assert.ok(this.onChange.calledWith(this.min)); }); - test('pressing ESC in the text input reverts the text value back to the current value', async function(assert) { + test('pressing ESC in the text input reverts the text value back to the current value', async function (assert) { this.setProperties(commonProperties()); const newValue = 8; @@ -168,7 +180,7 @@ module('Integration | Component | stepper input', function(hooks) { assert.equal(StepperInput.input.value, this.value); }); - test('clicking the label focuses in the input', async function(assert) { + test('clicking the label focuses in the input', async function (assert) { this.setProperties(commonProperties()); await render(commonTemplate); @@ -178,22 +190,19 @@ module('Integration | Component | stepper input', function(hooks) { assert.equal(document.activeElement, input); }); - test('focusing the input selects the input value', async function(assert) { + test('focusing the input selects the input value', async function (assert) { this.setProperties(commonProperties()); await render(commonTemplate); await StepperInput.input.focus(); assert.equal( - window - .getSelection() - .toString() - .trim(), + window.getSelection().toString().trim(), this.value.toString() ); }); - test('entering a fractional value floors the value', async function(assert) { + test('entering a fractional value floors the value', async function (assert) { this.setProperties(commonProperties()); const newValue = 3.14159; @@ -206,7 +215,7 @@ module('Integration | Component | stepper input', function(hooks) { assert.ok(this.onChange.calledWith(Math.floor(newValue))); }); - test('entering an invalid value reverts the value', async function(assert) { + test('entering an invalid value reverts the value', async function (assert) { this.setProperties(commonProperties()); const newValue = 'NaN'; diff --git a/ui/tests/integration/components/streaming-file-test.js b/ui/tests/integration/components/streaming-file-test.js index 94bb56ec3..31e7b9580 100644 --- a/ui/tests/integration/components/streaming-file-test.js +++ b/ui/tests/integration/components/streaming-file-test.js @@ -1,5 +1,5 @@ import { run } from '@ember/runloop'; -import { find, settled, triggerKeyEvent } from '@ember/test-helpers'; +import { find, render, triggerKeyEvent } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; @@ -12,7 +12,7 @@ import Log from 'nomad-ui/utils/classes/log'; const { assign } = Object; const A_KEY = 65; -const stringifyValues = obj => +const stringifyValues = (obj) => Object.keys(obj).reduce((newObj, key) => { newObj[key] = obj[key].toString(); return newObj; @@ -23,20 +23,20 @@ const makeLogger = (url, params) => url, params, plainText: true, - logFetch: url => fetch(url).then(res => res), + logFetch: (url) => fetch(url).then((res) => res), }); -module('Integration | Component | streaming file', function(hooks) { +module('Integration | Component | streaming file', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { - this.server = new Pretender(function() { + hooks.beforeEach(function () { + this.server = new Pretender(function () { this.get('/file/endpoint', () => [200, {}, 'Hello World']); this.get('/file/stream', () => [200, {}, logEncode(['Hello World'], 0)]); }); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); @@ -44,7 +44,9 @@ module('Integration | Component | streaming file', function(hooks) { `; - test('when mode is `head`, the logger signals head', async function(assert) { + test('when mode is `head`, the logger signals head', async function (assert) { + assert.expect(5); + const url = '/file/endpoint'; const params = { path: 'hello/world.txt', offset: 0, limit: 50000 }; this.setProperties({ @@ -53,8 +55,7 @@ module('Integration | Component | streaming file', function(hooks) { isStreaming: false, }); - await this.render(commonTemplate); - await settled(); + await render(commonTemplate); const request = this.server.handledRequests[0]; assert.equal(this.server.handledRequests.length, 1, 'One request made'); @@ -68,7 +69,7 @@ module('Integration | Component | streaming file', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('when mode is `tail`, the logger signals tail', async function(assert) { + test('when mode is `tail`, the logger signals tail', async function (assert) { const url = '/file/endpoint'; const params = { path: 'hello/world.txt', limit: 50000 }; this.setProperties({ @@ -77,8 +78,7 @@ module('Integration | Component | streaming file', function(hooks) { isStreaming: false, }); - await this.render(commonTemplate); - await settled(); + await render(commonTemplate); const request = this.server.handledRequests[0]; assert.equal(this.server.handledRequests.length, 1, 'One request made'); @@ -91,7 +91,7 @@ module('Integration | Component | streaming file', function(hooks) { assert.equal(find('[data-test-output]').textContent, 'Hello World'); }); - test('when mode is `streaming` and `isStreaming` is true, streaming starts', async function(assert) { + test('when mode is `streaming` and `isStreaming` is true, streaming starts', async function (assert) { const url = '/file/stream'; const params = { path: 'hello/world.txt', limit: 50000 }; this.setProperties({ @@ -104,15 +104,14 @@ module('Integration | Component | streaming file', function(hooks) { run.later(run, run.cancelTimers, 500); - await this.render(commonTemplate); - await settled(); + await render(commonTemplate); const request = this.server.handledRequests[0]; assert.equal(request.url.split('?')[0], url, `URL is ${url}`); assert.equal(find('[data-test-output]').textContent, 'Hello World'); }); - test('the ctrl+a/cmd+a shortcut selects only the text in the output window', async function(assert) { + test('the ctrl+a/cmd+a shortcut selects only the text in the output window', async function (assert) { const url = '/file/endpoint'; const params = { path: 'hello/world.txt', offset: 0, limit: 50000 }; this.setProperties({ @@ -121,32 +120,29 @@ module('Integration | Component | streaming file', function(hooks) { isStreaming: false, }); - await this.render(hbs` + await render(hbs` Extra text On either side `); - await settled(); // Windows and Linux shortcut - await triggerKeyEvent('[data-test-output]', 'keydown', A_KEY, { ctrlKey: true }); + await triggerKeyEvent('[data-test-output]', 'keydown', A_KEY, { + ctrlKey: true, + }); assert.equal( - window - .getSelection() - .toString() - .trim(), + window.getSelection().toString().trim(), find('[data-test-output]').textContent.trim() ); window.getSelection().removeAllRanges(); // MacOS shortcut - await triggerKeyEvent('[data-test-output]', 'keydown', A_KEY, { metaKey: true }); + await triggerKeyEvent('[data-test-output]', 'keydown', A_KEY, { + metaKey: true, + }); assert.equal( - window - .getSelection() - .toString() - .trim(), + window.getSelection().toString().trim(), find('[data-test-output]').textContent.trim() ); }); diff --git a/ui/tests/integration/components/task-group-row-test.js b/ui/tests/integration/components/task-group-row-test.js index 1f7ac954d..48f0cd77b 100644 --- a/ui/tests/integration/components/task-group-row-test.js +++ b/ui/tests/integration/components/task-group-row-test.js @@ -45,10 +45,10 @@ const makeJob = (server, props = {}) => { }); }; -module('Integration | Component | task group row', function(hooks) { +module('Integration | Component | task group row', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { fragmentSerializerInitializer(this.owner); this.store = this.owner.lookup('service:store'); this.token = this.owner.lookup('service:token'); @@ -60,7 +60,7 @@ module('Integration | Component | task group row', function(hooks) { window.localStorage.nomadTokenSecret = managementToken.secretId; }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); window.localStorage.clear(); }); @@ -69,7 +69,9 @@ module('Integration | Component | task group row', function(hooks) { `; - test('Task group row conditionally shows scaling buttons based on the presence of the scaling attr on the task group', async function(assert) { + test('Task group row conditionally shows scaling buttons based on the presence of the scaling attr on the task group', async function (assert) { + assert.expect(3); + makeJob(this.server, { noActiveDeployment: true }); this.token.fetchSelfTokenAndPolicies.perform(); await settled(); @@ -88,7 +90,7 @@ module('Integration | Component | task group row', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('Clicking scaling buttons immediately updates the rendered count but debounces the scaling API request', async function(assert) { + test('Clicking scaling buttons immediately updates the rendered count but debounces the scaling API request', async function (assert) { makeJob(this.server, { noActiveDeployment: true }); this.token.fetchSelfTokenAndPolicies.perform(); await settled(); @@ -109,19 +111,21 @@ module('Integration | Component | task group row', function(hooks) { assert.notOk( server.pretender.handledRequests.find( - req => req.method === 'POST' && req.url.endsWith('/scale') + (req) => req.method === 'POST' && req.url.endsWith('/scale') ) ); await settled(); const scaleRequests = server.pretender.handledRequests.filter( - req => req.method === 'POST' && req.url.endsWith('/scale') + (req) => req.method === 'POST' && req.url.endsWith('/scale') ); assert.equal(scaleRequests.length, 1); assert.equal(JSON.parse(scaleRequests[0].requestBody).Count, 4); }); - test('When the current count is equal to the max count, the increment count button is disabled', async function(assert) { + test('When the current count is equal to the max count, the increment count button is disabled', async function (assert) { + assert.expect(2); + makeJob(this.server, { noActiveDeployment: true }); this.token.fetchSelfTokenAndPolicies.perform(); await settled(); @@ -137,7 +141,9 @@ module('Integration | Component | task group row', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('When the current count is equal to the min count, the decrement count button is disabled', async function(assert) { + test('When the current count is equal to the min count, the decrement count button is disabled', async function (assert) { + assert.expect(2); + makeJob(this.server, { noActiveDeployment: true }); this.token.fetchSelfTokenAndPolicies.perform(); await settled(); @@ -153,7 +159,9 @@ module('Integration | Component | task group row', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('When there is an active deployment, both scale buttons are disabled', async function(assert) { + test('When there is an active deployment, both scale buttons are disabled', async function (assert) { + assert.expect(3); + makeJob(this.server, { activeDeployment: true }); this.token.fetchSelfTokenAndPolicies.perform(); await settled(); @@ -168,7 +176,7 @@ module('Integration | Component | task group row', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('When the current ACL token does not have the namespace:scale-job or namespace:submit-job policy rule', async function(assert) { + test('When the current ACL token does not have the namespace:scale-job or namespace:submit-job policy rule', async function (assert) { makeJob(this.server, { noActiveDeployment: true }); window.localStorage.nomadTokenSecret = clientToken.secretId; this.token.fetchSelfTokenAndPolicies.perform(); diff --git a/ui/tests/integration/components/task-log-test.js b/ui/tests/integration/components/task-log-test.js index ef3943e0a..90c3d41cb 100644 --- a/ui/tests/integration/components/task-log-test.js +++ b/ui/tests/integration/components/task-log-test.js @@ -28,10 +28,10 @@ const streamFrames = ['one\n', 'two\n', 'three\n', 'four\n', 'five\n']; let streamPointer = 0; let logMode = null; -module('Integration | Component | task log', function(hooks) { +module('Integration | Component | task log', function (hooks) { setupRenderingTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { const handler = ({ queryParams }) => { let frames; let data; @@ -45,42 +45,56 @@ module('Integration | Component | task log', function(hooks) { } if (frames === streamFrames) { - data = queryParams.plain ? frames[streamPointer] : logEncode(frames, streamPointer); + data = queryParams.plain + ? frames[streamPointer] + : logEncode(frames, streamPointer); streamPointer++; } else { - data = queryParams.plain ? frames.join('') : logEncode(frames, frames.length - 1); + data = queryParams.plain + ? frames.join('') + : logEncode(frames, frames.length - 1); } return [200, {}, data]; }; - this.server = new Pretender(function() { + this.server = new Pretender(function () { this.get(`http://${HOST}/v1/client/fs/logs/:allocation_id`, handler); this.get('/v1/client/fs/logs/:allocation_id', handler); this.get('/v1/regions', () => [200, {}, '[]']); }); }); - hooks.afterEach(function() { + hooks.afterEach(function () { window.localStorage.clear(); this.server.shutdown(); streamPointer = 0; logMode = null; }); - test('Basic appearance', async function(assert) { + test('Basic appearance', async function (assert) { + assert.expect(8); + run.later(run, run.cancelTimers, commonProps.interval); this.setProperties(commonProps); - await render(hbs``); + await render( + hbs`` + ); assert.ok(find('[data-test-log-action="stdout"]'), 'Stdout button'); assert.ok(find('[data-test-log-action="stderr"]'), 'Stderr button'); assert.ok(find('[data-test-log-action="head"]'), 'Head button'); assert.ok(find('[data-test-log-action="tail"]'), 'Tail button'); - assert.ok(find('[data-test-log-action="toggle-stream"]'), 'Stream toggle button'); + assert.ok( + find('[data-test-log-action="toggle-stream"]'), + 'Stream toggle button' + ); - assert.ok(find('[data-test-log-box].is-full-bleed.is-dark'), 'Body is full-bleed and dark'); + assert.ok( + find('[data-test-log-box].is-full-bleed.is-dark'), + 'Body is full-bleed and dark' + ); assert.ok( find('pre.cli-window'), @@ -90,15 +104,22 @@ module('Integration | Component | task log', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('Streaming starts on creation', async function(assert) { + test('Streaming starts on creation', async function (assert) { + assert.expect(3); + run.later(run, run.cancelTimers, commonProps.interval); this.setProperties(commonProps); - await render(hbs``); + await render( + hbs`` + ); - const logUrlRegex = new RegExp(`${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`); + const logUrlRegex = new RegExp( + `${HOST}/v1/client/fs/logs/${commonProps.allocation.id}` + ); assert.ok( - this.server.handledRequests.filter(req => logUrlRegex.test(req.url)).length, + this.server.handledRequests.filter((req) => logUrlRegex.test(req.url)) + .length, 'Log requests were made' ); @@ -112,12 +133,14 @@ module('Integration | Component | task log', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('Clicking Head loads the log head', async function(assert) { + test('Clicking Head loads the log head', async function (assert) { logMode = 'head'; run.later(run, run.cancelTimers, commonProps.interval); this.setProperties(commonProps); - await render(hbs``); + await render( + hbs`` + ); click('[data-test-log-action="head"]'); @@ -128,27 +151,41 @@ module('Integration | Component | task log', function(hooks) { ), 'Log head request was made' ); - assert.equal(find('[data-test-log-cli]').textContent, logHead[0], 'Head of the log is shown'); + assert.equal( + find('[data-test-log-cli]').textContent, + logHead[0], + 'Head of the log is shown' + ); }); - test('Clicking Tail loads the log tail', async function(assert) { + test('Clicking Tail loads the log tail', async function (assert) { logMode = 'tail'; run.later(run, run.cancelTimers, commonProps.interval); this.setProperties(commonProps); - await render(hbs``); + await render( + hbs`` + ); click('[data-test-log-action="tail"]'); await settled(); assert.ok( - this.server.handledRequests.find(({ queryParams: qp }) => qp.origin === 'end'), + this.server.handledRequests.find( + ({ queryParams: qp }) => qp.origin === 'end' + ), 'Log tail request was made' ); - assert.equal(find('[data-test-log-cli]').textContent, logTail[0], 'Tail of the log is shown'); + assert.equal( + find('[data-test-log-cli]').textContent, + logTail[0], + 'Tail of the log is shown' + ); }); - test('Clicking toggleStream starts and stops the log stream', async function(assert) { + test('Clicking toggleStream starts and stops the log stream', async function (assert) { + assert.expect(3); + run.later(run, run.cancelTimers, commonProps.interval); const { interval } = commonProps; @@ -162,7 +199,11 @@ module('Integration | Component | task log', function(hooks) { }, interval); await settled(); - assert.equal(find('[data-test-log-cli]').textContent, streamFrames[0], 'First frame loaded'); + assert.equal( + find('[data-test-log-cli]').textContent, + streamFrames[0], + 'First frame loaded' + ); run.later(() => { assert.equal( @@ -182,23 +223,27 @@ module('Integration | Component | task log', function(hooks) { ); }); - test('Clicking stderr switches the log to standard error', async function(assert) { + test('Clicking stderr switches the log to standard error', async function (assert) { run.later(run, run.cancelTimers, commonProps.interval); this.setProperties(commonProps); - await render(hbs``); + await render( + hbs`` + ); click('[data-test-log-action="stderr"]'); run.later(run, run.cancelTimers, commonProps.interval); await settled(); assert.ok( - this.server.handledRequests.filter(req => req.queryParams.type === 'stderr').length, + this.server.handledRequests.filter( + (req) => req.queryParams.type === 'stderr' + ).length, 'stderr log requests were made' ); }); - test('Clicking stderr/stdout mode buttons does nothing when the mode remains the same', async function(assert) { + test('Clicking stderr/stdout mode buttons does nothing when the mode remains the same', async function (assert) { const { interval } = commonProps; run.later(() => { @@ -207,9 +252,10 @@ module('Integration | Component | task log', function(hooks) { }, interval * 2); this.setProperties(commonProps); - await render(hbs``); + await render( + hbs`` + ); - await settled(); assert.equal( find('[data-test-log-cli]').textContent, streamFrames[0] + streamFrames[0] + streamFrames[1], @@ -217,7 +263,7 @@ module('Integration | Component | task log', function(hooks) { ); }); - test('When the client is inaccessible, task-log falls back to requesting logs through the server', async function(assert) { + test('When the client is inaccessible, task-log falls back to requesting logs through the server', async function (assert) { run.later(run, run.cancelTimers, allowedConnectionTime * 2); // override client response to timeout @@ -234,26 +280,34 @@ module('Integration | Component | task log', function(hooks) { @clientTimeout={{clientTimeout}} @serverTimeout={{serverTimeout}} />`); - const clientUrlRegex = new RegExp(`${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`); + const clientUrlRegex = new RegExp( + `${HOST}/v1/client/fs/logs/${commonProps.allocation.id}` + ); assert.ok( - this.server.handledRequests.filter(req => clientUrlRegex.test(req.url)).length, + this.server.handledRequests.filter((req) => clientUrlRegex.test(req.url)) + .length, 'Log request was initially made directly to the client' ); await settled(); const serverUrl = `/v1/client/fs/logs/${commonProps.allocation.id}`; assert.ok( - this.server.handledRequests.filter(req => req.url.startsWith(serverUrl)).length, + this.server.handledRequests.filter((req) => req.url.startsWith(serverUrl)) + .length, 'Log request was later made to the server' ); assert.ok( - this.server.handledRequests.filter(req => clientUrlRegex.test(req.url))[0].aborted, + this.server.handledRequests.filter((req) => + clientUrlRegex.test(req.url) + )[0].aborted, 'Client log request was aborted' ); }); - test('When both the client and the server are inaccessible, an error message is shown', async function(assert) { + test('When both the client and the server are inaccessible, an error message is shown', async function (assert) { + assert.expect(5); + run.later(run, run.cancelTimers, allowedConnectionTime * 5); // override client and server responses to timeout @@ -275,26 +329,35 @@ module('Integration | Component | task log', function(hooks) { @clientTimeout={{clientTimeout}} @serverTimeout={{serverTimeout}} />`); - await settled(); - const clientUrlRegex = new RegExp(`${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`); + const clientUrlRegex = new RegExp( + `${HOST}/v1/client/fs/logs/${commonProps.allocation.id}` + ); assert.ok( - this.server.handledRequests.filter(req => clientUrlRegex.test(req.url)).length, + this.server.handledRequests.filter((req) => clientUrlRegex.test(req.url)) + .length, 'Log request was initially made directly to the client' ); const serverUrl = `/v1/client/fs/logs/${commonProps.allocation.id}`; assert.ok( - this.server.handledRequests.filter(req => req.url.startsWith(serverUrl)).length, + this.server.handledRequests.filter((req) => req.url.startsWith(serverUrl)) + .length, 'Log request was later made to the server' ); - assert.ok(find('[data-test-connection-error]'), 'An error message is shown'); + assert.ok( + find('[data-test-connection-error]'), + 'An error message is shown' + ); await click('[data-test-connection-error-dismiss]'); - assert.notOk(find('[data-test-connection-error]'), 'The error message is dismissable'); + assert.notOk( + find('[data-test-connection-error]'), + 'The error message is dismissable' + ); await componentA11yAudit(this.element, assert); }); - test('When the client is inaccessible, the server is accessible, and stderr is pressed before the client timeout occurs, the no connection error is not shown', async function(assert) { + test('When the client is inaccessible, the server is accessible, and stderr is pressed before the client timeout occurs, the no connection error is not shown', async function (assert) { // override client response to timeout this.server.get( `http://${HOST}/v1/client/fs/logs/:allocation_id`, @@ -315,49 +378,65 @@ module('Integration | Component | task log', function(hooks) { @clientTimeout={{clientTimeout}} @serverTimeout={{serverTimeout}} />`); - await settled(); - - const clientUrlRegex = new RegExp(`${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`); - const clientRequests = this.server.handledRequests.filter(req => clientUrlRegex.test(req.url)); + const clientUrlRegex = new RegExp( + `${HOST}/v1/client/fs/logs/${commonProps.allocation.id}` + ); + const clientRequests = this.server.handledRequests.filter((req) => + clientUrlRegex.test(req.url) + ); assert.ok( - clientRequests.find(req => req.queryParams.type === 'stdout'), + clientRequests.find((req) => req.queryParams.type === 'stdout'), 'Client request for stdout' ); assert.ok( - clientRequests.find(req => req.queryParams.type === 'stderr'), + clientRequests.find((req) => req.queryParams.type === 'stderr'), 'Client request for stderr' ); const serverUrl = `/v1/client/fs/logs/${commonProps.allocation.id}`; assert.ok( this.server.handledRequests - .filter(req => req.url.startsWith(serverUrl)) - .find(req => req.queryParams.type === 'stderr'), + .filter((req) => req.url.startsWith(serverUrl)) + .find((req) => req.queryParams.type === 'stderr'), 'Server request for stderr' ); - assert.notOk(find('[data-test-connection-error]'), 'An error message is not shown'); + assert.notOk( + find('[data-test-connection-error]'), + 'An error message is not shown' + ); }); - test('The log streaming mode is persisted in localStorage', async function(assert) { + test('The log streaming mode is persisted in localStorage', async function (assert) { window.localStorage.nomadLogMode = JSON.stringify('stderr'); run.later(run, run.cancelTimers, commonProps.interval); this.setProperties(commonProps); - await render(hbs``); + await render( + hbs`` + ); - await settled(); - assert.ok(this.server.handledRequests.filter(req => req.queryParams.type === 'stderr').length); + assert.ok( + this.server.handledRequests.filter( + (req) => req.queryParams.type === 'stderr' + ).length + ); assert.notOk( - this.server.handledRequests.filter(req => req.queryParams.type === 'stdout').length + this.server.handledRequests.filter( + (req) => req.queryParams.type === 'stdout' + ).length ); click('[data-test-log-action="stdout"]'); run.later(run, run.cancelTimers, commonProps.interval); await settled(); - assert.ok(this.server.handledRequests.filter(req => req.queryParams.type === 'stdout').length); + assert.ok( + this.server.handledRequests.filter( + (req) => req.queryParams.type === 'stdout' + ).length + ); assert.equal(window.localStorage.nomadLogMode, JSON.stringify('stdout')); }); }); diff --git a/ui/tests/integration/components/toggle-test.js b/ui/tests/integration/components/toggle-test.js index 862b69e5e..e49238f16 100644 --- a/ui/tests/integration/components/toggle-test.js +++ b/ui/tests/integration/components/toggle-test.js @@ -1,4 +1,4 @@ -import { find, settled } from '@ember/test-helpers'; +import { find, render, settled } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; @@ -9,7 +9,7 @@ import togglePageObject from 'nomad-ui/tests/pages/components/toggle'; const Toggle = create(togglePageObject()); -module('Integration | Component | toggle', function(hooks) { +module('Integration | Component | toggle', function (hooks) { setupRenderingTest(hooks); const commonProperties = () => ({ @@ -28,10 +28,12 @@ module('Integration | Component | toggle', function(hooks) { `; - test('presents as a label with an inner checkbox and display span, and text', async function(assert) { + test('presents as a label with an inner checkbox and display span, and text', async function (assert) { + assert.expect(7); + const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); assert.equal(Toggle.label, props.label, `Label should be ${props.label}`); assert.ok(Toggle.isPresent); @@ -51,10 +53,12 @@ module('Integration | Component | toggle', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('the isActive property dictates the active state and class', async function(assert) { + test('the isActive property dictates the active state and class', async function (assert) { + assert.expect(5); + const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); assert.notOk(Toggle.isActive); assert.notOk(Toggle.hasActiveClass); @@ -68,10 +72,12 @@ module('Integration | Component | toggle', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('the isDisabled property dictates the disabled state and class', async function(assert) { + test('the isDisabled property dictates the disabled state and class', async function (assert) { + assert.expect(5); + const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); assert.notOk(Toggle.isDisabled); assert.notOk(Toggle.hasDisabledClass); @@ -85,10 +91,10 @@ module('Integration | Component | toggle', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('toggling the input calls the onToggle action', async function(assert) { + test('toggling the input calls the onToggle action', async function (assert) { const props = commonProperties(); this.setProperties(props); - await this.render(commonTemplate); + await render(commonTemplate); await Toggle.toggle(); assert.equal(props.onToggle.callCount, 1); diff --git a/ui/tests/integration/components/topo-viz-test.js b/ui/tests/integration/components/topo-viz-test.js index abc0b7a52..6b25e6752 100644 --- a/ui/tests/integration/components/topo-viz-test.js +++ b/ui/tests/integration/components/topo-viz-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { triggerEvent } from '@ember/test-helpers'; +import { render, triggerEvent } from '@ember/test-helpers'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; @@ -19,7 +19,7 @@ const alloc = (nodeId, jobId, taskGroupName, memory, cpu, props = {}) => ({ cpu, memory, }, - belongsTo: type => ({ + belongsTo: (type) => ({ id: () => (type === 'job' ? jobId : nodeId), }), ...props, @@ -31,7 +31,7 @@ const node = (datacenter, id, memory, cpu) => ({ resources: { memory, cpu }, }); -module('Integration | Component | TopoViz', function(hooks) { +module('Integration | Component | TopoViz', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); @@ -44,7 +44,9 @@ module('Integration | Component | TopoViz', function(hooks) { @onDataError={{this.onDataError}} /> `; - test('presents as a FlexMasonry of datacenters', async function(assert) { + test('presents as a FlexMasonry of datacenters', async function (assert) { + assert.expect(6); + this.setProperties({ nodes: [node('dc1', 'node0', 1000, 500), node('dc2', 'node1', 1000, 500)], @@ -55,7 +57,7 @@ module('Integration | Component | TopoViz', function(hooks) { ], }); - await this.render(commonTemplate); + await render(commonTemplate); assert.equal(TopoViz.datacenters.length, 2); assert.equal(TopoViz.datacenters[0].nodes.length, 1); @@ -66,7 +68,7 @@ module('Integration | Component | TopoViz', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('clicking on a node in a deeply nested TopoViz::Node will toggle node selection and call @onNodeSelect', async function(assert) { + test('clicking on a node in a deeply nested TopoViz::Node will toggle node selection and call @onNodeSelect', async function (assert) { this.setProperties({ // TopoViz must be dense for node selection to be a feature nodes: Array(55) @@ -76,7 +78,7 @@ module('Integration | Component | TopoViz', function(hooks) { onNodeSelect: sinon.spy(), }); - await this.render(commonTemplate); + await render(commonTemplate); await TopoViz.datacenters[0].nodes[0].selectNode(); assert.ok(this.onNodeSelect.calledOnce); @@ -87,7 +89,7 @@ module('Integration | Component | TopoViz', function(hooks) { assert.equal(this.onNodeSelect.getCall(1).args[0], null); }); - test('clicking on an allocation in a deeply nested TopoViz::Node will update the topology object with selections and call @onAllocationSelect and @onNodeSelect', async function(assert) { + test('clicking on an allocation in a deeply nested TopoViz::Node will update the topology object with selections and call @onAllocationSelect and @onNodeSelect', async function (assert) { this.setProperties({ nodes: [node('dc1', 'node0', 1000, 500)], allocations: [alloc('node0', 'job1', 'group', 100, 100)], @@ -95,11 +97,14 @@ module('Integration | Component | TopoViz', function(hooks) { onAllocationSelect: sinon.spy(), }); - await this.render(commonTemplate); + await render(commonTemplate); await TopoViz.datacenters[0].nodes[0].memoryRects[0].select(); assert.ok(this.onAllocationSelect.calledOnce); - assert.equal(this.onAllocationSelect.getCall(0).args[0], this.allocations[0]); + assert.equal( + this.onAllocationSelect.getCall(0).args[0], + this.allocations[0] + ); assert.ok(this.onNodeSelect.calledOnce); await TopoViz.datacenters[0].nodes[0].memoryRects[0].select(); @@ -109,7 +114,7 @@ module('Integration | Component | TopoViz', function(hooks) { assert.ok(this.onNodeSelect.alwaysCalledWith(null)); }); - test('clicking on an allocation in a deeply nested TopoViz::Node will associate sibling allocations with curves', async function(assert) { + test('clicking on an allocation in a deeply nested TopoViz::Node will associate sibling allocations with curves', async function (assert) { this.setProperties({ nodes: [ node('dc1', 'node0', 1000, 500), @@ -133,27 +138,35 @@ module('Integration | Component | TopoViz', function(hooks) { }); const selectedAllocations = this.allocations.filter( - alloc => alloc.belongsTo('job').id() === 'job1' && alloc.taskGroupName === 'group' + (alloc) => + alloc.belongsTo('job').id() === 'job1' && + alloc.taskGroupName === 'group' ); - await this.render(commonTemplate); + await render(commonTemplate); assert.notOk(TopoViz.allocationAssociationsArePresent); await TopoViz.datacenters[0].nodes[0].memoryRects[0].select(); assert.ok(TopoViz.allocationAssociationsArePresent); - assert.equal(TopoViz.allocationAssociations.length, selectedAllocations.length * 2); + assert.equal( + TopoViz.allocationAssociations.length, + selectedAllocations.length * 2 + ); // Lines get redrawn when the window resizes; make sure the lines persist. await triggerEvent(window, 'resize'); - assert.equal(TopoViz.allocationAssociations.length, selectedAllocations.length * 2); + assert.equal( + TopoViz.allocationAssociations.length, + selectedAllocations.length * 2 + ); await TopoViz.datacenters[0].nodes[0].memoryRects[0].select(); assert.notOk(TopoViz.allocationAssociationsArePresent); }); - test('when the count of sibling allocations is high enough relative to the node count, curves are not rendered', async function(assert) { + test('when the count of sibling allocations is high enough relative to the node count, curves are not rendered', async function (assert) { this.setProperties({ nodes: [node('dc1', 'node0', 1000, 500), node('dc1', 'node1', 1000, 500)], allocations: [ @@ -176,7 +189,7 @@ module('Integration | Component | TopoViz', function(hooks) { onAllocationSelect: sinon.spy(), }); - await this.render(commonTemplate); + await render(commonTemplate); assert.notOk(TopoViz.allocationAssociationsArePresent); await TopoViz.datacenters[0].nodes[0].memoryRects[0].select(); @@ -187,7 +200,7 @@ module('Integration | Component | TopoViz', function(hooks) { assert.equal(TopoViz.allocationAssociations.length, 0); }); - test('when one or more nodes are missing the resources property, those nodes are filtered out of the topology view and onDataError is called', async function(assert) { + test('when one or more nodes are missing the resources property, those nodes are filtered out of the topology view and onDataError is called', async function (assert) { const badNode = node('dc1', 'node0', 1000, 500); delete badNode.resources; @@ -205,7 +218,7 @@ module('Integration | Component | TopoViz', function(hooks) { onDataError: sinon.spy(), }); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(this.onDataError.calledOnce); assert.deepEqual(this.onDataError.getCall(0).args[0], [ diff --git a/ui/tests/integration/components/topo-viz/datacenter-test.js b/ui/tests/integration/components/topo-viz/datacenter-test.js index 25e7c3b8f..1d6d81119 100644 --- a/ui/tests/integration/components/topo-viz/datacenter-test.js +++ b/ui/tests/integration/components/topo-viz/datacenter-test.js @@ -1,4 +1,4 @@ -import { find } from '@ember/test-helpers'; +import { find, render } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; @@ -17,7 +17,7 @@ const nodeGen = (name, datacenter, memory, cpu, allocations = []) => ({ memory, cpu, node: { name }, - allocations: allocations.map(alloc => ({ + allocations: allocations.map((alloc) => ({ memory: alloc.memory, cpu: alloc.cpu, memoryPercent: alloc.memory / memory, @@ -30,13 +30,13 @@ const nodeGen = (name, datacenter, memory, cpu, allocations = []) => ({ }); // Used in Array#reduce to sum by a property common to an array of objects -const sumBy = prop => (sum, obj) => (sum += obj[prop]); +const sumBy = (prop) => (sum, obj) => (sum += obj[prop]); -module('Integration | Component | TopoViz::Datacenter', function(hooks) { +module('Integration | Component | TopoViz::Datacenter', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); - const commonProps = props => ({ + const commonProps = (props) => ({ isSingleColumn: true, isDense: false, heightScale: () => 50, @@ -55,7 +55,9 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { @onNodeSelect={{this.onNodeSelect}} /> `; - test('presents as a div with a label and a FlexMasonry with a collection of nodes', async function(assert) { + test('presents as a div with a label and a FlexMasonry with a collection of nodes', async function (assert) { + assert.expect(3); + this.setProperties( commonProps({ datacenter: { @@ -65,7 +67,7 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(TopoVizDatacenter.isPresent); assert.equal(TopoVizDatacenter.nodes.length, this.datacenter.nodes.length); @@ -73,7 +75,7 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('datacenter stats are an aggregate of node stats', async function(assert) { + test('datacenter stats are an aggregate of node stats', async function (assert) { this.setProperties( commonProps({ datacenter: { @@ -94,7 +96,7 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); const allocs = this.datacenter.nodes.reduce( (allocs, node) => allocs.concat(node.allocations), @@ -106,11 +108,16 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { const cpuTotal = this.datacenter.nodes.reduce(sumBy('cpu'), 0); assert.ok(TopoVizDatacenter.label.includes(this.datacenter.name)); - assert.ok(TopoVizDatacenter.label.includes(`${this.datacenter.nodes.length} Nodes`)); + assert.ok( + TopoVizDatacenter.label.includes(`${this.datacenter.nodes.length} Nodes`) + ); assert.ok(TopoVizDatacenter.label.includes(`${allocs.length} Allocs`)); assert.ok( TopoVizDatacenter.label.includes( - `${formatBytes(memoryReserved, 'MiB')}/${formatBytes(memoryTotal, 'MiB')}` + `${formatBytes(memoryReserved, 'MiB')}/${formatBytes( + memoryTotal, + 'MiB' + )}` ) ); assert.ok( @@ -120,18 +127,21 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { ); }); - test('when @isSingleColumn is true, the FlexMasonry layout gets one column, otherwise it gets two', async function(assert) { + test('when @isSingleColumn is true, the FlexMasonry layout gets one column, otherwise it gets two', async function (assert) { this.setProperties( commonProps({ isSingleColumn: true, datacenter: { name: 'dc1', - nodes: [nodeGen('node-1', 'dc1', 1000, 500), nodeGen('node-2', 'dc1', 1000, 500)], + nodes: [ + nodeGen('node-1', 'dc1', 1000, 500), + nodeGen('node-2', 'dc1', 1000, 500), + ], }, }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(find('[data-test-flex-masonry].flex-masonry-columns-1')); @@ -139,7 +149,9 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { assert.ok(find('[data-test-flex-masonry].flex-masonry-columns-2')); }); - test('args get passed down to the TopViz::Node children', async function(assert) { + test('args get passed down to the TopViz::Node children', async function (assert) { + assert.expect(4); + const heightSpy = sinon.spy(); this.setProperties( commonProps({ @@ -150,14 +162,16 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { }, datacenter: { name: 'dc1', - nodes: [nodeGen('node-1', 'dc1', 1000, 500, [{ memory: 100, cpu: 300 }])], + nodes: [ + nodeGen('node-1', 'dc1', 1000, 500, [{ memory: 100, cpu: 300 }]), + ], }, }) ); - await this.render(commonTemplate); + await render(commonTemplate); - TopoVizDatacenter.nodes[0].as(async TopoVizNode => { + TopoVizDatacenter.nodes[0].as(async (TopoVizNode) => { assert.notOk(TopoVizNode.labelIsPresent); assert.ok(heightSpy.calledWith(this.datacenter.nodes[0].memory)); @@ -165,7 +179,11 @@ module('Integration | Component | TopoViz::Datacenter', function(hooks) { assert.ok(this.onNodeSelect.calledWith(this.datacenter.nodes[0])); await TopoVizNode.memoryRects[0].select(); - assert.ok(this.onAllocationSelect.calledWith(this.datacenter.nodes[0].allocations[0])); + assert.ok( + this.onAllocationSelect.calledWith( + this.datacenter.nodes[0].allocations[0] + ) + ); }); }); }); diff --git a/ui/tests/integration/components/topo-viz/node-test.js b/ui/tests/integration/components/topo-viz/node-test.js index a85df060d..54588002d 100644 --- a/ui/tests/integration/components/topo-viz/node-test.js +++ b/ui/tests/integration/components/topo-viz/node-test.js @@ -1,4 +1,4 @@ -import { findAll } from '@ember/test-helpers'; +import { findAll, render } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; @@ -8,7 +8,10 @@ import faker from 'nomad-ui/mirage/faker'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import topoVisNodePageObject from 'nomad-ui/tests/pages/components/topo-viz/node'; -import { formatScheduledBytes, formatScheduledHertz } from 'nomad-ui/utils/units'; +import { + formatScheduledBytes, + formatScheduledHertz, +} from 'nomad-ui/utils/units'; const TopoVizNode = create(topoVisNodePageObject()); @@ -36,7 +39,7 @@ const allocGen = (node, memory, cpu, isScheduled = true) => ({ }, }); -const props = overrides => ({ +const props = (overrides) => ({ isDense: false, heightScale: () => 50, onAllocationSelect: sinon.spy(), @@ -46,7 +49,7 @@ const props = overrides => ({ ...overrides, }); -module('Integration | Component | TopoViz::Node', function(hooks) { +module('Integration | Component | TopoViz::Node', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); @@ -61,7 +64,9 @@ module('Integration | Component | TopoViz::Node', function(hooks) { @onNodeSelect={{this.onNodeSelect}} /> `; - test('presents as a div with a label and an svg with CPU and memory rows', async function(assert) { + test('presents as a div with a label and an svg with CPU and memory rows', async function (assert) { + assert.expect(4); + const node = nodeGen('Node One', 'dc1', 1000, 1000); this.setProperties( props({ @@ -76,7 +81,7 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(TopoVizNode.isPresent); assert.equal( @@ -88,7 +93,7 @@ module('Integration | Component | TopoViz::Node', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('the label contains aggregate information about the node', async function(assert) { + test('the label contains aggregate information about the node', async function (assert) { const node = nodeGen('Node One', 'dc1', 1000, 1000); this.setProperties( props({ @@ -103,19 +108,29 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(TopoVizNode.label.includes(node.node.name)); assert.ok( TopoVizNode.label.includes( - `${this.node.allocations.filterBy('allocation.isScheduled').length} Allocs` + `${ + this.node.allocations.filterBy('allocation.isScheduled').length + } Allocs` + ) + ); + assert.ok( + TopoVizNode.label.includes( + `${formatScheduledBytes(this.node.memory, 'MiB')}` + ) + ); + assert.ok( + TopoVizNode.label.includes( + `${formatScheduledHertz(this.node.cpu, 'MHz')}` ) ); - assert.ok(TopoVizNode.label.includes(`${formatScheduledBytes(this.node.memory, 'MiB')}`)); - assert.ok(TopoVizNode.label.includes(`${formatScheduledHertz(this.node.cpu, 'MHz')}`)); }); - test('the status icon indicates when the node is draining', async function(assert) { + test('the status icon indicates when the node is draining', async function (assert) { const node = nodeGen('Node One', 'dc1', 1000, 1000, { isDraining: true }); this.setProperties( props({ @@ -126,13 +141,13 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(TopoVizNode.statusIcon.includes('icon-is-clock-outline')); assert.equal(TopoVizNode.statusIconLabel, 'Client is draining'); }); - test('the status icon indicates when the node is ineligible for scheduling', async function(assert) { + test('the status icon indicates when the node is ineligible for scheduling', async function (assert) { const node = nodeGen('Node One', 'dc1', 1000, 1000, { isEligible: false }); this.setProperties( props({ @@ -143,13 +158,13 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(TopoVizNode.statusIcon.includes('icon-is-lock-closed')); assert.equal(TopoVizNode.statusIconLabel, 'Client is ineligible'); }); - test('when isDense is false, clicking the node does nothing', async function(assert) { + test('when isDense is false, clicking the node does nothing', async function (assert) { const node = nodeGen('Node One', 'dc1', 1000, 1000); this.setProperties( props({ @@ -161,14 +176,14 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); await TopoVizNode.selectNode(); assert.notOk(TopoVizNode.nodeIsInteractive); assert.notOk(this.onNodeSelect.called); }); - test('when isDense is true, clicking the node calls onNodeSelect', async function(assert) { + test('when isDense is true, clicking the node calls onNodeSelect', async function (assert) { const node = nodeGen('Node One', 'dc1', 1000, 1000); this.setProperties( props({ @@ -180,7 +195,7 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); await TopoVizNode.selectNode(); assert.ok(TopoVizNode.nodeIsInteractive); @@ -188,7 +203,7 @@ module('Integration | Component | TopoViz::Node', function(hooks) { assert.ok(this.onNodeSelect.calledWith(this.node)); }); - test('the node gets the is-selected class when the node is selected', async function(assert) { + test('the node gets the is-selected class when the node is selected', async function (assert) { const node = nodeGen('Node One', 'dc1', 1000, 1000, { isSelected: true }); this.setProperties( props({ @@ -200,12 +215,12 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(TopoVizNode.nodeIsSelected); }); - test('the node gets its height form the @heightScale arg', async function(assert) { + test('the node gets its height form the @heightScale arg', async function (assert) { const node = nodeGen('Node One', 'dc1', 1000, 1000); const height = 50; const heightSpy = sinon.spy(); @@ -222,14 +237,14 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.ok(heightSpy.called); assert.ok(heightSpy.calledWith(this.node.memory)); assert.equal(TopoVizNode.memoryRects[0].height, `${height}px`); }); - test('each allocation gets a memory rect and a cpu rect', async function(assert) { + test('each allocation gets a memory rect and a cpu rect', async function (assert) { const node = nodeGen('Node One', 'dc1', 1000, 1000); this.setProperties( props({ @@ -240,13 +255,15 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(commonTemplate); + await render(commonTemplate); assert.equal(TopoVizNode.memoryRects.length, this.node.allocations.length); assert.equal(TopoVizNode.cpuRects.length, this.node.allocations.length); }); - test('each allocation is sized according to its percentage of utilization', async function(assert) { + test('each allocation is sized according to its percentage of utilization', async function (assert) { + assert.expect(4); + const node = nodeGen('Node One', 'dc1', 1000, 1000); this.setProperties( props({ @@ -257,7 +274,7 @@ module('Integration | Component | TopoViz::Node', function(hooks) { }) ); - await this.render(hbs` + await render(hbs`
    this.set('name', 'Zoey')); await render(hbs` @@ -17,16 +17,21 @@ module('Integration | Component | trigger', function(hooks) { `); - assert.dom('[data-test-name]').hasText('Tomster', 'Initial state renders correctly.'); + assert + .dom('[data-test-name]') + .hasText('Tomster', 'Initial state renders correctly.'); await click('[data-test-button]'); assert .dom('[data-test-name]') - .hasText('Zoey', 'The name property changes when the button is clicked'); + .hasText( + 'Zoey', + 'The name property changes when the button is clicked' + ); }); - test('it sets the result of the action', async function(assert) { + test('it sets the result of the action', async function (assert) { this.set('tomster', () => 'Tomster'); await render(hbs` @@ -38,22 +43,27 @@ module('Integration | Component | trigger', function(hooks) { `); assert .dom('[data-test-name]') - .doesNotExist('Initial state does not render because there is no result yet.'); + .doesNotExist( + 'Initial state does not render because there is no result yet.' + ); await click('[data-test-button]'); assert .dom('[data-test-name]') - .hasText('Tomster', 'The result state updates after the triggered action'); + .hasText( + 'Tomster', + 'The result state updates after the triggered action' + ); }); }); - module('Asynchronous Interactions', function() { - test('it can trigger an asynchronous action', async function(assert) { + module('Asynchronous Interactions', function () { + test('it can trigger an asynchronous action', async function (assert) { this.set( 'onTrigger', () => - new Promise(resolve => { + new Promise((resolve) => { this.set('resolve', resolve); }) ); @@ -72,15 +82,21 @@ module('Integration | Component | trigger', function(hooks) { assert .dom('[data-test-div]') - .doesNotExist('The div does not render until after the action dispatches successfully'); + .doesNotExist( + 'The div does not render until after the action dispatches successfully' + ); await click('[data-test-button]'); assert .dom('[data-test-div-loading]') - .exists('Loading state is displayed when the action hasnt resolved yet'); + .exists( + 'Loading state is displayed when the action hasnt resolved yet' + ); assert .dom('[data-test-div]') - .doesNotExist('Success message does not display until after promise resolves'); + .doesNotExist( + 'Success message does not display until after promise resolves' + ); this.resolve(); await waitFor('[data-test-div]'); @@ -91,15 +107,21 @@ module('Integration | Component | trigger', function(hooks) { ); assert .dom('[data-test-div]') - .exists('Action has dispatched successfully after the promise resolves'); + .exists( + 'Action has dispatched successfully after the promise resolves' + ); await click('[data-test-button]'); assert .dom('[data-test-div]') - .doesNotExist('Aftering clicking the button, again, the state is reset'); + .doesNotExist( + 'Aftering clicking the button, again, the state is reset' + ); assert .dom('[data-test-div-loading]') - .exists('After clicking the button, again, we are back in the loading state'); + .exists( + 'After clicking the button, again, we are back in the loading state' + ); this.resolve(); await waitFor('[data-test-div]'); @@ -111,11 +133,11 @@ module('Integration | Component | trigger', function(hooks) { ); }); - test('it handles the success state', async function(assert) { + test('it handles the success state', async function (assert) { this.set( 'onTrigger', () => - new Promise(resolve => { + new Promise((resolve) => { this.set('resolve', resolve); }) ); @@ -132,14 +154,16 @@ module('Integration | Component | trigger', function(hooks) { assert .dom('[data-test-div]') - .doesNotExist('No text should appear until after the onSuccess callback is fired'); + .doesNotExist( + 'No text should appear until after the onSuccess callback is fired' + ); await click('[data-test-button]'); this.resolve(); await waitFor('[data-test-div]'); assert.verifySteps(['On success happened']); }); - test('it handles the error state', async function(assert) { + test('it handles the error state', async function (assert) { this.set( 'onTrigger', () => @@ -166,11 +190,15 @@ module('Integration | Component | trigger', function(hooks) { await click('[data-test-button]'); assert .dom('[data-test-div-loading]') - .exists('Loading state is displayed when the action hasnt resolved yet'); + .exists( + 'Loading state is displayed when the action hasnt resolved yet' + ); assert .dom('[data-test-div]') - .doesNotExist('No text should appear until after the onError callback is fired'); + .doesNotExist( + 'No text should appear until after the onError callback is fired' + ); this.reject(); await waitFor('[data-test-span]'); @@ -180,7 +208,9 @@ module('Integration | Component | trigger', function(hooks) { assert .dom('[data-test-div-loading]') - .exists('The previous error state was cleared and we show loading, again.'); + .exists( + 'The previous error state was cleared and we show loading, again.' + ); assert.dom('[data-test-div]').doesNotExist('The error state is cleared'); diff --git a/ui/tests/integration/components/two-step-button-test.js b/ui/tests/integration/components/two-step-button-test.js index 4dd9be00f..3ef1be602 100644 --- a/ui/tests/integration/components/two-step-button-test.js +++ b/ui/tests/integration/components/two-step-button-test.js @@ -9,7 +9,7 @@ import twoStepButton from 'nomad-ui/tests/pages/components/two-step-button'; const TwoStepButton = create(twoStepButton()); -module('Integration | Component | two step button', function(hooks) { +module('Integration | Component | two step button', function (hooks) { setupRenderingTest(hooks); const commonProperties = () => ({ @@ -35,22 +35,33 @@ module('Integration | Component | two step button', function(hooks) { @onCancel={{onCancel}} /> `; - test('presents as a button in the idle state', async function(assert) { + test('presents as a button in the idle state', async function (assert) { + assert.expect(6); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); assert.ok(find('[data-test-idle-button]'), 'Idle button is rendered'); - assert.equal(TwoStepButton.idleText, props.idleText, 'Button is labeled correctly'); + assert.equal( + TwoStepButton.idleText, + props.idleText, + 'Button is labeled correctly' + ); assert.notOk(find('[data-test-cancel-button]'), 'No cancel button yet'); assert.notOk(find('[data-test-confirm-button]'), 'No confirm button yet'); - assert.notOk(find('[data-test-confirmation-message]'), 'No confirmation message yet'); + assert.notOk( + find('[data-test-confirmation-message]'), + 'No confirmation message yet' + ); await componentA11yAudit(this.element, assert); }); - test('clicking the idle state button transitions into the promptForConfirmation state', async function(assert) { + test('clicking the idle state button transitions into the promptForConfirmation state', async function (assert) { + assert.expect(7); + const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -58,10 +69,18 @@ module('Integration | Component | two step button', function(hooks) { await TwoStepButton.idle(); assert.ok(find('[data-test-cancel-button]'), 'Cancel button is rendered'); - assert.equal(TwoStepButton.cancelText, props.cancelText, 'Button is labeled correctly'); + assert.equal( + TwoStepButton.cancelText, + props.cancelText, + 'Button is labeled correctly' + ); assert.ok(find('[data-test-confirm-button]'), 'Confirm button is rendered'); - assert.equal(TwoStepButton.confirmText, props.confirmText, 'Button is labeled correctly'); + assert.equal( + TwoStepButton.confirmText, + props.confirmText, + 'Button is labeled correctly' + ); assert.equal( TwoStepButton.confirmationMessage, @@ -73,7 +92,7 @@ module('Integration | Component | two step button', function(hooks) { await componentA11yAudit(this.element, assert); }); - test('canceling in the promptForConfirmation state calls the onCancel hook and resets to the idle state', async function(assert) { + test('canceling in the promptForConfirmation state calls the onCancel hook and resets to the idle state', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -86,7 +105,7 @@ module('Integration | Component | two step button', function(hooks) { assert.ok(find('[data-test-idle-button]'), 'Idle button is back'); }); - test('confirming the promptForConfirmation state calls the onConfirm hook and resets to the idle state', async function(assert) { + test('confirming the promptForConfirmation state calls the onConfirm hook and resets to the idle state', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -99,7 +118,9 @@ module('Integration | Component | two step button', function(hooks) { assert.ok(find('[data-test-idle-button]'), 'Idle button is back'); }); - test('when awaitingConfirmation is true, the cancel and submit buttons are disabled and the submit button is loading', async function(assert) { + test('when awaitingConfirmation is true, the cancel and submit buttons are disabled and the submit button is loading', async function (assert) { + assert.expect(4); + const props = commonProperties(); props.awaitingConfirmation = true; this.setProperties(props); @@ -108,13 +129,19 @@ module('Integration | Component | two step button', function(hooks) { await TwoStepButton.idle(); assert.ok(TwoStepButton.cancelIsDisabled, 'The cancel button is disabled'); - assert.ok(TwoStepButton.confirmIsDisabled, 'The confirm button is disabled'); - assert.ok(TwoStepButton.isRunning, 'The confirm button is in a loading state'); + assert.ok( + TwoStepButton.confirmIsDisabled, + 'The confirm button is disabled' + ); + assert.ok( + TwoStepButton.isRunning, + 'The confirm button is in a loading state' + ); await componentA11yAudit(this.element, assert); }); - test('when in the prompt state, clicking outside will reset state back to idle', async function(assert) { + test('when in the prompt state, clicking outside will reset state back to idle', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -128,7 +155,7 @@ module('Integration | Component | two step button', function(hooks) { assert.ok(find('[data-test-idle-button]'), 'Back in the idle state'); }); - test('when in the prompt state, clicking inside will not reset state back to idle', async function(assert) { + test('when in the prompt state, clicking inside will not reset state back to idle', async function (assert) { const props = commonProperties(); this.setProperties(props); await render(commonTemplate); @@ -142,7 +169,7 @@ module('Integration | Component | two step button', function(hooks) { assert.notOk(find('[data-test-idle-button]'), 'Still in the prompt state'); }); - test('when awaitingConfirmation is true, clicking outside does nothing', async function(assert) { + test('when awaitingConfirmation is true, clicking outside does nothing', async function (assert) { const props = commonProperties(); props.awaitingConfirmation = true; this.setProperties(props); @@ -157,7 +184,9 @@ module('Integration | Component | two step button', function(hooks) { assert.notOk(find('[data-test-idle-button]'), 'Still in the prompt state'); }); - test('when disabled is true, the idle button is disabled', async function(assert) { + test('when disabled is true, the idle button is disabled', async function (assert) { + assert.expect(3); + const props = commonProperties(); props.disabled = true; this.setProperties(props); @@ -166,7 +195,10 @@ module('Integration | Component | two step button', function(hooks) { assert.ok(TwoStepButton.isDisabled, 'The idle button is disabled'); await TwoStepButton.idle(); - assert.ok(find('[data-test-idle-button]'), 'Still in the idle state after clicking'); + assert.ok( + find('[data-test-idle-button]'), + 'Still in the idle state after clicking' + ); await componentA11yAudit(this.element, assert); }); diff --git a/ui/tests/integration/util/exec-command-editor-xterm-adapter-test.js b/ui/tests/integration/util/exec-command-editor-xterm-adapter-test.js index d895cc41a..1c1328298 100644 --- a/ui/tests/integration/util/exec-command-editor-xterm-adapter-test.js +++ b/ui/tests/integration/util/exec-command-editor-xterm-adapter-test.js @@ -6,132 +6,132 @@ import hbs from 'htmlbars-inline-precompile'; import { Terminal } from 'xterm'; import KEYS from 'nomad-ui/utils/keys'; -module('Integration | Utility | exec-command-editor-xterm-adapter', function(hooks) { - setupRenderingTest(hooks); +module( + 'Integration | Utility | exec-command-editor-xterm-adapter', + function (hooks) { + setupRenderingTest(hooks); - test('it can wrap to a previous line while backspacing', async function(assert) { - let done = assert.async(); + test('it can wrap to a previous line while backspacing', async function (assert) { + assert.expect(2); - await render(hbs` + let done = assert.async(); + + await render(hbs`
    `); - let terminal = new Terminal({ cols: 10 }); - terminal.open(document.getElementById('terminal')); + let terminal = new Terminal({ cols: 10 }); + terminal.open(document.getElementById('terminal')); - terminal.write('/bin/long-command'); + terminal.write('/bin/long-command'); - new ExecCommandEditorXtermAdapter( - terminal, - command => { - assert.equal(command, '/bin/long'); - done(); - }, - '/bin/long-command' - ); + new ExecCommandEditorXtermAdapter( + terminal, + (command) => { + assert.equal(command, '/bin/long'); + done(); + }, + '/bin/long-command' + ); - await terminal.simulateCommandDataEvent(KEYS.DELETE); - await terminal.simulateCommandDataEvent(KEYS.DELETE); - await terminal.simulateCommandDataEvent(KEYS.DELETE); - await terminal.simulateCommandDataEvent(KEYS.DELETE); - await terminal.simulateCommandDataEvent(KEYS.DELETE); - await terminal.simulateCommandDataEvent(KEYS.DELETE); - await terminal.simulateCommandDataEvent(KEYS.DELETE); - await terminal.simulateCommandDataEvent(KEYS.DELETE); + await terminal.simulateCommandDataEvent(KEYS.DELETE); + await terminal.simulateCommandDataEvent(KEYS.DELETE); + await terminal.simulateCommandDataEvent(KEYS.DELETE); + await terminal.simulateCommandDataEvent(KEYS.DELETE); + await terminal.simulateCommandDataEvent(KEYS.DELETE); + await terminal.simulateCommandDataEvent(KEYS.DELETE); + await terminal.simulateCommandDataEvent(KEYS.DELETE); + await terminal.simulateCommandDataEvent(KEYS.DELETE); - await settled(); + await settled(); - assert.equal( - terminal.buffer.active - .getLine(0) - .translateToString() - .trim(), - '/bin/long' - ); + assert.equal( + terminal.buffer.active.getLine(0).translateToString().trim(), + '/bin/long' + ); - await terminal.simulateCommandDataEvent(KEYS.ENTER); - }); + await terminal.simulateCommandDataEvent(KEYS.ENTER); + }); - test('it ignores arrow keys and unprintable characters other than ^U', async function(assert) { - let done = assert.async(); + test('it ignores arrow keys and unprintable characters other than ^U', async function (assert) { + assert.expect(4); - await render(hbs` + let done = assert.async(); + + await render(hbs`
    `); - let terminal = new Terminal({ cols: 72 }); - terminal.open(document.getElementById('terminal')); + let terminal = new Terminal({ cols: 72 }); + terminal.open(document.getElementById('terminal')); - terminal.write('/bin/bash'); + terminal.write('/bin/bash'); - new ExecCommandEditorXtermAdapter( - terminal, - command => { - assert.equal(command, '/bin/bash!'); - done(); - }, - '/bin/bash' - ); + new ExecCommandEditorXtermAdapter( + terminal, + (command) => { + assert.equal(command, '/bin/bash!'); + done(); + }, + '/bin/bash' + ); - await terminal.simulateCommandDataEvent(KEYS.RIGHT_ARROW); - await terminal.simulateCommandDataEvent(KEYS.RIGHT_ARROW); - await terminal.simulateCommandDataEvent(KEYS.LEFT_ARROW); - await terminal.simulateCommandDataEvent(KEYS.UP_ARROW); - await terminal.simulateCommandDataEvent(KEYS.UP_ARROW); - await terminal.simulateCommandDataEvent(KEYS.DOWN_ARROW); - await terminal.simulateCommandDataEvent(KEYS.CONTROL_A); - await terminal.simulateCommandDataEvent('!'); + await terminal.simulateCommandDataEvent(KEYS.RIGHT_ARROW); + await terminal.simulateCommandDataEvent(KEYS.RIGHT_ARROW); + await terminal.simulateCommandDataEvent(KEYS.LEFT_ARROW); + await terminal.simulateCommandDataEvent(KEYS.UP_ARROW); + await terminal.simulateCommandDataEvent(KEYS.UP_ARROW); + await terminal.simulateCommandDataEvent(KEYS.DOWN_ARROW); + await terminal.simulateCommandDataEvent(KEYS.CONTROL_A); + await terminal.simulateCommandDataEvent('!'); - await settled(); + await settled(); - assert.equal(terminal.buffer.active.cursorY, 0); - assert.equal(terminal.buffer.active.cursorX, 10); + assert.equal(terminal.buffer.active.cursorY, 0); + assert.equal(terminal.buffer.active.cursorX, 10); - assert.equal( - terminal.buffer.active - .getLine(0) - .translateToString() - .trim(), - '/bin/bash!' - ); + assert.equal( + terminal.buffer.active.getLine(0).translateToString().trim(), + '/bin/bash!' + ); - await terminal.simulateCommandDataEvent(KEYS.ENTER); - }); + await terminal.simulateCommandDataEvent(KEYS.ENTER); + }); - test('it supports typing ^U to delete the entire command', async function(assert) { - let done = assert.async(); + test('it supports typing ^U to delete the entire command', async function (assert) { + assert.expect(2); - await render(hbs` + let done = assert.async(); + + await render(hbs`
    `); - let terminal = new Terminal({ cols: 10 }); - terminal.open(document.getElementById('terminal')); + let terminal = new Terminal({ cols: 10 }); + terminal.open(document.getElementById('terminal')); - terminal.write('to-delete'); + terminal.write('to-delete'); - new ExecCommandEditorXtermAdapter( - terminal, - command => { - assert.equal(command, '!'); - done(); - }, - 'to-delete' - ); + new ExecCommandEditorXtermAdapter( + terminal, + (command) => { + assert.equal(command, '!'); + done(); + }, + 'to-delete' + ); - await terminal.simulateCommandDataEvent(KEYS.CONTROL_U); + await terminal.simulateCommandDataEvent(KEYS.CONTROL_U); - await settled(); + await settled(); - assert.equal( - terminal.buffer.active - .getLine(0) - .translateToString() - .trim(), - '' - ); + assert.equal( + terminal.buffer.active.getLine(0).translateToString().trim(), + '' + ); - await terminal.simulateCommandDataEvent('!'); - await terminal.simulateCommandDataEvent(KEYS.ENTER); - }); -}); + await terminal.simulateCommandDataEvent('!'); + await terminal.simulateCommandDataEvent(KEYS.ENTER); + }); + } +); diff --git a/ui/tests/integration/util/exec-socket-xterm-adapter-test.js b/ui/tests/integration/util/exec-socket-xterm-adapter-test.js index d6b78b109..49cdd9b00 100644 --- a/ui/tests/integration/util/exec-socket-xterm-adapter-test.js +++ b/ui/tests/integration/util/exec-socket-xterm-adapter-test.js @@ -1,3 +1,4 @@ +/* eslint-disable qunit/no-conditional-assertions */ import ExecSocketXtermAdapter from 'nomad-ui/utils/classes/exec-socket-xterm-adapter'; import { setupRenderingTest } from 'ember-qunit'; import { module, test } from 'qunit'; @@ -7,10 +8,12 @@ import { Terminal } from 'xterm'; import { HEARTBEAT_INTERVAL } from 'nomad-ui/utils/classes/exec-socket-xterm-adapter'; import sinon from 'sinon'; -module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { +module('Integration | Utility | exec-socket-xterm-adapter', function (hooks) { setupRenderingTest(hooks); - test('initiating socket sends authentication handshake', async function(assert) { + test('initiating socket sends authentication handshake', async function (assert) { + assert.expect(1); + let done = assert.async(); let terminal = new Terminal(); @@ -20,14 +23,15 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { `); - await settled(); - let firstMessage = true; let mockSocket = new Object({ send(message) { if (firstMessage) { firstMessage = false; - assert.deepEqual(message, JSON.stringify({ version: 1, auth_token: 'mysecrettoken' })); + assert.deepEqual( + message, + JSON.stringify({ version: 1, auth_token: 'mysecrettoken' }) + ); mockSocket.onclose(); done(); } @@ -41,7 +45,9 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { await settled(); }); - test('initiating socket sends authentication handshake even if unauthenticated', async function(assert) { + test('initiating socket sends authentication handshake even if unauthenticated', async function (assert) { + assert.expect(1); + let done = assert.async(); let terminal = new Terminal(); @@ -51,14 +57,15 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { `); - await settled(); - let firstMessage = true; let mockSocket = new Object({ send(message) { if (firstMessage) { firstMessage = false; - assert.deepEqual(message, JSON.stringify({ version: 1, auth_token: '' })); + assert.deepEqual( + message, + JSON.stringify({ version: 1, auth_token: '' }) + ); mockSocket.onclose(); done(); } @@ -72,7 +79,9 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { await settled(); }); - test('a heartbeat is sent periodically', async function(assert) { + test('a heartbeat is sent periodically', async function (assert) { + assert.expect(1); + let done = assert.async(); const clock = sinon.useFakeTimers({ @@ -87,8 +96,6 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { `); - await settled(); - let mockSocket = new Object({ send(message) { if (!message.includes('version') && !message.includes('tty_size')) { @@ -106,7 +113,9 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { clock.tick(HEARTBEAT_INTERVAL); }); - test('resizing the window passes a resize message through the socket', async function(assert) { + test('resizing the window passes a resize message through the socket', async function (assert) { + assert.expect(1); + let done = assert.async(); let terminal = new Terminal(); @@ -116,13 +125,13 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { `); - await settled(); - let mockSocket = new Object({ send(message) { assert.deepEqual( message, - JSON.stringify({ tty_size: { width: terminal.cols, height: terminal.rows } }) + JSON.stringify({ + tty_size: { width: terminal.cols, height: terminal.rows }, + }) ); mockSocket.onclose(); done(); @@ -136,7 +145,7 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { await settled(); }); - test('stdout frames without data are ignored', async function(assert) { + test('stdout frames without data are ignored', async function (assert) { assert.expect(0); let terminal = new Terminal(); @@ -146,8 +155,6 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { `); - await settled(); - let mockSocket = new Object({ send() {}, }); @@ -162,7 +169,7 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { mockSocket.onclose(); }); - test('stderr frames are ignored', async function(assert) { + test('stderr frames are ignored', async function (assert) { let terminal = new Terminal(); this.set('terminal', terminal); @@ -170,8 +177,6 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { `); - await settled(); - let mockSocket = new Object({ send() {}, }); @@ -189,10 +194,7 @@ module('Integration | Utility | exec-socket-xterm-adapter', function(hooks) { await settled(); assert.equal( - terminal.buffer.active - .getLine(0) - .translateToString() - .trim(), + terminal.buffer.active.getLine(0).translateToString().trim(), 'sh-3.2 🥳$' ); diff --git a/ui/tests/pages/allocations/detail.js b/ui/tests/pages/allocations/detail.js index b08b90eb8..cabc44672 100644 --- a/ui/tests/pages/allocations/detail.js +++ b/ui/tests/pages/allocations/detail.js @@ -81,7 +81,10 @@ export default create({ }, preempted: isPresent('[data-test-preemptions]'), - ...allocations('[data-test-preemptions] [data-test-allocation]', 'preemptions'), + ...allocations( + '[data-test-preemptions] [data-test-allocation]', + 'preemptions' + ), ports: collection('[data-test-allocation-port]', { name: text('[data-test-allocation-port-name]'), diff --git a/ui/tests/pages/allocations/fs.js b/ui/tests/pages/allocations/fs.js index fbfc8f9a7..c5cf4d549 100644 --- a/ui/tests/pages/allocations/fs.js +++ b/ui/tests/pages/allocations/fs.js @@ -34,10 +34,7 @@ export default create({ }), sortBy(id) { - return this.sortOptions - .toArray() - .findBy('id', id) - .sort(); + return this.sortOptions.toArray().findBy('id', id).sort(); }, directoryEntries: collection('[data-test-entry]', { diff --git a/ui/tests/pages/clients/detail.js b/ui/tests/pages/clients/detail.js index bcfe0d2de..366e1242d 100644 --- a/ui/tests/pages/clients/detail.js +++ b/ui/tests/pages/clients/detail.js @@ -28,7 +28,10 @@ export default create({ }), statusDefinition: text('[data-test-status-definition]'), - statusDecorationClass: attribute('class', '[data-test-status-definition] .status-text'), + statusDecorationClass: attribute( + 'class', + '[data-test-status-definition] .status-text' + ), addressDefinition: text('[data-test-address-definition]'), datacenterDefinition: text('[data-test-datacenter-definition]'), @@ -62,10 +65,13 @@ export default create({ metaTable: isPresent('[data-test-meta]'), emptyMetaMessage: isPresent('[data-test-empty-meta-message]'), - metaAttributes: collection('[data-test-meta] [data-test-attributes-section]', { - key: text('[data-test-key]'), - value: text('[data-test-value]'), - }), + metaAttributes: collection( + '[data-test-meta] [data-test-attributes-section]', + { + key: text('[data-test-key]'), + value: text('[data-test-value]'), + } + ), error: { isShown: isPresent('[data-test-error]'), @@ -88,22 +94,28 @@ export default create({ permissions: text('[data-test-permissions]'), }), - driverHeads: collection('[data-test-driver-status] [data-test-accordion-head]', { - name: text('[data-test-name]'), - detected: text('[data-test-detected]'), - lastUpdated: text('[data-test-last-updated]'), - healthIsShown: isPresent('[data-test-health]'), - health: text('[data-test-health]'), - healthClass: attribute('class', '[data-test-health] .color-swatch'), + driverHeads: collection( + '[data-test-driver-status] [data-test-accordion-head]', + { + name: text('[data-test-name]'), + detected: text('[data-test-detected]'), + lastUpdated: text('[data-test-last-updated]'), + healthIsShown: isPresent('[data-test-health]'), + health: text('[data-test-health]'), + healthClass: attribute('class', '[data-test-health] .color-swatch'), - toggle: clickable('[data-test-accordion-toggle]'), - }), + toggle: clickable('[data-test-accordion-toggle]'), + } + ), - driverBodies: collection('[data-test-driver-status] [data-test-accordion-body]', { - description: text('[data-test-health-description]'), - descriptionIsShown: isPresent('[data-test-health-description]'), - attributesAreShown: isPresent('[data-test-driver-attributes]'), - }), + driverBodies: collection( + '[data-test-driver-status] [data-test-accordion-body]', + { + description: text('[data-test-health-description]'), + descriptionIsShown: isPresent('[data-test-health-description]'), + attributesAreShown: isPresent('[data-test-driver-attributes]'), + } + ), drainDetails: { scope: '[data-test-drain-details]', @@ -151,10 +163,7 @@ export default create({ setDeadline(label) { this.deadlineOptions.open(); - this.deadlineOptions.options - .toArray() - .findBy('label', label) - .choose(); + this.deadlineOptions.options.toArray().findBy('label', label).choose(); }, }, @@ -166,7 +175,13 @@ export default create({ eligibilityError: notification('[data-test-eligibility-error]'), stopDrainError: notification('[data-test-stop-drain-error]'), drainError: notification('[data-test-drain-error]'), - drainStoppedNotification: notification('[data-test-drain-stopped-notification]'), - drainUpdatedNotification: notification('[data-test-drain-updated-notification]'), - drainCompleteNotification: notification('[data-test-drain-complete-notification]'), + drainStoppedNotification: notification( + '[data-test-drain-stopped-notification]' + ), + drainUpdatedNotification: notification( + '[data-test-drain-updated-notification]' + ), + drainCompleteNotification: notification( + '[data-test-drain-complete-notification]' + ), }); diff --git a/ui/tests/pages/clients/list.js b/ui/tests/pages/clients/list.js index f00c36a28..57af923d6 100644 --- a/ui/tests/pages/clients/list.js +++ b/ui/tests/pages/clients/list.js @@ -27,10 +27,7 @@ export default create({ }), sortBy(id) { - return this.sortOptions - .toArray() - .findBy('id', id) - .sort(); + return this.sortOptions.toArray().findBy('id', id).sort(); }, nodes: collection('[data-test-client-node-row]', { diff --git a/ui/tests/pages/clients/monitor.js b/ui/tests/pages/clients/monitor.js index 4ec32bef8..828fd84e3 100644 --- a/ui/tests/pages/clients/monitor.js +++ b/ui/tests/pages/clients/monitor.js @@ -1,6 +1,15 @@ -import { create, clickable, isPresent, text, visitable } from 'ember-cli-page-object'; +import { + create, + clickable, + isPresent, + text, + visitable, +} from 'ember-cli-page-object'; import { run } from '@ember/runloop'; -import { selectOpen, selectOpenChoose } from '../../utils/ember-power-select-extensions'; +import { + selectOpen, + selectOpenChoose, +} from '../../utils/ember-power-select-extensions'; export default create({ visit: visitable('/clients/:id/monitor'), diff --git a/ui/tests/pages/components/allocations.js b/ui/tests/pages/components/allocations.js index 748a0ab16..6f152a4ee 100644 --- a/ui/tests/pages/components/allocations.js +++ b/ui/tests/pages/components/allocations.js @@ -1,7 +1,16 @@ -import { attribute, collection, clickable, isPresent, text } from 'ember-cli-page-object'; +import { + attribute, + collection, + clickable, + isPresent, + text, +} from 'ember-cli-page-object'; import { singularize } from 'ember-inflector'; -export default function(selector = '[data-test-allocation]', propKey = 'allocations') { +export default function ( + selector = '[data-test-allocation]', + propKey = 'allocations' +) { const lookupKey = `${singularize(propKey)}For`; // Remove the bracket notation const attr = selector.substring(1, selector.length - 1); @@ -11,7 +20,10 @@ export default function(selector = '[data-test-allocation]', propKey = 'allocati id: attribute(attr), shortId: text('[data-test-short-id]'), createTime: text('[data-test-create-time]'), - createTooltip: attribute('aria-label', '[data-test-create-time] .tooltip'), + createTooltip: attribute( + 'aria-label', + '[data-test-create-time] .tooltip' + ), modifyTime: text('[data-test-modify-time]'), health: text('[data-test-health]'), status: text('[data-test-client-status]'), @@ -25,7 +37,9 @@ export default function(selector = '[data-test-allocation]', propKey = 'allocati cpuTooltip: attribute('aria-label', '[data-test-cpu] .tooltip'), mem: text('[data-test-mem]'), memTooltip: attribute('aria-label', '[data-test-mem] .tooltip'), - rescheduled: isPresent('[data-test-indicators] [data-test-icon="reschedule"]'), + rescheduled: isPresent( + '[data-test-indicators] [data-test-icon="reschedule"]' + ), visit: clickable('[data-test-short-id] a'), visitRow: clickable(), @@ -33,8 +47,8 @@ export default function(selector = '[data-test-allocation]', propKey = 'allocati visitClient: clickable('[data-test-client] a'), }), - [lookupKey]: function(id) { - return this[propKey].toArray().find(allocation => allocation.id === id); + [lookupKey]: function (id) { + return this[propKey].toArray().find((allocation) => allocation.id === id); }, }; } diff --git a/ui/tests/pages/components/clients.js b/ui/tests/pages/components/clients.js index 4be7fbee3..6cf592d3b 100644 --- a/ui/tests/pages/components/clients.js +++ b/ui/tests/pages/components/clients.js @@ -1,7 +1,7 @@ import { attribute, collection, clickable, text } from 'ember-cli-page-object'; import { singularize } from 'ember-inflector'; -export default function(selector = '[data-test-client]', propKey = 'clients') { +export default function (selector = '[data-test-client]', propKey = 'clients') { const lookupKey = `${singularize(propKey)}For`; // Remove the bracket notation const attr = selector.substring(1, selector.length - 1); @@ -33,8 +33,8 @@ export default function(selector = '[data-test-client]', propKey = 'clients') { visitRow: clickable(), }), - [lookupKey]: function(id) { - return this[propKey].toArray().find(client => client.id === id); + [lookupKey]: function (id) { + return this[propKey].toArray().find((client) => client.id === id); }, }; } diff --git a/ui/tests/pages/components/error.js b/ui/tests/pages/components/error.js index 066ef08fc..634cb6ab1 100644 --- a/ui/tests/pages/components/error.js +++ b/ui/tests/pages/components/error.js @@ -1,6 +1,6 @@ import { clickable, isPresent, text } from 'ember-cli-page-object'; -export default function(selectorBase = 'data-test-error') { +export default function (selectorBase = 'data-test-error') { return { scope: `[${selectorBase}]`, isPresent: isPresent(), diff --git a/ui/tests/pages/components/facet.js b/ui/tests/pages/components/facet.js index a4d737184..a5ce8a77b 100644 --- a/ui/tests/pages/components/facet.js +++ b/ui/tests/pages/components/facet.js @@ -1,7 +1,10 @@ import { clickable, collection, text, attribute } from 'ember-cli-page-object'; -import { selectChoose, clickTrigger } from 'ember-power-select/test-support/helpers'; +import { + selectChoose, + clickTrigger, +} from 'ember-power-select/test-support/helpers'; -export const multiFacet = scope => ({ +export const multiFacet = (scope) => ({ scope, toggle: clickable('[data-test-dropdown-trigger]'), @@ -15,7 +18,7 @@ export const multiFacet = scope => ({ }), }); -export const singleFacet = scope => ({ +export const singleFacet = (scope) => ({ scope, async toggle() { diff --git a/ui/tests/pages/components/gauge-chart.js b/ui/tests/pages/components/gauge-chart.js index 04b59287c..57f8da766 100644 --- a/ui/tests/pages/components/gauge-chart.js +++ b/ui/tests/pages/components/gauge-chart.js @@ -1,6 +1,6 @@ import { isPresent, text } from 'ember-cli-page-object'; -export default scope => ({ +export default (scope) => ({ scope, svgIsPresent: isPresent('[data-test-gauge-svg]'), diff --git a/ui/tests/pages/components/job-client-status-bar.js b/ui/tests/pages/components/job-client-status-bar.js index e29d84185..6172259cc 100644 --- a/ui/tests/pages/components/job-client-status-bar.js +++ b/ui/tests/pages/components/job-client-status-bar.js @@ -1,6 +1,6 @@ import { attribute, clickable, collection } from 'ember-cli-page-object'; -export default scope => ({ +export default (scope) => ({ scope, slices: collection('svg .bars g', { @@ -21,17 +21,11 @@ export default scope => ({ }), }, - visitSlice: async function(label) { - await this.slices - .toArray() - .findBy('label', label) - .click(); + visitSlice: async function (label) { + await this.slices.toArray().findBy('label', label).click(); }, - visitLegend: async function(label) { - await this.legend.clickableItems - .toArray() - .findBy('label', label) - .click(); + visitLegend: async function (label) { + await this.legend.clickableItems.toArray().findBy('label', label).click(); }, }); diff --git a/ui/tests/pages/components/notification.js b/ui/tests/pages/components/notification.js index 95e4a20a8..9c07c54ac 100644 --- a/ui/tests/pages/components/notification.js +++ b/ui/tests/pages/components/notification.js @@ -1,6 +1,6 @@ import { isPresent, clickable, text } from 'ember-cli-page-object'; -export default scope => ({ +export default (scope) => ({ scope, isPresent: isPresent(), diff --git a/ui/tests/pages/components/page-size-select.js b/ui/tests/pages/components/page-size-select.js index 51d09cbfc..3ea9f3747 100644 --- a/ui/tests/pages/components/page-size-select.js +++ b/ui/tests/pages/components/page-size-select.js @@ -1,9 +1,15 @@ import { clickable, collection, isPresent, text } from 'ember-cli-page-object'; export default () => ({ - isPresent: isPresent('[data-test-page-size-select-parent] .ember-power-select-trigger'), - open: clickable('[data-test-page-size-select-parent] .ember-power-select-trigger'), - selectedOption: text('[data-test-page-size-select-parent] .ember-power-select-selected-item'), + isPresent: isPresent( + '[data-test-page-size-select-parent] .ember-power-select-trigger' + ), + open: clickable( + '[data-test-page-size-select-parent] .ember-power-select-trigger' + ), + selectedOption: text( + '[data-test-page-size-select-parent] .ember-power-select-selected-item' + ), options: collection('.ember-power-select-option', { testContainer: '#ember-testing', resetScope: true, diff --git a/ui/tests/pages/components/popover-menu.js b/ui/tests/pages/components/popover-menu.js index e156198aa..8b38d2fb7 100644 --- a/ui/tests/pages/components/popover-menu.js +++ b/ui/tests/pages/components/popover-menu.js @@ -1,10 +1,16 @@ -import { clickable, focusable, isPresent, text, triggerable } from 'ember-cli-page-object'; +import { + clickable, + focusable, + isPresent, + text, + triggerable, +} from 'ember-cli-page-object'; const ARROW_DOWN = 40; const ESC = 27; const TAB = 9; -export default scope => ({ +export default (scope) => ({ scope, isPresent: isPresent(), diff --git a/ui/tests/pages/components/recommendation-card.js b/ui/tests/pages/components/recommendation-card.js index 823a4a4ca..5be7a7855 100644 --- a/ui/tests/pages/components/recommendation-card.js +++ b/ui/tests/pages/components/recommendation-card.js @@ -1,4 +1,10 @@ -import { attribute, collection, hasClass, isPresent, text } from 'ember-cli-page-object'; +import { + attribute, + collection, + hasClass, + isPresent, + text, +} from 'ember-cli-page-object'; import { getter } from 'ember-cli-page-object/macros'; import toggle from 'nomad-ui/tests/pages/components/toggle'; @@ -67,7 +73,7 @@ function totalsTableCell(scope) { scope, isIncrease: hasClass('increase'), isDecrease: hasClass('decrease'), - isNeutral: getter(function() { + isNeutral: getter(function () { return !this.isIncrease && !this.isDecrease; }), }; diff --git a/ui/tests/pages/components/stepper-input.js b/ui/tests/pages/components/stepper-input.js index b9eac7233..17b4af348 100644 --- a/ui/tests/pages/components/stepper-input.js +++ b/ui/tests/pages/components/stepper-input.js @@ -10,7 +10,7 @@ import { value, } from 'ember-cli-page-object'; -export default scope => ({ +export default (scope) => ({ scope, label: text('[data-test-stepper-label]'), diff --git a/ui/tests/pages/components/toggle.js b/ui/tests/pages/components/toggle.js index cdd1342cf..3db03a46f 100644 --- a/ui/tests/pages/components/toggle.js +++ b/ui/tests/pages/components/toggle.js @@ -1,6 +1,13 @@ -import { attribute, property, clickable, hasClass, isPresent, text } from 'ember-cli-page-object'; +import { + attribute, + property, + clickable, + hasClass, + isPresent, + text, +} from 'ember-cli-page-object'; -export default scope => ({ +export default (scope) => ({ scope, isPresent: isPresent(), diff --git a/ui/tests/pages/components/topo-viz.js b/ui/tests/pages/components/topo-viz.js index 657e5a165..455a084c4 100644 --- a/ui/tests/pages/components/topo-viz.js +++ b/ui/tests/pages/components/topo-viz.js @@ -1,11 +1,16 @@ import { collection, isPresent } from 'ember-cli-page-object'; import TopoVizDatacenter from './topo-viz/datacenter'; -export default scope => ({ +export default (scope) => ({ scope, - datacenters: collection('[data-test-topo-viz-datacenter]', TopoVizDatacenter()), + datacenters: collection( + '[data-test-topo-viz-datacenter]', + TopoVizDatacenter() + ), - allocationAssociationsArePresent: isPresent('[data-test-allocation-associations]'), + allocationAssociationsArePresent: isPresent( + '[data-test-allocation-associations]' + ), allocationAssociations: collection('[data-test-allocation-association]'), }); diff --git a/ui/tests/pages/components/topo-viz/datacenter.js b/ui/tests/pages/components/topo-viz/datacenter.js index 1388eeead..6f371ccfb 100644 --- a/ui/tests/pages/components/topo-viz/datacenter.js +++ b/ui/tests/pages/components/topo-viz/datacenter.js @@ -1,7 +1,7 @@ import { collection, text } from 'ember-cli-page-object'; import TopoVizNode from './node'; -export default scope => ({ +export default (scope) => ({ scope, label: text('[data-test-topo-viz-datacenter-label]'), diff --git a/ui/tests/pages/components/topo-viz/node.js b/ui/tests/pages/components/topo-viz/node.js index a31bab0f1..4874d63dc 100644 --- a/ui/tests/pages/components/topo-viz/node.js +++ b/ui/tests/pages/components/topo-viz/node.js @@ -21,7 +21,7 @@ const allocationRect = { pending: hasClass('pending'), }; -export default scope => ({ +export default (scope) => ({ scope, label: text('[data-test-label]'), diff --git a/ui/tests/pages/components/two-step-button.js b/ui/tests/pages/components/two-step-button.js index 1c787b9bb..403689e4b 100644 --- a/ui/tests/pages/components/two-step-button.js +++ b/ui/tests/pages/components/two-step-button.js @@ -1,6 +1,12 @@ -import { attribute, clickable, hasClass, isPresent, text } from 'ember-cli-page-object'; +import { + attribute, + clickable, + hasClass, + isPresent, + text, +} from 'ember-cli-page-object'; -export default scope => ({ +export default (scope) => ({ scope, isPresent: isPresent(), diff --git a/ui/tests/pages/exec.js b/ui/tests/pages/exec.js index a0b59222a..a9b8358a7 100644 --- a/ui/tests/pages/exec.js +++ b/ui/tests/pages/exec.js @@ -40,7 +40,9 @@ export default create({ terminal: { scope: '.xterm-helper-textarea', - pressEnter: triggerable('keydown', '', { eventProperties: { keyCode: 13 } }), + pressEnter: triggerable('keydown', '', { + eventProperties: { keyCode: 13 }, + }), }, jobDead: { diff --git a/ui/tests/pages/helpers/codemirror.js b/ui/tests/pages/helpers/codemirror.js index 8a64b6e5b..0fbfd3033 100644 --- a/ui/tests/pages/helpers/codemirror.js +++ b/ui/tests/pages/helpers/codemirror.js @@ -7,7 +7,7 @@ export function codeFillable(selector) { isDescriptor: true, get() { - return function(code) { + return function (code) { const cm = getCodeMirrorInstance(selector); cm.setValue(code); return this; diff --git a/ui/tests/pages/jobs/detail.js b/ui/tests/pages/jobs/detail.js index e82fc6f83..9c07feaa7 100644 --- a/ui/tests/pages/jobs/detail.js +++ b/ui/tests/pages/jobs/detail.js @@ -29,7 +29,10 @@ export default create({ return this.tabs.toArray().findBy('id', id); }, - recommendations: collection('[data-test-recommendation-accordion]', recommendationAccordion), + recommendations: collection( + '[data-test-recommendation-accordion]', + recommendationAccordion + ), stop: twoStepButton('[data-test-stop]'), start: twoStepButton('[data-test-start]'), @@ -82,7 +85,9 @@ export default create({ tooltip: attribute('aria-label'), }, }, - childrenSummary: jobClientStatusBar('[data-test-job-summary] [data-test-children-status-bar]'), + childrenSummary: jobClientStatusBar( + '[data-test-job-summary] [data-test-children-status-bar]' + ), allocationsSummary: jobClientStatusBar( '[data-test-job-summary] [data-test-allocation-status-bar]' ), diff --git a/ui/tests/pages/jobs/job/allocations.js b/ui/tests/pages/jobs/job/allocations.js index 8b15cb932..4678f06d0 100644 --- a/ui/tests/pages/jobs/job/allocations.js +++ b/ui/tests/pages/jobs/job/allocations.js @@ -40,10 +40,7 @@ export default create({ }), sortBy(id) { - return this.sortOptions - .toArray() - .findBy('id', id) - .sort(); + return this.sortOptions.toArray().findBy('id', id).sort(); }, error: error(), diff --git a/ui/tests/pages/jobs/job/clients.js b/ui/tests/pages/jobs/job/clients.js index 83d69fa9e..ba7bad745 100644 --- a/ui/tests/pages/jobs/job/clients.js +++ b/ui/tests/pages/jobs/job/clients.js @@ -33,10 +33,7 @@ export default create({ }), sortBy(id) { - return this.sortOptions - .toArray() - .findBy('id', id) - .sort(); + return this.sortOptions.toArray().findBy('id', id).sort(); }, facets: { diff --git a/ui/tests/pages/jobs/job/evaluations.js b/ui/tests/pages/jobs/job/evaluations.js index 79200460a..884744f0a 100644 --- a/ui/tests/pages/jobs/job/evaluations.js +++ b/ui/tests/pages/jobs/job/evaluations.js @@ -1,4 +1,11 @@ -import { attribute, clickable, create, collection, text, visitable } from 'ember-cli-page-object'; +import { + attribute, + clickable, + create, + collection, + text, + visitable, +} from 'ember-cli-page-object'; import error from 'nomad-ui/tests/pages/components/error'; @@ -15,10 +22,7 @@ export default create({ }), sortBy(id) { - return this.sortOptions - .toArray() - .findBy('id', id) - .sort(); + return this.sortOptions.toArray().findBy('id', id).sort(); }, error: error(), diff --git a/ui/tests/pages/jobs/job/task-group.js b/ui/tests/pages/jobs/job/task-group.js index 28bd08dbd..330569c8e 100644 --- a/ui/tests/pages/jobs/job/task-group.js +++ b/ui/tests/pages/jobs/job/task-group.js @@ -50,25 +50,36 @@ export default create({ }), hasScaleEvents: isPresent('[data-test-scale-events]'), - scaleEvents: collection('[data-test-scale-events] [data-test-accordion-head]', { - error: isPresent('[data-test-error]'), - time: text('[data-test-time]'), - count: text('[data-test-count]'), - countIcon: { scope: '[data-test-count-icon]' }, - message: text('[data-test-message]'), + scaleEvents: collection( + '[data-test-scale-events] [data-test-accordion-head]', + { + error: isPresent('[data-test-error]'), + time: text('[data-test-time]'), + count: text('[data-test-count]'), + countIcon: { scope: '[data-test-count-icon]' }, + message: text('[data-test-message]'), - isToggleable: isPresent('[data-test-accordion-toggle]:not(.is-invisible)'), - toggle: clickable('[data-test-accordion-toggle]'), - }), + isToggleable: isPresent( + '[data-test-accordion-toggle]:not(.is-invisible)' + ), + toggle: clickable('[data-test-accordion-toggle]'), + } + ), - scaleEventBodies: collection('[data-test-scale-events] [data-test-accordion-body]', { - meta: text(), - }), + scaleEventBodies: collection( + '[data-test-scale-events] [data-test-accordion-body]', + { + meta: text(), + } + ), hasScalingTimeline: isPresent('[data-test-scaling-timeline]'), - scalingAnnotations: collection('[data-test-scaling-timeline] [data-test-annotation]', { - open: clickable('button'), - }), + scalingAnnotations: collection( + '[data-test-scaling-timeline] [data-test-annotation]', + { + open: clickable('button'), + } + ), error: error(), diff --git a/ui/tests/pages/jobs/job/versions.js b/ui/tests/pages/jobs/job/versions.js index 416406f00..477012f5c 100644 --- a/ui/tests/pages/jobs/job/versions.js +++ b/ui/tests/pages/jobs/job/versions.js @@ -1,4 +1,10 @@ -import { attribute, create, collection, text, visitable } from 'ember-cli-page-object'; +import { + attribute, + create, + collection, + text, + visitable, +} from 'ember-cli-page-object'; import { getter } from 'ember-cli-page-object/macros'; import twoStepButton from 'nomad-ui/tests/pages/components/two-step-button'; @@ -15,7 +21,7 @@ export default create({ revertToButton: twoStepButton('[data-test-revert-to]'), revertToButtonIsDisabled: attribute('disabled', '[data-test-revert-to]'), - number: getter(function() { + number: getter(function () { return parseInt(this.text.match(/#(\d+)/)[1]); }), }), diff --git a/ui/tests/pages/layout.js b/ui/tests/pages/layout.js index b48f2b3af..2fe186795 100644 --- a/ui/tests/pages/layout.js +++ b/ui/tests/pages/layout.js @@ -85,7 +85,7 @@ export default create({ }), breadcrumbFor(id) { - return this.breadcrumbs.toArray().find(crumb => crumb.id === id); + return this.breadcrumbs.toArray().find((crumb) => crumb.id === id); }, error: { diff --git a/ui/tests/pages/optimize.js b/ui/tests/pages/optimize.js index 35fbddc4c..b37efbad7 100644 --- a/ui/tests/pages/optimize.js +++ b/ui/tests/pages/optimize.js @@ -30,19 +30,22 @@ export default create({ card: recommendationCard, - recommendationSummaries: collection('[data-test-recommendation-summary-row]', { - isActive: hasClass('is-active'), - isDisabled: hasClass('is-disabled'), + recommendationSummaries: collection( + '[data-test-recommendation-summary-row]', + { + isActive: hasClass('is-active'), + isDisabled: hasClass('is-disabled'), - slug: text('[data-test-slug]'), - namespace: text('[data-test-namespace]'), - date: text('[data-test-date]'), - allocationCount: text('[data-test-allocation-count]'), - cpu: text('[data-test-cpu]'), - memory: text('[data-test-memory]'), - aggregateCpu: text('[data-test-aggregate-cpu]'), - aggregateMemory: text('[data-test-aggregate-memory]'), - }), + slug: text('[data-test-slug]'), + namespace: text('[data-test-namespace]'), + date: text('[data-test-date]'), + allocationCount: text('[data-test-allocation-count]'), + cpu: text('[data-test-cpu]'), + memory: text('[data-test-memory]'), + aggregateCpu: text('[data-test-aggregate-cpu]'), + aggregateMemory: text('[data-test-aggregate-memory]'), + } + ), empty: { scope: '[data-test-empty-recommendations]', diff --git a/ui/tests/pages/servers/detail.js b/ui/tests/pages/servers/detail.js index 6a9499ead..4b6d41c0c 100644 --- a/ui/tests/pages/servers/detail.js +++ b/ui/tests/pages/servers/detail.js @@ -1,4 +1,11 @@ -import { create, collection, clickable, isPresent, text, visitable } from 'ember-cli-page-object'; +import { + create, + collection, + clickable, + isPresent, + text, + visitable, +} from 'ember-cli-page-object'; export default create({ visit: visitable('/servers/:name'), diff --git a/ui/tests/pages/servers/list.js b/ui/tests/pages/servers/list.js index cc884d4e1..2d29cffdd 100644 --- a/ui/tests/pages/servers/list.js +++ b/ui/tests/pages/servers/list.js @@ -1,4 +1,10 @@ -import { create, collection, clickable, text, visitable } from 'ember-cli-page-object'; +import { + create, + collection, + clickable, + text, + visitable, +} from 'ember-cli-page-object'; export default create({ pageSize: 8, diff --git a/ui/tests/pages/servers/monitor.js b/ui/tests/pages/servers/monitor.js index bded4c156..b040e467f 100644 --- a/ui/tests/pages/servers/monitor.js +++ b/ui/tests/pages/servers/monitor.js @@ -1,6 +1,15 @@ -import { create, clickable, isPresent, text, visitable } from 'ember-cli-page-object'; +import { + create, + clickable, + isPresent, + text, + visitable, +} from 'ember-cli-page-object'; import { run } from '@ember/runloop'; -import { selectOpen, selectOpenChoose } from '../../utils/ember-power-select-extensions'; +import { + selectOpen, + selectOpenChoose, +} from '../../utils/ember-power-select-extensions'; export default create({ visit: visitable('/servers/:name/monitor'), diff --git a/ui/tests/pages/storage/plugins/detail.js b/ui/tests/pages/storage/plugins/detail.js index 160df3400..3a18e9317 100644 --- a/ui/tests/pages/storage/plugins/detail.js +++ b/ui/tests/pages/storage/plugins/detail.js @@ -1,4 +1,10 @@ -import { clickable, create, isPresent, text, visitable } from 'ember-cli-page-object'; +import { + clickable, + create, + isPresent, + text, + visitable, +} from 'ember-cli-page-object'; import allocations from 'nomad-ui/tests/pages/components/allocations'; @@ -12,15 +18,21 @@ export default create({ nodeHealth: text('[data-test-plugin-node-health]'), provider: text('[data-test-plugin-provider]'), - controllerAvailabilityIsPresent: isPresent('[data-test-plugin-controller-availability]'), + controllerAvailabilityIsPresent: isPresent( + '[data-test-plugin-controller-availability]' + ), nodeAvailabilityIsPresent: isPresent('[data-test-plugin-node-availability]'), ...allocations('[data-test-controller-allocation]', 'controllerAllocations'), ...allocations('[data-test-node-allocation]', 'nodeAllocations'), - goToControllerAllocations: clickable('[data-test-go-to-controller-allocations]'), + goToControllerAllocations: clickable( + '[data-test-go-to-controller-allocations]' + ), goToNodeAllocations: clickable('[data-test-go-to-node-allocations]'), - goToControllerAllocationsText: text('[data-test-go-to-controller-allocations]'), + goToControllerAllocationsText: text( + '[data-test-go-to-controller-allocations]' + ), goToNodeAllocationsText: text('[data-test-go-to-node-allocations]'), controllerTableIsPresent: isPresent('[data-test-controller-allocations]'), diff --git a/ui/tests/pages/storage/plugins/plugin/allocations.js b/ui/tests/pages/storage/plugins/plugin/allocations.js index bd553fbe4..37084aeef 100644 --- a/ui/tests/pages/storage/plugins/plugin/allocations.js +++ b/ui/tests/pages/storage/plugins/plugin/allocations.js @@ -1,4 +1,10 @@ -import { clickable, create, isPresent, text, visitable } from 'ember-cli-page-object'; +import { + clickable, + create, + isPresent, + text, + visitable, +} from 'ember-cli-page-object'; import allocations from 'nomad-ui/tests/pages/components/allocations'; import { multiFacet } from 'nomad-ui/tests/pages/components/facet'; diff --git a/ui/tests/pages/topology.js b/ui/tests/pages/topology.js index 401115543..c62dfeef0 100644 --- a/ui/tests/pages/topology.js +++ b/ui/tests/pages/topology.js @@ -1,4 +1,12 @@ -import { attribute, clickable, collection, create, hasClass, text, visitable } from 'ember-cli-page-object'; +import { + attribute, + clickable, + collection, + create, + hasClass, + text, + visitable, +} from 'ember-cli-page-object'; import TopoViz from 'nomad-ui/tests/pages/components/topo-viz'; import notification from 'nomad-ui/tests/pages/components/notification'; diff --git a/ui/tests/unit/abilities/allocation-test.js b/ui/tests/unit/abilities/allocation-test.js index 27eb574de..209a00ead 100644 --- a/ui/tests/unit/abilities/allocation-test.js +++ b/ui/tests/unit/abilities/allocation-test.js @@ -4,11 +4,11 @@ import { setupTest } from 'ember-qunit'; import Service from '@ember/service'; import setupAbility from 'nomad-ui/tests/helpers/setup-ability'; -module('Unit | Ability | allocation', function(hooks) { +module('Unit | Ability | allocation', function (hooks) { setupTest(hooks); setupAbility('allocation')(hooks); - test('it permits alloc exec when ACLs are disabled', function(assert) { + test('it permits alloc exec when ACLs are disabled', function (assert) { const mockToken = Service.extend({ aclEnabled: false, }); @@ -18,7 +18,7 @@ module('Unit | Ability | allocation', function(hooks) { assert.ok(this.can.can('exec allocation')); }); - test('it permits alloc exec for management tokens', function(assert) { + test('it permits alloc exec for management tokens', function (assert) { const mockToken = Service.extend({ aclEnabled: true, selfToken: { type: 'management' }, @@ -29,7 +29,7 @@ module('Unit | Ability | allocation', function(hooks) { assert.ok(this.can.can('exec allocation')); }); - test('it permits alloc exec for client tokens with a policy that has namespace alloc-exec', function(assert) { + test('it permits alloc exec for client tokens with a policy that has namespace alloc-exec', function (assert) { const mockSystem = Service.extend({ aclEnabled: true, }); @@ -54,10 +54,12 @@ module('Unit | Ability | allocation', function(hooks) { this.owner.register('service:system', mockSystem); this.owner.register('service:token', mockToken); - assert.ok(this.can.can('exec allocation', null, { namespace: 'aNamespace' })); + assert.ok( + this.can.can('exec allocation', null, { namespace: 'aNamespace' }) + ); }); - test('it permits alloc exec for client tokens with a policy that has default namespace alloc-exec and no capabilities for active namespace', function(assert) { + test('it permits alloc exec for client tokens with a policy that has default namespace alloc-exec and no capabilities for active namespace', function (assert) { const mockSystem = Service.extend({ aclEnabled: true, }); @@ -86,10 +88,12 @@ module('Unit | Ability | allocation', function(hooks) { this.owner.register('service:system', mockSystem); this.owner.register('service:token', mockToken); - assert.ok(this.can.can('exec allocation', null, { namespace: 'anotherNamespace' })); + assert.ok( + this.can.can('exec allocation', null, { namespace: 'anotherNamespace' }) + ); }); - test('it blocks alloc exec for client tokens with a policy that has no alloc-exec capability', function(assert) { + test('it blocks alloc exec for client tokens with a policy that has no alloc-exec capability', function (assert) { const mockSystem = Service.extend({ aclEnabled: true, }); @@ -114,10 +118,12 @@ module('Unit | Ability | allocation', function(hooks) { this.owner.register('service:system', mockSystem); this.owner.register('service:token', mockToken); - assert.ok(this.can.cannot('exec allocation', null, { namespace: 'aNamespace' })); + assert.ok( + this.can.cannot('exec allocation', null, { namespace: 'aNamespace' }) + ); }); - test('it handles globs in namespace names', function(assert) { + test('it handles globs in namespace names', function (assert) { const mockSystem = Service.extend({ aclEnabled: true, }); @@ -162,12 +168,22 @@ module('Unit | Ability | allocation', function(hooks) { this.owner.register('service:system', mockSystem); this.owner.register('service:token', mockToken); - assert.ok(this.can.cannot('exec allocation', null, { namespace: 'production-web' })); - assert.ok(this.can.can('exec allocation', null, { namespace: 'production-api' })); - assert.ok(this.can.can('exec allocation', null, { namespace: 'production-other' })); - assert.ok(this.can.can('exec allocation', null, { namespace: 'something-suffixed' })); assert.ok( - this.can.cannot('exec allocation', null, { namespace: 'something-more-suffixed' }), + this.can.cannot('exec allocation', null, { namespace: 'production-web' }) + ); + assert.ok( + this.can.can('exec allocation', null, { namespace: 'production-api' }) + ); + assert.ok( + this.can.can('exec allocation', null, { namespace: 'production-other' }) + ); + assert.ok( + this.can.can('exec allocation', null, { namespace: 'something-suffixed' }) + ); + assert.ok( + this.can.cannot('exec allocation', null, { + namespace: 'something-more-suffixed', + }), 'expected the namespace with the greatest number of matched characters to be chosen' ); assert.ok( diff --git a/ui/tests/unit/abilities/client-test.js b/ui/tests/unit/abilities/client-test.js index 2a9fe9986..8d2c2a731 100644 --- a/ui/tests/unit/abilities/client-test.js +++ b/ui/tests/unit/abilities/client-test.js @@ -4,11 +4,11 @@ import { setupTest } from 'ember-qunit'; import Service from '@ember/service'; import setupAbility from 'nomad-ui/tests/helpers/setup-ability'; -module('Unit | Ability | client', function(hooks) { +module('Unit | Ability | client', function (hooks) { setupTest(hooks); setupAbility('client')(hooks); - test('it permits client read and write when ACLs are disabled', function(assert) { + test('it permits client read and write when ACLs are disabled', function (assert) { const mockToken = Service.extend({ aclEnabled: false, }); @@ -18,7 +18,7 @@ module('Unit | Ability | client', function(hooks) { assert.ok(this.ability.canWrite); }); - test('it permits client read and write for management tokens', function(assert) { + test('it permits client read and write for management tokens', function (assert) { const mockToken = Service.extend({ aclEnabled: true, selfToken: { type: 'management' }, @@ -29,7 +29,7 @@ module('Unit | Ability | client', function(hooks) { assert.ok(this.ability.canWrite); }); - test('it permits client read and write for tokens with a policy that has node-write', function(assert) { + test('it permits client read and write for tokens with a policy that has node-write', function (assert) { const mockToken = Service.extend({ aclEnabled: true, selfToken: { type: 'client' }, @@ -49,7 +49,7 @@ module('Unit | Ability | client', function(hooks) { assert.ok(this.ability.canWrite); }); - test('it permits client read and write for tokens with a policy that allows write and another policy that disallows it', function(assert) { + test('it permits client read and write for tokens with a policy that allows write and another policy that disallows it', function (assert) { const mockToken = Service.extend({ aclEnabled: true, selfToken: { type: 'client' }, @@ -76,7 +76,7 @@ module('Unit | Ability | client', function(hooks) { assert.ok(this.ability.canWrite); }); - test('it permits client read and blocks client write for tokens with a policy that does not allow node-write', function(assert) { + test('it permits client read and blocks client write for tokens with a policy that does not allow node-write', function (assert) { const mockToken = Service.extend({ aclEnabled: true, selfToken: { type: 'client' }, @@ -96,7 +96,7 @@ module('Unit | Ability | client', function(hooks) { assert.notOk(this.ability.canWrite); }); - test('it blocks client read and write for tokens without a node policy', function(assert) { + test('it blocks client read and write for tokens without a node policy', function (assert) { const mockToken = Service.extend({ aclEnabled: true, selfToken: { type: 'client' }, diff --git a/ui/tests/unit/abilities/job-test.js b/ui/tests/unit/abilities/job-test.js index 8cd2b0ac1..f1f89233b 100644 --- a/ui/tests/unit/abilities/job-test.js +++ b/ui/tests/unit/abilities/job-test.js @@ -4,11 +4,11 @@ import { setupTest } from 'ember-qunit'; import Service from '@ember/service'; import setupAbility from 'nomad-ui/tests/helpers/setup-ability'; -module('Unit | Ability | job', function(hooks) { +module('Unit | Ability | job', function (hooks) { setupTest(hooks); setupAbility('job')(hooks); - test('it permits job run when ACLs are disabled', function(assert) { + test('it permits job run when ACLs are disabled', function (assert) { const mockToken = Service.extend({ aclEnabled: false, }); @@ -18,7 +18,7 @@ module('Unit | Ability | job', function(hooks) { assert.ok(this.ability.canRun); }); - test('it permits job run for management tokens', function(assert) { + test('it permits job run for management tokens', function (assert) { const mockToken = Service.extend({ aclEnabled: true, selfToken: { type: 'management' }, @@ -29,7 +29,7 @@ module('Unit | Ability | job', function(hooks) { assert.ok(this.ability.canRun); }); - test('it permits job run for client tokens with a policy that has namespace submit-job', function(assert) { + test('it permits job run for client tokens with a policy that has namespace submit-job', function (assert) { const mockSystem = Service.extend({ aclEnabled: true, }); @@ -57,7 +57,7 @@ module('Unit | Ability | job', function(hooks) { assert.ok(this.can.can('run job', null, { namespace: 'aNamespace' })); }); - test('it permits job run for client tokens with a policy that has default namespace submit-job and no capabilities for active namespace', function(assert) { + test('it permits job run for client tokens with a policy that has default namespace submit-job and no capabilities for active namespace', function (assert) { const mockSystem = Service.extend({ aclEnabled: true, }); @@ -89,7 +89,7 @@ module('Unit | Ability | job', function(hooks) { assert.ok(this.can.can('run job', null, { namespace: 'anotherNamespace' })); }); - test('it blocks job run for client tokens with a policy that has no submit-job capability', function(assert) { + test('it blocks job run for client tokens with a policy that has no submit-job capability', function (assert) { const mockSystem = Service.extend({ aclEnabled: true, }); @@ -117,7 +117,7 @@ module('Unit | Ability | job', function(hooks) { assert.ok(this.can.cannot('run job', null, { namespace: 'aNamespace' })); }); - test('job scale requires a client token with the submit-job or scale-job capability', function(assert) { + test('job scale requires a client token with the submit-job or scale-job capability', function (assert) { const makePolicies = (namespace, ...capabilities) => [ { rulesJSON: { @@ -147,17 +147,26 @@ module('Unit | Ability | job', function(hooks) { assert.ok(this.can.cannot('scale job', null, { namespace: 'aNamespace' })); - tokenService.set('selfTokenPolicies', makePolicies('aNamespace', 'scale-job')); + tokenService.set( + 'selfTokenPolicies', + makePolicies('aNamespace', 'scale-job') + ); assert.ok(this.can.can('scale job', null, { namespace: 'aNamespace' })); - tokenService.set('selfTokenPolicies', makePolicies('aNamespace', 'submit-job')); + tokenService.set( + 'selfTokenPolicies', + makePolicies('aNamespace', 'submit-job') + ); assert.ok(this.can.can('scale job', null, { namespace: 'aNamespace' })); - tokenService.set('selfTokenPolicies', makePolicies('bNamespace', 'scale-job')); + tokenService.set( + 'selfTokenPolicies', + makePolicies('bNamespace', 'scale-job') + ); assert.ok(this.can.cannot('scale job', null, { namespace: 'aNamespace' })); }); - test('job dispatch requires a client token with the dispatch-job capability', function(assert) { + test('job dispatch requires a client token with the dispatch-job capability', function (assert) { const makePolicies = (namespace, ...capabilities) => [ { rulesJSON: { @@ -185,13 +194,18 @@ module('Unit | Ability | job', function(hooks) { this.owner.register('service:token', mockToken); const tokenService = this.owner.lookup('service:token'); - assert.ok(this.can.cannot('dispatch job', null, { namespace: 'aNamespace' })); + assert.ok( + this.can.cannot('dispatch job', null, { namespace: 'aNamespace' }) + ); - tokenService.set('selfTokenPolicies', makePolicies('aNamespace', 'dispatch-job')); + tokenService.set( + 'selfTokenPolicies', + makePolicies('aNamespace', 'dispatch-job') + ); assert.ok(this.can.can('dispatch job', null, { namespace: 'aNamespace' })); }); - test('it handles globs in namespace names', function(assert) { + test('it handles globs in namespace names', function (assert) { const mockSystem = Service.extend({ aclEnabled: true, }); @@ -236,12 +250,18 @@ module('Unit | Ability | job', function(hooks) { this.owner.register('service:system', mockSystem); this.owner.register('service:token', mockToken); - assert.ok(this.can.cannot('run job', null, { namespace: 'production-web' })); + assert.ok( + this.can.cannot('run job', null, { namespace: 'production-web' }) + ); assert.ok(this.can.can('run job', null, { namespace: 'production-api' })); assert.ok(this.can.can('run job', null, { namespace: 'production-other' })); - assert.ok(this.can.can('run job', null, { namespace: 'something-suffixed' })); assert.ok( - this.can.cannot('run job', null, { namespace: 'something-more-suffixed' }), + this.can.can('run job', null, { namespace: 'something-suffixed' }) + ); + assert.ok( + this.can.cannot('run job', null, { + namespace: 'something-more-suffixed', + }), 'expected the namespace with the greatest number of matched characters to be chosen' ); assert.ok( diff --git a/ui/tests/unit/abilities/recommendation-test.js b/ui/tests/unit/abilities/recommendation-test.js index 3d62fe0d1..4713ffa2f 100644 --- a/ui/tests/unit/abilities/recommendation-test.js +++ b/ui/tests/unit/abilities/recommendation-test.js @@ -4,78 +4,84 @@ import { setupTest } from 'ember-qunit'; import Service from '@ember/service'; import setupAbility from 'nomad-ui/tests/helpers/setup-ability'; -module('Unit | Ability | recommendation', function(hooks) { +module('Unit | Ability | recommendation', function (hooks) { setupTest(hooks); setupAbility('recommendation')(hooks); - module('when the Dynamic Application Sizing feature is present', function(hooks) { - hooks.beforeEach(function() { - const mockSystem = Service.extend({ - features: ['Dynamic Application Sizing'], + module( + 'when the Dynamic Application Sizing feature is present', + function (hooks) { + hooks.beforeEach(function () { + const mockSystem = Service.extend({ + features: ['Dynamic Application Sizing'], + }); + + this.owner.register('service:system', mockSystem); }); - this.owner.register('service:system', mockSystem); - }); + test('it permits accepting recommendations when ACLs are disabled', function (assert) { + const mockToken = Service.extend({ + aclEnabled: false, + }); - test('it permits accepting recommendations when ACLs are disabled', function(assert) { - const mockToken = Service.extend({ - aclEnabled: false, + this.owner.register('service:token', mockToken); + + assert.ok(this.ability.canAccept); }); - this.owner.register('service:token', mockToken); + test('it permits accepting recommendations for client tokens where any namespace has submit-job capabilities', function (assert) { + this.owner.lookup('service:system').set('activeNamespace', { + name: 'anotherNamespace', + }); - assert.ok(this.ability.canAccept); - }); - - test('it permits accepting recommendations for client tokens where any namespace has submit-job capabilities', function(assert) { - this.owner.lookup('service:system').set('activeNamespace', { - name: 'anotherNamespace', - }); - - const mockToken = Service.extend({ - aclEnabled: true, - selfToken: { type: 'client' }, - selfTokenPolicies: [ - { - rulesJSON: { - Namespaces: [ - { - Name: 'aNamespace', - Capabilities: [], - }, - { - Name: 'bNamespace', - Capabilities: ['submit-job'], - }, - ], + const mockToken = Service.extend({ + aclEnabled: true, + selfToken: { type: 'client' }, + selfTokenPolicies: [ + { + rulesJSON: { + Namespaces: [ + { + Name: 'aNamespace', + Capabilities: [], + }, + { + Name: 'bNamespace', + Capabilities: ['submit-job'], + }, + ], + }, }, - }, - ], + ], + }); + + this.owner.register('service:token', mockToken); + + assert.ok(this.ability.canAccept); + }); + } + ); + + module( + 'when the Dynamic Application Sizing feature is not present', + function (hooks) { + hooks.beforeEach(function () { + const mockSystem = Service.extend({ + features: [], + }); + + this.owner.register('service:system', mockSystem); }); - this.owner.register('service:token', mockToken); + test('it does not permit accepting recommendations regardless of ACL status', function (assert) { + const mockToken = Service.extend({ + aclEnabled: false, + }); - assert.ok(this.ability.canAccept); - }); - }); + this.owner.register('service:token', mockToken); - module('when the Dynamic Application Sizing feature is not present', function(hooks) { - hooks.beforeEach(function() { - const mockSystem = Service.extend({ - features: [], + assert.notOk(this.ability.canAccept); }); - - this.owner.register('service:system', mockSystem); - }); - - test('it does not permit accepting recommendations regardless of ACL status', function(assert) { - const mockToken = Service.extend({ - aclEnabled: false, - }); - - this.owner.register('service:token', mockToken); - - assert.notOk(this.ability.canAccept); - }); - }); + } + ); }); diff --git a/ui/tests/unit/adapters/allocation-test.js b/ui/tests/unit/adapters/allocation-test.js index e0e727ec8..31b49943e 100644 --- a/ui/tests/unit/adapters/allocation-test.js +++ b/ui/tests/unit/adapters/allocation-test.js @@ -2,10 +2,10 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; -module('Unit | Adapter | Allocation', function(hooks) { +module('Unit | Adapter | Allocation', function (hooks) { setupTest(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.adapterFor('allocation'); @@ -28,14 +28,17 @@ module('Unit | Adapter | Allocation', function(hooks) { this.system.get('shouldIncludeRegion'); await this.system.get('defaultRegion'); - const allocation = await this.store.findRecord('allocation', allocationId); + const allocation = await this.store.findRecord( + 'allocation', + allocationId + ); this.server.pretender.handledRequests.length = 0; return allocation; }; }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); @@ -46,8 +49,12 @@ module('Unit | Adapter | Allocation', function(hooks) { task: 'task-name', region: null, path: 'some/path', - ls: `GET /v1/client/fs/ls/alloc-1?path=${encodeURIComponent('some/path')}`, - stat: `GET /v1/client/fs/stat/alloc-1?path=${encodeURIComponent('some/path')}`, + ls: `GET /v1/client/fs/ls/alloc-1?path=${encodeURIComponent( + 'some/path' + )}`, + stat: `GET /v1/client/fs/stat/alloc-1?path=${encodeURIComponent( + 'some/path' + )}`, stop: 'POST /v1/allocation/alloc-1/stop', restart: 'PUT /v1/client/allocation/alloc-1/restart', }, @@ -57,7 +64,9 @@ module('Unit | Adapter | Allocation', function(hooks) { task: 'task-name', region: 'region-2', path: 'some/path', - ls: `GET /v1/client/fs/ls/alloc-1?path=${encodeURIComponent('some/path')}®ion=region-2`, + ls: `GET /v1/client/fs/ls/alloc-1?path=${encodeURIComponent( + 'some/path' + )}®ion=region-2`, stat: `GET /v1/client/fs/stat/alloc-1?path=${encodeURIComponent( 'some/path' )}®ion=region-2`, @@ -66,51 +75,63 @@ module('Unit | Adapter | Allocation', function(hooks) { }, ]; - testCases.forEach(testCase => { - test(`ls makes the correct API call ${testCase.variation}`, async function(assert) { + testCases.forEach((testCase) => { + test(`ls makes the correct API call ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - const allocation = await this.initialize(testCase.id, { region: testCase.region }); + const allocation = await this.initialize(testCase.id, { + region: testCase.region, + }); await this.subject().ls(allocation, testCase.path); const req = pretender.handledRequests[0]; assert.equal(`${req.method} ${req.url}`, testCase.ls); }); - test(`stat makes the correct API call ${testCase.variation}`, async function(assert) { + test(`stat makes the correct API call ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - const allocation = await this.initialize(testCase.id, { region: testCase.region }); + const allocation = await this.initialize(testCase.id, { + region: testCase.region, + }); await this.subject().stat(allocation, testCase.path); const req = pretender.handledRequests[0]; assert.equal(`${req.method} ${req.url}`, testCase.stat); }); - test(`stop makes the correct API call ${testCase.variation}`, async function(assert) { + test(`stop makes the correct API call ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - const allocation = await this.initialize(testCase.id, { region: testCase.region }); + const allocation = await this.initialize(testCase.id, { + region: testCase.region, + }); await this.subject().stop(allocation); const req = pretender.handledRequests[0]; assert.equal(`${req.method} ${req.url}`, testCase.stop); }); - test(`restart makes the correct API call ${testCase.variation}`, async function(assert) { + test(`restart makes the correct API call ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - const allocation = await this.initialize(testCase.id, { region: testCase.region }); + const allocation = await this.initialize(testCase.id, { + region: testCase.region, + }); await this.subject().restart(allocation); const req = pretender.handledRequests[0]; assert.equal(`${req.method} ${req.url}`, testCase.restart); }); - test(`restart with optional task name makes the correct API call ${testCase.variation}`, async function(assert) { + test(`restart with optional task name makes the correct API call ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - const allocation = await this.initialize(testCase.id, { region: testCase.region }); + const allocation = await this.initialize(testCase.id, { + region: testCase.region, + }); await this.subject().restart(allocation, testCase.task); const req = pretender.handledRequests[0]; assert.equal(`${req.method} ${req.url}`, testCase.restart); - assert.deepEqual(JSON.parse(req.requestBody), { TaskName: testCase.task }); + assert.deepEqual(JSON.parse(req.requestBody), { + TaskName: testCase.task, + }); }); }); }); diff --git a/ui/tests/unit/adapters/deployment-test.js b/ui/tests/unit/adapters/deployment-test.js index a26a48f91..499a03f62 100644 --- a/ui/tests/unit/adapters/deployment-test.js +++ b/ui/tests/unit/adapters/deployment-test.js @@ -2,10 +2,10 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; -module('Unit | Adapter | Deployment', function(hooks) { +module('Unit | Adapter | Deployment', function (hooks) { setupTest(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { this.store = this.owner.lookup('service:store'); this.system = this.owner.lookup('service:system'); this.subject = () => this.store.adapterFor('deployment'); @@ -22,19 +22,24 @@ module('Unit | Adapter | Deployment', function(hooks) { this.server.create('node'); const job = this.server.create('job', { createAllocations: false }); - const deploymentRecord = server.schema.deployments.where({ jobId: job.id }).models[0]; + const deploymentRecord = server.schema.deployments.where({ + jobId: job.id, + }).models[0]; this.system.get('shouldIncludeRegion'); await this.system.get('defaultRegion'); - const deployment = await this.store.findRecord('deployment', deploymentRecord.id); + const deployment = await this.store.findRecord( + 'deployment', + deploymentRecord.id + ); this.server.pretender.handledRequests.length = 0; return deployment; }; }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); @@ -42,38 +47,44 @@ module('Unit | Adapter | Deployment', function(hooks) { { variation: '', region: null, - fail: id => `POST /v1/deployment/fail/${id}`, - promote: id => `POST /v1/deployment/promote/${id}`, + fail: (id) => `POST /v1/deployment/fail/${id}`, + promote: (id) => `POST /v1/deployment/promote/${id}`, }, { variation: 'with non-default region', region: 'region-2', - fail: id => `POST /v1/deployment/fail/${id}?region=region-2`, - promote: id => `POST /v1/deployment/promote/${id}?region=region-2`, + fail: (id) => `POST /v1/deployment/fail/${id}?region=region-2`, + promote: (id) => `POST /v1/deployment/promote/${id}?region=region-2`, }, ]; - testCases.forEach(testCase => { - test(`promote makes the correct API call ${testCase.variation}`, async function(assert) { + testCases.forEach((testCase) => { + test(`promote makes the correct API call ${testCase.variation}`, async function (assert) { const deployment = await this.initialize({ region: testCase.region }); await this.subject().promote(deployment); const request = this.server.pretender.handledRequests[0]; - assert.equal(`${request.method} ${request.url}`, testCase.promote(deployment.id)); + assert.equal( + `${request.method} ${request.url}`, + testCase.promote(deployment.id) + ); assert.deepEqual(JSON.parse(request.requestBody), { DeploymentId: deployment.id, All: true, }); }); - test(`fail makes the correct API call ${testCase.variation}`, async function(assert) { + test(`fail makes the correct API call ${testCase.variation}`, async function (assert) { const deployment = await this.initialize({ region: testCase.region }); await this.subject().fail(deployment); const request = this.server.pretender.handledRequests[0]; - assert.equal(`${request.method} ${request.url}`, testCase.fail(deployment.id)); + assert.equal( + `${request.method} ${request.url}`, + testCase.fail(deployment.id) + ); assert.deepEqual(JSON.parse(request.requestBody), { DeploymentId: deployment.id, }); diff --git a/ui/tests/unit/adapters/job-test.js b/ui/tests/unit/adapters/job-test.js index e6d283bb9..2b696ba19 100644 --- a/ui/tests/unit/adapters/job-test.js +++ b/ui/tests/unit/adapters/job-test.js @@ -8,10 +8,10 @@ import { AbortController } from 'fetch'; import { TextEncoderLite } from 'text-encoder-lite'; import base64js from 'base64-js'; -module('Unit | Adapter | Job', function(hooks) { +module('Unit | Adapter | Job', function (hooks) { setupTest(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.adapterFor('job'); @@ -59,11 +59,11 @@ module('Unit | Adapter | Job', function(hooks) { }; }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); - test('The job endpoint is the only required endpoint for fetching a job', async function(assert) { + test('The job endpoint is the only required endpoint for fetching a job', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -81,7 +81,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('When a namespace is set in localStorage but a job in the default namespace is requested, the namespace query param is not present', async function(assert) { + test('When a namespace is set in localStorage but a job in the default namespace is requested, the namespace query param is not present', async function (assert) { await this.initializeUI({ namespace: 'some-namespace' }); const { pretender } = this.server; @@ -99,7 +99,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('When a namespace is in localStorage and the requested job is in the default namespace, the namespace query param is left out', async function(assert) { + test('When a namespace is in localStorage and the requested job is in the default namespace, the namespace query param is left out', async function (assert) { await this.initializeUI({ namespace: 'red-herring' }); const { pretender } = this.server; @@ -117,7 +117,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('When the job has a namespace other than default, it is in the URL', async function(assert) { + test('When the job has a namespace other than default, it is in the URL', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -135,7 +135,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('When there is no token set in the token service, no X-Nomad-Token header is set', async function(assert) { + test('When there is no token set in the token service, no X-Nomad-Token header is set', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -145,12 +145,14 @@ module('Unit | Adapter | Job', function(hooks) { await settled(); assert.notOk( - pretender.handledRequests.mapBy('requestHeaders').some(headers => headers['X-Nomad-Token']), + pretender.handledRequests + .mapBy('requestHeaders') + .some((headers) => headers['X-Nomad-Token']), 'No token header present on either job request' ); }); - test('When a token is set in the token service, then X-Nomad-Token header is set', async function(assert) { + test('When a token is set in the token service, then X-Nomad-Token header is set', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -164,12 +166,12 @@ module('Unit | Adapter | Job', function(hooks) { assert.ok( pretender.handledRequests .mapBy('requestHeaders') - .every(headers => headers['X-Nomad-Token'] === secret), + .every((headers) => headers['X-Nomad-Token'] === secret), 'The token header is present on both job requests' ); }); - test('findAll can be watched', async function(assert) { + test('findAll can be watched', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -198,7 +200,7 @@ module('Unit | Adapter | Job', function(hooks) { await settled(); }); - test('findRecord can be watched', async function(assert) { + test('findRecord can be watched', async function (assert) { await this.initializeUI(); const jobId = JSON.stringify(['job-1', 'default']); @@ -228,7 +230,7 @@ module('Unit | Adapter | Job', function(hooks) { await settled(); }); - test('relationships can be reloaded', async function(assert) { + test('relationships can be reloaded', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -244,7 +246,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('relationship reloads can be watched', async function(assert) { + test('relationship reloads can be watched', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -269,7 +271,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('findAll can be canceled', async function(assert) { + test('findAll can be canceled', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -296,7 +298,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.ok(xhr.aborted, 'Request was aborted'); }); - test('findRecord can be canceled', async function(assert) { + test('findRecord can be canceled', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -322,7 +324,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.ok(xhr.aborted, 'Request was aborted'); }); - test('relationship reloads can be canceled', async function(assert) { + test('relationship reloads can be canceled', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -348,7 +350,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.ok(xhr.aborted, 'Request was aborted'); }); - test('requests can be canceled even if multiple requests for the same URL were made', async function(assert) { + test('requests can be canceled even if multiple requests for the same URL were made', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -371,7 +373,11 @@ module('Unit | Adapter | Job', function(hooks) { const { request: xhr } = pretender.requestReferences[0]; const { request: xhr2 } = pretender.requestReferences[1]; assert.equal(xhr.status, 0, 'Request is still pending'); - assert.equal(pretender.requestReferences.length, 2, 'Two findRecord requests were made'); + assert.equal( + pretender.requestReferences.length, + 2, + 'Two findRecord requests were made' + ); assert.equal( pretender.requestReferences.mapBy('url').uniq().length, 1, @@ -389,7 +395,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.notOk(xhr2.aborted, 'Request two was not aborted'); }); - test('dispatch job encodes payload as base64', async function(assert) { + test('dispatch job encodes payload as base64', async function (assert) { const job = await this.initializeWithJob(); job.set('parameterized', true); @@ -410,7 +416,7 @@ module('Unit | Adapter | Job', function(hooks) { }); }); - test('when there is no region set, requests are made without the region query param', async function(assert) { + test('when there is no region set, requests are made without the region query param', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -430,7 +436,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('when there is a region set, requests are made with the region query param', async function(assert) { + test('when there is a region set, requests are made with the region query param', async function (assert) { const region = 'region-2'; await this.initializeUI({ region }); @@ -452,7 +458,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('when the region is set to the default region, requests are made without the region query param', async function(assert) { + test('when the region is set to the default region, requests are made without the region query param', async function (assert) { await this.initializeUI({ region: 'region-1' }); const { pretender } = this.server; @@ -472,7 +478,7 @@ module('Unit | Adapter | Job', function(hooks) { ); }); - test('fetchRawDefinition requests include the activeRegion', async function(assert) { + test('fetchRawDefinition requests include the activeRegion', async function (assert) { const region = 'region-2'; const job = await this.initializeWithJob({ region }); @@ -483,7 +489,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.equal(request.method, 'GET'); }); - test('forcePeriodic requests include the activeRegion', async function(assert) { + test('forcePeriodic requests include the activeRegion', async function (assert) { const region = 'region-2'; const job = await this.initializeWithJob({ region }); job.set('periodic', true); @@ -491,11 +497,14 @@ module('Unit | Adapter | Job', function(hooks) { await this.subject().forcePeriodic(job); const request = this.server.pretender.handledRequests[0]; - assert.equal(request.url, `/v1/job/${job.plainId}/periodic/force?region=${region}`); + assert.equal( + request.url, + `/v1/job/${job.plainId}/periodic/force?region=${region}` + ); assert.equal(request.method, 'POST'); }); - test('stop requests include the activeRegion', async function(assert) { + test('stop requests include the activeRegion', async function (assert) { const region = 'region-2'; const job = await this.initializeWithJob({ region }); @@ -506,7 +515,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.equal(request.method, 'DELETE'); }); - test('parse requests include the activeRegion', async function(assert) { + test('parse requests include the activeRegion', async function (assert) { const region = 'region-2'; await this.initializeUI({ region }); @@ -521,7 +530,7 @@ module('Unit | Adapter | Job', function(hooks) { }); }); - test('plan requests include the activeRegion', async function(assert) { + test('plan requests include the activeRegion', async function (assert) { const region = 'region-2'; const job = await this.initializeWithJob({ region }); job.set('_newDefinitionJSON', {}); @@ -533,7 +542,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.equal(request.method, 'POST'); }); - test('run requests include the activeRegion', async function(assert) { + test('run requests include the activeRegion', async function (assert) { const region = 'region-2'; const job = await this.initializeWithJob({ region }); job.set('_newDefinitionJSON', {}); @@ -545,7 +554,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.equal(request.method, 'POST'); }); - test('update requests include the activeRegion', async function(assert) { + test('update requests include the activeRegion', async function (assert) { const region = 'region-2'; const job = await this.initializeWithJob({ region }); job.set('_newDefinitionJSON', {}); @@ -557,7 +566,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.equal(request.method, 'POST'); }); - test('scale requests include the activeRegion', async function(assert) { + test('scale requests include the activeRegion', async function (assert) { const region = 'region-2'; const job = await this.initializeWithJob({ region }); @@ -568,7 +577,7 @@ module('Unit | Adapter | Job', function(hooks) { assert.equal(request.method, 'POST'); }); - test('dispatch requests include the activeRegion', async function(assert) { + test('dispatch requests include the activeRegion', async function (assert) { const region = 'region-2'; const job = await this.initializeWithJob({ region }); job.set('parameterized', true); @@ -576,7 +585,10 @@ module('Unit | Adapter | Job', function(hooks) { await this.subject().dispatch(job, {}, ''); const request = this.server.pretender.handledRequests[0]; - assert.equal(request.url, `/v1/job/${job.plainId}/dispatch?region=${region}`); + assert.equal( + request.url, + `/v1/job/${job.plainId}/dispatch?region=${region}` + ); assert.equal(request.method, 'POST'); }); }); diff --git a/ui/tests/unit/adapters/node-test.js b/ui/tests/unit/adapters/node-test.js index b92c1c0c1..c96ac3147 100644 --- a/ui/tests/unit/adapters/node-test.js +++ b/ui/tests/unit/adapters/node-test.js @@ -4,10 +4,10 @@ import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { setupTest } from 'ember-qunit'; import { settled } from '@ember/test-helpers'; -module('Unit | Adapter | Node', function(hooks) { +module('Unit | Adapter | Node', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.adapterFor('node'); @@ -28,11 +28,11 @@ module('Unit | Adapter | Node', function(hooks) { this.server.create('allocation', { id: 'node-2-2', nodeId: 'node-2' }); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); - test('findHasMany removes old related models from the store', async function(assert) { + test('findHasMany removes old related models from the store', async function (assert) { // Fetch the model and related allocations let node = await run(() => this.store.findRecord('node', 'node-1')); let allocations = await run(() => findHasMany(node, 'allocations')); @@ -46,7 +46,9 @@ module('Unit | Adapter | Node', function(hooks) { server.db.allocations.remove('node-1-1'); allocations = await run(() => findHasMany(node, 'allocations')); - const dbAllocations = this.server.db.allocations.where({ nodeId: node.get('id') }); + const dbAllocations = this.server.db.allocations.where({ + nodeId: node.get('id'), + }); assert.equal( allocations.get('length'), dbAllocations.length, @@ -59,7 +61,7 @@ module('Unit | Adapter | Node', function(hooks) { ); }); - test('findHasMany does not remove old unrelated models from the store', async function(assert) { + test('findHasMany does not remove old unrelated models from the store', async function (assert) { // Fetch the first node and related allocations const node = await run(() => this.store.findRecord('node', 'node-1')); await run(() => findHasMany(node, 'allocations')); @@ -70,10 +72,7 @@ module('Unit | Adapter | Node', function(hooks) { await settled(); assert.deepEqual( - this.store - .peekAll('allocation') - .mapBy('id') - .sort(), + this.store.peekAll('allocation').mapBy('id').sort(), ['node-1-1', 'node-1-2', 'node-2-1', 'node-2-2'], 'All allocations for the first and second node are in the store' ); @@ -83,10 +82,7 @@ module('Unit | Adapter | Node', function(hooks) { // Reload the related allocations now that one was removed server-side await run(() => findHasMany(node, 'allocations')); assert.deepEqual( - this.store - .peekAll('allocation') - .mapBy('id') - .sort(), + this.store.peekAll('allocation').mapBy('id').sort(), ['node-1-2', 'node-2-1', 'node-2-2'], 'The deleted allocation is removed from the store and the allocations associated with the other node are untouched' ); @@ -109,10 +105,11 @@ module('Unit | Adapter | Node', function(hooks) { }, ]; - testCases.forEach(testCase => { - test(`setEligible makes the correct POST request to /:node_id/eligibility ${testCase.variation}`, async function(assert) { + testCases.forEach((testCase) => { + test(`setEligible makes the correct POST request to /:node_id/eligibility ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - if (testCase.region) window.localStorage.nomadActiveRegion = testCase.region; + if (testCase.region) + window.localStorage.nomadActiveRegion = testCase.region; const node = await run(() => this.store.findRecord('node', testCase.id)); await this.subject().setEligible(node); @@ -125,9 +122,10 @@ module('Unit | Adapter | Node', function(hooks) { }); }); - test(`setIneligible makes the correct POST request to /:node_id/eligibility ${testCase.variation}`, async function(assert) { + test(`setIneligible makes the correct POST request to /:node_id/eligibility ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - if (testCase.region) window.localStorage.nomadActiveRegion = testCase.region; + if (testCase.region) + window.localStorage.nomadActiveRegion = testCase.region; const node = await run(() => this.store.findRecord('node', testCase.id)); await this.subject().setIneligible(node); @@ -140,9 +138,10 @@ module('Unit | Adapter | Node', function(hooks) { }); }); - test(`drain makes the correct POST request to /:node_id/drain with appropriate defaults ${testCase.variation}`, async function(assert) { + test(`drain makes the correct POST request to /:node_id/drain with appropriate defaults ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - if (testCase.region) window.localStorage.nomadActiveRegion = testCase.region; + if (testCase.region) + window.localStorage.nomadActiveRegion = testCase.region; const node = await run(() => this.store.findRecord('node', testCase.id)); await this.subject().drain(node); @@ -158,9 +157,10 @@ module('Unit | Adapter | Node', function(hooks) { }); }); - test(`drain makes the correct POST request to /:node_id/drain with the provided drain spec ${testCase.variation}`, async function(assert) { + test(`drain makes the correct POST request to /:node_id/drain with the provided drain spec ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - if (testCase.region) window.localStorage.nomadActiveRegion = testCase.region; + if (testCase.region) + window.localStorage.nomadActiveRegion = testCase.region; const node = await run(() => this.store.findRecord('node', testCase.id)); @@ -178,9 +178,10 @@ module('Unit | Adapter | Node', function(hooks) { }); }); - test(`forceDrain makes the correct POST request to /:node_id/drain with appropriate defaults ${testCase.variation}`, async function(assert) { + test(`forceDrain makes the correct POST request to /:node_id/drain with appropriate defaults ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - if (testCase.region) window.localStorage.nomadActiveRegion = testCase.region; + if (testCase.region) + window.localStorage.nomadActiveRegion = testCase.region; const node = await run(() => this.store.findRecord('node', testCase.id)); @@ -197,9 +198,10 @@ module('Unit | Adapter | Node', function(hooks) { }); }); - test(`forceDrain makes the correct POST request to /:node_id/drain with the provided drain spec ${testCase.variation}`, async function(assert) { + test(`forceDrain makes the correct POST request to /:node_id/drain with the provided drain spec ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - if (testCase.region) window.localStorage.nomadActiveRegion = testCase.region; + if (testCase.region) + window.localStorage.nomadActiveRegion = testCase.region; const node = await run(() => this.store.findRecord('node', testCase.id)); @@ -217,9 +219,10 @@ module('Unit | Adapter | Node', function(hooks) { }); }); - test(`cancelDrain makes the correct POST request to /:node_id/drain ${testCase.variation}`, async function(assert) { + test(`cancelDrain makes the correct POST request to /:node_id/drain ${testCase.variation}`, async function (assert) { const { pretender } = this.server; - if (testCase.region) window.localStorage.nomadActiveRegion = testCase.region; + if (testCase.region) + window.localStorage.nomadActiveRegion = testCase.region; const node = await run(() => this.store.findRecord('node', testCase.id)); diff --git a/ui/tests/unit/adapters/volume-test.js b/ui/tests/unit/adapters/volume-test.js index 683716a4e..8eeace873 100644 --- a/ui/tests/unit/adapters/volume-test.js +++ b/ui/tests/unit/adapters/volume-test.js @@ -5,10 +5,10 @@ import { module, test } from 'qunit'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { AbortController } from 'fetch'; -module('Unit | Adapter | Volume', function(hooks) { +module('Unit | Adapter | Volume', function (hooks) { setupTest(hooks); - hooks.beforeEach(async function() { + hooks.beforeEach(async function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.adapterFor('volume'); @@ -23,7 +23,10 @@ module('Unit | Adapter | Volume', function(hooks) { this.server.create('node'); this.server.create('job', { id: 'job-1', namespaceId: 'default' }); this.server.create('csi-plugin', 2); - this.server.create('csi-volume', { id: 'volume-1', namespaceId: 'some-namespace' }); + this.server.create('csi-volume', { + id: 'volume-1', + namespaceId: 'some-namespace', + }); this.server.create('region', { id: 'region-1' }); this.server.create('region', { id: 'region-2' }); @@ -43,22 +46,30 @@ module('Unit | Adapter | Volume', function(hooks) { }; }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); - test('The volume endpoint can be queried by type', async function(assert) { + test('The volume endpoint can be queried by type', async function (assert) { const { pretender } = this.server; await this.initializeUI(); - this.subject().query(this.store, { modelName: 'volume' }, { type: 'csi' }, null, {}); + this.subject().query( + this.store, + { modelName: 'volume' }, + { type: 'csi' }, + null, + {} + ); await settled(); - assert.deepEqual(pretender.handledRequests.mapBy('url'), ['/v1/volumes?type=csi']); + assert.deepEqual(pretender.handledRequests.mapBy('url'), [ + '/v1/volumes?type=csi', + ]); }); - test('When the volume has a namespace other than default, it is in the URL', async function(assert) { + test('When the volume has a namespace other than default, it is in the URL', async function (assert) { const { pretender } = this.server; const volumeName = 'csi/volume-1'; const volumeNamespace = 'some-namespace'; @@ -70,32 +81,46 @@ module('Unit | Adapter | Volume', function(hooks) { await settled(); assert.deepEqual(pretender.handledRequests.mapBy('url'), [ - `/v1/volume/${encodeURIComponent(volumeName)}?namespace=${volumeNamespace}`, + `/v1/volume/${encodeURIComponent( + volumeName + )}?namespace=${volumeNamespace}`, ]); }); - test('query can be watched', async function(assert) { + test('query can be watched', async function (assert) { await this.initializeUI(); const { pretender } = this.server; const request = () => - this.subject().query(this.store, { modelName: 'volume' }, { type: 'csi' }, null, { - reload: true, - adapterOptions: { watch: true }, - }); + this.subject().query( + this.store, + { modelName: 'volume' }, + { type: 'csi' }, + null, + { + reload: true, + adapterOptions: { watch: true }, + } + ); request(); - assert.equal(pretender.handledRequests[0].url, '/v1/volumes?type=csi&index=1'); + assert.equal( + pretender.handledRequests[0].url, + '/v1/volumes?type=csi&index=1' + ); await settled(); request(); - assert.equal(pretender.handledRequests[1].url, '/v1/volumes?type=csi&index=2'); + assert.equal( + pretender.handledRequests[1].url, + '/v1/volumes?type=csi&index=2' + ); await settled(); }); - test('query can be canceled', async function(assert) { + test('query can be canceled', async function (assert) { await this.initializeUI(); const { pretender } = this.server; @@ -122,16 +147,22 @@ module('Unit | Adapter | Volume', function(hooks) { assert.ok(xhr.aborted, 'Request was aborted'); }); - test('query and findAll have distinct watchList entries', async function(assert) { + test('query and findAll have distinct watchList entries', async function (assert) { await this.initializeUI(); const { pretender } = this.server; const request = () => - this.subject().query(this.store, { modelName: 'volume' }, { type: 'csi' }, null, { - reload: true, - adapterOptions: { watch: true }, - }); + this.subject().query( + this.store, + { modelName: 'volume' }, + { type: 'csi' }, + null, + { + reload: true, + adapterOptions: { watch: true }, + } + ); const findAllRequest = () => this.subject().findAll(null, { modelName: 'volume' }, null, { @@ -140,11 +171,17 @@ module('Unit | Adapter | Volume', function(hooks) { }); request(); - assert.equal(pretender.handledRequests[0].url, '/v1/volumes?type=csi&index=1'); + assert.equal( + pretender.handledRequests[0].url, + '/v1/volumes?type=csi&index=1' + ); await settled(); request(); - assert.equal(pretender.handledRequests[1].url, '/v1/volumes?type=csi&index=2'); + assert.equal( + pretender.handledRequests[1].url, + '/v1/volumes?type=csi&index=2' + ); await settled(); findAllRequest(); diff --git a/ui/tests/unit/components/gauge-chart-test.js b/ui/tests/unit/components/gauge-chart-test.js index 771da46a1..4ec1d282f 100644 --- a/ui/tests/unit/components/gauge-chart-test.js +++ b/ui/tests/unit/components/gauge-chart-test.js @@ -1,14 +1,14 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Component | gauge-chart', function(hooks) { +module('Unit | Component | gauge-chart', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.subject = this.owner.factoryFor('component:gauge-chart'); }); - test('percent is a function of value and total OR complement', function(assert) { + test('percent is a function of value and total OR complement', function (assert) { const chart = this.subject.create(); chart.setProperties({ value: 5, diff --git a/ui/tests/unit/components/line-chart-test.js b/ui/tests/unit/components/line-chart-test.js index e39d8230e..dcae81999 100644 --- a/ui/tests/unit/components/line-chart-test.js +++ b/ui/tests/unit/components/line-chart-test.js @@ -3,7 +3,7 @@ import { setupTest } from 'ember-qunit'; import d3Format from 'd3-format'; import setupGlimmerComponentFactory from 'nomad-ui/tests/helpers/glimmer-factory'; -module('Unit | Component | line-chart', function(hooks) { +module('Unit | Component | line-chart', function (hooks) { setupTest(hooks); setupGlimmerComponentFactory(hooks, 'line-chart'); @@ -15,7 +15,7 @@ module('Unit | Component | line-chart', function(hooks) { { foo: 4, bar: 500 }, ]; - test('x scale domain is the min and max values in data based on the xProp value', function(assert) { + test('x scale domain is the min and max values in data based on the xProp value', function (assert) { const chart = this.createComponent({ xProp: 'foo', data, @@ -36,10 +36,14 @@ module('Unit | Component | line-chart', function(hooks) { chart.args.data = [...data, { foo: 12, bar: 600 }]; [, xDomainHigh] = chart.xScale.domain(); - assert.equal(xDomainHigh, 12, 'When the data changes, the xScale is recalculated'); + assert.equal( + xDomainHigh, + 12, + 'When the data changes, the xScale is recalculated' + ); }); - test('y scale domain uses the max value in the data based off of yProp, but is always zero-based', function(assert) { + test('y scale domain uses the max value in the data based off of yProp, but is always zero-based', function (assert) { const chart = this.createComponent({ yProp: 'bar', data, @@ -56,10 +60,14 @@ module('Unit | Component | line-chart', function(hooks) { chart.args.data = [...data, { foo: 12, bar: 600 }]; [, yDomainHigh] = chart.yScale.domain(); - assert.equal(yDomainHigh, 600, 'When the data changes, the yScale is recalculated'); + assert.equal( + yDomainHigh, + 600, + 'When the data changes, the yScale is recalculated' + ); }); - test('the number of yTicks is always odd (to always have a mid-line) and is based off the chart height', function(assert) { + test('the number of yTicks is always odd (to always have a mid-line) and is based off the chart height', function (assert) { const chart = this.createComponent({ yProp: 'bar', data, @@ -75,7 +83,7 @@ module('Unit | Component | line-chart', function(hooks) { assert.equal(chart.yTicks.length, 7); }); - test('the values for yTicks are rounded to whole numbers', function(assert) { + test('the values for yTicks are rounded to whole numbers', function (assert) { const chart = this.createComponent({ yProp: 'bar', data, @@ -91,7 +99,7 @@ module('Unit | Component | line-chart', function(hooks) { assert.deepEqual(chart.yTicks, [0, 83, 167, 250, 333, 417, 500]); }); - test('the values for yTicks are fractions when the domain is between 0 and 1', function(assert) { + test('the values for yTicks are fractions when the domain is between 0 and 1', function (assert) { const chart = this.createComponent({ yProp: 'bar', data: [ @@ -107,7 +115,7 @@ module('Unit | Component | line-chart', function(hooks) { assert.deepEqual(chart.yTicks, [0, 0.25, 0.5]); }); - test('activeDatumLabel is the xProp value of the activeDatum formatted with xFormat', function(assert) { + test('activeDatumLabel is the xProp value of the activeDatum formatted with xFormat', function (assert) { const chart = this.createComponent({ xProp: 'foo', yProp: 'bar', @@ -123,7 +131,7 @@ module('Unit | Component | line-chart', function(hooks) { ); }); - test('activeDatumValue is the yProp value of the activeDatum formatted with yFormat', function(assert) { + test('activeDatumValue is the yProp value of the activeDatum formatted with yFormat', function (assert) { const chart = this.createComponent({ xProp: 'foo', yProp: 'bar', diff --git a/ui/tests/unit/components/scale-events-chart-test.js b/ui/tests/unit/components/scale-events-chart-test.js index e0b55c952..243f7aa18 100644 --- a/ui/tests/unit/components/scale-events-chart-test.js +++ b/ui/tests/unit/components/scale-events-chart-test.js @@ -3,21 +3,21 @@ import { setupTest } from 'ember-qunit'; import sinon from 'sinon'; import setupGlimmerComponentFactory from 'nomad-ui/tests/helpers/glimmer-factory'; -module('Unit | Component | scale-events-chart', function(hooks) { +module('Unit | Component | scale-events-chart', function (hooks) { setupTest(hooks); setupGlimmerComponentFactory(hooks, 'scale-events-chart'); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.refTime = new Date(); this.clock = sinon.useFakeTimers(this.refTime); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.clock.restore(); delete this.refTime; }); - test('the current date is appended as a datum for the line chart to render', function(assert) { + test('the current date is appended as a datum for the line chart to render', function (assert) { const events = [ { time: new Date('2020-08-02T04:06:00'), count: 2, hasCount: true }, { time: new Date('2020-08-01T04:06:00'), count: 2, hasCount: true }, @@ -33,7 +33,7 @@ module('Unit | Component | scale-events-chart', function(hooks) { assert.equal(+appendedDatum.time, +this.refTime); }); - test('if the earliest annotation is outside the domain of the events, the earliest annotation time is added as a datum for the line chart to render', function(assert) { + test('if the earliest annotation is outside the domain of the events, the earliest annotation time is added as a datum for the line chart to render', function (assert) { const annotationOutside = [ { time: new Date('2020-08-01T04:06:00'), hasCount: false, error: true }, { time: new Date('2020-08-02T04:06:00'), count: 2, hasCount: true }, diff --git a/ui/tests/unit/components/stats-time-series-test.js b/ui/tests/unit/components/stats-time-series-test.js index 695e327b4..0a308a865 100644 --- a/ui/tests/unit/components/stats-time-series-test.js +++ b/ui/tests/unit/components/stats-time-series-test.js @@ -5,14 +5,12 @@ import d3Format from 'd3-format'; import d3TimeFormat from 'd3-time-format'; import setupGlimmerComponentFactory from 'nomad-ui/tests/helpers/glimmer-factory'; -module('Unit | Component | stats-time-series', function(hooks) { +module('Unit | Component | stats-time-series', function (hooks) { setupTest(hooks); setupGlimmerComponentFactory(hooks, 'stats-time-series'); const ts = (offset, resolution = 'm') => - moment() - .subtract(offset, resolution) - .toDate(); + moment().subtract(offset, resolution).toDate(); const wideData = [ { timestamp: ts(20), percent: 0.5 }, @@ -47,10 +45,12 @@ module('Unit | Component | stats-time-series', function(hooks) { { timestamp: ts(18, 's'), percent: null }, ]; - test('xFormat is time-formatted for hours, minutes, and seconds', function(assert) { + test('xFormat is time-formatted for hours, minutes, and seconds', function (assert) { + assert.expect(11); + const chart = this.createComponent({ data: wideData }); - wideData.forEach(datum => { + wideData.forEach((datum) => { assert.equal( chart.xFormat(datum.timestamp), d3TimeFormat.timeFormat('%H:%M:%S')(datum.timestamp) @@ -58,15 +58,20 @@ module('Unit | Component | stats-time-series', function(hooks) { }); }); - test('yFormat is percent-formatted', function(assert) { + test('yFormat is percent-formatted', function (assert) { + assert.expect(11); + const chart = this.createComponent({ data: wideData }); - wideData.forEach(datum => { - assert.equal(chart.yFormat(datum.percent), d3Format.format('.1~%')(datum.percent)); + wideData.forEach((datum) => { + assert.equal( + chart.yFormat(datum.percent), + d3Format.format('.1~%')(datum.percent) + ); }); }); - test('x scale domain is at least five minutes', function(assert) { + test('x scale domain is at least five minutes', function (assert) { const chart = this.createComponent({ data: narrowData }); assert.equal( @@ -78,7 +83,7 @@ module('Unit | Component | stats-time-series', function(hooks) { ); }); - test('x scale domain is greater than five minutes when the domain of the data is larger than five minutes', function(assert) { + test('x scale domain is greater than five minutes when the domain of the data is larger than five minutes', function (assert) { const chart = this.createComponent({ data: wideData }); assert.equal( @@ -88,11 +93,14 @@ module('Unit | Component | stats-time-series', function(hooks) { ); }); - test('y scale domain is typically 0 to 1 (0 to 100%)', function(assert) { + test('y scale domain is typically 0 to 1 (0 to 100%)', function (assert) { const chart = this.createComponent({ data: wideData }); assert.deepEqual( - [Math.min(...wideData.mapBy('percent')), Math.max(...wideData.mapBy('percent'))], + [ + Math.min(...wideData.mapBy('percent')), + Math.max(...wideData.mapBy('percent')), + ], [0.3, 0.9], 'The bounds of the value prop of the dataset is narrower than 0 - 1' ); @@ -104,7 +112,7 @@ module('Unit | Component | stats-time-series', function(hooks) { ); }); - test('the extent of the y domain overrides the default 0 to 1 domain when there are values beyond these bounds', function(assert) { + test('the extent of the y domain overrides the default 0 to 1 domain when there are values beyond these bounds', function (assert) { const chart = this.createComponent({ data: unboundedData }); assert.deepEqual( @@ -130,9 +138,13 @@ module('Unit | Component | stats-time-series', function(hooks) { ); }); - test('when there are only empty frames in the data array, the default y domain is used', function(assert) { + test('when there are only empty frames in the data array, the default y domain is used', function (assert) { const chart = this.createComponent({ data: nullData }); - assert.deepEqual(chart.yScale(nullData, 0).domain(), [0, 1], 'The bounds are 0 and 1'); + assert.deepEqual( + chart.yScale(nullData, 0).domain(), + [0, 1], + 'The bounds are 0 and 1' + ); }); }); diff --git a/ui/tests/unit/components/tooltip-test.js b/ui/tests/unit/components/tooltip-test.js index e66f5a4e0..5bdc14892 100644 --- a/ui/tests/unit/components/tooltip-test.js +++ b/ui/tests/unit/components/tooltip-test.js @@ -2,12 +2,14 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import setupGlimmerComponentFactory from 'nomad-ui/tests/helpers/glimmer-factory'; -module('Unit | Component | tooltip', function(hooks) { +module('Unit | Component | tooltip', function (hooks) { setupTest(hooks); setupGlimmerComponentFactory(hooks, 'tooltip'); - test('long texts are ellipsised in the middle', function(assert) { - const tooltip = this.createComponent({ text: 'reeeeeeeeeeeeeeeeeally long text' }); + test('long texts are ellipsised in the middle', function (assert) { + const tooltip = this.createComponent({ + text: 'reeeeeeeeeeeeeeeeeally long text', + }); assert.equal(tooltip.text, 'reeeeeeeeeeeeee...long text'); }); }); diff --git a/ui/tests/unit/components/topo-viz-test.js b/ui/tests/unit/components/topo-viz-test.js index 3568170ef..b6a2b82e6 100644 --- a/ui/tests/unit/components/topo-viz-test.js +++ b/ui/tests/unit/components/topo-viz-test.js @@ -2,11 +2,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import setupGlimmerComponentFactory from 'nomad-ui/tests/helpers/glimmer-factory'; -module('Unit | Component | TopoViz', function(hooks) { +module('Unit | Component | TopoViz', function (hooks) { setupTest(hooks); setupGlimmerComponentFactory(hooks, 'topo-viz'); - test('the topology object properly organizes a tree of datacenters > nodes > allocations', async function(assert) { + test('the topology object properly organizes a tree of datacenters > nodes > allocations', async function (assert) { const nodes = [ { datacenter: 'dc1', id: 'node0', resources: {} }, { datacenter: 'dc2', id: 'node1', resources: {} }, @@ -32,9 +32,17 @@ module('Unit | Component | TopoViz', function(hooks) { topoViz.buildTopology(); - assert.deepEqual(topoViz.topology.datacenters.mapBy('name'), ['dc1', 'dc2']); - assert.deepEqual(topoViz.topology.datacenters[0].nodes.mapBy('node'), [nodes[0], nodes[2]]); - assert.deepEqual(topoViz.topology.datacenters[1].nodes.mapBy('node'), [nodes[1]]); + assert.deepEqual(topoViz.topology.datacenters.mapBy('name'), [ + 'dc1', + 'dc2', + ]); + assert.deepEqual(topoViz.topology.datacenters[0].nodes.mapBy('node'), [ + nodes[0], + nodes[2], + ]); + assert.deepEqual(topoViz.topology.datacenters[1].nodes.mapBy('node'), [ + nodes[1], + ]); assert.deepEqual( topoViz.topology.datacenters[0].nodes[0].allocations.mapBy('allocation'), node0Allocs @@ -49,7 +57,9 @@ module('Unit | Component | TopoViz', function(hooks) { ); }); - test('the topology object contains an allocation index keyed by jobId+taskGroupName', async function(assert) { + test('the topology object contains an allocation index keyed by jobId+taskGroupName', async function (assert) { + assert.expect(7); + const allocations = [ alloc({ nodeId: 'node0', jobId: 'job0', taskGroupName: 'one' }), alloc({ nodeId: 'node0', jobId: 'job0', taskGroupName: 'one' }), @@ -82,16 +92,18 @@ module('Unit | Component | TopoViz', function(hooks) { ].sort() ); - Object.keys(topoViz.topology.allocationIndex).forEach(key => { + Object.keys(topoViz.topology.allocationIndex).forEach((key) => { const [jobId, group] = JSON.parse(key); assert.deepEqual( topoViz.topology.allocationIndex[key].mapBy('allocation'), - allocations.filter(alloc => alloc.jobId === jobId && alloc.taskGroupName === group) + allocations.filter( + (alloc) => alloc.jobId === jobId && alloc.taskGroupName === group + ) ); }); }); - test('isSingleColumn is true when there is only one datacenter', async function(assert) { + test('isSingleColumn is true when there is only one datacenter', async function (assert) { const oneDc = [{ datacenter: 'dc1', id: 'node0', resources: {} }]; const twoDc = [...oneDc, { datacenter: 'dc2', id: 'node1', resources: {} }]; @@ -105,7 +117,7 @@ module('Unit | Component | TopoViz', function(hooks) { assert.notOk(topoViz2.isSingleColumn); }); - test('isSingleColumn is true when there are multiple datacenters with a high variance in node count', async function(assert) { + test('isSingleColumn is true when there are multiple datacenters with a high variance in node count', async function (assert) { const uniformDcs = [ { datacenter: 'dc1', id: 'node0', resources: {} }, { datacenter: 'dc2', id: 'node1', resources: {} }, @@ -118,8 +130,14 @@ module('Unit | Component | TopoViz', function(hooks) { { datacenter: 'dc2', id: 'node4', resources: {} }, ]; - const twoColumnViz = this.createComponent({ nodes: uniformDcs, allocations: [] }); - const oneColumViz = this.createComponent({ nodes: skewedDcs, allocations: [] }); + const twoColumnViz = this.createComponent({ + nodes: uniformDcs, + allocations: [], + }); + const oneColumViz = this.createComponent({ + nodes: skewedDcs, + allocations: [], + }); twoColumnViz.buildTopology(); oneColumViz.buildTopology(); @@ -128,7 +146,7 @@ module('Unit | Component | TopoViz', function(hooks) { assert.ok(oneColumViz.isSingleColumn); }); - test('datacenterIsSingleColumn is only ever false when isSingleColumn is false and the total node count is high', async function(assert) { + test('datacenterIsSingleColumn is only ever false when isSingleColumn is false and the total node count is high', async function (assert) { const manyUniformNodes = Array(25) .fill(null) .map((_, index) => ({ @@ -144,8 +162,14 @@ module('Unit | Component | TopoViz', function(hooks) { resources: {}, })); - const oneColumnViz = this.createComponent({ nodes: manyUniformNodes, allocations: [] }); - const twoColumnViz = this.createComponent({ nodes: manySkewedNodes, allocations: [] }); + const oneColumnViz = this.createComponent({ + nodes: manyUniformNodes, + allocations: [], + }); + const twoColumnViz = this.createComponent({ + nodes: manySkewedNodes, + allocations: [], + }); oneColumnViz.buildTopology(); twoColumnViz.buildTopology(); @@ -157,8 +181,10 @@ module('Unit | Component | TopoViz', function(hooks) { assert.ok(twoColumnViz.isSingleColumn); }); - test('dataForAllocation correctly calculates proportion of node utilization and group key', async function(assert) { - const nodes = [{ datacenter: 'dc1', id: 'node0', resources: { cpu: 100, memory: 250 } }]; + test('dataForAllocation correctly calculates proportion of node utilization and group key', async function (assert) { + const nodes = [ + { datacenter: 'dc1', id: 'node0', resources: { cpu: 100, memory: 250 } }, + ]; const allocations = [ alloc({ nodeId: 'node0', @@ -171,11 +197,17 @@ module('Unit | Component | TopoViz', function(hooks) { const topoViz = this.createComponent({ nodes, allocations }); topoViz.buildTopology(); - assert.equal(topoViz.topology.datacenters[0].nodes[0].allocations[0].cpuPercent, 0.5); - assert.equal(topoViz.topology.datacenters[0].nodes[0].allocations[0].memoryPercent, 0.1); + assert.equal( + topoViz.topology.datacenters[0].nodes[0].allocations[0].cpuPercent, + 0.5 + ); + assert.equal( + topoViz.topology.datacenters[0].nodes[0].allocations[0].memoryPercent, + 0.1 + ); }); - test('allocations that reference nonexistent nodes are ignored', async function(assert) { + test('allocations that reference nonexistent nodes are ignored', async function (assert) { const nodes = [{ datacenter: 'dc1', id: 'node0', resources: {} }]; const allocations = [ @@ -187,10 +219,13 @@ module('Unit | Component | TopoViz', function(hooks) { topoViz.buildTopology(); - assert.deepEqual(topoViz.topology.datacenters[0].nodes.mapBy('node'), [nodes[0]]); - assert.deepEqual(topoViz.topology.datacenters[0].nodes[0].allocations.mapBy('allocation'), [ - allocations[0], + assert.deepEqual(topoViz.topology.datacenters[0].nodes.mapBy('node'), [ + nodes[0], ]); + assert.deepEqual( + topoViz.topology.datacenters[0].nodes[0].allocations.mapBy('allocation'), + [allocations[0]] + ); }); }); diff --git a/ui/tests/unit/mixins/searchable-test.js b/ui/tests/unit/mixins/searchable-test.js index 5dbc4eaf8..feeb1b66a 100644 --- a/ui/tests/unit/mixins/searchable-test.js +++ b/ui/tests/unit/mixins/searchable-test.js @@ -4,15 +4,15 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import Searchable from 'nomad-ui/mixins/searchable'; -module('Unit | Mixin | Searchable', function(hooks) { +module('Unit | Mixin | Searchable', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { - this.subject = function() { + hooks.beforeEach(function () { + this.subject = function () { // eslint-disable-next-line ember/no-new-mixins const SearchableObject = EmberObject.extend(Searchable, { source: null, - searchProps: computed(function() { + searchProps: computed(function () { return ['id', 'name']; }), listToSearch: alias('source'), @@ -23,14 +23,17 @@ module('Unit | Mixin | Searchable', function(hooks) { }; }); - test('the searchable mixin does nothing when there is no search term', function(assert) { + test('the searchable mixin does nothing when there is no search term', function (assert) { const subject = this.subject(); - subject.set('source', [{ id: '1', name: 'hello' }, { id: '2', name: 'world' }]); + subject.set('source', [ + { id: '1', name: 'hello' }, + { id: '2', name: 'world' }, + ]); assert.deepEqual(subject.get('listSearched'), subject.get('source')); }); - test('the searchable mixin allows for regex search', function(assert) { + test('the searchable mixin allows for regex search', function (assert) { const subject = this.subject(); subject.set('source', [ { id: '1', name: 'hello' }, @@ -41,12 +44,15 @@ module('Unit | Mixin | Searchable', function(hooks) { subject.set('searchTerm', '.+l+[A-Z]$'); assert.deepEqual( subject.get('listSearched'), - [{ id: '1', name: 'hello' }, { id: '2', name: 'world' }], + [ + { id: '1', name: 'hello' }, + { id: '2', name: 'world' }, + ], 'hello and world matched for regex' ); }); - test('the searchable mixin only searches the declared search props', function(assert) { + test('the searchable mixin only searches the declared search props', function (assert) { const subject = this.subject(); subject.set('source', [ { id: '1', name: 'United States of America', continent: 'North America' }, @@ -57,12 +63,18 @@ module('Unit | Mixin | Searchable', function(hooks) { subject.set('searchTerm', 'America'); assert.deepEqual( subject.get('listSearched'), - [{ id: '1', name: 'United States of America', continent: 'North America' }], + [ + { + id: '1', + name: 'United States of America', + continent: 'North America', + }, + ], 'Only USA matched, since continent is not a search prop' ); }); - test('the fuzzy search mode is off by default', function(assert) { + test('the fuzzy search mode is off by default', function (assert) { const subject = this.subject(); subject.set('source', [ { id: '1', name: 'United States of America', continent: 'North America' }, @@ -78,7 +90,7 @@ module('Unit | Mixin | Searchable', function(hooks) { ); }); - test('the fuzzy search mode can be enabled', function(assert) { + test('the fuzzy search mode can be enabled', function (assert) { const subject = this.subject(); subject.set('source', [ { id: '1', name: 'United States of America', continent: 'North America' }, @@ -90,17 +102,35 @@ module('Unit | Mixin | Searchable', function(hooks) { subject.set('searchTerm', 'Ameerica'); assert.deepEqual( subject.get('listSearched'), - [{ id: '1', name: 'United States of America', continent: 'North America' }], + [ + { + id: '1', + name: 'United States of America', + continent: 'North America', + }, + ], 'America is matched due to fuzzy matching' ); }); - test('the fuzzy search can include match results', function(assert) { + test('the fuzzy search can include match results', function (assert) { const subject = this.subject(); subject.set('source', [ - EmberObject.create({ id: '1', name: 'United States of America', continent: 'North America' }), - EmberObject.create({ id: '2', name: 'Canada', continent: 'North America' }), - EmberObject.create({ id: '3', name: 'Mexico', continent: 'North America' }), + EmberObject.create({ + id: '1', + name: 'United States of America', + continent: 'North America', + }), + EmberObject.create({ + id: '2', + name: 'Canada', + continent: 'North America', + }), + EmberObject.create({ + id: '3', + name: 'Mexico', + continent: 'North America', + }), ]); subject.set('fuzzySearchEnabled', true); @@ -109,7 +139,9 @@ module('Unit | Mixin | Searchable', function(hooks) { assert.deepEqual( subject .get('listSearched') - .map(object => object.getProperties('id', 'name', 'continent', 'fuzzySearchMatches')), + .map((object) => + object.getProperties('id', 'name', 'continent', 'fuzzySearchMatches') + ), [ { id: '1', @@ -117,7 +149,13 @@ module('Unit | Mixin | Searchable', function(hooks) { continent: 'North America', fuzzySearchMatches: [ { - indices: [[2, 2], [4, 4], [9, 9], [11, 11], [17, 23]], + indices: [ + [2, 2], + [4, 4], + [9, 9], + [11, 11], + [17, 23], + ], value: 'United States of America', key: 'name', }, @@ -128,7 +166,7 @@ module('Unit | Mixin | Searchable', function(hooks) { ); }); - test('the exact match search mode can be disabled', function(assert) { + test('the exact match search mode can be disabled', function (assert) { const subject = this.subject(); subject.set('source', [ { id: '1', name: 'United States of America', continent: 'North America' }, @@ -154,7 +192,7 @@ module('Unit | Mixin | Searchable', function(hooks) { ); }); - test('the regex search mode can be disabled', function(assert) { + test('the regex search mode can be disabled', function (assert) { const subject = this.subject(); subject.set('source', [ { id: '1', name: 'United States of America', continent: 'North America' }, @@ -181,7 +219,7 @@ module('Unit | Mixin | Searchable', function(hooks) { ); }); - test('each search mode has independent search props', function(assert) { + test('each search mode has independent search props', function (assert) { const subject = this.subject(); subject.set('source', [ { id: '1', name: 'United States of America', continent: 'North America' }, @@ -204,7 +242,13 @@ module('Unit | Mixin | Searchable', function(hooks) { subject.set('searchTerm', 'America States'); assert.deepEqual( subject.get('listSearched'), - [{ id: '1', name: 'United States of America', continent: 'North America' }], + [ + { + id: '1', + name: 'United States of America', + continent: 'North America', + }, + ], 'Fuzzy match on one country, but not an exact match on continent' ); @@ -216,38 +260,53 @@ module('Unit | Mixin | Searchable', function(hooks) { ); }); - test('the resetPagination method is a no-op', function(assert) { + test('the resetPagination method is a no-op', function (assert) { const subject = this.subject(); - assert.strictEqual(subject.get('currentPage'), undefined, 'No currentPage value set'); + assert.strictEqual( + subject.get('currentPage'), + undefined, + 'No currentPage value set' + ); subject.resetPagination(); - assert.strictEqual(subject.get('currentPage'), undefined, 'Still no currentPage value set'); + assert.strictEqual( + subject.get('currentPage'), + undefined, + 'Still no currentPage value set' + ); }); }); -module('Unit | Mixin | Searchable (with pagination)', function(hooks) { +module('Unit | Mixin | Searchable (with pagination)', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { - this.subject = function() { + hooks.beforeEach(function () { + this.subject = function () { // eslint-disable-next-line ember/no-new-mixins const SearchablePaginatedObject = EmberObject.extend(Searchable, { source: null, - searchProps: computed(function() { + searchProps: computed(function () { return ['id', 'name']; }), listToSearch: alias('source'), currentPage: 1, }); - this.owner.register('test-container:searchable-paginated-object', SearchablePaginatedObject); + this.owner.register( + 'test-container:searchable-paginated-object', + SearchablePaginatedObject + ); return this.owner.lookup('test-container:searchable-paginated-object'); }; }); - test('the resetPagination method sets the currentPage to 1', function(assert) { + test('the resetPagination method sets the currentPage to 1', function (assert) { const subject = this.subject(); subject.set('currentPage', 5); - assert.equal(subject.get('currentPage'), 5, 'Current page is something other than 1'); + assert.equal( + subject.get('currentPage'), + 5, + 'Current page is something other than 1' + ); subject.resetPagination(); assert.equal(subject.get('currentPage'), 1, 'Current page gets reset to 1'); }); diff --git a/ui/tests/unit/models/allocation-test.js b/ui/tests/unit/models/allocation-test.js index dce5ed10d..a83ea2ca7 100644 --- a/ui/tests/unit/models/allocation-test.js +++ b/ui/tests/unit/models/allocation-test.js @@ -2,13 +2,13 @@ import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Model | allocation', function(hooks) { +module('Unit | Model | allocation', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); }); - test("When the allocation's job version matches the job's version, the task group comes from the job.", function(assert) { + test("When the allocation's job version matches the job's version, the task group comes from the job.", function (assert) { const job = run(() => this.store.createRecord('job', { name: 'this-job', @@ -39,7 +39,7 @@ module('Unit | Model | allocation', function(hooks) { assert.equal(allocation.get('taskGroup.name'), 'from-job'); }); - test("When the allocation's job version does not match the job's version, the task group comes from the alloc.", function(assert) { + test("When the allocation's job version does not match the job's version, the task group comes from the alloc.", function (assert) { const job = run(() => this.store.createRecord('job', { name: 'this-job', @@ -70,7 +70,7 @@ module('Unit | Model | allocation', function(hooks) { assert.equal(allocation.get('taskGroup.name'), 'from-allocation'); }); - test("When the allocation's job version does not match the job's version and the allocation has no task group, then task group is null", async function(assert) { + test("When the allocation's job version does not match the job's version and the allocation has no task group, then task group is null", async function (assert) { const job = run(() => this.store.createRecord('job', { name: 'this-job', diff --git a/ui/tests/unit/models/job-test.js b/ui/tests/unit/models/job-test.js index d16f46f8f..bff37e302 100644 --- a/ui/tests/unit/models/job-test.js +++ b/ui/tests/unit/models/job-test.js @@ -2,10 +2,10 @@ import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -module('Unit | Model | job', function(hooks) { +module('Unit | Model | job', function (hooks) { setupTest(hooks); - test('should expose aggregate allocations derived from task groups', function(assert) { + test('should expose aggregate allocations derived from task groups', function (assert) { const store = this.owner.lookup('service:store'); let summary; run(() => { diff --git a/ui/tests/unit/models/task-group-test.js b/ui/tests/unit/models/task-group-test.js index 7ef246f2f..091bcd745 100644 --- a/ui/tests/unit/models/task-group-test.js +++ b/ui/tests/unit/models/task-group-test.js @@ -6,10 +6,10 @@ import { run } from '@ember/runloop'; const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0); -module('Unit | Model | task-group', function(hooks) { +module('Unit | Model | task-group', function (hooks) { setupTest(hooks); - test("should expose reserved resource stats as aggregates of each task's reserved resources", function(assert) { + test("should expose reserved resource stats as aggregates of each task's reserved resources", function (assert) { const taskGroup = run(() => this.owner.lookup('service:store').createRecord('task-group', { name: 'group-example', @@ -63,13 +63,17 @@ module('Unit | Model | task-group', function(hooks) { ); }); - test("should expose mergedMeta as merged with the job's meta", function(assert) { + test("should expose mergedMeta as merged with the job's meta", function (assert) { + assert.expect(8); + const store = this.owner.lookup('service:store'); const jobWithMeta = run(() => store.createRecord('job', { name: 'example-with-meta', - meta: store.createFragment('structured-attributes', { raw: { a: 'b' } }), + meta: store.createFragment('structured-attributes', { + raw: { a: 'b' }, + }), taskGroups: [ { name: 'one', @@ -93,10 +97,7 @@ module('Unit | Model | task-group', function(hooks) { let expected = [{ a: 'b', c: 'd' }, { a: 'b' }, { a: 'b' }, { a: 'b' }]; expected.forEach((exp, i) => { assert.deepEqual( - jobWithMeta - .get('taskGroups') - .objectAt(i) - .get('mergedMeta'), + jobWithMeta.get('taskGroups').objectAt(i).get('mergedMeta'), exp, 'mergedMeta is merged with job meta' ); @@ -128,10 +129,7 @@ module('Unit | Model | task-group', function(hooks) { expected = [{ c: 'd' }, {}, {}, {}]; expected.forEach((exp, i) => { assert.deepEqual( - jobWithoutMeta - .get('taskGroups') - .objectAt(i) - .get('mergedMeta'), + jobWithoutMeta.get('taskGroups').objectAt(i).get('mergedMeta'), exp, 'mergedMeta is merged with job meta' ); diff --git a/ui/tests/unit/models/task-test.js b/ui/tests/unit/models/task-test.js index 43f719198..67a274bf9 100644 --- a/ui/tests/unit/models/task-test.js +++ b/ui/tests/unit/models/task-test.js @@ -3,10 +3,12 @@ import { setupTest } from 'ember-qunit'; import { run } from '@ember/runloop'; -module('Unit | Model | task', function(hooks) { +module('Unit | Model | task', function (hooks) { setupTest(hooks); - test("should expose mergedMeta as merged with the job's and task groups's meta", function(assert) { + test("should expose mergedMeta as merged with the job's and task groups's meta", function (assert) { + assert.expect(8); + const job = run(() => this.owner.lookup('service:store').createRecord('job', { name: 'example', @@ -61,10 +63,7 @@ module('Unit | Model | task', function(hooks) { expected.forEach((exp, i) => { assert.deepEqual( - tg - .get('tasks') - .objectAt(i) - .get('mergedMeta'), + tg.get('tasks').objectAt(i).get('mergedMeta'), exp, 'mergedMeta is merged with task meta' ); @@ -75,10 +74,7 @@ module('Unit | Model | task', function(hooks) { expected.forEach((exp, i) => { assert.deepEqual( - tg - .get('tasks') - .objectAt(i) - .get('mergedMeta'), + tg.get('tasks').objectAt(i).get('mergedMeta'), exp, 'mergedMeta is merged with job meta' ); diff --git a/ui/tests/unit/serializers/allocation-test.js b/ui/tests/unit/serializers/allocation-test.js index 5250d8336..7aa28f72d 100644 --- a/ui/tests/unit/serializers/allocation-test.js +++ b/ui/tests/unit/serializers/allocation-test.js @@ -2,9 +2,9 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import AllocationModel from 'nomad-ui/models/allocation'; -module('Unit | Serializer | Allocation', function(hooks) { +module('Unit | Serializer | Allocation', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('allocation'); }); @@ -165,7 +165,10 @@ module('Unit | Serializer | Allocation', function(hooks) { }, }, PreemptedByAllocation: 'preempter-allocation', - PreemptedAllocations: ['preempted-one-allocation', 'preempted-two-allocation'], + PreemptedAllocations: [ + 'preempted-one-allocation', + 'preempted-two-allocation', + ], }, out: { data: { @@ -382,9 +385,12 @@ module('Unit | Serializer | Allocation', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(AllocationModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(AllocationModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/serializers/application-test.js b/ui/tests/unit/serializers/application-test.js index 49cacd19b..b1f01389c 100644 --- a/ui/tests/unit/serializers/application-test.js +++ b/ui/tests/unit/serializers/application-test.js @@ -10,7 +10,10 @@ class TestSerializer extends ApplicationSerializer { mapToArray = [ 'ArrayableMap', - { beforeName: 'OriginalNameArrayableMap', afterName: 'RenamedArrayableMap' }, + { + beforeName: 'OriginalNameArrayableMap', + afterName: 'RenamedArrayableMap', + }, ]; separateNanos = ['Time']; @@ -26,10 +29,10 @@ class TestModel extends Model { @attr() timeNanos; } -module('Unit | Serializer | Application', function(hooks) { +module('Unit | Serializer | Application', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.owner.register('model:test', TestModel); this.owner.register('serializer:test', TestSerializer); @@ -99,9 +102,12 @@ module('Unit | Serializer | Application', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(TestModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(TestModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/serializers/deployment-test.js b/ui/tests/unit/serializers/deployment-test.js index faebc9b66..25afe6ce5 100644 --- a/ui/tests/unit/serializers/deployment-test.js +++ b/ui/tests/unit/serializers/deployment-test.js @@ -2,9 +2,9 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import DeploymentModel from 'nomad-ui/models/deployment'; -module('Unit | Serializer | Deployment', function(hooks) { +module('Unit | Serializer | Deployment', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('deployment'); }); @@ -119,9 +119,12 @@ module('Unit | Serializer | Deployment', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(DeploymentModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(DeploymentModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/serializers/evaluation-test.js b/ui/tests/unit/serializers/evaluation-test.js index 053828acd..6e17427eb 100644 --- a/ui/tests/unit/serializers/evaluation-test.js +++ b/ui/tests/unit/serializers/evaluation-test.js @@ -2,9 +2,9 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import EvaluationModel from 'nomad-ui/models/evaluation'; -module('Unit | Serializer | Evaluation', function(hooks) { +module('Unit | Serializer | Evaluation', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('evaluation'); }); @@ -103,9 +103,12 @@ module('Unit | Serializer | Evaluation', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(EvaluationModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(EvaluationModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/serializers/job-plan-test.js b/ui/tests/unit/serializers/job-plan-test.js index 994f811c2..f9e48c86f 100644 --- a/ui/tests/unit/serializers/job-plan-test.js +++ b/ui/tests/unit/serializers/job-plan-test.js @@ -2,9 +2,9 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import JobPlanModel from 'nomad-ui/models/job-plan'; -module('Unit | Serializer | JobPlan', function(hooks) { +module('Unit | Serializer | JobPlan', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('job-plan'); }); @@ -138,9 +138,12 @@ module('Unit | Serializer | JobPlan', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(JobPlanModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(JobPlanModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/serializers/job-summary-test.js b/ui/tests/unit/serializers/job-summary-test.js index 94ef7e2ac..33d656099 100644 --- a/ui/tests/unit/serializers/job-summary-test.js +++ b/ui/tests/unit/serializers/job-summary-test.js @@ -2,9 +2,9 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import JobSummaryModel from 'nomad-ui/models/job-summary'; -module('Unit | Serializer | JobSummary', function(hooks) { +module('Unit | Serializer | JobSummary', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('job-summary'); }); @@ -94,9 +94,12 @@ module('Unit | Serializer | JobSummary', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(JobSummaryModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(JobSummaryModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/serializers/job-test.js b/ui/tests/unit/serializers/job-test.js index ab2b393cd..6852da08b 100644 --- a/ui/tests/unit/serializers/job-test.js +++ b/ui/tests/unit/serializers/job-test.js @@ -2,14 +2,14 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import JobModel from 'nomad-ui/models/job'; -module('Unit | Serializer | Job', function(hooks) { +module('Unit | Serializer | Job', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('job'); }); - test('`default` is used as the namespace in the job ID when there is no namespace in the payload', async function(assert) { + test('`default` is used as the namespace in the job ID when there is no namespace in the payload', async function (assert) { const original = { ID: 'example', Name: 'example', @@ -19,7 +19,7 @@ module('Unit | Serializer | Job', function(hooks) { assert.equal(data.id, JSON.stringify([data.attributes.name, 'default'])); }); - test('The ID of the record is a composite of both the name and the namespace', async function(assert) { + test('The ID of the record is a composite of both the name and the namespace', async function (assert) { const original = { ID: 'example', Name: 'example', @@ -29,7 +29,10 @@ module('Unit | Serializer | Job', function(hooks) { const { data } = this.subject().normalize(JobModel, original); assert.equal( data.id, - JSON.stringify([data.attributes.name, data.relationships.namespace.data.id]) + JSON.stringify([ + data.attributes.name, + data.relationships.namespace.data.id, + ]) ); }); }); diff --git a/ui/tests/unit/serializers/network-test.js b/ui/tests/unit/serializers/network-test.js index 8c5d614de..424538554 100644 --- a/ui/tests/unit/serializers/network-test.js +++ b/ui/tests/unit/serializers/network-test.js @@ -2,14 +2,14 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import NetworkModel from 'nomad-ui/models/network'; -module('Unit | Serializer | Network', function(hooks) { +module('Unit | Serializer | Network', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('network'); }); - test('v4 IPs are passed through', async function(assert) { + test('v4 IPs are passed through', async function (assert) { const ip = '10.0.13.12'; const original = { IP: ip, @@ -19,7 +19,7 @@ module('Unit | Serializer | Network', function(hooks) { assert.equal(data.attributes.ip, ip); }); - test('v6 IPs are wrapped in square brackets', async function(assert) { + test('v6 IPs are wrapped in square brackets', async function (assert) { const ip = '2001:0dac:aba3:0000:0000:8a2e:0370:7334'; const original = { IP: ip, diff --git a/ui/tests/unit/serializers/node-test.js b/ui/tests/unit/serializers/node-test.js index 2df5fb18b..70ad13125 100644 --- a/ui/tests/unit/serializers/node-test.js +++ b/ui/tests/unit/serializers/node-test.js @@ -5,21 +5,25 @@ import NodeModel from 'nomad-ui/models/node'; import pushPayloadToStore from '../../utils/push-payload-to-store'; import { settled } from '@ember/test-helpers'; -module('Unit | Serializer | Node', function(hooks) { +module('Unit | Serializer | Node', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('node'); }); - test('local store is culled to reflect the state of findAll requests', async function(assert) { + test('local store is culled to reflect the state of findAll requests', async function (assert) { const findAllResponse = [ makeNode('1', 'One', '127.0.0.1:4646'), makeNode('2', 'Two', '127.0.0.2:4646'), makeNode('3', 'Three', '127.0.0.3:4646'), ]; - const payload = this.subject().normalizeFindAllResponse(this.store, NodeModel, findAllResponse); + const payload = this.subject().normalizeFindAllResponse( + this.store, + NodeModel, + findAllResponse + ); pushPayloadToStore(this.store, payload, NodeModel.modelName); assert.equal( @@ -29,10 +33,7 @@ module('Unit | Serializer | Node', function(hooks) { ); assert.equal( - this.store - .peekAll('node') - .filterBy('id') - .get('length'), + this.store.peekAll('node').filterBy('id').get('length'), findAllResponse.length, 'Each original record is now in the store' ); @@ -61,15 +62,15 @@ module('Unit | Serializer | Node', function(hooks) { ); assert.equal( - this.store - .peekAll('node') - .filterBy('id') - .get('length'), + this.store.peekAll('node').filterBy('id').get('length'), newFindAllResponse.length, 'The node length in the store reflects the new response' ); - assert.notOk(this.store.peekAll('node').findBy('id', '1'), 'Record One is no longer found'); + assert.notOk( + this.store.peekAll('node').findBy('id', '1'), + 'Record One is no longer found' + ); }); function makeNode(id, name, ip) { @@ -114,7 +115,10 @@ module('Unit | Serializer | Node', function(hooks) { healthy: false, }, ], - hostVolumes: [{ name: 'one', readOnly: true }, { name: 'two', readOnly: false }], + hostVolumes: [ + { name: 'one', readOnly: true }, + { name: 'two', readOnly: false }, + ], }, relationships: { allocations: { @@ -203,9 +207,12 @@ module('Unit | Serializer | Node', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(NodeModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(NodeModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/serializers/recommendation-summary-test.js b/ui/tests/unit/serializers/recommendation-summary-test.js index fd7683279..e18677536 100644 --- a/ui/tests/unit/serializers/recommendation-summary-test.js +++ b/ui/tests/unit/serializers/recommendation-summary-test.js @@ -2,9 +2,9 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import RecommendationSummaryModel from 'nomad-ui/models/recommendation-summary'; -module('Unit | Serializer | RecommendationSummary', function(hooks) { +module('Unit | Serializer | RecommendationSummary', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('recommendation-summary'); }); @@ -202,10 +202,14 @@ module('Unit | Serializer | RecommendationSummary', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { assert.deepEqual( - this.subject().normalizeArrayResponse(this.store, RecommendationSummaryModel, testCase.in), + this.subject().normalizeArrayResponse( + this.store, + RecommendationSummaryModel, + testCase.in + ), testCase.out ); }); diff --git a/ui/tests/unit/serializers/scale-event-test.js b/ui/tests/unit/serializers/scale-event-test.js index c6d8c1806..4946d32b9 100644 --- a/ui/tests/unit/serializers/scale-event-test.js +++ b/ui/tests/unit/serializers/scale-event-test.js @@ -2,9 +2,9 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import ScaleEventModel from 'nomad-ui/models/scale-event'; -module('Unit | Serializer | Scale Event', function(hooks) { +module('Unit | Serializer | Scale Event', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('scale-event'); }); @@ -79,9 +79,12 @@ module('Unit | Serializer | Scale Event', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(ScaleEventModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(ScaleEventModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/serializers/volume-test.js b/ui/tests/unit/serializers/volume-test.js index 792538fc7..ab2d68b4c 100644 --- a/ui/tests/unit/serializers/volume-test.js +++ b/ui/tests/unit/serializers/volume-test.js @@ -2,9 +2,9 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import VolumeModel from 'nomad-ui/models/volume'; -module('Unit | Serializer | Volume', function(hooks) { +module('Unit | Serializer | Volume', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { + hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.subject = () => this.store.serializerFor('volume'); }); @@ -16,8 +16,7 @@ module('Unit | Serializer | Volume', function(hooks) { const normalizationTestCases = [ { - name: - '`default` is used as the namespace in the volume ID when there is no namespace in the payload', + name: '`default` is used as the namespace in the volume ID when there is no namespace in the payload', in: { ID: 'volume-id', Name: 'volume-id', @@ -152,8 +151,7 @@ module('Unit | Serializer | Volume', function(hooks) { }, { - name: - 'Allocations are interpreted as embedded records and are properly normalized into included resources in a JSON API shape', + name: 'Allocations are interpreted as embedded records and are properly normalized into included resources in a JSON API shape', in: { ID: 'volume-id', Name: 'volume-id', @@ -358,9 +356,12 @@ module('Unit | Serializer | Volume', function(hooks) { }, ]; - normalizationTestCases.forEach(testCase => { - test(`normalization: ${testCase.name}`, async function(assert) { - assert.deepEqual(this.subject().normalize(VolumeModel, testCase.in), testCase.out); + normalizationTestCases.forEach((testCase) => { + test(`normalization: ${testCase.name}`, async function (assert) { + assert.deepEqual( + this.subject().normalize(VolumeModel, testCase.in), + testCase.out + ); }); }); }); diff --git a/ui/tests/unit/services/stats-trackers-registry-test.js b/ui/tests/unit/services/stats-trackers-registry-test.js index 8a700eab4..b4746dc49 100644 --- a/ui/tests/unit/services/stats-trackers-registry-test.js +++ b/ui/tests/unit/services/stats-trackers-registry-test.js @@ -8,16 +8,16 @@ import sinon from 'sinon'; import fetch from 'nomad-ui/utils/fetch'; import NodeStatsTracker from 'nomad-ui/utils/classes/node-stats-tracker'; -module('Unit | Service | Stats Trackers Registry', function(hooks) { +module('Unit | Service | Stats Trackers Registry', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { - this.subject = function() { + hooks.beforeEach(function () { + this.subject = function () { return this.owner.factoryFor('service:stats-trackers-registry').create(); }; }); - hooks.beforeEach(function() { + hooks.beforeEach(function () { // Inject a mock token service const authorizedRequestSpy = (this.tokenAuthorizedRequestSpy = sinon.spy()); const mockToken = Service.extend({ @@ -29,7 +29,7 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { this.owner.register('service:token', mockToken); this.token = this.owner.lookup('service:token'); - this.server = new Pretender(function() { + this.server = new Pretender(function () { this.get('/v1/client/stats', () => [ 200, {}, @@ -44,7 +44,7 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { }); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); @@ -56,15 +56,26 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { const mockNode = makeModelMock('node', { id: 'test' }); - test('Creates a tracker when one isn’t found', function(assert) { + test('Creates a tracker when one isn’t found', function (assert) { const registry = this.subject(); const id = 'id'; - assert.equal(registry.get('registryRef').size, 0, 'Nothing in the registry yet'); + assert.equal( + registry.get('registryRef').size, + 0, + 'Nothing in the registry yet' + ); const tracker = registry.getTracker(mockNode.create({ id })); - assert.ok(tracker instanceof NodeStatsTracker, 'The correct type of tracker is made'); - assert.equal(registry.get('registryRef').size, 1, 'The tracker was added to the registry'); + assert.ok( + tracker instanceof NodeStatsTracker, + 'The correct type of tracker is made' + ); + assert.equal( + registry.get('registryRef').size, + 1, + 'The tracker was added to the registry' + ); assert.deepEqual( Array.from(registry.get('registryRef').keys()), [`node:${id}`], @@ -72,18 +83,26 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { ); }); - test('Returns an existing tracker when one is found', function(assert) { + test('Returns an existing tracker when one is found', function (assert) { const registry = this.subject(); const node = mockNode.create(); const tracker1 = registry.getTracker(node); const tracker2 = registry.getTracker(node); - assert.equal(tracker1, tracker2, 'Returns an existing tracker for the same resource'); - assert.equal(registry.get('registryRef').size, 1, 'Only one tracker in the registry'); + assert.equal( + tracker1, + tracker2, + 'Returns an existing tracker for the same resource' + ); + assert.equal( + registry.get('registryRef').size, + 1, + 'Only one tracker in the registry' + ); }); - test('Registry does not depend on persistent object references', function(assert) { + test('Registry does not depend on persistent object references', function (assert) { const registry = this.subject(); const id = 'some-id'; @@ -98,11 +117,19 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { 'And the same className' ); - assert.equal(registry.getTracker(node1), registry.getTracker(node2), 'Return the same tracker'); - assert.equal(registry.get('registryRef').size, 1, 'Only one tracker in the registry'); + assert.equal( + registry.getTracker(node1), + registry.getTracker(node2), + 'Return the same tracker' + ); + assert.equal( + registry.get('registryRef').size, + 1, + 'Only one tracker in the registry' + ); }); - test('Has a max size', function(assert) { + test('Has a max size', function (assert) { const registry = this.subject(); const ref = registry.get('registryRef'); @@ -111,7 +138,7 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { assert.ok(ref.limit < Infinity, `A limit (${ref.limit}) is set`); }); - test('Registry re-attaches deleted resources to cached trackers', function(assert) { + test('Registry re-attaches deleted resources to cached trackers', function (assert) { const registry = this.subject(); const id = 'some-id'; @@ -131,7 +158,7 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { ); }); - test('Registry re-attaches destroyed resources to cached trackers', async function(assert) { + test('Registry re-attaches destroyed resources to cached trackers', async function (assert) { const registry = this.subject(); const id = 'some-id'; @@ -154,7 +181,7 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { ); }); - test('Removes least recently used when something needs to be removed', function(assert) { + test('Removes least recently used when something needs to be removed', function (assert) { const registry = this.subject(); const activeNode = mockNode.create({ id: 'active' }); const inactiveNode = mockNode.create({ id: 'inactive' }); @@ -186,7 +213,7 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { ); }); - test('Trackers are created using the token authorizedRequest', function(assert) { + test('Trackers are created using the token authorizedRequest', function (assert) { const registry = this.subject(); const node = mockNode.create(); @@ -194,7 +221,9 @@ module('Unit | Service | Stats Trackers Registry', function(hooks) { tracker.get('poll').perform(); assert.ok( - this.tokenAuthorizedRequestSpy.calledWith(`/v1/client/stats?node_id=${node.get('id')}`), + this.tokenAuthorizedRequestSpy.calledWith( + `/v1/client/stats?node_id=${node.get('id')}` + ), 'The token service authorizedRequest function was used' ); diff --git a/ui/tests/unit/services/token-test.js b/ui/tests/unit/services/token-test.js index edf1bc266..a88e6f22c 100644 --- a/ui/tests/unit/services/token-test.js +++ b/ui/tests/unit/services/token-test.js @@ -3,16 +3,16 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import Pretender from 'pretender'; -module('Unit | Service | Token', function(hooks) { +module('Unit | Service | Token', function (hooks) { setupTest(hooks); - hooks.beforeEach(function() { - this.subject = function() { + hooks.beforeEach(function () { + this.subject = function () { return this.owner.factoryFor('service:token').create(); }; }); - hooks.beforeEach(function() { + hooks.beforeEach(function () { const mockSystem = Service.extend({ activeRegion: 'region-1', shouldIncludeRegion: true, @@ -21,16 +21,16 @@ module('Unit | Service | Token', function(hooks) { this.owner.register('service:system', mockSystem); this.system = this.owner.lookup('service:system'); - this.server = new Pretender(function() { + this.server = new Pretender(function () { this.get('/path', () => [200, {}, null]); }); }); - hooks.afterEach(function() { + hooks.afterEach(function () { this.server.shutdown(); }); - test('authorizedRequest includes the region param when the system service says to', function(assert) { + test('authorizedRequest includes the region param when the system service says to', function (assert) { const token = this.subject(); token.authorizedRequest('/path'); @@ -50,7 +50,7 @@ module('Unit | Service | Token', function(hooks) { ); }); - test('authorizedRequest does not include the region param when the region param is already in the URL', function(assert) { + test('authorizedRequest does not include the region param when the region param is already in the URL', function (assert) { const token = this.subject(); token.authorizedRequest('/path?query=param®ion=already-here'); @@ -61,7 +61,7 @@ module('Unit | Service | Token', function(hooks) { ); }); - test('authorizedRawRequest bypasses adding the region param', function(assert) { + test('authorizedRawRequest bypasses adding the region param', function (assert) { const token = this.subject(); token.authorizedRawRequest('/path'); diff --git a/ui/tests/unit/utils/add-to-path-test.js b/ui/tests/unit/utils/add-to-path-test.js index 6317cfad0..e39822779 100644 --- a/ui/tests/unit/utils/add-to-path-test.js +++ b/ui/tests/unit/utils/add-to-path-test.js @@ -19,9 +19,9 @@ const testCases = [ }, ]; -module('Unit | Util | addToPath', function() { - testCases.forEach(testCase => { - test(testCase.name, function(assert) { +module('Unit | Util | addToPath', function () { + testCases.forEach((testCase) => { + test(testCase.name, function (assert) { assert.equal( addToPath.apply(null, testCase.in), testCase.out, diff --git a/ui/tests/unit/utils/allocation-stats-tracker-test.js b/ui/tests/unit/utils/allocation-stats-tracker-test.js index 8858c1721..3c319f0ff 100644 --- a/ui/tests/unit/utils/allocation-stats-tracker-test.js +++ b/ui/tests/unit/utils/allocation-stats-tracker-test.js @@ -3,17 +3,19 @@ import { assign } from '@ember/polyfills'; import { module, test } from 'qunit'; import sinon from 'sinon'; import Pretender from 'pretender'; -import AllocationStatsTracker, { stats } from 'nomad-ui/utils/classes/allocation-stats-tracker'; +import AllocationStatsTracker, { + stats, +} from 'nomad-ui/utils/classes/allocation-stats-tracker'; import fetch from 'nomad-ui/utils/fetch'; import statsTrackerFrameMissingBehavior from './behaviors/stats-tracker-frame-missing'; import { settled } from '@ember/test-helpers'; -module('Unit | Util | AllocationStatsTracker', function() { +module('Unit | Util | AllocationStatsTracker', function () { const refDate = Date.now() * 1000000; - const makeDate = ts => new Date(ts / 1000000); + const makeDate = (ts) => new Date(ts / 1000000); - const MockAllocation = overrides => + const MockAllocation = (overrides) => assign( { id: 'some-identifier', @@ -45,7 +47,7 @@ module('Unit | Util | AllocationStatsTracker', function() { overrides ); - const mockFrame = step => ({ + const mockFrame = (step) => ({ ResourceUsage: { CpuStats: { TotalTicks: step + 100, @@ -92,7 +94,7 @@ module('Unit | Util | AllocationStatsTracker', function() { Timestamp: refDate + step * 1000, }); - test('the AllocationStatsTracker constructor expects a fetch definition and an allocation', async function(assert) { + test('the AllocationStatsTracker constructor expects a fetch definition and an allocation', async function (assert) { const tracker = AllocationStatsTracker.create(); assert.throws( () => { @@ -103,7 +105,7 @@ module('Unit | Util | AllocationStatsTracker', function() { ); }); - test('the url property is computed based off the allocation id', async function(assert) { + test('the url property is computed based off the allocation id', async function (assert) { const allocation = MockAllocation(); const tracker = AllocationStatsTracker.create({ fetch, allocation }); @@ -114,7 +116,7 @@ module('Unit | Util | AllocationStatsTracker', function() { ); }); - test('reservedCPU and reservedMemory properties come from the allocation', async function(assert) { + test('reservedCPU and reservedMemory properties come from the allocation', async function (assert) { const allocation = MockAllocation(); const tracker = AllocationStatsTracker.create({ fetch, allocation }); @@ -130,7 +132,9 @@ module('Unit | Util | AllocationStatsTracker', function() { ); }); - test('the tasks list comes from the allocation', async function(assert) { + test('the tasks list comes from the allocation', async function (assert) { + assert.expect(7); + const allocation = MockAllocation(); const tracker = AllocationStatsTracker.create({ fetch, allocation }); @@ -139,9 +143,13 @@ module('Unit | Util | AllocationStatsTracker', function() { allocation.taskGroup.tasks.length, 'tasks matches lengths with the allocation task group' ); - allocation.taskGroup.tasks.forEach(task => { + allocation.taskGroup.tasks.forEach((task) => { const trackerTask = tracker.get('tasks').findBy('task', task.name); - assert.equal(trackerTask.reservedCPU, task.reservedCPU, `CPU matches for task ${task.name}`); + assert.equal( + trackerTask.reservedCPU, + task.reservedCPU, + `CPU matches for task ${task.name}` + ); assert.equal( trackerTask.reservedMemory, task.reservedMemory, @@ -150,9 +158,13 @@ module('Unit | Util | AllocationStatsTracker', function() { }); }); - test('poll results in requesting the url and calling append with the resulting JSON', async function(assert) { + test('poll results in requesting the url and calling append with the resulting JSON', async function (assert) { const allocation = MockAllocation(); - const tracker = AllocationStatsTracker.create({ fetch, allocation, append: sinon.spy() }); + const tracker = AllocationStatsTracker.create({ + fetch, + allocation, + append: sinon.spy(), + }); const mockFrame = { Some: { data: ['goes', 'here'], @@ -160,8 +172,12 @@ module('Unit | Util | AllocationStatsTracker', function() { }, }; - const server = new Pretender(function() { - this.get('/v1/client/allocation/:id/stats', () => [200, {}, JSON.stringify(mockFrame)]); + const server = new Pretender(function () { + this.get('/v1/client/allocation/:id/stats', () => [ + 200, + {}, + JSON.stringify(mockFrame), + ]); }); tracker.get('poll').perform(); @@ -182,7 +198,7 @@ module('Unit | Util | AllocationStatsTracker', function() { server.shutdown(); }); - test('append appropriately maps a data frame to the tracked stats for cpu and memory for the allocation as well as individual tasks', async function(assert) { + test('append appropriately maps a data frame to the tracked stats for cpu and memory for the allocation as well as individual tasks', async function (assert) { const allocation = MockAllocation(); const tracker = AllocationStatsTracker.create({ fetch, allocation }); @@ -192,9 +208,27 @@ module('Unit | Util | AllocationStatsTracker', function() { assert.deepEqual( tracker.get('tasks'), [ - { task: 'service', reservedCPU: 100, reservedMemory: 256, cpu: [], memory: [] }, - { task: 'sidecar', reservedCPU: 50, reservedMemory: 128, cpu: [], memory: [] }, - { task: 'log-shipper', reservedCPU: 50, reservedMemory: 128, cpu: [], memory: [] }, + { + task: 'service', + reservedCPU: 100, + reservedMemory: 256, + cpu: [], + memory: [], + }, + { + task: 'sidecar', + reservedCPU: 50, + reservedMemory: 128, + cpu: [], + memory: [], + }, + { + task: 'log-shipper', + reservedCPU: 50, + reservedMemory: 128, + cpu: [], + memory: [], + }, ], 'tasks represents the tasks for the allocation with no stats yet' ); @@ -307,8 +341,16 @@ module('Unit | Util | AllocationStatsTracker', function() { assert.deepEqual( tracker.get('memory'), [ - { timestamp: makeDate(refDate + 1000), used: 401 * 1024 * 1024, percent: 401 / 512 }, - { timestamp: makeDate(refDate + 2000), used: 402 * 1024 * 1024, percent: 402 / 512 }, + { + timestamp: makeDate(refDate + 1000), + used: 401 * 1024 * 1024, + percent: 401 / 512, + }, + { + timestamp: makeDate(refDate + 2000), + used: 402 * 1024 * 1024, + percent: 402 / 512, + }, ], 'Two frames of memory' ); @@ -432,10 +474,16 @@ module('Unit | Util | AllocationStatsTracker', function() { ); }); - test('each stat list has maxLength equal to bufferSize', async function(assert) { + test('each stat list has maxLength equal to bufferSize', async function (assert) { + assert.expect(16); + const allocation = MockAllocation(); const bufferSize = 10; - const tracker = AllocationStatsTracker.create({ fetch, allocation, bufferSize }); + const tracker = AllocationStatsTracker.create({ + fetch, + allocation, + bufferSize, + }); for (let i = 1; i <= 20; i++) { tracker.append(mockFrame(i)); @@ -463,7 +511,7 @@ module('Unit | Util | AllocationStatsTracker', function() { 'Old frames are removed in favor of newer ones' ); - tracker.get('tasks').forEach(task => { + tracker.get('tasks').forEach((task) => { assert.equal( task.cpu.length, bufferSize, @@ -510,12 +558,12 @@ module('Unit | Util | AllocationStatsTracker', function() { ); }); - test('the stats computed property macro constructs an AllocationStatsTracker based on an allocationProp and a fetch definition', async function(assert) { + test('the stats computed property macro constructs an AllocationStatsTracker based on an allocationProp and a fetch definition', async function (assert) { const allocation = MockAllocation(); const fetchSpy = sinon.spy(); const SomeClass = EmberObject.extend({ - stats: stats('alloc', function() { + stats: stats('alloc', function () { return () => fetchSpy(this); }), }); @@ -537,7 +585,7 @@ module('Unit | Util | AllocationStatsTracker', function() { ); }); - test('changing the value of the allocationProp constructs a new AllocationStatsTracker', async function(assert) { + test('changing the value of the allocationProp constructs a new AllocationStatsTracker', async function (assert) { const alloc1 = MockAllocation(); const alloc2 = MockAllocation(); const SomeClass = EmberObject.extend({ @@ -553,8 +601,9 @@ module('Unit | Util | AllocationStatsTracker', function() { someObject.set('alloc', alloc2); const stats2 = someObject.get('stats'); - assert.notOk( - stats1 === stats2, + assert.notStrictEqual( + stats1, + stats2, 'Changing the value of alloc results in creating a new AllocationStatsTracker instance' ); }); diff --git a/ui/tests/unit/utils/behaviors/stats-tracker-frame-missing.js b/ui/tests/unit/utils/behaviors/stats-tracker-frame-missing.js index 4807c3be2..77d77f673 100644 --- a/ui/tests/unit/utils/behaviors/stats-tracker-frame-missing.js +++ b/ui/tests/unit/utils/behaviors/stats-tracker-frame-missing.js @@ -3,7 +3,7 @@ import { test } from 'qunit'; import sinon from 'sinon'; import { settled } from '@ember/test-helpers'; -const MockResponse = json => ({ +const MockResponse = (json) => ({ ok: true, json() { return resolve(json); @@ -17,7 +17,7 @@ export default function statsTrackerFrameMissing({ mockFrame, compileResources, }) { - test('a bad response from a fetch request is handled gracefully', async function(assert) { + test('a bad response from a fetch request is handled gracefully', async function (assert) { const frame = mockFrame(1); const [compiledCPU, compiledMemory] = compileResources(frame); @@ -27,27 +27,46 @@ export default function statsTrackerFrameMissing({ }; const resource = ResourceConstructor(); - const tracker = TrackerConstructor.create({ fetch, [resourceName]: resource }); + const tracker = TrackerConstructor.create({ + fetch, + [resourceName]: resource, + }); tracker.get('poll').perform(); await settled(); assert.deepEqual(tracker.get('cpu'), [compiledCPU], 'One frame of cpu'); - assert.deepEqual(tracker.get('memory'), [compiledMemory], 'One frame of memory'); + assert.deepEqual( + tracker.get('memory'), + [compiledMemory], + 'One frame of memory' + ); shouldFail = true; tracker.get('poll').perform(); await settled(); - assert.deepEqual(tracker.get('cpu'), [compiledCPU], 'Still one frame of cpu'); - assert.deepEqual(tracker.get('memory'), [compiledMemory], 'Still one frame of memory'); + assert.deepEqual( + tracker.get('cpu'), + [compiledCPU], + 'Still one frame of cpu' + ); + assert.deepEqual( + tracker.get('memory'), + [compiledMemory], + 'Still one frame of memory' + ); assert.equal(tracker.get('frameMisses'), 1, 'Frame miss is tracked'); shouldFail = false; tracker.get('poll').perform(); await settled(); - assert.deepEqual(tracker.get('cpu'), [compiledCPU, compiledCPU], 'Still one frame of cpu'); + assert.deepEqual( + tracker.get('cpu'), + [compiledCPU, compiledCPU], + 'Still one frame of cpu' + ); assert.deepEqual( tracker.get('memory'), [compiledMemory, compiledMemory], @@ -56,7 +75,7 @@ export default function statsTrackerFrameMissing({ assert.equal(tracker.get('frameMisses'), 0, 'Frame misses is reset'); }); - test('enough bad responses from fetch consecutively (as set by maxFrameMisses) results in a pause', async function(assert) { + test('enough bad responses from fetch consecutively (as set by maxFrameMisses) results in a pause', async function (assert) { const fetch = () => { return resolve({ ok: false }); }; @@ -85,6 +104,9 @@ export default function statsTrackerFrameMissing({ await settled(); assert.equal(tracker.get('frameMisses'), 0, 'Misses reset'); - assert.ok(tracker.pause.called, 'Pause called now that frameMisses == maxFrameMisses'); + assert.ok( + tracker.pause.called, + 'Pause called now that frameMisses == maxFrameMisses' + ); }); } diff --git a/ui/tests/unit/utils/encode-test.js b/ui/tests/unit/utils/encode-test.js index 0fa9fd78a..001fd5feb 100644 --- a/ui/tests/unit/utils/encode-test.js +++ b/ui/tests/unit/utils/encode-test.js @@ -1,15 +1,15 @@ import { base64DecodeString, base64EncodeString } from 'nomad-ui/utils/encode'; import { module, test } from 'qunit'; -module('Unit | Utility | encode', function() { - test('it encodes a null input', function(assert) { +module('Unit | Utility | encode', function () { + test('it encodes a null input', function (assert) { const encoded = base64EncodeString(null); const decoded = base64DecodeString(encoded); assert.equal(decoded, ''); }); - test('it encodes an empty string', function(assert) { + test('it encodes an empty string', function (assert) { const input = ''; const encoded = base64EncodeString(input); const decoded = base64DecodeString(encoded); @@ -17,19 +17,19 @@ module('Unit | Utility | encode', function() { assert.equal(decoded, input); }); - test('it decodes a null input', function(assert) { + test('it decodes a null input', function (assert) { const decoded = base64DecodeString(null); assert.equal(decoded, ''); }); - test('it decodes an empty string', function(assert) { + test('it decodes an empty string', function (assert) { const decoded = base64DecodeString(''); assert.equal(decoded, ''); }); - test('it encodes and decodes non-ascii with base64', function(assert) { + test('it encodes and decodes non-ascii with base64', function (assert) { const input = 'hello 🥳'; const encoded = base64EncodeString(input); const decoded = base64DecodeString(encoded); diff --git a/ui/tests/unit/utils/escape-task-name-test.js b/ui/tests/unit/utils/escape-task-name-test.js index 649d2acce..2c8e3d9a9 100644 --- a/ui/tests/unit/utils/escape-task-name-test.js +++ b/ui/tests/unit/utils/escape-task-name-test.js @@ -1,8 +1,8 @@ import escapeTaskName from 'nomad-ui/utils/escape-task-name'; import { module, test } from 'qunit'; -module('Unit | Utility | escape-task-name', function() { - test('it escapes task names for the faux exec CLI', function(assert) { +module('Unit | Utility | escape-task-name', function () { + test('it escapes task names for the faux exec CLI', function (assert) { assert.equal(escapeTaskName('plain'), 'plain'); assert.equal(escapeTaskName('a space'), 'a\\ space'); assert.equal(escapeTaskName('dollar $ign'), 'dollar\\ \\$ign'); diff --git a/ui/tests/unit/utils/format-duration-test.js b/ui/tests/unit/utils/format-duration-test.js index 9a3e49230..dd02d0f77 100644 --- a/ui/tests/unit/utils/format-duration-test.js +++ b/ui/tests/unit/utils/format-duration-test.js @@ -1,18 +1,19 @@ import { module, test } from 'qunit'; import formatDuration from 'nomad-ui/utils/format-duration'; -module('Unit | Util | formatDuration', function() { - test('When all units have values, all units are displayed', function(assert) { - const expectation = '39 years 1 month 13 days 23h 31m 30s 987ms 654µs 400ns'; +module('Unit | Util | formatDuration', function () { + test('When all units have values, all units are displayed', function (assert) { + const expectation = + '39 years 1 month 13 days 23h 31m 30s 987ms 654µs 400ns'; assert.equal(formatDuration(1234567890987654321), expectation, expectation); }); - test('Any unit without values gets dropped from the display', function(assert) { + test('Any unit without values gets dropped from the display', function (assert) { const expectation = '14 days 6h 56m 890ms 980µs'; assert.equal(formatDuration(1234560890980000), expectation, expectation); }); - test('The units option allows for units coarser than nanoseconds', function(assert) { + test('The units option allows for units coarser than nanoseconds', function (assert) { const expectation1 = '1s 200ms'; const expectation2 = '20m'; const expectation3 = '1 month 1 day'; @@ -21,15 +22,23 @@ module('Unit | Util | formatDuration', function() { assert.equal(formatDuration(32, 'd'), expectation3, expectation3); }); - test('When duration is 0, 0 is shown in terms of the units provided to the function', function(assert) { + test('When duration is 0, 0 is shown in terms of the units provided to the function', function (assert) { assert.equal(formatDuration(0), '0ns', 'formatDuration(0) -> 0ns'); - assert.equal(formatDuration(0, 'year'), '0 years', 'formatDuration(0, "year") -> 0 years'); + assert.equal( + formatDuration(0, 'year'), + '0 years', + 'formatDuration(0, "year") -> 0 years' + ); }); - test('The longForm option expands suffixes to words', function(assert) { + test('The longForm option expands suffixes to words', function (assert) { const expectation1 = '3 seconds 20ms'; const expectation2 = '5 hours 59 minutes'; assert.equal(formatDuration(3020, 'ms', true), expectation1, expectation1); - assert.equal(formatDuration(60 * 5 + 59, 'm', true), expectation2, expectation2); + assert.equal( + formatDuration(60 * 5 + 59, 'm', true), + expectation2, + expectation2 + ); }); }); diff --git a/ui/tests/unit/utils/generate-exec-url-test.js b/ui/tests/unit/utils/generate-exec-url-test.js index b2897cc4e..81481e8e3 100644 --- a/ui/tests/unit/utils/generate-exec-url-test.js +++ b/ui/tests/unit/utils/generate-exec-url-test.js @@ -4,19 +4,19 @@ import sinon from 'sinon'; const emptyOptions = { queryParams: {} }; -module('Unit | Utility | generate-exec-url', function(hooks) { - hooks.beforeEach(function() { +module('Unit | Utility | generate-exec-url', function (hooks) { + hooks.beforeEach(function () { this.urlForSpy = sinon.spy(); this.router = { urlFor: this.urlForSpy, currentRoute: { queryParams: {} } }; }); - test('it generates an exec job URL', function(assert) { + test('it generates an exec job URL', function (assert) { generateExecUrl(this.router, { job: { plainId: 'job-name' } }); assert.ok(this.urlForSpy.calledWith('exec', 'job-name', emptyOptions)); }); - test('it generates an exec job URL with an allocation and task group when there are multiple tasks', function(assert) { + test('it generates an exec job URL with an allocation and task group when there are multiple tasks', function (assert) { generateExecUrl(this.router, { job: { plainId: 'job-name' }, allocation: { @@ -26,13 +26,18 @@ module('Unit | Utility | generate-exec-url', function(hooks) { }); assert.ok( - this.urlForSpy.calledWith('exec.task-group', 'job-name', 'task-group-name', { - queryParams: { allocation: 'allocation-short-id' }, - }) + this.urlForSpy.calledWith( + 'exec.task-group', + 'job-name', + 'task-group-name', + { + queryParams: { allocation: 'allocation-short-id' }, + } + ) ); }); - test('it generates an exec job URL with an allocation, task group, and task when there is only one task', function(assert) { + test('it generates an exec job URL with an allocation, task group, and task when there is only one task', function (assert) { generateExecUrl(this.router, { job: { plainId: 'job-name' }, allocation: { @@ -54,18 +59,23 @@ module('Unit | Utility | generate-exec-url', function(hooks) { ); }); - test('it generates an exec task group URL', function(assert) { + test('it generates an exec task group URL', function (assert) { generateExecUrl(this.router, { job: { plainId: 'job-name' }, taskGroup: { name: 'task-group-name' }, }); assert.ok( - this.urlForSpy.calledWith('exec.task-group', 'job-name', 'task-group-name', emptyOptions) + this.urlForSpy.calledWith( + 'exec.task-group', + 'job-name', + 'task-group-name', + emptyOptions + ) ); }); - test('it generates an exec task URL', function(assert) { + test('it generates an exec task URL', function (assert) { generateExecUrl(this.router, { allocation: { shortId: 'allocation-short-id' }, job: { plainId: 'job-name' }, @@ -84,7 +94,7 @@ module('Unit | Utility | generate-exec-url', function(hooks) { ); }); - test('it generates an exec task URL without an allocation', function(assert) { + test('it generates an exec task URL without an allocation', function (assert) { generateExecUrl(this.router, { job: { plainId: 'job-name' }, taskGroup: { name: 'task-group-name' }, @@ -92,11 +102,16 @@ module('Unit | Utility | generate-exec-url', function(hooks) { }); assert.ok( - this.urlForSpy.calledWith('exec.task-group.task', 'job-name', 'task-group-name', 'task-name') + this.urlForSpy.calledWith( + 'exec.task-group.task', + 'job-name', + 'task-group-name', + 'task-name' + ) ); }); - test('it includes job namespace and region when they exist', function(assert) { + test('it includes job namespace and region when they exist', function (assert) { generateExecUrl(this.router, { job: { namespace: { @@ -105,13 +120,25 @@ module('Unit | Utility | generate-exec-url', function(hooks) { plainId: 'job-name', region: 'a-region', }, - allocation: { shortId: 'id', taskGroup: { name: 'task-group-name', tasks: [0, 1] } }, + allocation: { + shortId: 'id', + taskGroup: { name: 'task-group-name', tasks: [0, 1] }, + }, }); assert.ok( - this.urlForSpy.calledWith('exec.task-group', 'job-name', 'task-group-name', { - queryParams: { allocation: 'id', namespace: 'a-namespace', region: 'a-region' }, - }) + this.urlForSpy.calledWith( + 'exec.task-group', + 'job-name', + 'task-group-name', + { + queryParams: { + allocation: 'id', + namespace: 'a-namespace', + region: 'a-region', + }, + } + ) ); }); }); diff --git a/ui/tests/unit/utils/job-client-status-test.js b/ui/tests/unit/utils/job-client-status-test.js index 70a6532a4..e625a4f57 100644 --- a/ui/tests/unit/utils/job-client-status-test.js +++ b/ui/tests/unit/utils/job-client-status-test.js @@ -51,8 +51,8 @@ class AllocationMock { } } -module('Unit | Util | JobClientStatus', function() { - test('it handles the case where all nodes are running', async function(assert) { +module('Unit | Util | JobClientStatus', function () { + test('it handles the case where all nodes are running', async function (assert) { const node = new NodeMock('node-1', 'dc1'); const nodes = [node]; const job = { @@ -84,7 +84,7 @@ module('Unit | Util | JobClientStatus', function() { assert.deepEqual(result, expected); }); - test('it handles the degraded case where a node has a failing allocation', async function(assert) { + test('it handles the degraded case where a node has a failing allocation', async function (assert) { const node = new NodeMock('node-2', 'dc1'); const nodes = [node]; const job = { @@ -120,7 +120,7 @@ module('Unit | Util | JobClientStatus', function() { assert.deepEqual(result, expected); }); - test('it handles the case where a node has all lost allocations', async function(assert) { + test('it handles the case where a node has all lost allocations', async function (assert) { const node = new NodeMock('node-1', 'dc1'); const nodes = [node]; const job = { @@ -156,7 +156,7 @@ module('Unit | Util | JobClientStatus', function() { assert.deepEqual(result, expected); }); - test('it handles the case where a node has all failed allocations', async function(assert) { + test('it handles the case where a node has all failed allocations', async function (assert) { const node = new NodeMock('node-1', 'dc1'); const nodes = [node]; const job = { @@ -192,7 +192,7 @@ module('Unit | Util | JobClientStatus', function() { assert.deepEqual(result, expected); }); - test('it handles the degraded case where the expected number of allocations doesnt match the actual number of allocations', async function(assert) { + test('it handles the degraded case where the expected number of allocations doesnt match the actual number of allocations', async function (assert) { const node = new NodeMock('node-1', 'dc1'); const nodes = [node]; const job = { @@ -228,7 +228,7 @@ module('Unit | Util | JobClientStatus', function() { assert.deepEqual(result, expected); }); - test('it handles the not scheduled case where a node has no allocations', async function(assert) { + test('it handles the not scheduled case where a node has no allocations', async function (assert) { const node = new NodeMock('node-1', 'dc1'); const nodes = [node]; const job = { @@ -260,7 +260,7 @@ module('Unit | Util | JobClientStatus', function() { assert.deepEqual(result, expected); }); - test('it handles the queued case where the job is pending', async function(assert) { + test('it handles the queued case where the job is pending', async function (assert) { const node = new NodeMock('node-1', 'dc1'); const nodes = [node]; const job = { @@ -296,7 +296,7 @@ module('Unit | Util | JobClientStatus', function() { assert.deepEqual(result, expected); }); - test('it filters nodes by the datacenter of the job', async function(assert) { + test('it filters nodes by the datacenter of the job', async function (assert) { const node1 = new NodeMock('node-1', 'dc1'); const node2 = new NodeMock('node-2', 'dc2'); const nodes = [node1, node2]; diff --git a/ui/tests/unit/utils/log-test.js b/ui/tests/unit/utils/log-test.js index bae13b32b..920784aa0 100644 --- a/ui/tests/unit/utils/log-test.js +++ b/ui/tests/unit/utils/log-test.js @@ -38,26 +38,31 @@ const MockStreamer = EmberObject.extend({ const Log = _Log.extend({ init() { this._super(); - const props = this.logStreamer.getProperties('url', 'params', 'logFetch', 'write'); + const props = this.logStreamer.getProperties( + 'url', + 'params', + 'logFetch', + 'write' + ); this.set('logStreamer', MockStreamer.create(props)); }, }); -module('Unit | Util | Log', function(hooks) { - hooks.beforeEach(function() { +module('Unit | Util | Log', function (hooks) { + hooks.beforeEach(function () { initSpy = sinon.spy(); startSpy = sinon.spy(); stopSpy = sinon.spy(); fetchSpy = sinon.spy(); }); - const makeMocks = output => ({ + const makeMocks = (output) => ({ url: '/test-url/', params: { a: 'param', another: 'one', }, - logFetch: function() { + logFetch: function () { fetchSpy(...arguments); return RSVP.Promise.resolve({ text() { @@ -67,28 +72,31 @@ module('Unit | Util | Log', function(hooks) { }, }); - test('logStreamer is created on init', async function(assert) { + test('logStreamer is created on init', async function (assert) { const log = Log.create(makeMocks('')); assert.ok(log.get('logStreamer'), 'logStreamer property is defined'); assert.ok(initSpy.calledOnce, 'logStreamer init was called'); }); - test('gotoHead builds the correct URL', async function(assert) { + test('gotoHead builds the correct URL', async function (assert) { + assert.expect(1); + const mocks = makeMocks(''); const expectedUrl = `${mocks.url}?a=param&another=one&offset=0&origin=start`; const log = Log.create(mocks); run(() => { log.get('gotoHead').perform(); - assert.ok(fetchSpy.calledWith(expectedUrl), `gotoHead URL was ${expectedUrl}`); + assert.ok( + fetchSpy.calledWith(expectedUrl), + `gotoHead URL was ${expectedUrl}` + ); }); }); - test('When gotoHead returns too large of a log, the log is truncated', async function(assert) { - const longLog = Array(50001) - .fill('a') - .join(''); + test('When gotoHead returns too large of a log, the log is truncated', async function (assert) { + const longLog = Array(50001).fill('a').join(''); const encodedLongLog = `{"Offset":0,"Data":"${window.btoa(longLog)}"}`; const truncationMessage = '\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------'; @@ -102,10 +110,7 @@ module('Unit | Util | Log', function(hooks) { await settled(); assert.ok( - log - .get('output') - .toString() - .endsWith(truncationMessage), + log.get('output').toString().endsWith(truncationMessage), 'Truncation message is shown' ); assert.equal( @@ -115,26 +120,35 @@ module('Unit | Util | Log', function(hooks) { ); }); - test('gotoTail builds the correct URL', async function(assert) { + test('gotoTail builds the correct URL', async function (assert) { + assert.expect(1); + const mocks = makeMocks(''); const expectedUrl = `${mocks.url}?a=param&another=one&offset=50000&origin=end`; const log = Log.create(mocks); run(() => { log.get('gotoTail').perform(); - assert.ok(fetchSpy.calledWith(expectedUrl), `gotoTail URL was ${expectedUrl}`); + assert.ok( + fetchSpy.calledWith(expectedUrl), + `gotoTail URL was ${expectedUrl}` + ); }); }); - test('startStreaming starts the log streamer', async function(assert) { + test('startStreaming starts the log streamer', async function (assert) { const log = Log.create(makeMocks('')); log.startStreaming(); assert.ok(startSpy.calledOnce, 'Streaming started'); - assert.equal(log.get('logPointer'), 'tail', 'Streaming points the log to the tail'); + assert.equal( + log.get('logPointer'), + 'tail', + 'Streaming points the log to the tail' + ); }); - test('When the log streamer calls `write`, the output is appended', async function(assert) { + test('When the log streamer calls `write`, the output is appended', async function (assert) { const log = Log.create(makeMocks('')); const chunk1 = 'Hello'; const chunk2 = ' World'; @@ -150,10 +164,14 @@ module('Unit | Util | Log', function(hooks) { assert.equal(log.get('output'), chunk1 + chunk2, 'Second chunk written'); log.get('logStreamer').step(chunk3); - assert.equal(log.get('output'), chunk1 + chunk2 + chunk3, 'Third chunk written'); + assert.equal( + log.get('output'), + chunk1 + chunk2 + chunk3, + 'Third chunk written' + ); }); - test('stop stops the log streamer', async function(assert) { + test('stop stops the log streamer', async function (assert) { const log = Log.create(makeMocks('')); log.stop(); diff --git a/ui/tests/unit/utils/message-from-adapter-error-test.js b/ui/tests/unit/utils/message-from-adapter-error-test.js index 7336ee943..2fe795356 100644 --- a/ui/tests/unit/utils/message-from-adapter-error-test.js +++ b/ui/tests/unit/utils/message-from-adapter-error-test.js @@ -10,7 +10,10 @@ const testCases = [ }, { name: 'Generic Error', - in: [new ServerError([{ detail: 'DB Max Connections' }], 'Server Error'), 'run tests'], + in: [ + new ServerError([{ detail: 'DB Max Connections' }], 'Server Error'), + 'run tests', + ], out: 'DB Max Connections', }, { @@ -31,9 +34,9 @@ const testCases = [ }, ]; -module('Unit | Util | messageFromAdapterError', function() { - testCases.forEach(testCase => { - test(testCase.name, function(assert) { +module('Unit | Util | messageFromAdapterError', function () { + testCases.forEach((testCase) => { + test(testCase.name, function (assert) { assert.equal( messageFromAdapterError.apply(null, testCase.in), testCase.out, diff --git a/ui/tests/unit/utils/node-stats-tracker-test.js b/ui/tests/unit/utils/node-stats-tracker-test.js index 55871280e..1729e14e7 100644 --- a/ui/tests/unit/utils/node-stats-tracker-test.js +++ b/ui/tests/unit/utils/node-stats-tracker-test.js @@ -3,17 +3,19 @@ import { assign } from '@ember/polyfills'; import { module, test } from 'qunit'; import sinon from 'sinon'; import Pretender from 'pretender'; -import NodeStatsTracker, { stats } from 'nomad-ui/utils/classes/node-stats-tracker'; +import NodeStatsTracker, { + stats, +} from 'nomad-ui/utils/classes/node-stats-tracker'; import fetch from 'nomad-ui/utils/fetch'; import statsTrackerFrameMissingBehavior from './behaviors/stats-tracker-frame-missing'; import { settled } from '@ember/test-helpers'; -module('Unit | Util | NodeStatsTracker', function() { +module('Unit | Util | NodeStatsTracker', function () { const refDate = Date.now() * 1000000; - const makeDate = ts => new Date(ts / 1000000); + const makeDate = (ts) => new Date(ts / 1000000); - const MockNode = overrides => + const MockNode = (overrides) => assign( { id: 'some-identifier', @@ -25,7 +27,7 @@ module('Unit | Util | NodeStatsTracker', function() { overrides ); - const mockFrame = step => ({ + const mockFrame = (step) => ({ CPUTicksConsumed: step + 1000, Memory: { Used: (step + 2048) * 1024 * 1024, @@ -33,7 +35,7 @@ module('Unit | Util | NodeStatsTracker', function() { Timestamp: refDate + step, }); - test('the NodeStatsTracker constructor expects a fetch definition and a node', async function(assert) { + test('the NodeStatsTracker constructor expects a fetch definition and a node', async function (assert) { const tracker = NodeStatsTracker.create(); assert.throws( () => { @@ -44,7 +46,7 @@ module('Unit | Util | NodeStatsTracker', function() { ); }); - test('the url property is computed based off the node id', async function(assert) { + test('the url property is computed based off the node id', async function (assert) { const node = MockNode(); const tracker = NodeStatsTracker.create({ fetch, node }); @@ -55,11 +57,15 @@ module('Unit | Util | NodeStatsTracker', function() { ); }); - test('reservedCPU and reservedMemory properties come from the node', async function(assert) { + test('reservedCPU and reservedMemory properties come from the node', async function (assert) { const node = MockNode(); const tracker = NodeStatsTracker.create({ fetch, node }); - assert.equal(tracker.get('reservedCPU'), node.resources.cpu, 'reservedCPU comes from the node'); + assert.equal( + tracker.get('reservedCPU'), + node.resources.cpu, + 'reservedCPU comes from the node' + ); assert.equal( tracker.get('reservedMemory'), node.resources.memory, @@ -67,9 +73,13 @@ module('Unit | Util | NodeStatsTracker', function() { ); }); - test('poll results in requesting the url and calling append with the resulting JSON', async function(assert) { + test('poll results in requesting the url and calling append with the resulting JSON', async function (assert) { const node = MockNode(); - const tracker = NodeStatsTracker.create({ fetch, node, append: sinon.spy() }); + const tracker = NodeStatsTracker.create({ + fetch, + node, + append: sinon.spy(), + }); const mockFrame = { Some: { data: ['goes', 'here'], @@ -77,7 +87,7 @@ module('Unit | Util | NodeStatsTracker', function() { }, }; - const server = new Pretender(function() { + const server = new Pretender(function () { this.get('/v1/client/stats', () => [200, {}, JSON.stringify(mockFrame)]); }); @@ -99,7 +109,7 @@ module('Unit | Util | NodeStatsTracker', function() { server.shutdown(); }); - test('append appropriately maps a data frame to the tracked stats for cpu and memory for the node', async function(assert) { + test('append appropriately maps a data frame to the tracked stats for cpu and memory for the node', async function (assert) { const node = MockNode(); const tracker = NodeStatsTracker.create({ fetch, node }); @@ -116,7 +126,13 @@ module('Unit | Util | NodeStatsTracker', function() { assert.deepEqual( tracker.get('memory'), - [{ timestamp: makeDate(refDate + 1), used: 2049 * 1024 * 1024, percent: 2049 / 4096 }], + [ + { + timestamp: makeDate(refDate + 1), + used: 2049 * 1024 * 1024, + percent: 2049 / 4096, + }, + ], 'One frame of memory' ); @@ -134,14 +150,22 @@ module('Unit | Util | NodeStatsTracker', function() { assert.deepEqual( tracker.get('memory'), [ - { timestamp: makeDate(refDate + 1), used: 2049 * 1024 * 1024, percent: 2049 / 4096 }, - { timestamp: makeDate(refDate + 2), used: 2050 * 1024 * 1024, percent: 2050 / 4096 }, + { + timestamp: makeDate(refDate + 1), + used: 2049 * 1024 * 1024, + percent: 2049 / 4096, + }, + { + timestamp: makeDate(refDate + 2), + used: 2050 * 1024 * 1024, + percent: 2050 / 4096, + }, ], 'Two frames of memory' ); }); - test('each stat list has maxLength equal to bufferSize', async function(assert) { + test('each stat list has maxLength equal to bufferSize', async function (assert) { const node = MockNode(); const bufferSize = 10; const tracker = NodeStatsTracker.create({ fetch, node, bufferSize }); @@ -173,12 +197,12 @@ module('Unit | Util | NodeStatsTracker', function() { ); }); - test('the stats computed property macro constructs a NodeStatsTracker based on a nodeProp and a fetch definition', async function(assert) { + test('the stats computed property macro constructs a NodeStatsTracker based on a nodeProp and a fetch definition', async function (assert) { const node = MockNode(); const fetchSpy = sinon.spy(); const SomeClass = EmberObject.extend({ - stats: stats('theNode', function() { + stats: stats('theNode', function () { return () => fetchSpy(this); }), }); @@ -200,7 +224,7 @@ module('Unit | Util | NodeStatsTracker', function() { ); }); - test('changing the value of the nodeProp constructs a new NodeStatsTracker', async function(assert) { + test('changing the value of the nodeProp constructs a new NodeStatsTracker', async function (assert) { const node1 = MockNode(); const node2 = MockNode(); const SomeClass = EmberObject.extend({ @@ -216,8 +240,9 @@ module('Unit | Util | NodeStatsTracker', function() { someObject.set('theNode', node2); const stats2 = someObject.get('stats'); - assert.notOk( - stats1 === stats2, + assert.notStrictEqual( + stats1, + stats2, 'Changing the value of the node results in creating a new NodeStatsTracker instance' ); }); diff --git a/ui/tests/unit/utils/rolling-array-test.js b/ui/tests/unit/utils/rolling-array-test.js index f3af28cb4..9bf5c290a 100644 --- a/ui/tests/unit/utils/rolling-array-test.js +++ b/ui/tests/unit/utils/rolling-array-test.js @@ -2,8 +2,8 @@ import { isArray } from '@ember/array'; import { module, test } from 'qunit'; import RollingArray from 'nomad-ui/utils/classes/rolling-array'; -module('Unit | Util | RollingArray', function() { - test('has a maxLength property that gets set in the constructor', function(assert) { +module('Unit | Util | RollingArray', function () { + test('has a maxLength property that gets set in the constructor', function (assert) { const array = RollingArray(10, 'a', 'b', 'c'); assert.equal(array.maxLength, 10, 'maxLength is set in the constructor'); assert.deepEqual( @@ -13,7 +13,7 @@ module('Unit | Util | RollingArray', function() { ); }); - test('push works like Array#push', function(assert) { + test('push works like Array#push', function (assert) { const array = RollingArray(10); const pushReturn = array.push('a'); assert.equal( @@ -21,7 +21,11 @@ module('Unit | Util | RollingArray', function() { array.length, 'the return value from push is equal to the return value of Array#push' ); - assert.equal(array[0], 'a', 'the arguments passed to push are appended to the array'); + assert.equal( + array[0], + 'a', + 'the arguments passed to push are appended to the array' + ); array.push('b', 'c', 'd'); assert.deepEqual( @@ -31,7 +35,7 @@ module('Unit | Util | RollingArray', function() { ); }); - test('when pushing past maxLength, items are removed from the head of the array', function(assert) { + test('when pushing past maxLength, items are removed from the head of the array', function (assert) { const array = RollingArray(3); const pushReturn = array.push(1, 2, 3, 4); assert.deepEqual( @@ -46,7 +50,7 @@ module('Unit | Util | RollingArray', function() { ); }); - test('when splicing past maxLength, items are removed from the head of the array', function(assert) { + test('when splicing past maxLength, items are removed from the head of the array', function (assert) { const array = RollingArray(3, 'a', 'b', 'c'); array.splice(1, 0, 'z'); @@ -71,7 +75,7 @@ module('Unit | Util | RollingArray', function() { ); }); - test('unshift throws instead of prepending elements', function(assert) { + test('unshift throws instead of prepending elements', function (assert) { const array = RollingArray(5); assert.throws( @@ -83,9 +87,9 @@ module('Unit | Util | RollingArray', function() { ); }); - test('RollingArray is an instance of Array', function(assert) { + test('RollingArray is an instance of Array', function (assert) { const array = RollingArray(5); - assert.ok(array.constructor === Array, 'The constructor is Array'); + assert.strictEqual(array.constructor, Array, 'The constructor is Array'); assert.ok(array instanceof Array, 'The instanceof check is true'); assert.ok(isArray(array), 'The ember isArray helper works'); }); diff --git a/ui/tests/unit/utils/stream-frames-test.js b/ui/tests/unit/utils/stream-frames-test.js index 90bab68c8..c8877e52e 100644 --- a/ui/tests/unit/utils/stream-frames-test.js +++ b/ui/tests/unit/utils/stream-frames-test.js @@ -4,9 +4,9 @@ import { TextEncoderLite } from 'text-encoder-lite'; import base64js from 'base64-js'; const Encoder = new TextEncoderLite('utf-8'); -const encode = str => base64js.fromByteArray(Encoder.encode(str)); +const encode = (str) => base64js.fromByteArray(Encoder.encode(str)); -module('Unit | Util | stream-frames', function() { +module('Unit | Util | stream-frames', function () { const { btoa } = window; const decodeTestCases = [ { @@ -46,8 +46,8 @@ module('Unit | Util | stream-frames', function() { }, ]; - decodeTestCases.forEach(testCase => { - test(`decode: ${testCase.name}`, function(assert) { + decodeTestCases.forEach((testCase) => { + test(`decode: ${testCase.name}`, function (assert) { assert.deepEqual(decode(testCase.in), testCase.out); }); }); diff --git a/ui/tests/unit/utils/stream-logger-test.js b/ui/tests/unit/utils/stream-logger-test.js index b0cbb28a2..4bbf0c4e1 100644 --- a/ui/tests/unit/utils/stream-logger-test.js +++ b/ui/tests/unit/utils/stream-logger-test.js @@ -3,8 +3,8 @@ import { Promise } from 'rsvp'; import sinon from 'sinon'; import StreamLogger from 'nomad-ui/utils/classes/stream-logger'; -module('Unit | Util | StreamLogger', function() { - test('when a StreamLogger is stopped before the poll request responds, the request is immediately canceled upon completion', async function(assert) { +module('Unit | Util | StreamLogger', function () { + test('when a StreamLogger is stopped before the poll request responds, the request is immediately canceled upon completion', async function (assert) { const fetchMock = new FetchMock(); const fetch = fetchMock.request(); @@ -24,7 +24,7 @@ module('Unit | Util | StreamLogger', function() { assert.equal(fetchMock.reader.cancel.callCount, 1); }); - test('when the streaming request sends the done flag, the poll task completes', async function(assert) { + test('when the streaming request sends the done flag, the poll task completes', async function (assert) { const fetchMock = new FetchMock(); const fetch = fetchMock.request(); @@ -56,7 +56,7 @@ class FetchMock { if (this._closeRequest) { throw new Error('Can only call FetchMock.request once'); } - return new Promise(resolve => { + return new Promise((resolve) => { this._closeRequest = resolve; }); } @@ -65,7 +65,9 @@ class FetchMock { if (this._closeRequest) { this._closeRequest(this.response); } else { - throw new Error('Must call FetchMock.request() before FetchMock.closeRequest'); + throw new Error( + 'Must call FetchMock.request() before FetchMock.closeRequest' + ); } } } @@ -89,7 +91,7 @@ class ReadableStreamMock { read() { this.readSpy(); - return new Promise(resolve => { + return new Promise((resolve) => { resolve({ value: new ArrayBuffer(0), done: true }); }); } diff --git a/ui/tests/unit/utils/units-test.js b/ui/tests/unit/utils/units-test.js index bb2f06e02..5c8e5b39f 100644 --- a/ui/tests/unit/utils/units-test.js +++ b/ui/tests/unit/utils/units-test.js @@ -2,14 +2,14 @@ import { module, test } from 'qunit'; import * as units from 'nomad-ui/utils/units'; function table(fn, cases) { - cases.forEach(testCase => { - test(testCase.name || testCase.out, function(assert) { + cases.forEach((testCase) => { + test(testCase.name || testCase.out, function (assert) { assert.deepEqual(fn.apply(null, testCase.in), testCase.out); }); }); } -module('Unit | Util | units#formatBytes', function() { +module('Unit | Util | units#formatBytes', function () { table.call(this, units.formatBytes, [ { in: [null], out: '0 Bytes', name: 'formats null as 0 bytes' }, { in: [undefined], out: '0 Bytes', name: 'formats undefined as 0 bytes' }, @@ -18,11 +18,23 @@ module('Unit | Util | units#formatBytes', function() { { in: [1023], out: '1,023 Bytes' }, { in: [1024], out: '1 KiB', name: 'formats 1024 <= x < 1024^2 as KiB' }, { in: [1024 ** 2 - 1024 * 0.01], out: '1,023.99 KiB' }, - { in: [1024 ** 2], out: '1 MiB', name: 'formats 1024^2 <= x < 1024^3 as MiB' }, + { + in: [1024 ** 2], + out: '1 MiB', + name: 'formats 1024^2 <= x < 1024^3 as MiB', + }, { in: [1024 ** 2 * 1.016], out: '1.02 MiB' }, - { in: [1024 ** 3], out: '1 GiB', name: 'formats 1024^3 <= x < 1024^4 as GiB' }, + { + in: [1024 ** 3], + out: '1 GiB', + name: 'formats 1024^3 <= x < 1024^4 as GiB', + }, { in: [1024 ** 3 * 512.5], out: '512.5 GiB' }, - { in: [1024 ** 4], out: '1 TiB', name: 'formats 1024^4 <= x < 1024^5 as TiB' }, + { + in: [1024 ** 4], + out: '1 TiB', + name: 'formats 1024^4 <= x < 1024^5 as TiB', + }, { in: [1024 ** 4 * 2.1234], out: '2.12 TiB' }, { in: [1024 ** 5], out: '1 PiB', name: 'formats x > 1024^5 as PiB' }, { in: [1024 ** 5 * 4000], out: '4,000 PiB' }, @@ -31,11 +43,15 @@ module('Unit | Util | units#formatBytes', function() { out: '1 TiB', name: 'accepts a starting unit size as an optional argument', }, - { in: [1024 ** 2 * -1], out: '-1 MiB', name: 'negative values are still reduced' }, + { + in: [1024 ** 2 * -1], + out: '-1 MiB', + name: 'negative values are still reduced', + }, ]); }); -module('Unit | Util | units#formatScheduledBytes', function() { +module('Unit | Util | units#formatScheduledBytes', function () { table.call(this, units.formatScheduledBytes, [ { in: [null], out: '0 Bytes', name: 'formats null as 0 bytes' }, { in: [undefined], out: '0 Bytes', name: 'formats undefined as 0 bytes' }, @@ -55,11 +71,15 @@ module('Unit | Util | units#formatScheduledBytes', function() { out: '2,000 MiB', name: 'accepts a starting unit size as an optional argument', }, - { in: [1024 ** 3 * -1], out: '-1,024 MiB', name: 'negative values are still reduced' }, + { + in: [1024 ** 3 * -1], + out: '-1,024 MiB', + name: 'negative values are still reduced', + }, ]); }); -module('Unit | Util | units#formatHertz', function() { +module('Unit | Util | units#formatHertz', function () { table.call(this, units.formatHertz, [ { in: [null], out: '0 Hz', name: 'formats null as 0 Hz' }, { in: [undefined], out: '0 Hz', name: 'formats undefined as 0 Hz' }, @@ -67,11 +87,23 @@ module('Unit | Util | units#formatHertz', function() { { in: [999], out: '999 Hz' }, { in: [1000], out: '1 KHz', name: 'formats 1000 <= x < 1000^2 as KHz' }, { in: [1000 ** 2 - 10], out: '999.99 KHz' }, - { in: [1000 ** 2], out: '1 MHz', name: 'formats 1000^2 <= x < 1000^3 as MHz' }, + { + in: [1000 ** 2], + out: '1 MHz', + name: 'formats 1000^2 <= x < 1000^3 as MHz', + }, { in: [1000 ** 2 * 5.234], out: '5.23 MHz' }, - { in: [1000 ** 3], out: '1 GHz', name: 'formats 1000^3 <= x < 1000^4 as GHz' }, + { + in: [1000 ** 3], + out: '1 GHz', + name: 'formats 1000^3 <= x < 1000^4 as GHz', + }, { in: [1000 ** 3 * 500.238], out: '500.24 GHz' }, - { in: [1000 ** 4], out: '1 THz', name: 'formats 1000^4 <= x < 1000^5 as THz' }, + { + in: [1000 ** 4], + out: '1 THz', + name: 'formats 1000^4 <= x < 1000^5 as THz', + }, { in: [1000 ** 4 * 12], out: '12 THz' }, { in: [1000 ** 5], out: '1 PHz', name: 'formats x > 1000^5 as PHz' }, { in: [1000 ** 5 * 34567.89], out: '34,567.89 PHz' }, @@ -80,11 +112,15 @@ module('Unit | Util | units#formatHertz', function() { out: '2 MHz', name: 'accepts a starting unit size as an optional argument', }, - { in: [1000 ** 3 * -1], out: '-1 GHz', name: 'negative values are still reduced' }, + { + in: [1000 ** 3 * -1], + out: '-1 GHz', + name: 'negative values are still reduced', + }, ]); }); -module('Unit | Util | units#formatScheduledHertz', function() { +module('Unit | Util | units#formatScheduledHertz', function () { table.call(this, units.formatScheduledHertz, [ { in: [null], out: '0 Hz', name: 'formats null as 0 Hz' }, { in: [undefined], out: '0 Hz', name: 'formats undefined as 0 Hz' }, @@ -104,13 +140,21 @@ module('Unit | Util | units#formatScheduledHertz', function() { out: '2,000 MHz', name: 'accepts a starting unit size as an optional argument', }, - { in: [1000 ** 3 * -1], out: '-1,000 MHz', name: 'negative values are still reduced' }, + { + in: [1000 ** 3 * -1], + out: '-1,000 MHz', + name: 'negative values are still reduced', + }, ]); }); -module('Unit | Util | units#reduceBytes', function() { +module('Unit | Util | units#reduceBytes', function () { table.call(this, units.reduceBytes, [ - { in: [], out: [0, 'Bytes'], name: 'No args behavior results in valid output' }, + { + in: [], + out: [0, 'Bytes'], + name: 'No args behavior results in valid output', + }, { in: [1024 ** 6], out: [1024, 'PiB'], name: 'Max default unit is PiB' }, { in: [1024 ** 6 * 1.12345], @@ -127,13 +171,21 @@ module('Unit | Util | units#reduceBytes', function() { out: [1024, 'MiB'], name: 'accepts a starting unit size as an optional argument', }, - { in: [1024 ** 3 * -1], out: [-1, 'GiB'], name: 'negative values are still reduced' }, + { + in: [1024 ** 3 * -1], + out: [-1, 'GiB'], + name: 'negative values are still reduced', + }, ]); }); -module('Unit | Util | units#reduceHertz', function() { +module('Unit | Util | units#reduceHertz', function () { table.call(this, units.reduceHertz, [ - { in: [], out: [0, 'Hz'], name: 'No args behavior results in valid output' }, + { + in: [], + out: [0, 'Hz'], + name: 'No args behavior results in valid output', + }, { in: [1000 ** 6], out: [1000, 'PHz'], name: 'Max default unit is PHz' }, { in: [1000 ** 6 * 1.12345], @@ -150,6 +202,10 @@ module('Unit | Util | units#reduceHertz', function() { out: [2, 'GHz'], name: 'accepts a starting unit size as an optional argument', }, - { in: [1000 ** 3 * -1], out: [-1, 'GHz'], name: 'negative values are still reduced' }, + { + in: [1000 ** 3 * -1], + out: [-1, 'GHz'], + name: 'negative values are still reduced', + }, ]); }); diff --git a/ui/tests/utils/clean-whitespace.js b/ui/tests/utils/clean-whitespace.js index cc3d5282e..13427811c 100644 --- a/ui/tests/utils/clean-whitespace.js +++ b/ui/tests/utils/clean-whitespace.js @@ -1,8 +1,5 @@ // cleans whitespace from a string, for example for cleaning // textContent in DOM nodes with indentation export default function cleanWhitespace(string) { - return string - .replace(/\n/g, '') - .replace(/ +/g, ' ') - .trim(); + return string.replace(/\n/g, '').replace(/ +/g, ' ').trim(); } diff --git a/ui/tests/utils/ember-power-select-extensions.js b/ui/tests/utils/ember-power-select-extensions.js index 245d8ec45..1e63256ac 100644 --- a/ui/tests/utils/ember-power-select-extensions.js +++ b/ui/tests/utils/ember-power-select-extensions.js @@ -10,12 +10,17 @@ import { click, settled } from '@ember/test-helpers'; // these two moments. Doing it before opening means hanging on open not on select. Doing it // after means hanging after the select has occurred (too late). async function openIfClosedAndGetContentId(trigger) { - let contentId = trigger.attributes['aria-owns'] && `${trigger.attributes['aria-owns'].value}`; + let contentId = + trigger.attributes['aria-owns'] && + `${trigger.attributes['aria-owns'].value}`; let content = contentId ? document.querySelector(`#${contentId}`) : undefined; // If the dropdown is closed, open it - if (!content || content.classList.contains('ember-basic-dropdown-content-placeholder')) { + if ( + !content || + content.classList.contains('ember-basic-dropdown-content-placeholder') + ) { await click(trigger); - await settled(); + contentId = `${trigger.attributes['aria-owns'].value}`; } return contentId; @@ -30,7 +35,9 @@ export async function selectOpen(cssPathOrTrigger) { trigger = cssPathOrTrigger.querySelector('.ember-power-select-trigger'); } } else { - trigger = document.querySelector(`${cssPathOrTrigger} .ember-power-select-trigger`); + trigger = document.querySelector( + `${cssPathOrTrigger} .ember-power-select-trigger` + ); if (!trigger) { trigger = document.querySelector(cssPathOrTrigger); @@ -50,20 +57,28 @@ export async function selectOpen(cssPathOrTrigger) { return await openIfClosedAndGetContentId(trigger); } -export async function selectOpenChoose(contentId, valueOrSelector, optionIndex) { +export async function selectOpenChoose( + contentId, + valueOrSelector, + optionIndex +) { let target; // Select the option with the given text - let options = document.querySelectorAll(`#${contentId} .ember-power-select-option`); + let options = document.querySelectorAll( + `#${contentId} .ember-power-select-option` + ); let potentialTargets = [].slice .apply(options) - .filter(opt => opt.textContent.indexOf(valueOrSelector) > -1); + .filter((opt) => opt.textContent.indexOf(valueOrSelector) > -1); if (potentialTargets.length === 0) { - potentialTargets = document.querySelectorAll(`#${contentId} ${valueOrSelector}`); + potentialTargets = document.querySelectorAll( + `#${contentId} ${valueOrSelector}` + ); } if (potentialTargets.length > 1) { let filteredTargets = [].slice .apply(potentialTargets) - .filter(t => t.textContent.trim() === valueOrSelector); + .filter((t) => t.textContent.trim() === valueOrSelector); if (optionIndex === undefined) { target = filteredTargets[0] || potentialTargets[0]; } else { diff --git a/ui/yarn.lock b/ui/yarn.lock index fefe48c54..576b50462 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -9,7 +9,7 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": +"@babel/code-frame@7.12.11", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== @@ -2603,19 +2603,18 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@eslint/eslintrc@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" - integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg== +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== dependencies: ajv "^6.12.4" debug "^4.1.1" espree "^7.3.0" - globals "^12.1.0" + globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" - lodash "^4.17.20" minimatch "^3.0.4" strip-json-comments "^3.1.1" @@ -2840,6 +2839,20 @@ resolved "https://registry.yarnpkg.com/@hashicorp/structure-icons/-/structure-icons-1.9.2.tgz#c75f955b2eec414ecb92f3926c79b4ca01731d3c" integrity sha512-AffJv0V9npr1EWlM1XrpaKPG9PzddV48OE+jspqy7aRoFZB5++oNnPx4MZQKmFInOljnzNwfktVRkJiQHy7haw== +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + "@mdx-js/mdx@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" @@ -4223,10 +4236,10 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.0.4.tgz#827e5f5ae32f5e5c1637db61f253a112229b5e2f" - integrity sha512-xzzzaqgEQfmuhbhAoqjJ8T/1okb6gAzXn/eQRNpAN1AEUoHJTNF9xCDRTtf/s3SKldtZfa+RJeTs+BQq+eZ/sw== +ajv@^8.0.1: + version "8.8.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" + integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -7675,6 +7688,14 @@ css-tree@1.0.0-alpha.29: mdn-data "~1.1.0" source-map "^0.5.3" +css-tree@^1.0.0-alpha.39: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-url-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec" @@ -9940,20 +9961,29 @@ escodegen@^1.14.1: optionalDependencies: source-map "~0.6.1" +eslint-config-prettier@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== + eslint-plugin-ember-a11y-testing@a11y-tool-sandbox/eslint-plugin-ember-a11y-testing#ca31c9698c7cb105f1c9761d98fcaca7d6874459: version "0.0.0" resolved "https://codeload.github.com/a11y-tool-sandbox/eslint-plugin-ember-a11y-testing/tar.gz/ca31c9698c7cb105f1c9761d98fcaca7d6874459" dependencies: requireindex "~1.1.0" -eslint-plugin-ember@^8.9.1: - version "8.14.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-8.14.0.tgz#fc4c5119b5a1c87604a7bf920ca397783b96b7be" - integrity sha512-PQhR58omMAZzcJOB8GLWzL6l/vjRZ2Uo1eohxOmgPUyeBkHfZAMlvK+OSeneMLIr4azLQ4GISkklwv6lxb8qnw== +eslint-plugin-ember@^10.5.8: + version "10.5.8" + resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-10.5.8.tgz#87e004a5ebed88f94008364554daf57df2c9c718" + integrity sha512-d21mJ+F+htgi6HhrjwbOfllJojF4ZWGruW13HkBoGS2SaHqKUyvIH/8j3EjSxlsGFiNfhTEUWkNaUSLJxgbtWg== dependencies: "@ember-data/rfc395-data" "^0.0.4" + css-tree "^1.0.0-alpha.39" ember-rfc176-data "^0.3.15" + eslint-utils "^3.0.0" + estraverse "^5.2.0" lodash.kebabcase "^4.1.1" + requireindex "^1.2.0" snake-case "^3.0.3" eslint-plugin-es@^3.0.0: @@ -9976,6 +10006,21 @@ eslint-plugin-node@^11.1.0: resolve "^1.10.1" semver "^6.1.0" +eslint-plugin-prettier@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5" + integrity sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-qunit@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-qunit/-/eslint-plugin-qunit-6.2.0.tgz#f4efda29da99523e560848d9592c39c0590c308d" + integrity sha512-KvPmkIC2MHpfRxs/r8WUeeGkG6y+3qwSi2AZIBtjcM/YG6Z3k0GxW5Hbu3l7X0TDhljVCeBb9Q5puUkHzl83Mw== + dependencies: + eslint-utils "^3.0.0" + requireindex "^1.2.0" + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -9999,6 +10044,13 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" @@ -10009,29 +10061,32 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint@^7.5.0: - version "7.19.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.19.0.tgz#6719621b196b5fad72e43387981314e5d0dc3f41" - integrity sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg== +eslint@^7.32.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: - "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.3.0" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" enquirer "^2.3.5" + escape-string-regexp "^4.0.0" eslint-scope "^5.1.1" eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" espree "^7.3.1" - esquery "^1.2.0" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^6.0.0" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" + glob-parent "^5.1.2" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -10039,7 +10094,7 @@ eslint@^7.5.0: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.20" + lodash.merge "^4.6.2" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -10048,7 +10103,7 @@ eslint@^7.5.0: semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" - table "^6.0.4" + table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" @@ -10076,10 +10131,10 @@ esprima@~3.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9" integrity sha1-U88kes2ncxPlUcOqLnM0LT+099k= -esquery@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -10351,6 +10406,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -10475,10 +10535,10 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a" - integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" @@ -11161,14 +11221,14 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@^5.1.0: +glob-parent@^5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== dependencies: is-glob "^4.0.1" -glob-parent@~5.1.0, glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -11271,12 +11331,12 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== +globals@^13.6.0, globals@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" + integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== dependencies: - type-fest "^0.8.1" + type-fest "^0.20.2" globals@^9.18.0: version "9.18.0" @@ -13397,6 +13457,11 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + lodash.uniq@4.5.0, lodash.uniq@^4.2.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -13664,6 +13729,11 @@ mdast-util-to-hast@10.0.1: unist-util-position "^3.0.0" unist-util-visit "^2.0.0" +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + mdn-data@~1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" @@ -15233,10 +15303,17 @@ pretender@^3.4.3: fake-xml-http-request "^2.1.1" route-recognizer "^0.3.3" -prettier@^1.4.4: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" + integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== prettier@~2.2.1: version "2.2.1" @@ -16180,6 +16257,11 @@ require-relative@^0.8.7: resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= +requireindex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" + integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== + requireindex@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162" @@ -17226,7 +17308,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -17539,15 +17621,16 @@ sync-disk-cache@^2.0.0: rimraf "^3.0.0" username-sync "^1.0.2" -table@^6.0.4: - version "6.0.7" - resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34" - integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g== +table@^6.0.9: + version "6.7.5" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.5.tgz#f04478c351ef3d8c7904f0e8be90a1b62417d238" + integrity sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw== dependencies: - ajv "^7.0.2" - lodash "^4.17.20" + ajv "^8.0.1" + lodash.truncate "^4.4.2" slice-ansi "^4.0.0" - string-width "^4.2.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" tap-parser@^7.0.0: version "7.0.0" @@ -18069,6 +18152,11 @@ type-fest@^0.11.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"