diff --git a/changelog/23702.txt b/changelog/23702.txt new file mode 100644 index 0000000000..3fee98a1e3 --- /dev/null +++ b/changelog/23702.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Adds a warning when whitespace is detected in a key of a KV secret +``` \ No newline at end of file diff --git a/ui/lib/core/addon/components/kv-object-editor.hbs b/ui/lib/core/addon/components/kv-object-editor.hbs index 9c803b8c9c..30e8b5e562 100644 --- a/ui/lib/core/addon/components/kv-object-editor.hbs +++ b/ui/lib/core/addon/components/kv-object-editor.hbs @@ -25,6 +25,7 @@ @value={{row.name}} placeholder={{this.placeholders.key}} {{on "change" (fn this.updateRow row index)}} + {{on "input" (fn this.validateKey index)}} class="input" /> @@ -70,6 +71,15 @@ {{/if}} + {{#if (includes index this.whitespaceWarningRows)}} +
+ +
+ {{/if}} {{/each}} {{#if this.hasDuplicateKeys}} diff --git a/ui/lib/core/addon/components/kv-object-editor.js b/ui/lib/core/addon/components/kv-object-editor.js index 69cdc2d455..40d0f3974f 100644 --- a/ui/lib/core/addon/components/kv-object-editor.js +++ b/ui/lib/core/addon/components/kv-object-editor.js @@ -9,6 +9,7 @@ import { isNone } from '@ember/utils'; import { assert } from '@ember/debug'; import { action } from '@ember/object'; import { guidFor } from '@ember/object/internals'; +import { A } from '@ember/array'; import KVObject from 'vault/lib/kv-object'; /** @@ -38,6 +39,7 @@ import KVObject from 'vault/lib/kv-object'; export default class KvObjectEditor extends Component { @tracked kvData; + whitespaceWarningRows = A(); get placeholders() { return { @@ -73,6 +75,7 @@ export default class KvObjectEditor extends Component { const oldObj = this.kvData.objectAt(index); assert('object guids match', guidFor(oldObj) === guidFor(object)); this.kvData.removeAt(index); + this.whitespaceWarningRows.removeObject(index); this.args.onChange(this.kvData.toJSON()); } @action @@ -81,4 +84,16 @@ export default class KvObjectEditor extends Component { this.args.onKeyUp(event.target.value); } } + @action + validateKey(rowIndex, event) { + const { value } = event.target; + const keyHasWhitespace = new RegExp('\\s', 'g').test(value); + const rows = [...this.whitespaceWarningRows]; + const rowHasWarning = rows.includes(rowIndex); + if (!keyHasWhitespace && rowHasWarning) { + this.whitespaceWarningRows.removeObject(rowIndex); + } else if (keyHasWhitespace && !rowHasWarning) { + this.whitespaceWarningRows.addObject(rowIndex); + } + } } diff --git a/ui/tests/integration/components/kv-object-editor-test.js b/ui/tests/integration/components/kv-object-editor-test.js index b6642ffa64..8f7eeccdbb 100644 --- a/ui/tests/integration/components/kv-object-editor-test.js +++ b/ui/tests/integration/components/kv-object-editor-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render } from '@ember/test-helpers'; +import { render, fillIn, click } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { create } from 'ember-cli-page-object'; @@ -22,14 +22,14 @@ module('Integration | Component | kv-object-editor', function (hooks) { }); test('it renders with no initial value', async function (assert) { - await render(hbs`{{kv-object-editor onChange=this.spy}}`); + await render(hbs``); assert.strictEqual(component.rows.length, 1, 'renders a single row'); await component.addRow(); assert.strictEqual(component.rows.length, 1, 'will only render row with a blank key'); }); test('it calls onChange when the val changes', async function (assert) { - await render(hbs`{{kv-object-editor onChange=this.spy}}`); + await render(hbs``); await component.rows.objectAt(0).kvKey('foo').kvVal('bar'); assert.strictEqual(this.spy.callCount, 2, 'calls onChange each time change is triggered'); assert.deepEqual( @@ -50,7 +50,7 @@ module('Integration | Component | kv-object-editor', function (hooks) { test('it renders passed data', async function (assert) { const metadata = { foo: 'bar', baz: 'bop' }; this.set('value', metadata); - await render(hbs`{{kv-object-editor value=this.value}}`); + await render(hbs``); assert.strictEqual( component.rows.length, Object.keys(metadata).length + 1, @@ -59,7 +59,7 @@ module('Integration | Component | kv-object-editor', function (hooks) { }); test('it deletes a row', async function (assert) { - await render(hbs`{{kv-object-editor onChange=this.spy}}`); + await render(hbs``); await component.rows.objectAt(0).kvKey('foo').kvVal('bar'); await component.addRow(); assert.strictEqual(component.rows.length, 2); @@ -74,7 +74,7 @@ module('Integration | Component | kv-object-editor', function (hooks) { test('it shows a warning if there are duplicate keys', async function (assert) { const metadata = { foo: 'bar', baz: 'bop' }; this.set('value', metadata); - await render(hbs`{{kv-object-editor value=this.value onChange=this.spy}}`); + await render(hbs``); await component.rows.objectAt(0).kvKey('foo'); assert.ok(component.showsDuplicateError, 'duplicate keys are allowed but an error message is shown'); @@ -97,4 +97,17 @@ module('Integration | Component | kv-object-editor', function (hooks) { assert.dom('textarea').doesNotExist('Value input hidden when block is provided'); assert.dom('[data-test-yield]').exists('Component yields block'); }); + + test('it should display whitespace warning for keys', async function (assert) { + await render(hbs``); + await fillIn('[data-test-kv-key="0"]', 'test '); + assert.dom('[data-test-kv-whitespace-warning="0"]').exists(); + await fillIn('[data-test-kv-key="0"]', 'test'); + assert.dom('[data-test-kv-whitespace-warning="0"]').doesNotExist(); + await fillIn('[data-test-kv-key="0"]', 'test '); + await click('[data-test-kv-add-row="0"]'); + assert.dom('[data-test-kv-whitespace-warning="0"]').exists(); + await click('[data-test-kv-delete-row="0"]'); + assert.dom('[data-test-kv-whitespace-warning="0"]').doesNotExist(); + }); });