From 5d29e6ce587aebc85ddbd5780e2eb584d5088390 Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Wed, 30 Nov 2022 17:24:40 -0800 Subject: [PATCH] ui: delete pki key functionality (#18146) * add deletekey * fix types * move page components into folder * finish tests * make linting changes * declare flashmessages ts service * restructure pki test files * add delete test * add more folders --- ui/app/adapters/pki/key.js | 7 ++- ui/app/models/pki/key.js | 15 ++--- .../{flash-messages.js => flash-messages.ts} | 8 +-- ui/app/utils/error-message.js | 2 +- .../components/{ => page}/pki-key-details.hbs | 0 .../addon/components/page/pki-key-details.ts | 41 +++++++++++++ .../pki-role-details.hbs} | 0 .../pki-role-details.ts} | 0 .../pki/addon/components/pki-key-details.ts | 27 --------- .../pki/addon/templates/keys/key/details.hbs | 2 +- .../addon/templates/roles/role/details.hbs | 2 +- ui/tests/helpers/pki/keys/page-details.js | 11 ++++ .../{pki-engine.js => pki/roles/form.js} | 1 - .../page-details.js} | 0 .../components/pki/keys/page-details-test.js | 53 ++++++++++++++++ .../components/pki/pki-key-parameters-test.js | 2 +- .../components/pki/pki-key-usage-test.js | 2 +- .../form-test.js} | 2 +- .../page-details-test.js} | 4 +- ui/tests/unit/adapters/pki/key-test.js | 60 +++++++++++++++++++ .../services/flash-messages.d.ts | 44 ++++++++++++++ 21 files changed, 234 insertions(+), 49 deletions(-) rename ui/app/services/{flash-messages.js => flash-messages.ts} (59%) rename ui/lib/pki/addon/components/{ => page}/pki-key-details.hbs (100%) create mode 100644 ui/lib/pki/addon/components/page/pki-key-details.ts rename ui/lib/pki/addon/components/{pki-role-details-page.hbs => page/pki-role-details.hbs} (100%) rename ui/lib/pki/addon/components/{pki-role-details-page.ts => page/pki-role-details.ts} (100%) delete mode 100644 ui/lib/pki/addon/components/pki-key-details.ts create mode 100644 ui/tests/helpers/pki/keys/page-details.js rename ui/tests/helpers/{pki-engine.js => pki/roles/form.js} (99%) rename ui/tests/helpers/pki/{page-role-details.js => roles/page-details.js} (100%) create mode 100644 ui/tests/integration/components/pki/keys/page-details-test.js rename ui/tests/integration/components/pki/{pki-role-form-test.js => roles/form-test.js} (98%) rename ui/tests/integration/components/pki/{page-role-detail-test.js => roles/page-details-test.js} (92%) create mode 100644 ui/tests/unit/adapters/pki/key-test.js create mode 100644 ui/types/ember-cli-flash/services/flash-messages.d.ts diff --git a/ui/app/adapters/pki/key.js b/ui/app/adapters/pki/key.js index 91a979509a24..58541ef76a75 100644 --- a/ui/app/adapters/pki/key.js +++ b/ui/app/adapters/pki/key.js @@ -1,9 +1,7 @@ import ApplicationAdapter from '../application'; import { encodePath } from 'vault/utils/path-encoding-helpers'; - export default class PkiKeyAdapter extends ApplicationAdapter { namespace = 'v1'; - getUrl(backend, id) { const url = `${this.buildURL()}/${encodePath(backend)}`; if (id) { @@ -21,4 +19,9 @@ export default class PkiKeyAdapter extends ApplicationAdapter { const { backend, id } = query; return this.ajax(this.getUrl(backend, id), 'GET'); } + + deleteRecord(store, type, snapshot) { + const { id, record } = snapshot; + return this.ajax(this.getUrl(record.backend, id), 'DELETE'); + } } diff --git a/ui/app/models/pki/key.js b/ui/app/models/pki/key.js index d767289e773f..7c5f2bb1a65e 100644 --- a/ui/app/models/pki/key.js +++ b/ui/app/models/pki/key.js @@ -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() { + return this.secretMountPath.currentPath; } } diff --git a/ui/app/services/flash-messages.js b/ui/app/services/flash-messages.ts similarity index 59% rename from ui/app/services/flash-messages.js rename to ui/app/services/flash-messages.ts index ae0258579fe3..a2de0a1a705f 100644 --- a/ui/app/services/flash-messages.js +++ b/ui/app/services/flash-messages.ts @@ -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, }); - }, -}); + } +} diff --git a/ui/app/utils/error-message.js b/ui/app/utils/error-message.js index 21ef2a50f21d..82614f5a6b68 100644 --- a/ui/app/utils/error-message.js +++ b/ui/app/utils/error-message.js @@ -1,6 +1,6 @@ // accepts an error and returns error.errors joined with a comma, error.message or a fallback message export default function (error, fallbackMessage = 'An error occurred, please try again') { - if (error?.errors) { + if (error instanceof Error && error?.errors) { return error.errors.join(', '); } return error?.message || fallbackMessage; diff --git a/ui/lib/pki/addon/components/pki-key-details.hbs b/ui/lib/pki/addon/components/page/pki-key-details.hbs similarity index 100% rename from ui/lib/pki/addon/components/pki-key-details.hbs rename to ui/lib/pki/addon/components/page/pki-key-details.hbs diff --git a/ui/lib/pki/addon/components/page/pki-key-details.ts b/ui/lib/pki/addon/components/page/pki-key-details.ts new file mode 100644 index 000000000000..713fc426fbdf --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-key-details.ts @@ -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 { + @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)); + } + } +} diff --git a/ui/lib/pki/addon/components/pki-role-details-page.hbs b/ui/lib/pki/addon/components/page/pki-role-details.hbs similarity index 100% rename from ui/lib/pki/addon/components/pki-role-details-page.hbs rename to ui/lib/pki/addon/components/page/pki-role-details.hbs diff --git a/ui/lib/pki/addon/components/pki-role-details-page.ts b/ui/lib/pki/addon/components/page/pki-role-details.ts similarity index 100% rename from ui/lib/pki/addon/components/pki-role-details-page.ts rename to ui/lib/pki/addon/components/page/pki-role-details.ts diff --git a/ui/lib/pki/addon/components/pki-key-details.ts b/ui/lib/pki/addon/components/pki-key-details.ts deleted file mode 100644 index c9eaa37930ac..000000000000 --- a/ui/lib/pki/addon/components/pki-key-details.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { action } from '@ember/object'; -import Component from '@glimmer/component'; -import { inject as service } from '@ember/service'; -interface Args { - key: { - backend: string; - keyName: string; - keyId: string; - }; -} - -export default class PkiKeyDetails extends Component { - @service declare secretMountPath: { currentPath: string }; - - get breadcrumbs() { - return [ - { label: 'secrets', route: 'secrets', linkExternal: true }, - { label: this.secretMountPath.currentPath || 'pki', route: 'overview' }, - { label: 'keys', route: 'keys.index' }, - { label: this.args.key.keyId }, - ]; - } - - @action deleteKey() { - // TODO handle delete - } -} diff --git a/ui/lib/pki/addon/templates/keys/key/details.hbs b/ui/lib/pki/addon/templates/keys/key/details.hbs index f371d1b43daa..89251f23543a 100644 --- a/ui/lib/pki/addon/templates/keys/key/details.hbs +++ b/ui/lib/pki/addon/templates/keys/key/details.hbs @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/ui/lib/pki/addon/templates/roles/role/details.hbs b/ui/lib/pki/addon/templates/roles/role/details.hbs index 8e79cd382c7c..4b5f7a632a74 100644 --- a/ui/lib/pki/addon/templates/roles/role/details.hbs +++ b/ui/lib/pki/addon/templates/roles/role/details.hbs @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/ui/tests/helpers/pki/keys/page-details.js b/ui/tests/helpers/pki/keys/page-details.js new file mode 100644 index 000000000000..1be50fc05ee0 --- /dev/null +++ b/ui/tests/helpers/pki/keys/page-details.js @@ -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]', +}; diff --git a/ui/tests/helpers/pki-engine.js b/ui/tests/helpers/pki/roles/form.js similarity index 99% rename from ui/tests/helpers/pki-engine.js rename to ui/tests/helpers/pki/roles/form.js index 07b7e6b22be7..9f361ea55179 100644 --- a/ui/tests/helpers/pki-engine.js +++ b/ui/tests/helpers/pki/roles/form.js @@ -1,7 +1,6 @@ export const PKI_BASE_URL = `/vault/cluster/secrets/backend/pki/roles`; export const SELECTORS = { - // Pki role roleName: '[data-test-input="name"]', issuerRef: '[data-test-input="issuerRef"]', customTtl: '[data-test-field="customTtl"]', diff --git a/ui/tests/helpers/pki/page-role-details.js b/ui/tests/helpers/pki/roles/page-details.js similarity index 100% rename from ui/tests/helpers/pki/page-role-details.js rename to ui/tests/helpers/pki/roles/page-details.js diff --git a/ui/tests/integration/components/pki/keys/page-details-test.js b/ui/tests/integration/components/pki/keys/page-details-test.js new file mode 100644 index 000000000000..b254ee19ea94 --- /dev/null +++ b/ui/tests/integration/components/pki/keys/page-details-test.js @@ -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/keys/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` + + `, + { 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); + }); +}); diff --git a/ui/tests/integration/components/pki/pki-key-parameters-test.js b/ui/tests/integration/components/pki/pki-key-parameters-test.js index 3aaa877de3f8..13b1ce4c8c4c 100644 --- a/ui/tests/integration/components/pki/pki-key-parameters-test.js +++ b/ui/tests/integration/components/pki/pki-key-parameters-test.js @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit'; import { render, fillIn } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupEngine } from 'ember-engines/test-support'; -import { SELECTORS } from 'vault/tests/helpers/pki-engine'; +import { SELECTORS } from 'vault/tests/helpers/pki/roles/form'; module('Integration | Component | pki-key-parameters', function (hooks) { setupRenderingTest(hooks); diff --git a/ui/tests/integration/components/pki/pki-key-usage-test.js b/ui/tests/integration/components/pki/pki-key-usage-test.js index 927d3854f8e8..e290030c7b35 100644 --- a/ui/tests/integration/components/pki/pki-key-usage-test.js +++ b/ui/tests/integration/components/pki/pki-key-usage-test.js @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit'; import { render, click, findAll } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupEngine } from 'ember-engines/test-support'; -import { SELECTORS } from 'vault/tests/helpers/pki-engine'; +import { SELECTORS } from 'vault/tests/helpers/pki/roles/form'; module('Integration | Component | pki-key-usage', function (hooks) { setupRenderingTest(hooks); diff --git a/ui/tests/integration/components/pki/pki-role-form-test.js b/ui/tests/integration/components/pki/roles/form-test.js similarity index 98% rename from ui/tests/integration/components/pki/pki-role-form-test.js rename to ui/tests/integration/components/pki/roles/form-test.js index 92d51762a374..2704b7fa9c28 100644 --- a/ui/tests/integration/components/pki/pki-role-form-test.js +++ b/ui/tests/integration/components/pki/roles/form-test.js @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit'; import { render, click, fillIn, find } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupEngine } from 'ember-engines/test-support'; -import { SELECTORS } from 'vault/tests/helpers/pki-engine'; +import { SELECTORS } from 'vault/tests/helpers/pki/roles/form'; import { setupMirage } from 'ember-cli-mirage/test-support'; module('Integration | Component | pki-role-form', function (hooks) { diff --git a/ui/tests/integration/components/pki/page-role-detail-test.js b/ui/tests/integration/components/pki/roles/page-details-test.js similarity index 92% rename from ui/tests/integration/components/pki/page-role-detail-test.js rename to ui/tests/integration/components/pki/roles/page-details-test.js index 893fb05b2d5f..f77a3e617370 100644 --- a/ui/tests/integration/components/pki/page-role-detail-test.js +++ b/ui/tests/integration/components/pki/roles/page-details-test.js @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupEngine } from 'ember-engines/test-support'; -import { SELECTORS } from 'vault/tests/helpers/pki/page-role-details'; +import { SELECTORS } from 'vault/tests/helpers/pki/roles/page-details'; module('Integration | Component | pki role details page', function (hooks) { setupRenderingTest(hooks); @@ -24,7 +24,7 @@ module('Integration | Component | pki role details page', function (hooks) { assert.expect(7); await render( hbs` - + `, { owner: this.engine } ); diff --git a/ui/tests/unit/adapters/pki/key-test.js b/ui/tests/unit/adapters/pki/key-test.js new file mode 100644 index 000000000000..2adea524e0bb --- /dev/null +++ b/ui/tests/unit/adapters/pki/key-test.js @@ -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(); + }); +}); diff --git a/ui/types/ember-cli-flash/services/flash-messages.d.ts b/ui/types/ember-cli-flash/services/flash-messages.d.ts new file mode 100644 index 000000000000..ea399b4631d9 --- /dev/null +++ b/ui/types/ember-cli-flash/services/flash-messages.d.ts @@ -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 = { [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 { + message: string; + } + + interface FlashFunction { + (message: string, options?: Partial): FlashMessageService; + } + + class FlashMessageService extends Service { + queue: A; + 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; +}