-
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: delete pki key functionality #18146
Changes from 9 commits
9b94c3c
274b189
76024ce
e071d4f
991ab59
044c28b
0668c66
fc86187
0e1a028
a8abf67
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 |
---|---|---|
@@ -1,18 +1,19 @@ | ||
import Model, { attr } from '@ember-data/model'; | ||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs'; | ||
import { inject as service } from '@ember/service'; | ||
import { withFormFields } from 'vault/decorators/model-form-fields'; | ||
|
||
@withFormFields(['keyId', 'keyName', 'keyType', 'keyBits']) | ||
export default class PkiKeyModel extends Model { | ||
@attr('string', { readOnly: true }) backend; | ||
@service secretMountPath; | ||
|
||
@attr('boolean') isDefault; | ||
@attr('string', { possibleValues: ['internal', 'external'] }) type; | ||
@attr('string', { detailsLabel: 'Key ID' }) keyId; | ||
@attr('string') keyName; | ||
@attr('string') keyType; | ||
@attr('string', { detailsLabel: 'Key bit length' }) keyBits; // TODO confirm with crypto team to remove this field from details page | ||
@attr('string') keyBits; | ||
|
||
// TODO refactor when field-to-attrs util is refactored as decorator | ||
constructor() { | ||
super(...arguments); | ||
this.formFields = expandAttributeMeta(this, ['keyId', 'keyName', 'keyType', 'keyBits']); | ||
get backend() { | ||
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. Is it bad for this to be a getter? In other situations we've added it as an attribute in the adapter - but I felt it sometimes got lost there. Happy to move it there instead! 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. Ooh I love this! I wonder if we can standardize everywhere else 👀 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. Another option is to use |
||
return this.secretMountPath.currentPath; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
import FlashMessages from 'ember-cli-flash/services/flash-messages'; | ||
|
||
export default FlashMessages.extend({ | ||
stickyInfo(message) { | ||
export default class FlashMessageService extends FlashMessages { | ||
stickyInfo(message: string) { | ||
return this.info(message, { | ||
sticky: true, | ||
priority: 300, | ||
}); | ||
}, | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { action } from '@ember/object'; | ||
import Component from '@glimmer/component'; | ||
import RouterService from '@ember/routing/router-service'; | ||
import FlashMessageService from 'vault/services/flash-messages'; | ||
import { inject as service } from '@ember/service'; | ||
import errorMessage from 'vault/utils/error-message'; | ||
interface Args { | ||
key: { | ||
rollbackAttributes: () => void; | ||
destroyRecord: () => void; | ||
backend: string; | ||
keyName: string; | ||
keyId: string; | ||
}; | ||
} | ||
|
||
export default class PkiKeyDetails extends Component<Args> { | ||
@service declare readonly router: RouterService; | ||
@service declare readonly flashMessages: FlashMessageService; | ||
|
||
get breadcrumbs() { | ||
return [ | ||
{ label: 'secrets', route: 'secrets', linkExternal: true }, | ||
{ label: this.args.key.backend || 'pki', route: 'overview' }, | ||
{ label: 'keys', route: 'keys.index' }, | ||
{ label: this.args.key.keyId }, | ||
]; | ||
} | ||
|
||
@action | ||
async deleteKey() { | ||
try { | ||
await this.args.key.destroyRecord(); | ||
this.flashMessages.success('Key deleted successfully'); | ||
this.router.transitionTo('vault.cluster.secrets.backend.pki.keys.index'); | ||
} catch (error) { | ||
this.args.key.rollbackAttributes(); | ||
this.flashMessages.danger(errorMessage(error)); | ||
} | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
<PkiKeyDetails @key={{this.model}} /> | ||
<Page::PkiKeyDetails @key={{this.model}} /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
<PkiRoleDetailsPage @role={{this.model}} /> | ||
<Page::PkiRoleDetails @role={{this.model}} /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export const SELECTORS = { | ||
breadcrumbContainer: '[data-test-breadcrumbs]', | ||
breadcrumbs: '[data-test-breadcrumbs] li', | ||
title: '[data-test-key-details-title]', | ||
keyIdValue: '[data-test-value-div="Key ID"]', | ||
keyNameValue: '[data-test-value-div="Key name"]', | ||
keyTypeValue: '[data-test-value-div="Key type"]', | ||
keyBitsValue: '[data-test-value-div="Key bits"]', | ||
keyDeleteButton: '[data-test-pki-key-delete] button', | ||
confirmDelete: '[data-test-confirm-button]', | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { module, test } from 'qunit'; | ||
import { setupRenderingTest } from 'ember-qunit'; | ||
import { click, render } from '@ember/test-helpers'; | ||
import { hbs } from 'ember-cli-htmlbars'; | ||
import { setupEngine } from 'ember-engines/test-support'; | ||
import { setupMirage } from 'ember-cli-mirage/test-support'; | ||
import { SELECTORS } from 'vault/tests/helpers/pki/key/page-details'; | ||
|
||
module('Integration | Component | pki key details page', function (hooks) { | ||
setupRenderingTest(hooks); | ||
setupEngine(hooks, 'pki'); | ||
setupMirage(hooks); | ||
|
||
hooks.beforeEach(function () { | ||
this.owner.lookup('service:flash-messages').registerTypes(['success', 'danger']); | ||
this.store = this.owner.lookup('service:store'); | ||
this.secretMountPath = this.owner.lookup('service:secret-mount-path'); | ||
this.backend = 'pki-test'; | ||
this.secretMountPath.currentPath = this.backend; | ||
this.store.pushPayload('pki/key', { | ||
modelName: 'pki/key', | ||
key_id: '724862ff-6438-bad0-b598-77a6c7f4e934', | ||
key_type: 'ec', | ||
key_name: 'test-key', | ||
}); | ||
this.model = this.store.peekRecord('pki/key', '724862ff-6438-bad0-b598-77a6c7f4e934'); | ||
}); | ||
|
||
test('it renders the page component and deletes a key', async function (assert) { | ||
assert.expect(9); | ||
this.server.delete(`${this.backend}/key/${this.model.keyId}`, () => { | ||
assert.ok(true, 'confirming delete fires off destroyRecord()'); | ||
}); | ||
|
||
await render( | ||
hbs` | ||
<Page::PkiKeyDetails @key={{this.model}} /> | ||
`, | ||
{ owner: this.engine } | ||
); | ||
|
||
assert.dom(SELECTORS.breadcrumbContainer).exists({ count: 1 }, 'breadcrumb containers exist'); | ||
assert.dom(SELECTORS.breadcrumbs).exists({ count: 4 }, 'Shows 4 breadcrumbs'); | ||
assert.dom(SELECTORS.title).containsText('View key', 'title renders'); | ||
assert.dom(SELECTORS.keyIdValue).hasText(' 724862ff-6438-bad0-b598-77a6c7f4e934', 'key id renders'); | ||
assert.dom(SELECTORS.keyNameValue).hasText('test-key', 'key name renders'); | ||
assert.dom(SELECTORS.keyTypeValue).hasText('ec', 'key type renders'); | ||
assert.dom(SELECTORS.keyBitsValue).doesNotExist('does not render empty value'); | ||
assert.dom(SELECTORS.keyDeleteButton).exists('renders delete button'); | ||
await click(SELECTORS.keyDeleteButton); | ||
await click(SELECTORS.confirmDelete); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { module, test } from 'qunit'; | ||
import { setupTest } from 'ember-qunit'; | ||
import { setupMirage } from 'ember-cli-mirage/test-support'; | ||
|
||
module('Unit | Adapter | pki/key', function (hooks) { | ||
setupTest(hooks); | ||
setupMirage(hooks); | ||
|
||
hooks.beforeEach(function () { | ||
this.store = this.owner.lookup('service:store'); | ||
this.secretMountPath = this.owner.lookup('service:secret-mount-path'); | ||
this.backend = 'pki-test'; | ||
this.secretMountPath.currentPath = this.backend; | ||
this.data = { | ||
key_id: '724862ff-6438-bad0-b598-77a6c7f4e934', | ||
key_type: 'ec', | ||
key_name: 'test-key', | ||
key_bits: '256', | ||
}; | ||
}); | ||
|
||
hooks.afterEach(function () { | ||
this.server.shutdown(); | ||
}); | ||
|
||
test('it should make request to correct endpoint on query', async function (assert) { | ||
assert.expect(1); | ||
const { key_id, ...otherAttrs } = this.data; // excludes key_id from key_info data | ||
const key_info = { [key_id]: { ...otherAttrs } }; | ||
this.server.get(`${this.backend}/keys`, (schema, req) => { | ||
assert.strictEqual(req.queryParams.list, 'true', 'request is made to correct endpoint on query'); | ||
return { data: { keys: [key_id], key_info } }; | ||
}); | ||
|
||
this.store.query('pki/key', { backend: this.backend }); | ||
}); | ||
|
||
test('it should make request to correct endpoint on queryRecord', async function (assert) { | ||
assert.expect(1); | ||
|
||
this.server.get(`${this.backend}/key/${this.data.key_id}`, () => { | ||
assert.ok(true, 'request is made to correct endpoint on query record'); | ||
return { data: this.data }; | ||
}); | ||
|
||
this.store.queryRecord('pki/key', { backend: this.backend, id: this.data.key_id }); | ||
}); | ||
|
||
test('it should make request to correct endpoint on delete', async function (assert) { | ||
assert.expect(1); | ||
this.store.pushPayload('pki/key', { modelName: 'pki/key', ...this.data }); | ||
this.server.get(`${this.backend}/key/${this.data.key_id}`, () => ({ data: this.data })); | ||
this.server.delete(`${this.backend}/key/${this.data.key_id}`, () => { | ||
assert.ok(true, 'request made to correct endpoint on delete'); | ||
}); | ||
|
||
const model = await this.store.queryRecord('pki/key', { backend: this.backend, id: this.data.key_id }); | ||
await model.destroyRecord(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
declare module 'ember-cli-flash/services/flash-messages' { | ||
import Service from '@ember/service'; | ||
import FlashObject from 'ember-cli-flash/flash/object'; | ||
import { A } from '@ember/array'; | ||
|
||
type Partial<T> = { [K in keyof T]?: T[K] }; | ||
|
||
interface MessageOptions { | ||
type: string; | ||
priority: number; | ||
timeout: number; | ||
sticky: boolean; | ||
showProgress: boolean; | ||
extendedTimeout: number; | ||
destroyOnClick: boolean; | ||
onDestroy: () => void; | ||
[key: string]: unknown; | ||
} | ||
|
||
interface CustomMessageInfo extends Partial<MessageOptions> { | ||
message: string; | ||
} | ||
|
||
interface FlashFunction { | ||
(message: string, options?: Partial<MessageOptions>): FlashMessageService; | ||
} | ||
|
||
class FlashMessageService extends Service { | ||
queue: A<FlashObject>; | ||
success: FlashFunction; | ||
warning: FlashFunction; | ||
info: FlashFunction; | ||
error: FlashFunction; | ||
danger: FlashFunction; | ||
alert: FlashFunction; | ||
secondary: FlashFunction; | ||
add(messageInfo: CustomMessageInfo): FlashMessageService; | ||
clearMessages(): FlashMessageService; | ||
registerTypes(types: string[]): FlashMessageService; | ||
getFlashObject(): FlashObject; | ||
} | ||
|
||
export default FlashMessageService; | ||
} |
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.
✨ 🤩