-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Solve cntrl+f issue on KVv2 JSON details and edit views #28808
Changes from 12 commits
b4b816f
f4776be
e6090e3
5321c2e
d3ba3e9
6064b84
1dfc2a2
6eeee82
c9d5bb0
941d03c
76f340d
a2ee07f
8e8a071
3bdf3c7
abcd62a
112cd6d
11bbd46
59f6d4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
```release-note:improvement | ||
ui: Replace KVv2 json secret details view with Hds::CodeBlock component allowing users to search the full secret height. | ||
``` | ||
```release-note:bug | ||
ui: Allow users to search the full json object within the json code-editor edit/create view. | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,8 @@ | |
@showToolbar={{false}} | ||
@value={{stringify this.unwrapData}} | ||
@readOnly={{true}} | ||
@viewportMargin="Infinity" | ||
{{! ideally we calculate the "height" of the json data, but 100 should cover most cases }} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment as above. |
||
@viewportMargin="100" | ||
@gutters={{false}} | ||
@theme="hashi-read-only auto-height" | ||
/> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,7 +51,7 @@ | |
mode=@mode | ||
readOnly=@readOnly | ||
theme=@theme | ||
viewportMarg=@viewportMargin | ||
viewportMargin=@viewportMargin | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we set a default here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The default is inherit from CodeMirror. If you don't pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh - is that default just builtin? In codemirror I see a default of an empty string (here) Maybe we could include that in the JSDOC below that it defaults to 10? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do, and good call out. I also modified the code-mirror line that you pointed out to explicitly state the default. Codemirror docs give this rec:
|
||
onSetup=this.onSetup | ||
onUpdate=this.onUpdate | ||
onFocus=this.onFocus | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,12 +13,27 @@ | |
|
||
<hr class="is-marginless has-background-gray-200" /> | ||
{{#if @showJson}} | ||
<JsonEditor | ||
@title="{{if (eq @type 'create') 'Secret' 'Version'}} data" | ||
@value={{this.stringifiedSecretData}} | ||
@valueUpdated={{this.handleJson}} | ||
@readOnly={{eq @type "details"}} | ||
/> | ||
{{#if (eq @type "details")}} | ||
<Hds::CodeBlock | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😍 love seeing this component being used more! |
||
data-test-code-block="secret-data" | ||
@value={{this.stringifiedSecretData}} | ||
@language="json" | ||
@hasCopyButton={{true}} | ||
@maxHeight="300px" | ||
as |CB| | ||
> | ||
<CB.Title @tag="h3"> | ||
Version data | ||
</CB.Title> | ||
</Hds::CodeBlock> | ||
{{else}} | ||
<JsonEditor | ||
@title="{{if (eq @type 'create') 'Secret' 'Version'}} data" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: When you create a new version you see Version data vs Secret data. |
||
@value={{this.stringifiedSecretData}} | ||
@valueUpdated={{this.handleJson}} | ||
@viewportMargin={{this.viewportMargin}} | ||
/> | ||
{{/if}} | ||
{{#if (or @modelValidations.secretData.errors this.lintingErrors)}} | ||
<AlertInline | ||
@color={{if this.lintingErrors "warning" "critical"}} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,6 @@ | |
<Hds::Text::Body @tag="p" @weight="semibold">Reveal subkeys in JSON</Hds::Text::Body> | ||
</Toggle> | ||
{{#if this.showSubkeys}} | ||
<Hds::CodeBlock @value={{stringify @subkeys}} @hasLineNumbers={{false}} data-test-subkeys /> | ||
<Hds::CodeBlock @value={{stringify @subkeys}} @hasLineNumbers={{false}} data-test-code-block="subkeys" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated all selectors added to the |
||
{{/if}} | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -299,22 +299,34 @@ module('Acceptance | kv-v2 workflow | edge cases', function (hooks) { | |
codemirror().getValue(), | ||
`{ | ||
\"\": \"\" | ||
}` | ||
}`, | ||
'JSON editor displays correct empty object' | ||
); | ||
codemirror().setValue('{ "foo3": { "name": "bar3" } }'); | ||
await click(FORM.saveBtn); | ||
|
||
// Details view | ||
await click(PAGE.secretTab('Secret')); | ||
assert.dom(FORM.toggleJson).isNotDisabled(); | ||
assert.dom(FORM.toggleJson).isChecked(); | ||
assert.false(codemirror().getValue().includes('*'), 'Values are not obscured on details view'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A little clean up that was missed when we removed obscuring json values from the codemirror editor view PR #28261. |
||
assert.dom(FORM.toggleJson).isNotDisabled('JSON toggle is not disabled'); | ||
assert.dom(FORM.toggleJson).isChecked("JSON toggle is checked 'on'"); | ||
|
||
assert | ||
.dom(GENERAL.codeBlock('secret-data')) | ||
.hasText('Version data { "foo3": { "name": "bar3" } }', 'Values are displayed in the details view'); | ||
|
||
// New version view | ||
await click(PAGE.detail.createNewVersion); | ||
assert.dom(FORM.toggleJson).isNotDisabled(); | ||
assert.dom(FORM.toggleJson).isChecked(); | ||
assert.false(codemirror().getValue().includes('*'), 'Values are not obscured on edit view'); | ||
assert.deepEqual( | ||
codemirror().getValue(), | ||
`{ | ||
"foo3": { | ||
"name": "bar3" | ||
} | ||
}`, | ||
'Values are displayed in the new version view' | ||
); | ||
}); | ||
|
||
test('on enter the JSON editor cursor goes to the next line', async function (assert) { | ||
|
@@ -359,12 +371,16 @@ module('Acceptance | kv-v2 workflow | edge cases', function (hooks) { | |
await click(PAGE.secretTab('Secret')); | ||
await click(PAGE.detail.versionDropdown); | ||
await click(`${PAGE.detail.version(1)} a`); | ||
assert.strictEqual(codemirror().getValue(), expectedDataV1, 'Version one data is displayed'); | ||
assert | ||
.dom(GENERAL.codeBlock('secret-data')) | ||
.hasText(`Version data ${expectedDataV1}`, 'Version one data is displayed'); | ||
|
||
// Navigate back the second version and make sure the secret data is correct | ||
await click(PAGE.detail.versionDropdown); | ||
await click(`${PAGE.detail.version(2)} a`); | ||
assert.strictEqual(codemirror().getValue(), expectedDataV2, 'Version two data is displayed'); | ||
assert | ||
.dom(GENERAL.codeBlock('secret-data')) | ||
.hasText(`Version data ${expectedDataV2}`, 'Version two data is displayed'); | ||
}); | ||
|
||
test('does not register as advanced when value includes {', async function (assert) { | ||
|
Monkeychip marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,8 +90,8 @@ export const PAGE = { | |
}, | ||
paths: { | ||
copyButton: (label) => `${PAGE.infoRowValue(label)} button`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I go back and forth on putting this in the general selectors. Opted to keep as you see here until it becomes more obvious that these selectors will be used outside of kv. |
||
codeSnippet: (section) => `[data-test-commands="${section}"] code`, | ||
snippetCopy: (section) => `[data-test-commands="${section}"] button`, | ||
codeSnippet: (section) => `[data-test-code-block="${section}"] code`, | ||
snippetCopy: (section) => `[data-test-code-block="${section}"] button`, | ||
}, | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -206,3 +206,49 @@ export const fillInAwsConfig = async (situation = 'withAccess') => { | |
await fillIn(GENERAL.ttl.input('Identity token TTL'), '7200'); | ||
} | ||
}; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Admittedly, I got carried away on making this helper. I could have saved myself time and hardcoded or modified the KvDataFields defaults during a test situation. However, I can imagine this being a helpful function for future KV things. Gives us a quick way to make longer more complicated json objects. Also, it was kind of fun. |
||
// Example usage | ||
// createLongJson (2, 3) will create a json object with 2 original keys, each with 3 nested keys | ||
// { | ||
// "key-0": { | ||
// "nested-key-0": { | ||
// "nested-key-1": { | ||
// "nested-key-2": "nested-value" | ||
// } | ||
// } | ||
// }, | ||
// "key-1": { | ||
// "nested-key-0": { | ||
// "nested-key-1": { | ||
// "nested-key-2": "nested-value" | ||
// } | ||
// } | ||
// } | ||
// } | ||
|
||
export function createLongJson(lines = 10, nestLevel = 3) { | ||
const keys = Array.from({ length: nestLevel }, (_, i) => `nested-key-${i}`); | ||
const jsonObject = {}; | ||
|
||
for (let i = 0; i < lines; i++) { | ||
nestLevel > 0 | ||
? (jsonObject[`key-${i}`] = createNestedObject({}, keys, 'nested-value')) | ||
: (jsonObject[`key-${i}`] = 'non-nested-value'); | ||
} | ||
return jsonObject; | ||
} | ||
|
||
function createNestedObject(obj = {}, keys, value) { | ||
let current = obj; | ||
|
||
for (let i = 0; i < keys.length - 1; i++) { | ||
const key = keys[i]; | ||
if (!current[key]) { | ||
current[key] = {}; | ||
} | ||
current = current[key]; | ||
} | ||
|
||
current[keys[keys.length - 1]] = value; | ||
return obj; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ import hbs from 'htmlbars-inline-precompile'; | |
import jsonEditor from '../../pages/components/json-editor'; | ||
import sinon from 'sinon'; | ||
import { setRunOptions } from 'ember-a11y-testing/test-support'; | ||
import { createLongJson } from 'vault/tests/helpers/secret-engine/secret-engine-helpers'; | ||
|
||
const component = create(jsonEditor); | ||
|
||
|
@@ -29,13 +30,16 @@ module('Integration | Component | json-editor', function (hooks) { | |
this.set('onFocusOut', sinon.spy()); | ||
this.set('json_blob', JSON_BLOB); | ||
this.set('bad_json_blob', BAD_JSON_BLOB); | ||
this.set('long_json', JSON.stringify(createLongJson(), null, `\t`)); | ||
this.set('hashi-read-only-theme', 'hashi-read-only auto-height'); | ||
setRunOptions({ | ||
rules: { | ||
// CodeMirror has a secret textarea without a label that causes this problem | ||
label: { enabled: false }, | ||
// TODO: investigate and fix Codemirror styling | ||
'color-contrast': { enabled: false }, | ||
// failing on .CodeMirror-scroll | ||
'scrollable-region-focusable': { enabled: false }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm I wonder why this is failing now and wasn't before 🤔 is it worth looking into? I wonder if setting a default viewport margin value resolves this 💭 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. This is an ally testing thing—copied the same thing from Chelsea's fixes, here's one example but there are several more. From what I can tell it's because I'm testing just the component and now it scrolls. |
||
}, | ||
}); | ||
}); | ||
|
@@ -129,4 +133,31 @@ module('Integration | Component | json-editor', function (hooks) { | |
'even after hitting enter the value is still set correctly' | ||
); | ||
}); | ||
|
||
test('when @viewportMargin is not set user is unable to search a long secret', async function (assert) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test name feels a little confusing. Is there a default viewport margin and so the data is cut off so only a portion is rendered on the DOM? Or is there none at all so there's no scrolling? Rewording to something like "no viewport margin renders only X (the default if we have one) lines of data on the DOM" feels a little more like it's explaining what's happening. Then in the test we can see we're testing by using ctrl+F |
||
await render(hbs` | ||
<JsonEditor | ||
@value={{this.long_json}} | ||
@mode="ruby" | ||
@valueUpdated={{fn (mut this.value)}} | ||
/> | ||
`); | ||
assert | ||
.dom('.CodeMirror-code') | ||
.doesNotIncludeText('key-9', 'Without viewportMargin, user cannot search for key-9'); | ||
}); | ||
|
||
test('when @viewportMargin is set user is able to search a long secret', async function (assert) { | ||
await render(hbs` | ||
<JsonEditor | ||
@value={{this.long_json}} | ||
@mode="ruby" | ||
@valueUpdated={{fn (mut this.value)}} | ||
@viewportMargin="100" | ||
/> | ||
`); | ||
assert | ||
.dom('.CodeMirror-code') | ||
.containsText('key-9', 'With viewportMargin set, user can search for key-9'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,9 @@ import { hbs } from 'ember-cli-htmlbars'; | |
import { fillIn, render, click } from '@ember/test-helpers'; | ||
import codemirror from 'vault/tests/helpers/codemirror'; | ||
import { PAGE, FORM } from 'vault/tests/helpers/kv/kv-selectors'; | ||
import { GENERAL } from 'vault/tests/helpers/general-selectors'; | ||
import { setRunOptions } from 'ember-a11y-testing/test-support'; | ||
import { createLongJson } from 'vault/tests/helpers/secret-engine/secret-engine-helpers'; | ||
|
||
module('Integration | Component | kv-v2 | KvDataFields', function (hooks) { | ||
setupRenderingTest(hooks); | ||
|
@@ -22,6 +25,12 @@ module('Integration | Component | kv-v2 | KvDataFields', function (hooks) { | |
this.backend = 'my-kv-engine'; | ||
this.path = 'my-secret'; | ||
this.secret = this.store.createRecord('kv/data', { backend: this.backend }); | ||
setRunOptions({ | ||
rules: { | ||
// failing on .CodeMirror-scroll | ||
'scrollable-region-focusable': { enabled: false }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment wondering why this is failing now |
||
}, | ||
}); | ||
}); | ||
|
||
test('it updates the secret model', async function (assert) { | ||
|
@@ -88,7 +97,7 @@ module('Integration | Component | kv-v2 | KvDataFields', function (hooks) { | |
assert.dom(PAGE.infoRowValue('foo')).hasText('bar', 'secret value shows after toggle'); | ||
}); | ||
|
||
test('it shows readonly json editor when viewing secret details of complex secret', async function (assert) { | ||
test('it shows hds codeblock when viewing secret details of complex secret', async function (assert) { | ||
this.secret.secretData = { | ||
foo: { | ||
bar: 'baz', | ||
|
@@ -100,7 +109,24 @@ module('Integration | Component | kv-v2 | KvDataFields', function (hooks) { | |
owner: this.engine, | ||
}); | ||
assert.dom(PAGE.infoRowValue('foo')).doesNotExist('does not render rows of secret data'); | ||
assert.dom('[data-test-component="code-mirror-modifier"]').hasClass('readonly-codemirror'); | ||
assert.dom('[data-test-component="code-mirror-modifier"]').includesText(`{ "foo": { "bar": "baz" }}`); | ||
assert.dom(GENERAL.codeBlock('secret-data')).exists('hds codeBlock exists'); | ||
assert | ||
.dom(GENERAL.codeBlock('secret-data')) | ||
.hasText(`Version data { "foo": { "bar": "baz" } } `, 'Json data is displayed'); | ||
}); | ||
|
||
test('it defaults to a viewportMargin 10 when there is no secret data', async function (assert) { | ||
await render(hbs`<KvDataFields @showJson={{true}} @secret={{this.secret}} />`, { owner: this.engine }); | ||
assert.strictEqual(codemirror().options.viewportMargin, 10, 'viewportMargin defaults to 10'); | ||
}); | ||
|
||
test('it calculates viewportMargin based on secret size', async function (assert) { | ||
this.secret.secretData = createLongJson(100); | ||
await render(hbs`<KvDataFields @showJson={{true}} @secret={{this.secret}} />`, { owner: this.engine }); | ||
assert.strictEqual( | ||
codemirror().options.viewportMargin, | ||
100, | ||
'viewportMargin is set to 100 matching the height of the json' | ||
); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
previously this was defaulting to 10, so another option is to remove this all together and let it default to 10 until someone complains?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If 100 doesn't have any major performance implications I think that should be okay here. I'm not sure what the data this typically renders - so it's possible 10 may be more reasonable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can gather this is output for the ui console/terminal window. 10 for most situations should be enough unless they're trying to read a really long secret.
Is 100 going to have any performance implications? not that I can tell via testing, but also there's no documentation to say otherwise. From what I gather, they're cautioning against using "Infinity" because that could be bad if your json was millions of lines long.