mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	UI: Ember deprecation - addObject, removeObject (#25952)
* Update add-to-array and remove-from-array helpers * remove search-select-has-many, moved logic directly into mfa-login-enforcement-form (see #16470) * Replace add/remove object in MFA files - All MFA tests pass * Replace in PKI components (pki tests all passing) * Replace in core addon where applicable * glimmerize console service -- console tests pass * more replacements * update string-list, add comment to vertical-bar-chart * Refactor CSP Event service - only used one place (auth-form) so simplified that usage - glimmerize and refactor so that the tests work * small updates * more cleanup * Fix tests * Remove objectAt from console-helpers * Address PR comments * move commandIndex clearing back * Remove extra model set
This commit is contained in:
		| @@ -144,7 +144,7 @@ export default ApplicationAdapter.extend({ | |||||||
|   async _updateAllowedRoles(store, { role, backend, db, type = 'add' }) { |   async _updateAllowedRoles(store, { role, backend, db, type = 'add' }) { | ||||||
|     const connection = await store.queryRecord('database/connection', { backend, id: db }); |     const connection = await store.queryRecord('database/connection', { backend, id: db }); | ||||||
|     const roles = [...(connection.allowed_roles || [])]; |     const roles = [...(connection.allowed_roles || [])]; | ||||||
|     const allowedRoles = type === 'add' ? addToArray([roles, role]) : removeFromArray([roles, role]); |     const allowedRoles = type === 'add' ? addToArray(roles, role) : removeFromArray(roles, role); | ||||||
|     connection.allowed_roles = allowedRoles; |     connection.allowed_roles = allowedRoles; | ||||||
|     return connection.save(); |     return connection.save(); | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import NamedPathAdapter from 'vault/adapters/named-path'; | |||||||
| import { encodePath } from 'vault/utils/path-encoding-helpers'; | import { encodePath } from 'vault/utils/path-encoding-helpers'; | ||||||
| import { service } from '@ember/service'; | import { service } from '@ember/service'; | ||||||
| import AdapterError from '@ember-data/adapter/error'; | import AdapterError from '@ember-data/adapter/error'; | ||||||
|  | import { addManyToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| export default class LdapRoleAdapter extends NamedPathAdapter { | export default class LdapRoleAdapter extends NamedPathAdapter { | ||||||
|   @service flashMessages; |   @service flashMessages; | ||||||
| @@ -33,7 +34,7 @@ export default class LdapRoleAdapter extends NamedPathAdapter { | |||||||
|   async query(store, type, query, recordArray, options) { |   async query(store, type, query, recordArray, options) { | ||||||
|     const { showPartialError } = options.adapterOptions || {}; |     const { showPartialError } = options.adapterOptions || {}; | ||||||
|     const { backend } = query; |     const { backend } = query; | ||||||
|     const roles = []; |     let roles = []; | ||||||
|     const errors = []; |     const errors = []; | ||||||
|  |  | ||||||
|     for (const roleType of ['static', 'dynamic']) { |     for (const roleType of ['static', 'dynamic']) { | ||||||
| @@ -42,7 +43,7 @@ export default class LdapRoleAdapter extends NamedPathAdapter { | |||||||
|         const models = await this.ajax(url, 'GET', { data: { list: true } }).then((resp) => { |         const models = await this.ajax(url, 'GET', { data: { list: true } }).then((resp) => { | ||||||
|           return resp.data.keys.map((name) => ({ id: name, name, backend, type: roleType })); |           return resp.data.keys.map((name) => ({ id: name, name, backend, type: roleType })); | ||||||
|         }); |         }); | ||||||
|         roles.addObjects(models); |         roles = addManyToArray(roles, models); | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|         if (error.httpStatus !== 404) { |         if (error.httpStatus !== 404) { | ||||||
|           errors.push(error); |           errors.push(error); | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
| import { next } from '@ember/runloop'; | import { next } from '@ember/runloop'; | ||||||
| import { service } from '@ember/service'; | import { service } from '@ember/service'; | ||||||
| import { match, alias, or } from '@ember/object/computed'; | import { match, or } from '@ember/object/computed'; | ||||||
| import { dasherize } from '@ember/string'; | import { dasherize } from '@ember/string'; | ||||||
| import Component from '@ember/component'; | import Component from '@ember/component'; | ||||||
| import { computed } from '@ember/object'; | import { computed } from '@ember/object'; | ||||||
| @@ -166,9 +166,12 @@ export default Component.extend(DEFAULTS, { | |||||||
|     return templateName; |     return templateName; | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   hasCSPError: alias('csp.connectionViolations'), |   cspError: computed('csp.connectionViolations.length', function () { | ||||||
|  |     if (this.csp.connectionViolations.length) { | ||||||
|   cspErrorText: `This is a standby Vault node but can't communicate with the active node via request forwarding. Sign in at the active node to use the Vault UI.`, |       return `This is a standby Vault node but can't communicate with the active node via request forwarding. Sign in at the active node to use the Vault UI.`; | ||||||
|  |     } | ||||||
|  |     return ''; | ||||||
|  |   }), | ||||||
|  |  | ||||||
|   allSupportedMethods: computed('methodsToShow', 'hasMethodsWithPath', 'authMethods', function () { |   allSupportedMethods: computed('methodsToShow', 'hasMethodsWithPath', 'authMethods', function () { | ||||||
|     const hasMethodsWithPath = this.hasMethodsWithPath; |     const hasMethodsWithPath = this.hasMethodsWithPath; | ||||||
|   | |||||||
| @@ -175,7 +175,9 @@ export default class HorizontalBarChart extends Component { | |||||||
|         this.isLabel = false; |         this.isLabel = false; | ||||||
|         this.tooltipText = []; // clear stats |         this.tooltipText = []; // clear stats | ||||||
|         this.args.chartLegend.forEach(({ key, label }) => { |         this.args.chartLegend.forEach(({ key, label }) => { | ||||||
|           this.tooltipText.pushObject(`${formatNumber([data[key]])} ${label}`); |           // since we're relying on D3 not ember reactivity, | ||||||
|  |           // pushing directly to this.tooltipText updates the DOM | ||||||
|  |           this.tooltipText.push(`${formatNumber([data[key]])} ${label}`); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         select(hoveredElement).style('opacity', 1); |         select(hoveredElement).style('opacity', 1); | ||||||
|   | |||||||
| @@ -155,7 +155,9 @@ export default class VerticalBarChart extends Component { | |||||||
|       this.tooltipStats = []; // clear stats |       this.tooltipStats = []; // clear stats | ||||||
|       this.args.chartLegend.forEach(({ key, label }) => { |       this.args.chartLegend.forEach(({ key, label }) => { | ||||||
|         stackedNumbers.push(data[key]); |         stackedNumbers.push(data[key]); | ||||||
|         this.tooltipStats.pushObject(`${formatNumber([data[key]])} ${label}`); |         // since we're relying on D3 not ember reactivity, | ||||||
|  |         // pushing directly to this.tooltipStats updates the DOM | ||||||
|  |         this.tooltipStats.push(`${formatNumber([data[key]])} ${label}`); | ||||||
|       }); |       }); | ||||||
|       this.tooltipTotal = `${formatNumber([calculateSum(stackedNumbers)])} ${ |       this.tooltipTotal = `${formatNumber([calculateSum(stackedNumbers)])} ${ | ||||||
|         data.new_clients ? 'total' : 'new' |         data.new_clients ? 'total' : 'new' | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { action } from '@ember/object'; | |||||||
| import { tracked } from '@glimmer/tracking'; | import { tracked } from '@glimmer/tracking'; | ||||||
| import { task } from 'ember-concurrency'; | import { task } from 'ember-concurrency'; | ||||||
| import { waitFor } from '@ember/test-waiters'; | import { waitFor } from '@ember/test-waiters'; | ||||||
|  | import { removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @module KeymgmtProviderEdit |  * @module KeymgmtProviderEdit | ||||||
| @@ -94,8 +95,9 @@ export default class KeymgmtProviderEdit extends Component { | |||||||
|   @action |   @action | ||||||
|   async onDeleteKey(model) { |   async onDeleteKey(model) { | ||||||
|     try { |     try { | ||||||
|  |       const providerKeys = removeFromArray(this.args.model.keys, model); | ||||||
|       await model.destroyRecord(); |       await model.destroyRecord(); | ||||||
|       this.args.model.keys.removeObject(model); |       this.args.model.keys = providerKeys; | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       this.flashMessages.danger(error.errors.join('. ')); |       this.flashMessages.danger(error.errors.join('. ')); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -8,7 +8,8 @@ import { tracked } from '@glimmer/tracking'; | |||||||
| import { action } from '@ember/object'; | import { action } from '@ember/object'; | ||||||
| import { service } from '@ember/service'; | import { service } from '@ember/service'; | ||||||
| import { task } from 'ember-concurrency'; | import { task } from 'ember-concurrency'; | ||||||
| import handleHasManySelection from 'core/utils/search-select-has-many'; | import { addManyToArray, addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  | import { removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @module MfaLoginEnforcementForm |  * @module MfaLoginEnforcementForm | ||||||
| @@ -64,7 +65,7 @@ export default class MfaLoginEnforcementForm extends Component { | |||||||
|     for (const { label, key } of this.targetTypes) { |     for (const { label, key } of this.targetTypes) { | ||||||
|       const targetArray = await this.args.model[key]; |       const targetArray = await this.args.model[key]; | ||||||
|       const targets = targetArray.map((value) => ({ label, key, value })); |       const targets = targetArray.map((value) => ({ label, key, value })); | ||||||
|       this.targets.addObjects(targets); |       this.targets = addManyToArray(this.targets, targets); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   async resetTargetState() { |   async resetTargetState() { | ||||||
| @@ -102,7 +103,6 @@ export default class MfaLoginEnforcementForm extends Component { | |||||||
|  |  | ||||||
|   updateModelForKey(key) { |   updateModelForKey(key) { | ||||||
|     const newValue = this.targets.filter((t) => t.key === key).map((t) => t.value); |     const newValue = this.targets.filter((t) => t.key === key).map((t) => t.value); | ||||||
|     // Set the model value directly instead of using Array methods (like .addObject) |  | ||||||
|     this.args.model[key] = newValue; |     this.args.model[key] = newValue; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -126,8 +126,18 @@ export default class MfaLoginEnforcementForm extends Component { | |||||||
|  |  | ||||||
|   @action |   @action | ||||||
|   async onMethodChange(selectedIds) { |   async onMethodChange(selectedIds) { | ||||||
|  |     // first make sure the async relationship is loaded | ||||||
|     const methods = await this.args.model.mfa_methods; |     const methods = await this.args.model.mfa_methods; | ||||||
|     handleHasManySelection(selectedIds, methods, this.store, 'mfa-method'); |     // then remove items that are no longer selected | ||||||
|  |     const updatedList = methods.filter((model) => { | ||||||
|  |       return selectedIds.includes(model.id); | ||||||
|  |     }); | ||||||
|  |     // then add selected items that don't exist in the list already | ||||||
|  |     const modelIds = updatedList.map((model) => model.id); | ||||||
|  |     const toAdd = selectedIds | ||||||
|  |       .filter((id) => !modelIds.includes(id)) | ||||||
|  |       .map((id) => this.store.peekRecord('mfa-method', id)); | ||||||
|  |     this.args.model.mfa_methods = addManyToArray(updatedList, toAdd); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @action |   @action | ||||||
| @@ -150,7 +160,7 @@ export default class MfaLoginEnforcementForm extends Component { | |||||||
|   addTarget() { |   addTarget() { | ||||||
|     const { label, key } = this.selectedTarget; |     const { label, key } = this.selectedTarget; | ||||||
|     const value = this.selectedTargetValue; |     const value = this.selectedTargetValue; | ||||||
|     this.targets.addObject({ label, value, key }); |     this.targets = addToArray(this.targets, { label, value, key }); | ||||||
|     // recalculate value for appropriate model property |     // recalculate value for appropriate model property | ||||||
|     this.updateModelForKey(key); |     this.updateModelForKey(key); | ||||||
|     this.selectedTargetValue = null; |     this.selectedTargetValue = null; | ||||||
| @@ -158,7 +168,7 @@ export default class MfaLoginEnforcementForm extends Component { | |||||||
|   } |   } | ||||||
|   @action |   @action | ||||||
|   removeTarget(target) { |   removeTarget(target) { | ||||||
|     this.targets.removeObject(target); |     this.targets = removeFromArray(this.targets, target); | ||||||
|     // recalculate value for appropriate model property |     // recalculate value for appropriate model property | ||||||
|     this.updateModelForKey(target.key); |     this.updateModelForKey(target.key); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -195,6 +195,7 @@ export default class SecretCreateOrUpdate extends Component { | |||||||
|     if (isBlank(item.name)) { |     if (isBlank(item.name)) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |     // secretData is a KVObject/ArrayProxy so removeObject is fine here | ||||||
|     data.removeObject(item); |     data.removeObject(item); | ||||||
|     this.checkRows(); |     this.checkRows(); | ||||||
|     this.handleChange(); |     this.handleChange(); | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { tracked } from '@glimmer/tracking'; | |||||||
| import { action } from '@ember/object'; | import { action } from '@ember/object'; | ||||||
| import { capitalize } from '@ember/string'; | import { capitalize } from '@ember/string'; | ||||||
| import { task } from 'ember-concurrency'; | import { task } from 'ember-concurrency'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| export default class MfaMethodCreateController extends Controller { | export default class MfaMethodCreateController extends Controller { | ||||||
|   @service store; |   @service store; | ||||||
| @@ -95,7 +96,8 @@ export default class MfaMethodCreateController extends Controller { | |||||||
|         // first save method |         // first save method | ||||||
|         yield this.method.save(); |         yield this.method.save(); | ||||||
|         if (this.enforcement) { |         if (this.enforcement) { | ||||||
|           this.enforcement.mfa_methods.addObject(this.method); |           // mfa_methods is type PromiseManyArray so slice in necessary to convert it to an Array | ||||||
|  |           this.enforcement.mfa_methods = addToArray(this.enforcement.mfa_methods.slice(), this.method); | ||||||
|           try { |           try { | ||||||
|             // now save enforcement and catch error separately |             // now save enforcement and catch error separately | ||||||
|             yield this.enforcement.save(); |             yield this.enforcement.save(); | ||||||
|   | |||||||
| @@ -10,7 +10,13 @@ function dedupe(items) { | |||||||
|   return items.filter((v, i) => items.indexOf(v) === i); |   return items.filter((v, i) => items.indexOf(v) === i); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function addToArray([array, string]) { | export function addManyToArray(array, otherArray) { | ||||||
|  |   assert(`Both values must be an array`, Array.isArray(array) && Array.isArray(otherArray)); | ||||||
|  |   const newArray = [...array].concat(otherArray); | ||||||
|  |   return dedupe(newArray); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function addToArray(array, string) { | ||||||
|   if (!Array.isArray(array)) { |   if (!Array.isArray(array)) { | ||||||
|     assert(`Value provided is not an array`, false); |     assert(`Value provided is not an array`, false); | ||||||
|   } |   } | ||||||
| @@ -19,4 +25,9 @@ export function addToArray([array, string]) { | |||||||
|   return dedupe(newArray); |   return dedupe(newArray); | ||||||
| } | } | ||||||
|  |  | ||||||
| export default buildHelper(addToArray); | export default buildHelper(function ([array, string]) { | ||||||
|  |   if (Array.isArray(string)) { | ||||||
|  |     return addManyToArray(array, string); | ||||||
|  |   } | ||||||
|  |   return addToArray(array, string); | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -10,10 +10,14 @@ function dedupe(items) { | |||||||
|   return items.filter((v, i) => items.indexOf(v) === i); |   return items.filter((v, i) => items.indexOf(v) === i); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function removeFromArray([array, string]) { | export function removeManyFromArray(array, toRemove) { | ||||||
|   if (!Array.isArray(array)) { |   assert(`Both values must be an array`, Array.isArray(array) && Array.isArray(toRemove)); | ||||||
|     assert(`Value provided is not an array`, false); |   const a = [...(array || [])]; | ||||||
|   } |   return a.filter((v) => !toRemove.includes(v)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function removeFromArray(array, string) { | ||||||
|  |   assert(`Value provided is not an array`, Array.isArray(array)); | ||||||
|   const newArray = [...array]; |   const newArray = [...array]; | ||||||
|   const idx = newArray.indexOf(string); |   const idx = newArray.indexOf(string); | ||||||
|   if (idx >= 0) { |   if (idx >= 0) { | ||||||
| @@ -22,4 +26,9 @@ export function removeFromArray([array, string]) { | |||||||
|   return dedupe(newArray); |   return dedupe(newArray); | ||||||
| } | } | ||||||
|  |  | ||||||
| export default buildHelper(removeFromArray); | export default buildHelper(function ([array, string]) { | ||||||
|  |   if (Array.isArray(string)) { | ||||||
|  |     return removeManyFromArray(array, string); | ||||||
|  |   } | ||||||
|  |   return removeFromArray(array, string); | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -241,7 +241,8 @@ export function shiftCommandIndex(keyCode: number, history: CommandLog[], index: | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (newInputValue !== '') { |   if (newInputValue !== '') { | ||||||
|     newInputValue = history.objectAt(index)?.content; |     const objAt = history[index]; | ||||||
|  |     newInputValue = objAt?.content; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return [index, newInputValue]; |   return [index, newInputValue]; | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import { computed } from '@ember/object'; | |||||||
| import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs'; | import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs'; | ||||||
| import apiPath from 'vault/utils/api-path'; | import apiPath from 'vault/utils/api-path'; | ||||||
| import lazyCapabilities from 'vault/macros/lazy-capabilities'; | import lazyCapabilities from 'vault/macros/lazy-capabilities'; | ||||||
|  | import { removeManyFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  |  | ||||||
| export const COMPUTEDS = { | export const COMPUTEDS = { | ||||||
|   operationFields: computed('newFields', function () { |   operationFields: computed('newFields', function () { | ||||||
| @@ -15,7 +16,7 @@ export const COMPUTEDS = { | |||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   operationFieldsWithoutSpecial: computed('operationFields', function () { |   operationFieldsWithoutSpecial: computed('operationFields', function () { | ||||||
|     return this.operationFields.slice().removeObjects(['operationAll', 'operationNone']); |     return removeManyFromArray(this.operationFields, ['operationAll', 'operationNone']); | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   tlsFields: computed(function () { |   tlsFields: computed(function () { | ||||||
| @@ -25,12 +26,12 @@ export const COMPUTEDS = { | |||||||
|   // For rendering on the create/edit pages |   // For rendering on the create/edit pages | ||||||
|   defaultFields: computed('newFields', 'operationFields', 'tlsFields', function () { |   defaultFields: computed('newFields', 'operationFields', 'tlsFields', function () { | ||||||
|     const excludeFields = ['role'].concat(this.operationFields, this.tlsFields); |     const excludeFields = ['role'].concat(this.operationFields, this.tlsFields); | ||||||
|     return this.newFields.slice().removeObjects(excludeFields); |     return removeManyFromArray(this.newFields, excludeFields); | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   // For adapter/serializer |   // For adapter/serializer | ||||||
|   nonOperationFields: computed('newFields', 'operationFields', function () { |   nonOperationFields: computed('newFields', 'operationFields', function () { | ||||||
|     return this.newFields.slice().removeObjects(this.operationFields); |     return removeManyFromArray(this.newFields, this.operationFields); | ||||||
|   }), |   }), | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -64,9 +65,11 @@ export default Model.extend(COMPUTEDS, { | |||||||
|  |  | ||||||
|     const attributes = ['operationAddAttribute', 'operationGetAttributes']; |     const attributes = ['operationAddAttribute', 'operationGetAttributes']; | ||||||
|     const server = ['operationDiscoverVersions']; |     const server = ['operationDiscoverVersions']; | ||||||
|     const others = this.operationFieldsWithoutSpecial |     const others = removeManyFromArray(this.operationFieldsWithoutSpecial, [ | ||||||
|       .slice() |       ...objects, | ||||||
|       .removeObjects(objects.concat(attributes, server)); |       ...attributes, | ||||||
|  |       ...server, | ||||||
|  |     ]); | ||||||
|     const groups = [ |     const groups = [ | ||||||
|       { 'Managed Cryptographic Objects': objects }, |       { 'Managed Cryptographic Objects': objects }, | ||||||
|       { 'Object Attributes': attributes }, |       { 'Object Attributes': attributes }, | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import { methods } from 'vault/helpers/mountable-auth-methods'; | |||||||
| import { withModelValidations } from 'vault/decorators/model-validations'; | import { withModelValidations } from 'vault/decorators/model-validations'; | ||||||
| import { isPresent } from '@ember/utils'; | import { isPresent } from '@ember/utils'; | ||||||
| import { service } from '@ember/service'; | import { service } from '@ember/service'; | ||||||
|  | import { addManyToArray, addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| const validations = { | const validations = { | ||||||
|   name: [{ type: 'presence', message: 'Name is required' }], |   name: [{ type: 'presence', message: 'Name is required' }], | ||||||
| @@ -52,7 +53,7 @@ export default class MfaLoginEnforcementModel extends Model { | |||||||
|  |  | ||||||
|   async prepareTargets() { |   async prepareTargets() { | ||||||
|     let authMethods; |     let authMethods; | ||||||
|     const targets = []; |     let targets = []; | ||||||
|  |  | ||||||
|     if (this.auth_method_accessors.length || this.auth_method_types.length) { |     if (this.auth_method_accessors.length || this.auth_method_types.length) { | ||||||
|       // fetch all auth methods and lookup by accessor to get mount path and type |       // fetch all auth methods and lookup by accessor to get mount path and type | ||||||
| @@ -68,7 +69,8 @@ export default class MfaLoginEnforcementModel extends Model { | |||||||
|       const selectedAuthMethods = authMethods.filter((model) => { |       const selectedAuthMethods = authMethods.filter((model) => { | ||||||
|         return this.auth_method_accessors.includes(model.accessor); |         return this.auth_method_accessors.includes(model.accessor); | ||||||
|       }); |       }); | ||||||
|       targets.addObjects( |       targets = addManyToArray( | ||||||
|  |         targets, | ||||||
|         selectedAuthMethods.map((method) => ({ |         selectedAuthMethods.map((method) => ({ | ||||||
|           icon: this.iconForMount(method.type), |           icon: this.iconForMount(method.type), | ||||||
|           link: 'vault.cluster.access.method', |           link: 'vault.cluster.access.method', | ||||||
| @@ -82,7 +84,7 @@ export default class MfaLoginEnforcementModel extends Model { | |||||||
|     this.auth_method_types.forEach((type) => { |     this.auth_method_types.forEach((type) => { | ||||||
|       const icon = this.iconForMount(type); |       const icon = this.iconForMount(type); | ||||||
|       const mountCount = authMethods.filterBy('type', type).length; |       const mountCount = authMethods.filterBy('type', type).length; | ||||||
|       targets.addObject({ |       targets = addToArray(targets, { | ||||||
|         key: 'auth_method_types', |         key: 'auth_method_types', | ||||||
|         icon, |         icon, | ||||||
|         title: type, |         title: type, | ||||||
| @@ -92,7 +94,7 @@ export default class MfaLoginEnforcementModel extends Model { | |||||||
|  |  | ||||||
|     for (const key of ['identity_entities', 'identity_groups']) { |     for (const key of ['identity_entities', 'identity_groups']) { | ||||||
|       (await this[key]).forEach((model) => { |       (await this[key]).forEach((model) => { | ||||||
|         targets.addObject({ |         targets = addToArray(targets, { | ||||||
|           key, |           key, | ||||||
|           icon: 'user', |           icon: 'user', | ||||||
|           link: 'vault.cluster.access.identity.show', |           link: 'vault.cluster.access.identity.show', | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ export default class SyncDestinationSerializer extends ApplicationSerializer { | |||||||
|         const type = key.replace(/\/$/, ''); |         const type = key.replace(/\/$/, ''); | ||||||
|         const id = `${type}/${name}`; |         const id = `${type}/${name}`; | ||||||
|         // create object with destination's id and attributes, add to payload |         // create object with destination's id and attributes, add to payload | ||||||
|         transformedPayload.pushObject({ id, name, type }); |         transformedPayload.push({ id, name, type }); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|     return transformedPayload; |     return transformedPayload; | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import { resolve, reject } from 'rsvp'; | |||||||
| import getStorage from 'vault/lib/token-storage'; | import getStorage from 'vault/lib/token-storage'; | ||||||
| import ENV from 'vault/config/environment'; | import ENV from 'vault/config/environment'; | ||||||
| import { allSupportedAuthBackends } from 'vault/helpers/supported-auth-backends'; | import { allSupportedAuthBackends } from 'vault/helpers/supported-auth-backends'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| const TOKEN_SEPARATOR = '☃'; | const TOKEN_SEPARATOR = '☃'; | ||||||
| const TOKEN_PREFIX = 'vault-'; | const TOKEN_PREFIX = 'vault-'; | ||||||
| @@ -250,7 +251,6 @@ export default Service.extend({ | |||||||
|  |  | ||||||
|   persistAuthData() { |   persistAuthData() { | ||||||
|     const [firstArg, resp] = arguments; |     const [firstArg, resp] = arguments; | ||||||
|     const tokens = this.tokens; |  | ||||||
|     const currentNamespace = this.namespaceService.path || ''; |     const currentNamespace = this.namespaceService.path || ''; | ||||||
|     // dropdown vs tab format |     // dropdown vs tab format | ||||||
|     const mountPath = firstArg?.data?.path || firstArg?.selectedAuth; |     const mountPath = firstArg?.data?.path || firstArg?.selectedAuth; | ||||||
| @@ -303,8 +303,7 @@ export default Service.extend({ | |||||||
|     if (!data.displayName) { |     if (!data.displayName) { | ||||||
|       data.displayName = (this.getTokenData(tokenName) || {}).displayName; |       data.displayName = (this.getTokenData(tokenName) || {}).displayName; | ||||||
|     } |     } | ||||||
|     tokens.addObject(tokenName); |     this.set('tokens', addToArray(this.tokens, tokenName)); | ||||||
|     this.set('tokens', tokens); |  | ||||||
|     this.set('allowExpiration', false); |     this.set('allowExpiration', false); | ||||||
|     this.setTokenData(tokenName, data); |     this.setTokenData(tokenName, data); | ||||||
|     return resolve({ |     return resolve({ | ||||||
|   | |||||||
| @@ -6,12 +6,12 @@ | |||||||
| // Low level service that allows users to input paths to make requests to vault | // Low level service that allows users to input paths to make requests to vault | ||||||
| // this service provides the UI synecdote to the cli commands read, write, delete, and list | // this service provides the UI synecdote to the cli commands read, write, delete, and list | ||||||
| import Service from '@ember/service'; | import Service from '@ember/service'; | ||||||
|  |  | ||||||
| import { getOwner } from '@ember/application'; | import { getOwner } from '@ember/application'; | ||||||
| import { computed } from '@ember/object'; |  | ||||||
| import { shiftCommandIndex } from 'vault/lib/console-helpers'; | import { shiftCommandIndex } from 'vault/lib/console-helpers'; | ||||||
| import { encodePath } from 'vault/utils/path-encoding-helpers'; | import { encodePath } from 'vault/utils/path-encoding-helpers'; | ||||||
| import { sanitizePath, ensureTrailingSlash } from 'core/utils/sanitize-path'; | import { sanitizePath, ensureTrailingSlash } from 'core/utils/sanitize-path'; | ||||||
|  | import { tracked } from '@glimmer/tracking'; | ||||||
|  | import { addManyToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| const VERBS = { | const VERBS = { | ||||||
|   read: 'GET', |   read: 'GET', | ||||||
| @@ -20,51 +20,51 @@ const VERBS = { | |||||||
|   delete: 'DELETE', |   delete: 'DELETE', | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default Service.extend({ | export default class ConsoleService extends Service { | ||||||
|   isOpen: false, |   @tracked isOpen = false; | ||||||
|  |   @tracked commandIndex = null; | ||||||
|  |   @tracked log = []; | ||||||
|  |  | ||||||
|   adapter() { |  | ||||||
|     return getOwner(this).lookup('adapter:console'); |  | ||||||
|   }, |  | ||||||
|   get commandHistory() { |   get commandHistory() { | ||||||
|     return this.log.filter((log) => log.type === 'command'); |     return this.log.filter((log) => log.type === 'command'); | ||||||
|   }, |   } | ||||||
|   log: computed(function () { |  | ||||||
|     return []; |   // Not a getter so it can be stubbed in tests | ||||||
|   }), |   adapter() { | ||||||
|   commandIndex: null, |     return getOwner(this).lookup('adapter:console'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   shiftCommandIndex(keyCode, setCommandFn = () => {}) { |   shiftCommandIndex(keyCode, setCommandFn = () => {}) { | ||||||
|     const [newIndex, newCommand] = shiftCommandIndex(keyCode, this.commandHistory, this.commandIndex); |     const [newIndex, newCommand] = shiftCommandIndex(keyCode, this.commandHistory, this.commandIndex); | ||||||
|     if (newCommand !== undefined && newIndex !== undefined) { |     if (newCommand !== undefined && newIndex !== undefined) { | ||||||
|       this.set('commandIndex', newIndex); |       this.commandIndex = newIndex; | ||||||
|       setCommandFn(newCommand); |       setCommandFn(newCommand); | ||||||
|     } |     } | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   clearLog(clearAll = false) { |   clearLog(clearAll = false) { | ||||||
|     const log = this.log; |  | ||||||
|     let history; |     let history; | ||||||
|     if (!clearAll) { |     if (!clearAll) { | ||||||
|       history = this.commandHistory.slice(); |       history = this.commandHistory.slice(); | ||||||
|       history.setEach('hidden', true); |       history.setEach('hidden', true); | ||||||
|     } |     } | ||||||
|     log.clear(); |     this.log = []; | ||||||
|     if (history) { |     if (history) { | ||||||
|       log.addObjects(history); |       this.log = addManyToArray(this.log, history); | ||||||
|     } |     } | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   logAndOutput(command, logContent) { |   logAndOutput(command, logContent) { | ||||||
|     const log = this.log; |     const log = this.log.slice(); | ||||||
|     if (command) { |     if (command) { | ||||||
|       log.pushObject({ type: 'command', content: command }); |       log.push({ type: 'command', content: command }); | ||||||
|       this.set('commandIndex', null); |       this.commandIndex = null; | ||||||
|     } |     } | ||||||
|     if (logContent) { |     if (logContent) { | ||||||
|       log.pushObject(logContent); |       log.push(logContent); | ||||||
|     } |     } | ||||||
|   }, |     this.log = log; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   ajax(operation, path, options = {}) { |   ajax(operation, path, options = {}) { | ||||||
|     const verb = VERBS[operation]; |     const verb = VERBS[operation]; | ||||||
| @@ -75,7 +75,7 @@ export default Service.extend({ | |||||||
|       data, |       data, | ||||||
|       wrapTTL, |       wrapTTL, | ||||||
|     }); |     }); | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   kvGet(path, data, flags = {}) { |   kvGet(path, data, flags = {}) { | ||||||
|     const { wrapTTL, metadata } = flags; |     const { wrapTTL, metadata } = flags; | ||||||
| @@ -84,21 +84,21 @@ export default Service.extend({ | |||||||
|     const [backend, secretPath] = path.split(/\/(.+)?/); |     const [backend, secretPath] = path.split(/\/(.+)?/); | ||||||
|     const kvPath = `${backend}/${pathSegment}/${secretPath}`; |     const kvPath = `${backend}/${pathSegment}/${secretPath}`; | ||||||
|     return this.ajax('read', sanitizePath(kvPath), { wrapTTL }); |     return this.ajax('read', sanitizePath(kvPath), { wrapTTL }); | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   read(path, data, flags) { |   read(path, data, flags) { | ||||||
|     const wrapTTL = flags?.wrapTTL; |     const wrapTTL = flags?.wrapTTL; | ||||||
|     return this.ajax('read', sanitizePath(path), { wrapTTL }); |     return this.ajax('read', sanitizePath(path), { wrapTTL }); | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   write(path, data, flags) { |   write(path, data, flags) { | ||||||
|     const wrapTTL = flags?.wrapTTL; |     const wrapTTL = flags?.wrapTTL; | ||||||
|     return this.ajax('write', sanitizePath(path), { data, wrapTTL }); |     return this.ajax('write', sanitizePath(path), { data, wrapTTL }); | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   delete(path) { |   delete(path) { | ||||||
|     return this.ajax('delete', sanitizePath(path)); |     return this.ajax('delete', sanitizePath(path)); | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   list(path, data, flags) { |   list(path, data, flags) { | ||||||
|     const wrapTTL = flags?.wrapTTL; |     const wrapTTL = flags?.wrapTTL; | ||||||
| @@ -109,5 +109,5 @@ export default Service.extend({ | |||||||
|       }, |       }, | ||||||
|       wrapTTL, |       wrapTTL, | ||||||
|     }); |     }); | ||||||
|   }, |   } | ||||||
| }); | } | ||||||
|   | |||||||
| @@ -3,34 +3,35 @@ | |||||||
|  * SPDX-License-Identifier: BUSL-1.1 |  * SPDX-License-Identifier: BUSL-1.1 | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| /*eslint-disable no-constant-condition*/ |  | ||||||
| import { computed } from '@ember/object'; |  | ||||||
|  |  | ||||||
| import Service from '@ember/service'; | import Service from '@ember/service'; | ||||||
|  | import { tracked } from '@glimmer/tracking'; | ||||||
| import { task, waitForEvent } from 'ember-concurrency'; | import { task, waitForEvent } from 'ember-concurrency'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| export default Service.extend({ | export default class CspEventService extends Service { | ||||||
|   events: computed(function () { |   @tracked connectionViolations = []; | ||||||
|     return []; |  | ||||||
|   }), |  | ||||||
|   connectionViolations: computed('events.@each.violatedDirective', function () { |  | ||||||
|     return this.events.some((e) => e.violatedDirective.startsWith('connect-src')); |  | ||||||
|   }), |  | ||||||
|  |  | ||||||
|   attach() { |   attach() { | ||||||
|     this.monitor.perform(); |     this.monitor.perform(); | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   remove() { |   remove() { | ||||||
|     this.monitor.cancelAll(); |     this.monitor.cancelAll(); | ||||||
|   }, |   } | ||||||
|  |  | ||||||
|   monitor: task(function* () { |   handleEvent(event) { | ||||||
|     this.events.clear(); |     if (event.violatedDirective.startsWith('connect-src')) { | ||||||
|  |       this.connectionViolations = addToArray(this.connectionViolations, event); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @task | ||||||
|  |   *monitor() { | ||||||
|  |     this.connectionViolations = []; | ||||||
|  |  | ||||||
|     while (true) { |     while (true) { | ||||||
|       const event = yield waitForEvent(window.document, 'securitypolicyviolation'); |       const event = yield waitForEvent(window.document, 'securitypolicyviolation'); | ||||||
|       this.events.addObject(event); |       this.handleEvent(event); | ||||||
|     } |     } | ||||||
|   }), |   } | ||||||
| }); | } | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import { capitalize } from '@ember/string'; | |||||||
|  |  | ||||||
| import getStorage from 'vault/lib/token-storage'; | import getStorage from 'vault/lib/token-storage'; | ||||||
| import { STORAGE_KEYS, DEFAULTS, MACHINES } from 'vault/helpers/wizard-constants'; | import { STORAGE_KEYS, DEFAULTS, MACHINES } from 'vault/helpers/wizard-constants'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
| const { | const { | ||||||
|   TUTORIAL_STATE, |   TUTORIAL_STATE, | ||||||
|   COMPONENT_STATE, |   COMPONENT_STATE, | ||||||
| @@ -101,7 +102,7 @@ export default Service.extend(DEFAULTS, { | |||||||
|     } else { |     } else { | ||||||
|       if (this.featureMachineHistory) { |       if (this.featureMachineHistory) { | ||||||
|         if (!this.featureMachineHistory.includes(state)) { |         if (!this.featureMachineHistory.includes(state)) { | ||||||
|           const newHistory = this.featureMachineHistory.addObject(state); |           const newHistory = addToArray(this.featureMachineHistory, state); | ||||||
|           this.set('featureMachineHistory', newHistory); |           this.set('featureMachineHistory', newHistory); | ||||||
|         } else { |         } else { | ||||||
|           //we're repeating steps |           //we're repeating steps | ||||||
| @@ -293,7 +294,7 @@ export default Service.extend(DEFAULTS, { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     this.startFeature(); |     this.startFeature(); | ||||||
|     const nextFeature = this.featureList.length > 1 ? capitalize(this.featureList.objectAt(1)) : 'Finish'; |     const nextFeature = this.featureList.length > 1 ? capitalize(this.featureList[1]) : 'Finish'; | ||||||
|     this.set('nextFeature', nextFeature); |     this.set('nextFeature', nextFeature); | ||||||
|     let next; |     let next; | ||||||
|     if (this.currentMachine === 'secrets' && this.featureState === 'display') { |     if (this.currentMachine === 'secrets' && this.featureState === 'display') { | ||||||
| @@ -311,9 +312,9 @@ export default Service.extend(DEFAULTS, { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   startFeature() { |   startFeature() { | ||||||
|     const FeatureMachineConfig = MACHINES[this.featureList.objectAt(0)]; |     const FeatureMachineConfig = MACHINES[this.featureList[0]]; | ||||||
|     FeatureMachine = Machine(FeatureMachineConfig); |     FeatureMachine = Machine(FeatureMachineConfig); | ||||||
|     this.set('currentMachine', this.featureList.objectAt(0)); |     this.set('currentMachine', this.featureList[0]); | ||||||
|     if (this.storageHasKey(FEATURE_STATE)) { |     if (this.storageHasKey(FEATURE_STATE)) { | ||||||
|       this.saveState('featureState', this.getExtState(FEATURE_STATE)); |       this.saveState('featureState', this.getExtState(FEATURE_STATE)); | ||||||
|     } else { |     } else { | ||||||
| @@ -337,7 +338,7 @@ export default Service.extend(DEFAULTS, { | |||||||
|       completed.push(done); |       completed.push(done); | ||||||
|       this.saveExtState(COMPLETED_FEATURES, completed); |       this.saveExtState(COMPLETED_FEATURES, completed); | ||||||
|     } else { |     } else { | ||||||
|       this.saveExtState(COMPLETED_FEATURES, this.getExtState(COMPLETED_FEATURES).addObject(done)); |       this.saveExtState(COMPLETED_FEATURES, addToArray(this.getExtState(COMPLETED_FEATURES), done)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     this.saveExtState(FEATURE_LIST, features.length ? features : null); |     this.saveExtState(FEATURE_LIST, features.length ? features : null); | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ | |||||||
|       </nav> |       </nav> | ||||||
|     {{/if}} |     {{/if}} | ||||||
|     <div class="box is-marginless is-shadowless"> |     <div class="box is-marginless is-shadowless"> | ||||||
|       <MessageError @errorMessage={{if (and this.cluster.standby this.hasCSPError) this.cspErrorText this.error}} /> |       <MessageError @errorMessage={{if (and this.cluster.standby this.cspError) this.cspError this.error}} /> | ||||||
|       {{#if this.selectedAuthBackend.path}} |       {{#if this.selectedAuthBackend.path}} | ||||||
|         <div class="has-bottom-margin-s"> |         <div class="has-bottom-margin-s"> | ||||||
|           <p class="is-label">{{this.selectedAuthBackend.path}}</p> |           <p class="is-label">{{this.selectedAuthBackend.path}}</p> | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ import { capitalize } from 'vault/helpers/capitalize'; | |||||||
| import { humanize } from 'vault/helpers/humanize'; | import { humanize } from 'vault/helpers/humanize'; | ||||||
| import { dasherize } from 'vault/helpers/dasherize'; | import { dasherize } from 'vault/helpers/dasherize'; | ||||||
| import { assert } from '@ember/debug'; | import { assert } from '@ember/debug'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  | import { removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @module FormField |  * @module FormField | ||||||
| @@ -193,9 +195,12 @@ export default class FormFieldComponent extends Component { | |||||||
|  |  | ||||||
|   @action |   @action | ||||||
|   handleChecklist(event) { |   handleChecklist(event) { | ||||||
|     const valueArray = this.args.model[this.valuePath]; |     let updatedValue = this.args.model[this.valuePath]; | ||||||
|     const method = event.target.checked ? 'addObject' : 'removeObject'; |     if (event.target.checked) { | ||||||
|     valueArray[method](event.target.value); |       updatedValue = addToArray(updatedValue, event.target.value); | ||||||
|     this.setAndBroadcast(valueArray); |     } else { | ||||||
|  |       updatedValue = removeFromArray(updatedValue, event.target.value); | ||||||
|  |     } | ||||||
|  |     this.setAndBroadcast(updatedValue); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ import KVObject from 'vault/lib/kv-object'; | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| export default class KvObjectEditor extends Component { | export default class KvObjectEditor extends Component { | ||||||
|  |   // kvData is type ArrayProxy, so addObject etc are fine here | ||||||
|   @tracked kvData; |   @tracked kvData; | ||||||
|  |  | ||||||
|   get placeholders() { |   get placeholders() { | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import Component from '@glimmer/component'; | |||||||
| import { action } from '@ember/object'; | import { action } from '@ember/object'; | ||||||
| import { tracked } from '@glimmer/tracking'; | import { tracked } from '@glimmer/tracking'; | ||||||
| import { assert } from '@ember/debug'; | import { assert } from '@ember/debug'; | ||||||
|  | import { removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @module ObjectListInput |  * @module ObjectListInput | ||||||
| @@ -65,7 +66,7 @@ export default class ObjectListInput extends Component { | |||||||
|  |  | ||||||
|   @action |   @action | ||||||
|   handleInput(idx, { target }) { |   handleInput(idx, { target }) { | ||||||
|     const inputObj = this.inputList.objectAt(idx); |     const inputObj = this.inputList[idx]; | ||||||
|     inputObj[target.name] = target.value; |     inputObj[target.name] = target.value; | ||||||
|     this.handleChange(); |     this.handleChange(); | ||||||
|   } |   } | ||||||
| @@ -79,8 +80,8 @@ export default class ObjectListInput extends Component { | |||||||
|  |  | ||||||
|   @action |   @action | ||||||
|   removeRow(idx) { |   removeRow(idx) { | ||||||
|     const row = this.inputList.objectAt(idx); |     const row = this.inputList[idx]; | ||||||
|     this.inputList.removeObject(row); |     this.inputList = removeFromArray(this.inputList, row); | ||||||
|     this.handleChange(); |     this.handleChange(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ import { action } from '@ember/object'; | |||||||
| import { tracked } from '@glimmer/tracking'; | import { tracked } from '@glimmer/tracking'; | ||||||
| import { task } from 'ember-concurrency'; | import { task } from 'ember-concurrency'; | ||||||
| import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-utils'; | import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-utils'; | ||||||
|  | import { removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @module SearchSelectWithModal |  * @module SearchSelectWithModal | ||||||
| @@ -31,7 +33,7 @@ import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-ut | |||||||
|  * /> |  * /> | ||||||
|  * |  * | ||||||
|  // * component functionality |  // * component functionality | ||||||
|  * @param {function} onChange - The onchange action for this form field. ** SEE UTIL ** search-select-has-many.js if selecting models from a hasMany relationship |  * @param {function} onChange - The onchange action for this form field. ** SEE EXAMPLE ** mfa-login-enforcement-form.js (onMethodChange) for example when selecting models from a hasMany relationship | ||||||
|  * @param {array} [inputValue] - Array of strings corresponding to the input's initial value, e.g. an array of model ids that on edit will appear as selected items below the input |  * @param {array} [inputValue] - Array of strings corresponding to the input's initial value, e.g. an array of model ids that on edit will appear as selected items below the input | ||||||
|  * @param {boolean} [shouldRenderName=false] - By default an item's id renders in the dropdown, `true` displays the name with its id in smaller text beside it *NOTE: the boolean flips automatically with 'identity' models |  * @param {boolean} [shouldRenderName=false] - By default an item's id renders in the dropdown, `true` displays the name with its id in smaller text beside it *NOTE: the boolean flips automatically with 'identity' models | ||||||
|  * @param {array} [excludeOptions] - array of strings containing model ids to filter from the dropdown (ex: ['allow_all']) |  * @param {array} [excludeOptions] - array of strings containing model ids to filter from the dropdown (ex: ['allow_all']) | ||||||
| @@ -81,7 +83,7 @@ export default class SearchSelectWithModal extends Component { | |||||||
|     return inputValues.map((option) => { |     return inputValues.map((option) => { | ||||||
|       const matchingOption = this.dropdownOptions.find((opt) => opt.id === option); |       const matchingOption = this.dropdownOptions.find((opt) => opt.id === option); | ||||||
|       // remove any matches from dropdown list |       // remove any matches from dropdown list | ||||||
|       this.dropdownOptions.removeObject(matchingOption); |       this.dropdownOptions = removeFromArray(this.dropdownOptions, matchingOption); | ||||||
|       return { |       return { | ||||||
|         id: option, |         id: option, | ||||||
|         name: matchingOption ? matchingOption.name : option, |         name: matchingOption ? matchingOption.name : option, | ||||||
| @@ -168,8 +170,8 @@ export default class SearchSelectWithModal extends Component { | |||||||
|   // ----- |   // ----- | ||||||
|   @action |   @action | ||||||
|   discardSelection(selected) { |   discardSelection(selected) { | ||||||
|     this.selectedOptions.removeObject(selected); |     this.selectedOptions = removeFromArray(this.selectedOptions, selected); | ||||||
|     this.dropdownOptions.pushObject(selected); |     this.dropdownOptions = addToArray(this.dropdownOptions, selected); | ||||||
|     this.handleChange(); |     this.handleChange(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -196,8 +198,8 @@ export default class SearchSelectWithModal extends Component { | |||||||
|       this.showModal = true; |       this.showModal = true; | ||||||
|     } else { |     } else { | ||||||
|       // user has selected an existing item, handleChange immediately |       // user has selected an existing item, handleChange immediately | ||||||
|       this.selectedOptions.pushObject(selection); |       this.selectedOptions = addToArray(this.selectedOptions, selection); | ||||||
|       this.dropdownOptions.removeObject(selection); |       this.dropdownOptions = removeFromArray(this.dropdownOptions, selection); | ||||||
|       this.handleChange(); |       this.handleChange(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -209,7 +211,7 @@ export default class SearchSelectWithModal extends Component { | |||||||
|     this.showModal = false; |     this.showModal = false; | ||||||
|     if (model && model.currentState.isSaved) { |     if (model && model.currentState.isSaved) { | ||||||
|       const { name } = model; |       const { name } = model; | ||||||
|       this.selectedOptions.pushObject({ name, id: name }); |       this.selectedOptions = addToArray(this.selectedOptions, { name, id: name }); | ||||||
|       this.handleChange(); |       this.handleChange(); | ||||||
|     } |     } | ||||||
|     this.nameInput = null; |     this.nameInput = null; | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ import { action } from '@ember/object'; | |||||||
| import { tracked } from '@glimmer/tracking'; | import { tracked } from '@glimmer/tracking'; | ||||||
| import { resolve } from 'rsvp'; | import { resolve } from 'rsvp'; | ||||||
| import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-utils'; | import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-utils'; | ||||||
|  | import { removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
| /** | /** | ||||||
|  * @module SearchSelect |  * @module SearchSelect | ||||||
|  * The `SearchSelect` is an implementation of the [ember-power-select](https://github.com/cibernox/ember-power-select) used for form elements where options come dynamically from the API. |  * The `SearchSelect` is an implementation of the [ember-power-select](https://github.com/cibernox/ember-power-select) used for form elements where options come dynamically from the API. | ||||||
| @@ -28,7 +30,7 @@ import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-ut | |||||||
|  * /> |  * /> | ||||||
|  * |  * | ||||||
|  // * component functionality |  // * component functionality | ||||||
|  * @param {function} onChange - The onchange action for this form field. ** SEE UTIL ** search-select-has-many.js if selecting models from a hasMany relationship |  * @param {function} onChange - The onchange action for this form field. ** SEE EXAMPLE ** mfa-login-enforcement-form.js (onMethodChange) for example when selecting models from a hasMany relationship | ||||||
|  * @param {array} [inputValue] - Array of strings corresponding to the input's initial value, e.g. an array of model ids that on edit will appear as selected items below the input |  * @param {array} [inputValue] - Array of strings corresponding to the input's initial value, e.g. an array of model ids that on edit will appear as selected items below the input | ||||||
|  * @param {boolean} [disallowNewItems=false] - Controls whether or not the user can add a new item if none found |  * @param {boolean} [disallowNewItems=false] - Controls whether or not the user can add a new item if none found | ||||||
|  * @param {boolean} [shouldRenderName=false] - By default an item's id renders in the dropdown, `true` displays the name with its id in smaller text beside it *NOTE: the boolean flips automatically with 'identity' models or if this.idKey !== 'id' |  * @param {boolean} [shouldRenderName=false] - By default an item's id renders in the dropdown, `true` displays the name with its id in smaller text beside it *NOTE: the boolean flips automatically with 'identity' models or if this.idKey !== 'id' | ||||||
| @@ -118,7 +120,7 @@ export default class SearchSelect extends Component { | |||||||
|         : false; |         : false; | ||||||
|  |  | ||||||
|       // remove any matches from dropdown list |       // remove any matches from dropdown list | ||||||
|       this.dropdownOptions.removeObject(matchingOption); |       this.dropdownOptions = removeFromArray(this.dropdownOptions, matchingOption); | ||||||
|       return { |       return { | ||||||
|         id: option, |         id: option, | ||||||
|         name: matchingOption ? matchingOption[this.nameKey] : option, |         name: matchingOption ? matchingOption[this.nameKey] : option, | ||||||
| @@ -263,9 +265,9 @@ export default class SearchSelect extends Component { | |||||||
|  |  | ||||||
|   @action |   @action | ||||||
|   discardSelection(selected) { |   discardSelection(selected) { | ||||||
|     this.selectedOptions.removeObject(selected); |     this.selectedOptions = removeFromArray(this.selectedOptions, selected); | ||||||
|     if (!selected.new) { |     if (!selected.new) { | ||||||
|       this.dropdownOptions.pushObject(selected); |       this.dropdownOptions = addToArray(this.dropdownOptions, selected); | ||||||
|     } |     } | ||||||
|     this.handleChange(); |     this.handleChange(); | ||||||
|   } |   } | ||||||
| @@ -291,10 +293,10 @@ export default class SearchSelect extends Component { | |||||||
|   selectOrCreate(selection) { |   selectOrCreate(selection) { | ||||||
|     if (selection && selection.__isSuggestion__) { |     if (selection && selection.__isSuggestion__) { | ||||||
|       const name = selection.__value__; |       const name = selection.__value__; | ||||||
|       this.selectedOptions.pushObject({ name, id: name, new: true }); |       this.selectedOptions = addToArray(this.selectedOptions, { name, id: name, new: true }); | ||||||
|     } else { |     } else { | ||||||
|       this.selectedOptions.pushObject(selection); |       this.selectedOptions = addToArray(this.selectedOptions, selection); | ||||||
|       this.dropdownOptions.removeObject(selection); |       this.dropdownOptions = removeFromArray(this.dropdownOptions, selection); | ||||||
|     } |     } | ||||||
|     this.handleChange(); |     this.handleChange(); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -10,6 +10,8 @@ import { action } from '@ember/object'; | |||||||
| import { set } from '@ember/object'; | import { set } from '@ember/object'; | ||||||
| import { next } from '@ember/runloop'; | import { next } from '@ember/runloop'; | ||||||
| import { tracked } from '@glimmer/tracking'; | import { tracked } from '@glimmer/tracking'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  | import { removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @module StringList |  * @module StringList | ||||||
| @@ -33,6 +35,7 @@ export default class StringList extends Component { | |||||||
|   constructor() { |   constructor() { | ||||||
|     super(...arguments); |     super(...arguments); | ||||||
|  |  | ||||||
|  |     // inputList is type ArrayProxy, so addObject etc are fine here | ||||||
|     this.inputList = ArrayProxy.create({ |     this.inputList = ArrayProxy.create({ | ||||||
|       // trim the `value` when accessing objects |       // trim the `value` when accessing objects | ||||||
|       content: [], |       content: [], | ||||||
| @@ -90,9 +93,11 @@ export default class StringList extends Component { | |||||||
|   @action |   @action | ||||||
|   inputChanged(idx, event) { |   inputChanged(idx, event) { | ||||||
|     if (event.target.value.includes(',') && !this.indicesWithComma.includes(idx)) { |     if (event.target.value.includes(',') && !this.indicesWithComma.includes(idx)) { | ||||||
|       this.indicesWithComma.pushObject(idx); |       this.indicesWithComma = addToArray(this.indicesWithComma, idx); | ||||||
|  |     } | ||||||
|  |     if (!event.target.value.includes(',')) { | ||||||
|  |       this.indicesWithComma = removeFromArray(this.indicesWithComma, idx); | ||||||
|     } |     } | ||||||
|     if (!event.target.value.includes(',')) this.indicesWithComma.removeObject(idx); |  | ||||||
|  |  | ||||||
|     const inputObj = this.inputList.objectAt(idx); |     const inputObj = this.inputList.objectAt(idx); | ||||||
|     set(inputObj, 'value', event.target.value); |     set(inputObj, 'value', event.target.value); | ||||||
| @@ -109,8 +114,8 @@ export default class StringList extends Component { | |||||||
|  |  | ||||||
|   @action |   @action | ||||||
|   removeInput(idx) { |   removeInput(idx) { | ||||||
|     const inputs = this.inputList; |     const itemToRemove = this.inputList.objectAt(idx); | ||||||
|     inputs.removeObject(inputs.objectAt(idx)); |     this.inputList.removeObject(itemToRemove); | ||||||
|     this.args.onChange(this.toVal()); |     this.args.onChange(this.toVal()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,38 +0,0 @@ | |||||||
| /** |  | ||||||
|  * Copyright (c) HashiCorp, Inc. |  | ||||||
|  * SPDX-License-Identifier: BUSL-1.1 |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Util to add/remove models in a hasMany relationship via the search-select component |  | ||||||
|  * |  | ||||||
|  * @example of using the util within an action in a component |  | ||||||
|  *```js |  | ||||||
|  * @action |  | ||||||
|  * async onSearchSelectChange(selectedIds) { |  | ||||||
|  *    const methods = await this.args.model.mfa_methods; |  | ||||||
|  *   handleHasManySelection(selectedIds, methods, this.store, 'mfa-method'); |  | ||||||
|  *  } |  | ||||||
|  * |  | ||||||
|  * @param selectedIds array of selected options from search-select component |  | ||||||
|  * @param modelCollection array-like, list of models from the hasMany relationship |  | ||||||
|  * @param store the store so we can call peekRecord() |  | ||||||
|  * @param modelRecord string passed to peekRecord |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| export default function handleHasManySelection(selectedIds, modelCollection, store, modelRecord) { |  | ||||||
|   // first check for existing models that have been removed from selection |  | ||||||
|   modelCollection.forEach((model) => { |  | ||||||
|     if (!selectedIds.includes(model.id)) { |  | ||||||
|       modelCollection.removeObject(model); |  | ||||||
|     } |  | ||||||
|   }); |  | ||||||
|   // now check for selected items that don't exist and add them to the model |  | ||||||
|   const modelIds = modelCollection.map((model) => model.id); |  | ||||||
|   selectedIds.forEach((id) => { |  | ||||||
|     if (!modelIds.includes(id)) { |  | ||||||
|       const model = store.peekRecord(modelRecord, id); |  | ||||||
|       modelCollection.addObject(model); |  | ||||||
|     } |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| @@ -32,7 +32,6 @@ export default class RolesPageComponent extends Component { | |||||||
|     try { |     try { | ||||||
|       const message = `Successfully deleted role ${model.name}`; |       const message = `Successfully deleted role ${model.name}`; | ||||||
|       await model.destroyRecord(); |       await model.destroyRecord(); | ||||||
|       this.args.roles.removeObject(model); |  | ||||||
|       this.flashMessages.success(message); |       this.flashMessages.success(message); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       const message = errorMessage(error, 'Error deleting role. Please try again or contact support'); |       const message = errorMessage(error, 'Error deleting role. Please try again or contact support'); | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import type PkiConfigClusterModel from 'vault/models/pki/config/cluster'; | |||||||
| import type PkiConfigCrlModel from 'vault/models/pki/config/crl'; | import type PkiConfigCrlModel from 'vault/models/pki/config/crl'; | ||||||
| import type PkiConfigUrlsModel from 'vault/models/pki/config/urls'; | import type PkiConfigUrlsModel from 'vault/models/pki/config/urls'; | ||||||
| import type { FormField, TtlEvent } from 'vault/app-types'; | import type { FormField, TtlEvent } from 'vault/app-types'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| interface Args { | interface Args { | ||||||
|   acme: PkiConfigAcmeModel; |   acme: PkiConfigAcmeModel; | ||||||
| @@ -76,7 +77,7 @@ export default class PkiConfigurationEditComponent extends Component<Args> { | |||||||
|           message: errorMessage(error), |           message: errorMessage(error), | ||||||
|         }; |         }; | ||||||
|         this.flashMessages.danger(`Error updating config/${modelName}`, { sticky: true }); |         this.flashMessages.danger(`Error updating config/${modelName}`, { sticky: true }); | ||||||
|         this.errors.pushObject(errorObject); |         this.errors = addToArray(this.errors, errorObject); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,8 @@ import errorMessage from 'vault/utils/error-message'; | |||||||
| import type RouterService from '@ember/routing/router-service'; | import type RouterService from '@ember/routing/router-service'; | ||||||
| import type FlashMessageService from 'vault/services/flash-messages'; | import type FlashMessageService from 'vault/services/flash-messages'; | ||||||
| import type PkiIssuerModel from 'vault/models/pki/issuer'; | import type PkiIssuerModel from 'vault/models/pki/issuer'; | ||||||
|  | import { removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
|  |  | ||||||
| interface Args { | interface Args { | ||||||
|   model: PkiIssuerModel; |   model: PkiIssuerModel; | ||||||
| @@ -36,8 +38,11 @@ export default class PkiIssuerEditComponent extends Component<Args> { | |||||||
|  |  | ||||||
|   @action |   @action | ||||||
|   setUsage(value: string) { |   setUsage(value: string) { | ||||||
|     const method = this.usageValues.includes(value) ? 'removeObject' : 'addObject'; |     if (this.usageValues.includes(value)) { | ||||||
|     this.usageValues[method](value); |       this.usageValues = removeFromArray(this.usageValues, value); | ||||||
|  |     } else { | ||||||
|  |       this.usageValues = addToArray(this.usageValues, value); | ||||||
|  |     } | ||||||
|     this.args.model.usage = this.usageValues.join(','); |     this.args.model.usage = this.usageValues.join(','); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import { tracked } from '@glimmer/tracking'; | |||||||
| import errorMessage from 'vault/utils/error-message'; | import errorMessage from 'vault/utils/error-message'; | ||||||
| import { waitFor } from '@ember/test-waiters'; | import { waitFor } from '@ember/test-waiters'; | ||||||
| import { parseCertificate } from 'vault/utils/parse-pki-cert'; | import { parseCertificate } from 'vault/utils/parse-pki-cert'; | ||||||
|  | import { addToArray } from 'vault/helpers/add-to-array'; | ||||||
| /** | /** | ||||||
|  * @module PkiIssuerCrossSign |  * @module PkiIssuerCrossSign | ||||||
|  * PkiIssuerCrossSign components render from a parent issuer's details page to cross-sign an intermediate issuer (from a different mount). |  * PkiIssuerCrossSign components render from a parent issuer's details page to cross-sign an intermediate issuer (from a different mount). | ||||||
| @@ -92,7 +93,7 @@ export default class PkiIssuerCrossSign extends Component { | |||||||
|  |  | ||||||
|       // for cross-signing error handling we want to record the list of issuers before the process starts |       // for cross-signing error handling we want to record the list of issuers before the process starts | ||||||
|       this.intermediateIssuers[intermediateMount] = issuers; |       this.intermediateIssuers[intermediateMount] = issuers; | ||||||
|       this.validationErrors.addObject({ |       this.validationErrors = addToArray(this.validationErrors, { | ||||||
|         newCrossSignedIssuer: this.nameValidation(newCrossSignedIssuer, issuers), |         newCrossSignedIssuer: this.nameValidation(newCrossSignedIssuer, issuers), | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| @@ -109,9 +110,9 @@ export default class PkiIssuerCrossSign extends Component { | |||||||
|           intermediateIssuer, |           intermediateIssuer, | ||||||
|           newCrossSignedIssuer |           newCrossSignedIssuer | ||||||
|         ); |         ); | ||||||
|         this.signedIssuers.addObject({ ...data, hasError: false }); |         this.signedIssuers = addToArray(this.signedIssuers, { ...data, hasError: false }); | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|         this.signedIssuers.addObject({ |         this.signedIssuers = addToArray(this.signedIssuers, { | ||||||
|           ...this.formData[row], |           ...this.formData[row], | ||||||
|           hasError: errorMessage(error), |           hasError: errorMessage(error), | ||||||
|           hasUnsupportedParams: error.cause ? error.cause.map((e) => e.message).join(', ') : null, |           hasUnsupportedParams: error.cause ? error.cause.map((e) => e.message).join(', ') : null, | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ export default function (server) { | |||||||
|     let records = []; |     let records = []; | ||||||
|     if (isMethod) { |     if (isMethod) { | ||||||
|       methods.forEach((method) => { |       methods.forEach((method) => { | ||||||
|         records.addObjects(schema.db[dbKeyFromType(method)].where({})); |         records = [...records, ...schema.db[dbKeyFromType(method)].where({})]; | ||||||
|       }); |       }); | ||||||
|     } else { |     } else { | ||||||
|       records = schema.db.mfaLoginEnforcements.where({}); |       records = schema.db.mfaLoginEnforcements.where({}); | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ module('Acceptance | mfa-method', function (hooks) { | |||||||
|     this.store = this.owner.lookup('service:store'); |     this.store = this.owner.lookup('service:store'); | ||||||
|     this.getMethods = () => |     this.getMethods = () => | ||||||
|       ['Totp', 'Duo', 'Okta', 'Pingid'].reduce((methods, type) => { |       ['Totp', 'Duo', 'Okta', 'Pingid'].reduce((methods, type) => { | ||||||
|         methods.addObjects(this.server.db[`mfa${type}Methods`].where({})); |         methods = [...methods, ...this.server.db[`mfa${type}Methods`].where({})]; | ||||||
|         return methods; |         return methods; | ||||||
|       }, []); |       }, []); | ||||||
|     return authPage.login(); |     return authPage.login(); | ||||||
|   | |||||||
| @@ -51,9 +51,9 @@ module('Integration | Component | auth form', function (hooks) { | |||||||
|     assert.expect(2); |     assert.expect(2); | ||||||
|     this.set('cluster', EmberObject.create({ standby: true })); |     this.set('cluster', EmberObject.create({ standby: true })); | ||||||
|     this.set('selectedAuth', 'token'); |     this.set('selectedAuth', 'token'); | ||||||
|     await render(hbs`{{auth-form cluster=this.cluster selectedAuth=this.selectedAuth}}`); |     await render(hbs`<AuthForm @cluster={{this.cluster}} @selectedAuth={{this.selectedAuth}} />`); | ||||||
|     assert.false(component.errorMessagePresent, false); |     assert.false(component.errorMessagePresent, false); | ||||||
|     this.owner.lookup('service:csp-event').events.addObject({ violatedDirective: 'connect-src' }); |     this.owner.lookup('service:csp-event').handleEvent({ violatedDirective: 'connect-src' }); | ||||||
|     await settled(); |     await settled(); | ||||||
|     assert.strictEqual(component.errorText, CSP_ERR_TEXT); |     assert.strictEqual(component.errorText, CSP_ERR_TEXT); | ||||||
|   }); |   }); | ||||||
| @@ -75,7 +75,7 @@ module('Integration | Component | auth form', function (hooks) { | |||||||
|  |  | ||||||
|     this.set('cluster', EmberObject.create({})); |     this.set('cluster', EmberObject.create({})); | ||||||
|     this.set('selectedAuth', 'token'); |     this.set('selectedAuth', 'token'); | ||||||
|     await render(hbs`{{auth-form cluster=this.cluster selectedAuth=this.selectedAuth}}`); |     await render(hbs`<AuthForm @cluster={{this.cluster}} @selectedAuth={{this.selectedAuth}} />`); | ||||||
|     return component.login().then(() => { |     return component.login().then(() => { | ||||||
|       assert.strictEqual(component.errorText, 'Error Authentication failed: Not allowed'); |       assert.strictEqual(component.errorText, 'Error Authentication failed: Not allowed'); | ||||||
|       server.shutdown(); |       server.shutdown(); | ||||||
| @@ -86,17 +86,20 @@ module('Integration | Component | auth form', function (hooks) { | |||||||
|     assert.expect(1); |     assert.expect(1); | ||||||
|     const server = new Pretender(function () { |     const server = new Pretender(function () { | ||||||
|       this.get('/v1/auth/**', () => { |       this.get('/v1/auth/**', () => { | ||||||
|         return [400, { 'Content-Type': 'application/json' }]; |         return [400, { 'Content-Type': 'application/json' }, JSON.stringify({ errors: ['API Error here'] })]; | ||||||
|       }); |       }); | ||||||
|       this.get('/v1/sys/internal/ui/mounts', this.passthrough); |       this.get('/v1/sys/internal/ui/mounts', this.passthrough); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     this.set('cluster', EmberObject.create({})); |     this.set('cluster', EmberObject.create({})); | ||||||
|     this.set('selectedAuth', 'token'); |     this.set('selectedAuth', 'token'); | ||||||
|     await render(hbs`{{auth-form cluster=this.cluster selectedAuth=this.selectedAuth}}`); |     await render(hbs`<AuthForm @cluster={{this.cluster}} @selectedAuth={{this.selectedAuth}} />`); | ||||||
|     // returns null because test does not return details of failed network request. On the app it will return the details of the error instead of null. |  | ||||||
|     return component.login().then(() => { |     return component.login().then(() => { | ||||||
|       assert.strictEqual(component.errorText, 'Error Authentication failed: null'); |       assert.strictEqual( | ||||||
|  |         component.errorText, | ||||||
|  |         'Error Authentication failed: API Error here', | ||||||
|  |         'shows the error from the API' | ||||||
|  |       ); | ||||||
|       server.shutdown(); |       server.shutdown(); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| @@ -134,7 +137,7 @@ module('Integration | Component | auth form', function (hooks) { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     this.set('cluster', EmberObject.create({})); |     this.set('cluster', EmberObject.create({})); | ||||||
|     await render(hbs`{{auth-form cluster=this.cluster }}`); |     await render(hbs`<AuthForm @cluster={{this.cluster}} />`); | ||||||
|  |  | ||||||
|     assert.strictEqual(component.tabs.length, 2, 'renders a tab for userpass and Other'); |     assert.strictEqual(component.tabs.length, 2, 'renders a tab for userpass and Other'); | ||||||
|     assert.strictEqual(component.tabs.objectAt(0).name, 'foo', 'uses the path in the label'); |     assert.strictEqual(component.tabs.objectAt(0).name, 'foo', 'uses the path in the label'); | ||||||
| @@ -155,7 +158,7 @@ module('Integration | Component | auth form', function (hooks) { | |||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|     this.set('cluster', EmberObject.create({})); |     this.set('cluster', EmberObject.create({})); | ||||||
|     await render(hbs`{{auth-form cluster=this.cluster }}`); |     await render(hbs`<AuthForm @cluster={{this.cluster}} />`); | ||||||
|  |  | ||||||
|     assert.strictEqual( |     assert.strictEqual( | ||||||
|       component.descriptionText, |       component.descriptionText, | ||||||
| @@ -183,7 +186,7 @@ module('Integration | Component | auth form', function (hooks) { | |||||||
|  |  | ||||||
|     this.set('cluster', EmberObject.create({})); |     this.set('cluster', EmberObject.create({})); | ||||||
|     this.set('selectedAuth', 'foo/'); |     this.set('selectedAuth', 'foo/'); | ||||||
|     await render(hbs`{{auth-form cluster=this.cluster selectedAuth=this.selectedAuth}}`); |     await render(hbs`<AuthForm @cluster={{this.cluster}} @selectedAuth={{this.selectedAuth}} />`); | ||||||
|     await component.login(); |     await component.login(); | ||||||
|  |  | ||||||
|     await settled(); |     await settled(); | ||||||
| @@ -267,7 +270,7 @@ module('Integration | Component | auth form', function (hooks) { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     this.set('wrappedToken', '54321'); |     this.set('wrappedToken', '54321'); | ||||||
|     await render(hbs`{{auth-form cluster=this.cluster wrappedToken=this.wrappedToken}}`); |     await render(hbs`<AuthForm @cluster={{this.cluster}} @wrappedToken={{this.wrappedToken}} />`); | ||||||
|     later(() => cancelTimers(), 50); |     later(() => cancelTimers(), 50); | ||||||
|  |  | ||||||
|     await settled(); |     await settled(); | ||||||
|   | |||||||
| @@ -147,7 +147,7 @@ module('Integration | Component | ldap | Page::Library::CreateAndEdit', function | |||||||
|  |  | ||||||
|     await this.renderComponent(); |     await this.renderComponent(); | ||||||
|  |  | ||||||
|     await click('[data-test-string-list-button="delete"]'); |     await click('[data-test-string-list-row="0"] [data-test-string-list-button="delete"]'); | ||||||
|     await click('[data-test-input="disable_check_in_enforcement"] input#Disabled'); |     await click('[data-test-input="disable_check_in_enforcement"] input#Disabled'); | ||||||
|     await click('[data-test-save]'); |     await click('[data-test-save]'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -153,8 +153,8 @@ module('Integration | Component | mfa-login-enforcement-form', function (hooks) | |||||||
|   test('it should populate fields with model data', async function (assert) { |   test('it should populate fields with model data', async function (assert) { | ||||||
|     this.model.name = 'foo'; |     this.model.name = 'foo'; | ||||||
|     const [method] = await this.store.query('mfa-method', {}); |     const [method] = await this.store.query('mfa-method', {}); | ||||||
|     this.model.mfa_methods.addObject(method); |     this.model.mfa_methods = [method]; | ||||||
|     this.model.auth_method_accessors.addObject('auth_userpass_1234'); |     this.model.auth_method_accessors = ['auth_userpass_1234']; | ||||||
|  |  | ||||||
|     await render(hbs` |     await render(hbs` | ||||||
|       <Mfa::MfaLoginEnforcementForm |       <Mfa::MfaLoginEnforcementForm | ||||||
| @@ -207,12 +207,12 @@ module('Integration | Component | mfa-login-enforcement-form', function (hooks) | |||||||
|         keys: ['1234'], |         keys: ['1234'], | ||||||
|       }, |       }, | ||||||
|     })); |     })); | ||||||
|     this.model.auth_method_accessors.addObject('auth_userpass_1234'); |     this.model.auth_method_accessors = ['auth_userpass_1234']; | ||||||
|     this.model.auth_method_types.addObject('userpass'); |     this.model.auth_method_types = ['userpass']; | ||||||
|     const [entity] = await this.store.query('identity/entity', {}); |     const [entity] = await this.store.query('identity/entity', {}); | ||||||
|     this.model.identity_entities.addObject(entity); |     this.model.identity_entities = [entity]; | ||||||
|     const [group] = await this.store.query('identity/group', {}); |     const [group] = await this.store.query('identity/group', {}); | ||||||
|     this.model.identity_groups.addObject(group); |     this.model.identity_groups = [group]; | ||||||
|  |  | ||||||
|     await render(hbs` |     await render(hbs` | ||||||
|       <Mfa::MfaLoginEnforcementForm |       <Mfa::MfaLoginEnforcementForm | ||||||
|   | |||||||
| @@ -159,6 +159,7 @@ module('Integration | Component | path filter config list', function (hooks) { | |||||||
|     await clickTrigger(); |     await clickTrigger(); | ||||||
|     await searchSelect.deleteButtons.objectAt(1).click(); |     await searchSelect.deleteButtons.objectAt(1).click(); | ||||||
|     await clickTrigger(); |     await clickTrigger(); | ||||||
|  |     await typeInSearch('ns1'); | ||||||
|     assert.dom('.ember-power-select-group').hasText('Namespaces ns1', 'puts ns back within group'); |     assert.dom('.ember-power-select-group').hasText('Namespaces ns1', 'puts ns back within group'); | ||||||
|     await clickTrigger(); |     await clickTrigger(); | ||||||
|   }); |   }); | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ module('Integration | Helper | add-to-array', function (hooks) { | |||||||
|  |  | ||||||
|   test('it correctly adds a value to an array without mutating the original', function (assert) { |   test('it correctly adds a value to an array without mutating the original', function (assert) { | ||||||
|     const ARRAY = ['horse', 'cow', 'chicken']; |     const ARRAY = ['horse', 'cow', 'chicken']; | ||||||
|     const result = addToArray([ARRAY, 'pig']); |     const result = addToArray(ARRAY, 'pig'); | ||||||
|     assert.deepEqual(result, [...ARRAY, 'pig'], 'Result has additional item'); |     assert.deepEqual(result, [...ARRAY, 'pig'], 'Result has additional item'); | ||||||
|     assert.deepEqual(ARRAY, ['horse', 'cow', 'chicken'], 'original array is not mutated'); |     assert.deepEqual(ARRAY, ['horse', 'cow', 'chicken'], 'original array is not mutated'); | ||||||
|   }); |   }); | ||||||
| @@ -20,7 +20,7 @@ module('Integration | Helper | add-to-array', function (hooks) { | |||||||
|   test('it fails if the first value is not an array', function (assert) { |   test('it fails if the first value is not an array', function (assert) { | ||||||
|     let result; |     let result; | ||||||
|     try { |     try { | ||||||
|       result = addToArray(['not-array', 'string']); |       result = addToArray('not-array', 'string'); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       result = e.message; |       result = e.message; | ||||||
|     } |     } | ||||||
| @@ -29,13 +29,13 @@ module('Integration | Helper | add-to-array', function (hooks) { | |||||||
|  |  | ||||||
|   test('it works with non-string arrays', function (assert) { |   test('it works with non-string arrays', function (assert) { | ||||||
|     const ARRAY = ['five', 6, '7']; |     const ARRAY = ['five', 6, '7']; | ||||||
|     const result = addToArray([ARRAY, 10]); |     const result = addToArray(ARRAY, 10); | ||||||
|     assert.deepEqual(result, ['five', 6, '7', 10], 'added number value'); |     assert.deepEqual(result, ['five', 6, '7', 10], 'added number value'); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it de-dupes the result', function (assert) { |   test('it de-dupes the result', function (assert) { | ||||||
|     const ARRAY = ['horse', 'cow', 'chicken']; |     const ARRAY = ['horse', 'cow', 'chicken']; | ||||||
|     const result = addToArray([ARRAY, 'horse']); |     const result = addToArray(ARRAY, 'horse'); | ||||||
|     assert.deepEqual(result, ['horse', 'cow', 'chicken']); |     assert.deepEqual(result, ['horse', 'cow', 'chicken']); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -5,28 +5,28 @@ | |||||||
|  |  | ||||||
| import { module, test } from 'qunit'; | import { module, test } from 'qunit'; | ||||||
| import { setupRenderingTest } from 'ember-qunit'; | import { setupRenderingTest } from 'ember-qunit'; | ||||||
| import { removeFromArray } from '../../../helpers/remove-from-array'; | import { removeManyFromArray, removeFromArray } from 'vault/helpers/remove-from-array'; | ||||||
|  |  | ||||||
| module('Integration | Helper | remove-from-array', function (hooks) { | module('Integration | Helper | remove-from-array', function (hooks) { | ||||||
|   setupRenderingTest(hooks); |   setupRenderingTest(hooks); | ||||||
|  |  | ||||||
|   test('it correctly removes a value from an array without mutating the original', function (assert) { |   test('it correctly removes a value from an array without mutating the original', function (assert) { | ||||||
|     const ARRAY = ['horse', 'cow', 'chicken']; |     const ARRAY = ['horse', 'cow', 'chicken']; | ||||||
|     const result = removeFromArray([ARRAY, 'horse']); |     const result = removeFromArray(ARRAY, 'horse'); | ||||||
|     assert.deepEqual(result, ['cow', 'chicken'], 'Result does not have removed item'); |     assert.deepEqual(result, ['cow', 'chicken'], 'Result does not have removed item'); | ||||||
|     assert.deepEqual(ARRAY, ['horse', 'cow', 'chicken'], 'original array is not mutated'); |     assert.deepEqual(ARRAY, ['horse', 'cow', 'chicken'], 'original array is not mutated'); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it returns the same value if the item is not found', function (assert) { |   test('it returns the same value if the item is not found', function (assert) { | ||||||
|     const ARRAY = ['horse', 'cow', 'chicken']; |     const ARRAY = ['horse', 'cow', 'chicken']; | ||||||
|     const result = removeFromArray([ARRAY, 'pig']); |     const result = removeFromArray(ARRAY, 'pig'); | ||||||
|     assert.deepEqual(result, ARRAY, 'Results are the same as original array'); |     assert.deepEqual(result, ARRAY, 'Results are the same as original array'); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it fails if the first value is not an array', function (assert) { |   test('it fails if the first value is not an array', function (assert) { | ||||||
|     let result; |     let result; | ||||||
|     try { |     try { | ||||||
|       result = removeFromArray(['not-array', 'string']); |       result = removeFromArray('not-array', 'string'); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       result = e.message; |       result = e.message; | ||||||
|     } |     } | ||||||
| @@ -35,15 +35,23 @@ module('Integration | Helper | remove-from-array', function (hooks) { | |||||||
|  |  | ||||||
|   test('it works with non-string arrays', function (assert) { |   test('it works with non-string arrays', function (assert) { | ||||||
|     const ARRAY = ['five', 6, '7']; |     const ARRAY = ['five', 6, '7']; | ||||||
|     const result1 = removeFromArray([ARRAY, 6]); |     const result1 = removeFromArray(ARRAY, 6); | ||||||
|     const result2 = removeFromArray([ARRAY, 7]); |     const result2 = removeFromArray(ARRAY, 7); | ||||||
|     assert.deepEqual(result1, ['five', '7'], 'removed number value'); |     assert.deepEqual(result1, ['five', '7'], 'removed number value'); | ||||||
|     assert.deepEqual(result2, ARRAY, 'did not match on different types'); |     assert.deepEqual(result2, ARRAY, 'did not match on different types'); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   test('it de-dupes the result', function (assert) { |   test('it de-dupes the result', function (assert) { | ||||||
|     const ARRAY = ['horse', 'cow', 'chicken', 'cow']; |     const ARRAY = ['horse', 'cow', 'chicken', 'cow']; | ||||||
|     const result = removeFromArray([ARRAY, 'horse']); |     const result = removeFromArray(ARRAY, 'horse'); | ||||||
|     assert.deepEqual(result, ['cow', 'chicken']); |     assert.deepEqual(result, ['cow', 'chicken']); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   test('it works with two arrays', function (assert) { | ||||||
|  |     const ARRAY = ['five', 6, '7']; | ||||||
|  |     const result1 = removeManyFromArray(ARRAY, [6, '7']); | ||||||
|  |     const result2 = removeManyFromArray(ARRAY, ['foo', 'five']); | ||||||
|  |     assert.deepEqual(result1, ['five'], 'removed multiple values'); | ||||||
|  |     assert.deepEqual(result2, [6, '7'], 'did nothing with values that were not in the array'); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Chelsea Shaw
					Chelsea Shaw