mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 01:32:33 +00:00
UI: Add KV view for wrap tool (#29677)
* add kv view for wrap tool * add changelog entry * update toggle and tests * update changelog, style updates, fix linting error bug * update tests * update test to include multiline input * clean up * test improvements and clean up * shift away from disabling button on error * update test for json lint warning * add check after back * move assertions to a better test for them
This commit is contained in:
3
changelog/29677.txt
Normal file
3
changelog/29677.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: adds key value pair string inputs as optional form for wrap tool
|
||||
```
|
||||
@@ -40,16 +40,30 @@
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<NamespaceReminder @mode="perform" @noun="wrap" />
|
||||
<MessageError @errorMessage={{this.errorMessage}} />
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<Toolbar>
|
||||
<ToolbarFilters>
|
||||
<Toggle @name="json" @checked={{this.showJson}} @onChange={{this.handleToggle}}>
|
||||
<span class="has-text-grey">JSON</span>
|
||||
</Toggle>
|
||||
</ToolbarFilters>
|
||||
</Toolbar>
|
||||
{{#if this.showJson}}
|
||||
<JsonEditor
|
||||
class="has-top-margin-s"
|
||||
@title="Data to wrap"
|
||||
@subTitle="json-formatted"
|
||||
@value={{this.wrapData}}
|
||||
@value={{this.stringifiedWrapData}}
|
||||
@valueUpdated={{this.codemirrorUpdated}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<KvObjectEditor
|
||||
class="has-top-margin-l"
|
||||
@label="Data to wrap"
|
||||
@value={{this.wrapData}}
|
||||
@onChange={{fn (mut this.wrapData)}}
|
||||
@warnNonStringValues={{true}}
|
||||
/>
|
||||
{{/if}}
|
||||
<TtlPicker
|
||||
@label="Wrap TTL"
|
||||
@initialValue="30m"
|
||||
@@ -58,10 +72,17 @@
|
||||
@helperTextEnabled="Wrap will expire after"
|
||||
@changeOnInit={{true}}
|
||||
/>
|
||||
{{#if this.hasLintingErrors}}
|
||||
<AlertInline
|
||||
@color="warning"
|
||||
class="has-top-padding-s"
|
||||
@message="JSON is unparsable. Fix linting errors to avoid data discrepancies."
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<Hds::Button @text="Wrap data" type="submit" disabled={{this.buttonDisabled}} data-test-tools-submit />
|
||||
<Hds::Button @text="Wrap data" type="submit" data-test-tools-submit />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Component from '@glimmer/component';
|
||||
import { service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { stringify } from 'core/helpers/stringify';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
/**
|
||||
@@ -21,18 +22,38 @@ export default class ToolsWrap extends Component {
|
||||
@service store;
|
||||
@service flashMessages;
|
||||
|
||||
@tracked buttonDisabled = false;
|
||||
@tracked hasLintingErrors = false;
|
||||
@tracked token = '';
|
||||
@tracked wrapTTL = null;
|
||||
@tracked wrapData = '{\n}';
|
||||
@tracked wrapData = null;
|
||||
@tracked errorMessage = '';
|
||||
@tracked showJson = true;
|
||||
|
||||
get startingValue() {
|
||||
// must pass the third param called "space" in JSON.stringify to structure object with whitespace
|
||||
// otherwise the following codemirror modifier check will pass `this._editor.getValue() !== namedArgs.content` and _setValue will be called.
|
||||
// the method _setValue moves the cursor to the beginning of the text field.
|
||||
// the effect is that the cursor jumps after the first key input.
|
||||
return JSON.stringify({ '': '' }, null, 2);
|
||||
}
|
||||
|
||||
get stringifiedWrapData() {
|
||||
return this?.wrapData ? stringify([this.wrapData], {}) : this.startingValue;
|
||||
}
|
||||
|
||||
@action
|
||||
handleToggle() {
|
||||
this.showJson = !this.showJson;
|
||||
this.hasLintingErrors = false;
|
||||
}
|
||||
|
||||
@action
|
||||
reset(clearData = true) {
|
||||
this.token = '';
|
||||
this.errorMessage = '';
|
||||
this.wrapTTL = null;
|
||||
if (clearData) this.wrapData = '{\n}';
|
||||
this.hasLintingErrors = false;
|
||||
if (clearData) this.wrapData = null;
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -44,15 +65,15 @@ export default class ToolsWrap extends Component {
|
||||
@action
|
||||
codemirrorUpdated(val, codemirror) {
|
||||
codemirror.performLint();
|
||||
const hasErrors = codemirror?.state.lint.marked?.length > 0;
|
||||
this.buttonDisabled = hasErrors;
|
||||
if (!hasErrors) this.wrapData = val;
|
||||
this.hasLintingErrors = codemirror?.state.lint.marked?.length > 0;
|
||||
if (!this.hasLintingErrors) this.wrapData = JSON.parse(val);
|
||||
}
|
||||
|
||||
@action
|
||||
async handleSubmit(evt) {
|
||||
evt.preventDefault();
|
||||
const data = JSON.parse(this.wrapData);
|
||||
|
||||
const data = this.wrapData;
|
||||
const wrapTTL = this.wrapTTL || null;
|
||||
|
||||
try {
|
||||
|
||||
@@ -37,8 +37,13 @@ module('Integration | Component | tools/wrap', function (hooks) {
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom('h1').hasText('Wrap Data', 'Title renders');
|
||||
assert.dom('label').hasText('Data to wrap (json-formatted)');
|
||||
assert.strictEqual(codemirror().getValue(' '), '{ }', 'json editor initializes with empty object');
|
||||
assert.dom('[data-test-toggle-label="json"]').hasText('JSON');
|
||||
assert.dom('[data-test-component="json-editor-title"]').hasText('Data to wrap (json-formatted)');
|
||||
assert.strictEqual(
|
||||
codemirror().getValue(' '),
|
||||
`{ \"\": \"\" }`, // eslint-disable-line no-useless-escape
|
||||
'json editor initializes with empty object that includes whitespace'
|
||||
);
|
||||
assert.dom(TTL.toggleByLabel('Wrap TTL')).isNotChecked('Wrap TTL defaults to unchecked');
|
||||
assert.dom(TS.submit).isEnabled();
|
||||
assert.dom(TS.toolsInput('wrapping-token')).doesNotExist();
|
||||
@@ -104,6 +109,67 @@ module('Integration | Component | tools/wrap', function (hooks) {
|
||||
await click(TS.submit);
|
||||
});
|
||||
|
||||
test('it toggles between views and preserves input data', async function (assert) {
|
||||
assert.expect(6);
|
||||
await this.renderComponent();
|
||||
await codemirror().setValue(this.wrapData);
|
||||
assert.dom('[data-test-component="json-editor-title"]').hasText('Data to wrap (json-formatted)');
|
||||
await click('[data-test-toggle-input="json"]');
|
||||
assert.dom('[data-test-component="json-editor-title"]').doesNotExist();
|
||||
assert.dom('[data-test-kv-key="0"]').hasValue('foo');
|
||||
assert.dom('[data-test-kv-value="0"]').hasValue('bar');
|
||||
await click('[data-test-toggle-input="json"]');
|
||||
assert.dom('[data-test-component="json-editor-title"]').exists();
|
||||
assert.strictEqual(
|
||||
codemirror().getValue(' '),
|
||||
`{ \"foo": \"bar" }`, // eslint-disable-line no-useless-escape
|
||||
'json editor has original data'
|
||||
);
|
||||
});
|
||||
|
||||
test('it submits from kv view', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const multilineData = `this is a multi-line secret
|
||||
that contains
|
||||
some seriously important config`;
|
||||
const flashSpy = sinon.spy(this.owner.lookup('service:flash-messages'), 'success');
|
||||
const updatedWrapData = JSON.stringify({
|
||||
...JSON.parse(this.wrapData),
|
||||
foo: 'bar',
|
||||
foo2: multilineData,
|
||||
});
|
||||
|
||||
this.server.post('sys/wrapping/wrap', (schema, { requestBody, requestHeaders }) => {
|
||||
const payload = JSON.parse(requestBody);
|
||||
assert.propEqual(payload, JSON.parse(updatedWrapData), `payload contains data: ${requestBody}`);
|
||||
assert.strictEqual(requestHeaders['X-Vault-Wrap-TTL'], '30m', 'request header has default wrap ttl');
|
||||
return {
|
||||
wrap_info: {
|
||||
token: this.token,
|
||||
accessor: '5yjKx6Om9NmBx1mjiN1aIrnm',
|
||||
ttl: 1800,
|
||||
creation_time: '2024-06-07T12:02:22.096254-07:00',
|
||||
creation_path: 'sys/wrapping/wrap',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
await this.renderComponent();
|
||||
await click('[data-test-toggle-input="json"]');
|
||||
await fillIn('[data-test-kv-key="0"]', 'foo');
|
||||
await fillIn('[data-test-kv-value="0"]', 'bar');
|
||||
await click('[data-test-kv-add-row="0"]');
|
||||
await fillIn('[data-test-kv-key="1"]', 'foo2');
|
||||
await fillIn('[data-test-kv-value="1"]', multilineData);
|
||||
await click(TS.submit);
|
||||
await waitUntil(() => find(TS.toolsInput('wrapping-token')));
|
||||
assert.true(flashSpy.calledWith('Wrap was successful.'), 'it renders success flash');
|
||||
assert.dom(TS.toolsInput('wrapping-token')).hasText(this.token);
|
||||
assert.dom('label').hasText('Wrapped token');
|
||||
assert.dom('.CodeMirror').doesNotExist();
|
||||
});
|
||||
|
||||
test('it resets on done', async function (assert) {
|
||||
await this.renderComponent();
|
||||
await codemirror().setValue(this.wrapData);
|
||||
@@ -113,7 +179,11 @@ module('Integration | Component | tools/wrap', function (hooks) {
|
||||
|
||||
await waitUntil(() => find(TS.button('Done')));
|
||||
await click(TS.button('Done'));
|
||||
assert.strictEqual(codemirror().getValue(' '), '{ }', 'json editor resets to empty object');
|
||||
assert.strictEqual(
|
||||
codemirror().getValue(' '),
|
||||
`{ \"\": \"\" }`, // eslint-disable-line no-useless-escape
|
||||
'json editor initializes with empty object that includes whitespace'
|
||||
);
|
||||
assert.dom(TTL.toggleByLabel('Wrap TTL')).isNotChecked('Wrap TTL resets to unchecked');
|
||||
await click(TTL.toggleByLabel('Wrap TTL'));
|
||||
assert.dom(TTL.valueInputByLabel('Wrap TTL')).hasValue('30', 'ttl resets to default when toggled');
|
||||
@@ -126,16 +196,51 @@ module('Integration | Component | tools/wrap', function (hooks) {
|
||||
|
||||
await waitUntil(() => find(TS.button('Back')));
|
||||
await click(TS.button('Back'));
|
||||
assert.strictEqual(codemirror().getValue(' '), `{"foo": "bar"}`, 'json editor has original data');
|
||||
assert.strictEqual(
|
||||
codemirror().getValue(' '),
|
||||
`{ \"foo": \"bar" }`, // eslint-disable-line no-useless-escape
|
||||
'json editor has original data'
|
||||
);
|
||||
assert.dom(TTL.toggleByLabel('Wrap TTL')).isNotChecked('Wrap TTL defaults to unchecked');
|
||||
});
|
||||
|
||||
test('it disables/enables submit based on json linting', async function (assert) {
|
||||
test('it renders/hides warning based on json linting', async function (assert) {
|
||||
await this.renderComponent();
|
||||
await codemirror().setValue(`{bad json}`);
|
||||
assert.dom(TS.submit).isDisabled('submit disables if json editor has linting errors');
|
||||
|
||||
assert
|
||||
.dom('[data-test-inline-alert]')
|
||||
.hasText(
|
||||
'JSON is unparsable. Fix linting errors to avoid data discrepancies.',
|
||||
'Linting error message is shown for json view'
|
||||
);
|
||||
await codemirror().setValue(this.wrapData);
|
||||
assert.dom(TS.submit).isEnabled('submit reenables if json editor has no linting errors');
|
||||
assert.dom('[data-test-inline-alert]').doesNotExist();
|
||||
});
|
||||
|
||||
test('it hides json warning on back and on done', async function (assert) {
|
||||
await this.renderComponent();
|
||||
await codemirror().setValue(`{bad json}`);
|
||||
assert
|
||||
.dom('[data-test-inline-alert]')
|
||||
.hasText(
|
||||
'JSON is unparsable. Fix linting errors to avoid data discrepancies.',
|
||||
'Linting error message is shown for json view'
|
||||
);
|
||||
await click(TS.submit);
|
||||
await waitUntil(() => find(TS.button('Done')));
|
||||
await click(TS.button('Done'));
|
||||
assert.dom('[data-test-inline-alert]').doesNotExist();
|
||||
|
||||
await codemirror().setValue(`{bad json}`);
|
||||
assert
|
||||
.dom('[data-test-inline-alert]')
|
||||
.hasText(
|
||||
'JSON is unparsable. Fix linting errors to avoid data discrepancies.',
|
||||
'Linting error message is shown for json view'
|
||||
);
|
||||
await click(TS.submit);
|
||||
await waitUntil(() => find(TS.button('Back')));
|
||||
await click(TS.button('Back'));
|
||||
assert.dom('[data-test-inline-alert]').doesNotExist();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user