-
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
UI secret navigation improvements #5976
Changes from all commits
0e2ff96
fedc17e
9eb0cf9
173dd03
2ea4f10
5711204
832a3f1
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,40 @@ | ||
import Mixin from '@ember/object/mixin'; | ||
import utils from 'vault/lib/key-utils'; | ||
import { task } from 'ember-concurrency'; | ||
|
||
// This mixin is currently used in a controller and a component, but we | ||
// don't see cancellation of the task as the while loop runs in either | ||
|
||
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. @gregone had some opinions on this type of In my case we judged it not to be a concern right now, does anything of what's said there apply here? Actually do Components have 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. Ah yeah - in this case This mixin is also very specific to the secrets list route - it wouldn't be used elsewhere. |
||
// Controller in Ember are singletons so there's no cancellation there | ||
// during the loop. For components, it might be expected that the task would | ||
// be cancelled when we transitioned to a new route and a rerender occured, but this is not | ||
// the case since we are catching the error. Since Ember's route transitions are lazy | ||
// and we're catching any 404s, the loop continues until the transtion succeeds, or exhausts | ||
// the ancestors array and transitions to the root | ||
export default Mixin.create({ | ||
navToNearestAncestor: task(function*(key) { | ||
let ancestors = utils.ancestorKeysForKey(key); | ||
let errored = false; | ||
let nearest = ancestors.pop(); | ||
while (nearest) { | ||
try { | ||
let transition = this.transitionToRoute('vault.cluster.secrets.backend.list', nearest); | ||
transition.data.isDeletion = true; | ||
yield transition.promise; | ||
} catch (e) { | ||
// in the route error event handler, we're only throwing when it's a 404, | ||
// other errors will be in the route and will not be caught, so the task will complete | ||
errored = true; | ||
nearest = ancestors.pop(); | ||
} finally { | ||
if (!errored) { | ||
nearest = null; | ||
// eslint-disable-next-line | ||
return; | ||
} | ||
errored = false; | ||
} | ||
} | ||
yield this.transitionToRoute('vault.cluster.secrets.backend.list-root'); | ||
}), | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { currentURL, currentRouteName } from '@ember/test-helpers'; | ||
import { settled, currentURL, currentRouteName } from '@ember/test-helpers'; | ||
import { create } from 'ember-cli-page-object'; | ||
import { module, test } from 'qunit'; | ||
import { setupApplicationTest } from 'ember-qunit'; | ||
|
@@ -79,6 +79,65 @@ module('Acceptance | secrets/secret/create', function(hooks) { | |
assert.ok(showPage.editIsPresent, 'shows the edit button'); | ||
}); | ||
|
||
// https://github.com/hashicorp/vault/issues/5960 | ||
test('version 1: nested paths creation maintains ability to navigate the tree', async function(assert) { | ||
let enginePath = `kv-${new Date().getTime()}`; | ||
let secretPath = '1/2/3/4'; | ||
// mount version 1 engine | ||
await mountSecrets.visit(); | ||
await mountSecrets.selectType('kv'); | ||
await withFlash( | ||
mountSecrets | ||
.next() | ||
.path(enginePath) | ||
.version(1) | ||
.submit() | ||
); | ||
|
||
await listPage.create(); | ||
await editPage.createSecret(secretPath, 'foo', 'bar'); | ||
|
||
// setup an ancestor for when we delete | ||
await listPage.visitRoot({ backend: enginePath }); | ||
await listPage.secrets.filterBy('text', '1/')[0].click(); | ||
await listPage.create(); | ||
await editPage.createSecret('1/2', 'foo', 'bar'); | ||
|
||
// lol we have to do this because ember-cli-page-object doesn't like *'s in visitable | ||
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. Not sure if I misunderstood this comment but just incase, I saw this and thought I'd share how I got around what I think you mean here in Consul: As far as I remember I mainly took this from Feel free to steal if it helps, if you want to see usage somewhere I can looksee where I am using it and link you to it. 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. Ha yep, that was exactly the issue I was having - for sure going to steal this later, but probably just leave as-is for now. I see you added the ability to pass an array of paths too - how do you determine which one to visit when you do that? 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. Cool, I owe you an aws icon anyway 😂 If you create the visitable with 2 paths, say: visitable(['/:dc/kv/:kv', '/:dc/kv'], str => str) .. and you don't provide all the keys when you try to visit it, something like: visit(
{
dc: 'dc-1'
}
); then it will use the one that it has complete data for. So that above visit call will use the second array item for the URL in the above code, and: visit(
{
dc: 'dc-1',
kv: 'some/kv/deep/down'
}
); ...would use the first one. Not sure if I've explained that well enough, ping me if you like if it's not clear. I think (as far as I remember) I made it backwards compatible with the original implementation, so you can still just pass it a string like the original one. 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. Ohhh I see it's the keys you pass in that determine the route you visit, that's the part I was missing. Thanks for explaining! 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. Cool np, yeah in this case if I want the index page, I don't pass it a KV, whereas if I want to test an actual KV page I pass it a KV. Actually I'm just remembering now, I think I use it a lot for the 'create or edit' thing. If I pass a 'slug' it means I want an edit page, whereas if I don't I want a 'create'. It's almost like how the router works I suppose. |
||
await listPage.visitRoot({ backend: enginePath }); | ||
await listPage.secrets.filterBy('text', '1/')[0].click(); | ||
await listPage.secrets.filterBy('text', '2/')[0].click(); | ||
await listPage.secrets.filterBy('text', '3/')[0].click(); | ||
await listPage.create(); | ||
|
||
await editPage.createSecret(secretPath + 'a', 'foo', 'bar'); | ||
await listPage.visitRoot({ backend: enginePath }); | ||
await listPage.secrets.filterBy('text', '1/')[0].click(); | ||
await listPage.secrets.filterBy('text', '2/')[0].click(); | ||
let secretLink = listPage.secrets.filterBy('text', '3/')[0]; | ||
assert.ok(secretLink, 'link to the 3/ branch displays properly'); | ||
|
||
await listPage.secrets.filterBy('text', '3/')[0].click(); | ||
await listPage.secrets[0].menuToggle(); | ||
await settled(); | ||
await listPage.delete(); | ||
await listPage.confirmDelete(); | ||
await settled(); | ||
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.list'); | ||
assert.equal(currentURL(), `/vault/secrets/${enginePath}/list/1/2/3/`, 'remains on the page'); | ||
|
||
await listPage.secrets[0].menuToggle(); | ||
await listPage.delete(); | ||
await listPage.confirmDelete(); | ||
await settled(); | ||
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.list'); | ||
assert.equal( | ||
currentURL(), | ||
`/vault/secrets/${enginePath}/list/1/`, | ||
'navigates to the ancestor created earlier' | ||
); | ||
}); | ||
|
||
test('it redirects to the path ending in / for list pages', async function(assert) { | ||
const path = `foo/bar/kv-path-${new Date().getTime()}`; | ||
await listPage.visitRoot({ backend: 'secret' }); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { create, collection, visitable, clickable, isPresent } from 'ember-cli-page-object'; | ||
import { create, collection, text, visitable, clickable, isPresent } from 'ember-cli-page-object'; | ||
import { getter } from 'ember-cli-page-object/macros'; | ||
|
||
export default create({ | ||
|
@@ -8,15 +8,21 @@ export default create({ | |
createIsPresent: isPresent('[data-test-secret-create]'), | ||
configure: clickable('[data-test-secret-backend-configure]'), | ||
configureIsPresent: isPresent('[data-test-secret-backend-configure]'), | ||
|
||
tabs: collection('[data-test-tab]'), | ||
secrets: collection('[data-test-secret-link]', { | ||
menuToggle: clickable('[data-test-popup-menu-trigger]'), | ||
id: text(), | ||
click: clickable(), | ||
}), | ||
menuItems: collection('.ember-basic-dropdown-content li', { | ||
testContainer: '#ember-testing', | ||
}), | ||
|
||
delete: clickable('[data-test-confirm-action-trigger]', { | ||
testContainer: '#ember-testing', | ||
}), | ||
confirmDelete: clickable('[data-test-confirm-button]', { | ||
testContainer: '#ember-testing', | ||
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. Just wondering what the 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 has to do with ember-basic-dropdown and where the popup gets inserted into the DOM. The test application gets rendered inside of #ember-testing, so that's the default container and the "wormhole" gets rendered along side it. If you don't tell ember-cli-page-object where to look, it defaults to the application div as the test container, so it can't find things that render to the wormhole. 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. Ah gotcha, cool thanks for the info (on all of these ^) |
||
}), | ||
backendIsEmpty: getter(function() { | ||
return this.secrets.length === 0; | ||
}), | ||
|
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.
What does returning here do? Still learning Ember
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.
The EC task needs to yield the promise in order to function properly and here were weren't returning it. The controller
transitionToRoute
already does return the Transition, which is a promise so this makes it so they match.