UI: VAULT-13341 add toggle and select to pki role-form (#19840)

This commit is contained in:
Kianna 2023-04-03 14:07:36 -07:00 committed by GitHub
parent 3ed31ff262
commit 985b016da5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 143 additions and 56 deletions

View File

@ -1,6 +1,8 @@
<div class="field"> <div>
<p class="control has-icons-left has-icons-right"> <div class="field">
<span class="input has-text-grey-light">{{or @placeholder "Search"}}</span> <p class="control has-icons-left has-icons-right">
<Icon @name="search" class="search-icon has-text-grey-light" /> <span class="input has-text-grey-light">{{or @placeholder "Search"}}</span>
</p> <Icon @name="search" class="search-icon has-text-grey-light" />
</p>
</div>
</div> </div>

View File

@ -2,35 +2,70 @@
<div class="box is-sideless is-fullwidth is-marginless"> <div class="box is-sideless is-fullwidth is-marginless">
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" /> <MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
{{! ARG TODO write a test for namespace reminder }} {{! ARG TODO write a test for namespace reminder }}
<NamespaceReminder @mode={{if @model.isNew "create" "update"}} @noun="PKI role" /> <NamespaceReminder @mode={{if @role.isNew "create" "update"}} @noun="PKI role" />
{{#each @model.formFieldGroups as |fieldGroup|}} {{#each @role.formFieldGroups as |fieldGroup|}}
{{#each-in fieldGroup as |group fields|}} {{#each-in fieldGroup as |group fields|}}
{{! DEFAULT VIEW }} {{! DEFAULT VIEW }}
{{#if (eq group "default")}} {{#if (eq group "default")}}
{{#each fields as |attr|}} {{#each fields as |attr|}}
<FormField {{#if (eq attr.name "issuerRef")}}
data-test-field={{attr}} <div class="has-top-margin-m {{unless this.showDefaultIssuer 'has-bottom-margin-xs' 'has-bottom-margin-m'}}">
@attr={{attr}} <FormFieldLabel
@model={{@model}} for={{attr.name}}
@modelValidations={{this.modelValidations}} @label="Issuer ref"
@showHelpText={{false}} @helpText={{(if this.showHelpText attr.options.helpText)}}
> @subText={{attr.options.subText}}
<PkiNotValidAfterForm @attr={{attr}} @model={{@model}} /> />
</FormField> <Toggle
@name={{concat attr.name "-toggle"}}
@status="success"
@size="small"
@checked={{this.showDefaultIssuer}}
@onChange={{this.toggleShowDefaultIssuer}}
>
<span class="has-text-grey">Use default issuer</span>
</Toggle>
</div>
{{#unless this.showDefaultIssuer}}
<div class="has-top-margin-xs has-bottom-margin-l">
<div class="select is-fullwidth">
<Select
@name={{attr.name}}
@options={{this.issuers}}
@valueAttribute={{"issuerDisplayName"}}
@labelAttribute={{"issuerDisplayName"}}
@isFullwidth={{true}}
@selectedValue={{@role.issuerRef}}
@onChange={{action (mut @role.issuerRef)}}
/>
</div>
</div>
{{/unless}}
{{else}}
<FormField
data-test-field={{attr}}
@attr={{attr}}
@model={{@role}}
@modelValidations={{this.modelValidations}}
@showHelpText={{false}}
>
<PkiNotValidAfterForm @attr={{attr}} @model={{@role}} />
</FormField>
{{/if}}
{{/each}} {{/each}}
{{else}} {{else}}
{{#let (camelize (concat "show" group)) as |prop|}} {{#let (camelize (concat "show" group)) as |prop|}}
<ToggleButton <ToggleButton
@isOpen={{get @model prop}} @isOpen={{get @role prop}}
@openLabel={{concat "Hide " group}} @openLabel={{concat "Hide " group}}
@closedLabel={{group}} @closedLabel={{group}}
@onClick={{fn (mut (get @model prop))}} @onClick={{fn (mut (get @role prop))}}
class="is-block" class="is-block"
data-test-toggle-group={{group}} data-test-toggle-group={{group}}
/> />
{{#if (get @model prop)}} {{#if (get @role prop)}}
<div class="box is-tall is-marginless" data-test-toggle-div={{group}}> <div class="box is-tall is-marginless" data-test-toggle-div={{group}}>
{{#let (get @model.fieldGroupsInfo group) as |toggleGroup|}} {{#let (get @role.fieldGroupsInfo group) as |toggleGroup|}}
{{! HEADER }} {{! HEADER }}
{{#if toggleGroup.header}} {{#if toggleGroup.header}}
<div class="has-bottom-margin-s"> <div class="has-bottom-margin-s">
@ -44,16 +79,16 @@
{{/if}} {{/if}}
{{! FIELDS }} {{! FIELDS }}
{{#if (eq group "Key usage")}} {{#if (eq group "Key usage")}}
<PkiKeyUsage @model={{@model}} /> <PkiKeyUsage @model={{@role}} />
{{else if (eq group "Key parameters")}} {{else if (eq group "Key parameters")}}
<PkiKeyParameters @model={{@model}} @fields={{fields}} /> <PkiKeyParameters @model={{@role}} @fields={{fields}} />
{{else}} {{else}}
{{#each fields as |attr|}} {{#each fields as |attr|}}
<FormField <FormField
data-test-field={{true}} data-test-field={{true}}
@attr={{attr}} @attr={{attr}}
@model={{@model}} @model={{@role}}
@modelValidations={{@modelValidations}} @modelValidations={{@roleValidations}}
@showHelpText={{false}} @showHelpText={{false}}
> >
{{yield attr}} {{yield attr}}
@ -87,7 +122,7 @@
disabled={{this.save.isRunning}} disabled={{this.save.isRunning}}
data-test-pki-role-save data-test-pki-role-save
> >
{{if @model.isNew "Create" "Update"}} {{if @role.isNew "Create" "Update"}}
</button> </button>
<button <button
type="button" type="button"

View File

@ -7,6 +7,7 @@ import Component from '@glimmer/component';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking'; import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
/** /**
* @module PkiRoleForm * @module PkiRoleForm
@ -18,7 +19,8 @@ import { tracked } from '@glimmer/tracking';
* ``` * ```
* @callback onCancel * @callback onCancel
* @callback onSave * @callback onSave
* @param {Object} model - pki/role model. * @param {Object} role - pki/role model.
* @param {Array} issuers - pki/issuer model.
* @param {onCancel} onCancel - Callback triggered when cancel button is clicked. * @param {onCancel} onCancel - Callback triggered when cancel button is clicked.
* @param {onSave} onSave - Callback triggered on save success. * @param {onSave} onSave - Callback triggered on save success.
*/ */
@ -31,6 +33,19 @@ export default class PkiRoleForm extends Component {
@tracked errorBanner; @tracked errorBanner;
@tracked invalidFormAlert; @tracked invalidFormAlert;
@tracked modelValidations; @tracked modelValidations;
@tracked showDefaultIssuer = true;
constructor() {
super(...arguments);
this.showDefaultIssuer = this.args.role.issuerRef === 'default';
}
get issuers() {
return this.args.issuers?.map((issuer) => {
return { issuerDisplayName: issuer.issuerName || issuer.issuerId };
});
}
get breadcrumbs() { get breadcrumbs() {
const crumbs = [ const crumbs = [
@ -38,8 +53,8 @@ export default class PkiRoleForm extends Component {
{ label: this.secretMountPath.currentPath, route: 'overview' }, { label: this.secretMountPath.currentPath, route: 'overview' },
{ label: 'roles', route: 'roles.index' }, { label: 'roles', route: 'roles.index' },
]; ];
if (!this.args.model.isNew) { if (!this.args.role.isNew) {
crumbs.push({ label: this.args.model.id, route: 'roles.role.details' }, { label: 'edit' }); crumbs.push({ label: this.args.role.id, route: 'roles.role.details' }, { label: 'edit' });
} }
return crumbs; return crumbs;
} }
@ -48,12 +63,12 @@ export default class PkiRoleForm extends Component {
*save(event) { *save(event) {
event.preventDefault(); event.preventDefault();
try { try {
const { isValid, state, invalidFormMessage } = this.args.model.validate(); const { isValid, state, invalidFormMessage } = this.args.role.validate();
this.modelValidations = isValid ? null : state; this.modelValidations = isValid ? null : state;
this.invalidFormAlert = invalidFormMessage; this.invalidFormAlert = invalidFormMessage;
if (isValid) { if (isValid) {
const { isNew, name } = this.args.model; const { isNew, name } = this.args.role;
yield this.args.model.save(); yield this.args.role.save();
this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} the role ${name}.`); this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} the role ${name}.`);
this.args.onSave(); this.args.onSave();
} }
@ -63,4 +78,13 @@ export default class PkiRoleForm extends Component {
this.invalidFormAlert = 'There was an error submitting this form.'; this.invalidFormAlert = 'There was an error submitting this form.';
} }
} }
@action
toggleShowDefaultIssuer() {
this.showDefaultIssuer = !this.showDefaultIssuer;
if (this.showDefaultIssuer) {
this.args.role.issuerRef = 'default';
}
}
} }

View File

@ -6,15 +6,18 @@
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { withConfirmLeave } from 'core/decorators/confirm-leave'; import { withConfirmLeave } from 'core/decorators/confirm-leave';
import { hash } from 'rsvp';
@withConfirmLeave() @withConfirmLeave('model.role', ['model.issuers'])
export default class PkiRolesCreateRoute extends Route { export default class PkiRolesCreateRoute extends Route {
@service store; @service store;
@service secretMountPath; @service secretMountPath;
model() { model() {
return this.store.createRecord('pki/role', { const backend = this.secretMountPath.currentPath;
backend: this.secretMountPath.currentPath, return hash({
role: this.store.createRecord('pki/role', { backend }),
issuers: this.store.query('pki/issuer', { backend }),
}); });
} }

View File

@ -6,23 +6,31 @@
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { withConfirmLeave } from 'core/decorators/confirm-leave'; import { withConfirmLeave } from 'core/decorators/confirm-leave';
import { hash } from 'rsvp';
@withConfirmLeave() @withConfirmLeave('model.role', ['model.issuers'])
export default class PkiRoleEditRoute extends Route { export default class PkiRoleEditRoute extends Route {
@service store; @service store;
@service secretMountPath; @service secretMountPath;
model() { model() {
const { role } = this.paramsFor('roles/role'); const { role } = this.paramsFor('roles/role');
return this.store.queryRecord('pki/role', { const backend = this.secretMountPath.currentPath;
backend: this.secretMountPath.currentPath,
id: role, return hash({
role: this.store.queryRecord('pki/role', {
backend,
id: role,
}),
issuers: this.store.query('pki/issuer', { backend }),
}); });
} }
setupController(controller, resolvedModel) { setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel); super.setupController(controller, resolvedModel);
const { id } = resolvedModel; const {
role: { id },
} = resolvedModel;
controller.breadcrumbs = [ controller.breadcrumbs = [
{ label: 'secrets', route: 'secrets', linkExternal: true }, { label: 'secrets', route: 'secrets', linkExternal: true },
{ label: this.secretMountPath.currentPath, route: 'overview' }, { label: this.secretMountPath.currentPath, route: 'overview' },

View File

@ -10,7 +10,9 @@
</PageHeader> </PageHeader>
<PkiRoleForm <PkiRoleForm
@role={{this.model.role}}
@issuers={{this.model.issuers}}
@model={{this.model}} @model={{this.model}}
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.index"}} @onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.index"}}
@onSave={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.id}} @onSave={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.role.id}}
/> />

View File

@ -9,7 +9,8 @@
</p.levelLeft> </p.levelLeft>
</PageHeader> </PageHeader>
<PkiRoleForm <PkiRoleForm
@model={{this.model}} @role={{this.model.role}}
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.id}} @issuers={{this.model.issuers}}
@onSave={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.id}} @onCancel={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.role.id}}
@onSave={{transition-to "vault.cluster.secrets.backend.pki.roles.role.details" this.model.role.id}}
/> />

View File

@ -142,7 +142,8 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
// Edit role // Edit role
await click(SELECTORS.editRoleLink); await click(SELECTORS.editRoleLink);
await fillIn(SELECTORS.roleForm.issuerRef, 'foobar'); await click(SELECTORS.roleForm.issuerRefToggle);
await fillIn(SELECTORS.roleForm.issuerRefSelect, 'foobar');
role = this.store.peekRecord('pki/role', roleId); role = this.store.peekRecord('pki/role', roleId);
assert.true(role.hasDirtyAttributes, 'Role has dirty attrs'); assert.true(role.hasDirtyAttributes, 'Role has dirty attrs');
// Exit page via cancel button // Exit page via cancel button
@ -153,7 +154,8 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
// Edit again // Edit again
await click(SELECTORS.editRoleLink); await click(SELECTORS.editRoleLink);
await fillIn(SELECTORS.roleForm.issuerRef, 'foobar2'); await click(SELECTORS.roleForm.issuerRefToggle);
await fillIn(SELECTORS.roleForm.issuerRefSelect, 'foobar2');
role = this.store.peekRecord('pki/role', roleId); role = this.store.peekRecord('pki/role', roleId);
assert.true(role.hasDirtyAttributes, 'Role has dirty attrs'); assert.true(role.hasDirtyAttributes, 'Role has dirty attrs');
// Exit page via breadcrumbs // Exit page via breadcrumbs

View File

@ -8,6 +8,8 @@ export const PKI_BASE_URL = `/vault/cluster/secrets/backend/pki/roles`;
export const SELECTORS = { export const SELECTORS = {
roleName: '[data-test-input="name"]', roleName: '[data-test-input="name"]',
issuerRef: '[data-test-input="issuerRef"]', issuerRef: '[data-test-input="issuerRef"]',
issuerRefSelect: '[data-test-select="issuerRef"]',
issuerRefToggle: '[data-test-toggle-label="issuerRef-toggle"]',
customTtl: '[data-test-field="customTtl"]', customTtl: '[data-test-field="customTtl"]',
backdateValidity: '[data-test-ttl-value="Backdate validity"]', backdateValidity: '[data-test-ttl-value="Backdate validity"]',
maxTtl: '[data-test-toggle-label="Max TTL"]', maxTtl: '[data-test-toggle-label="Max TTL"]',

View File

@ -19,8 +19,11 @@ module('Integration | Component | pki-role-form', function (hooks) {
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
this.model = this.store.createRecord('pki/role'); this.role = this.store.createRecord('pki/role');
this.model.backend = 'pki'; this.store.createRecord('pki/issuer', { issuerName: 'issuer-0', issuerId: 'abcd-efgh' });
this.store.createRecord('pki/issuer', { issuerName: 'issuer-1', issuerId: 'ijkl-mnop' });
this.issuers = this.store.peekAll('pki/issuer');
this.role.backend = 'pki';
this.onCancel = sinon.spy(); this.onCancel = sinon.spy();
}); });
@ -29,14 +32,15 @@ module('Integration | Component | pki-role-form', function (hooks) {
await render( await render(
hbs` hbs`
<PkiRoleForm <PkiRoleForm
@model={{this.model}} @role={{this.role}}
@issuers={{this.issuers}}
@onCancel={{this.onCancel}} @onCancel={{this.onCancel}}
@onSave={{this.onSave}} @onSave={{this.onSave}}
/> />
`, `,
{ owner: this.engine } { owner: this.engine }
); );
assert.dom(SELECTORS.issuerRef).exists('shows form-field issuer ref'); assert.dom(SELECTORS.issuerRefToggle).exists('shows issuer ref toggle');
assert.dom(SELECTORS.backdateValidity).exists('shows form-field backdate validity'); assert.dom(SELECTORS.backdateValidity).exists('shows form-field backdate validity');
assert.dom(SELECTORS.customTtl).exists('shows custom yielded form field'); assert.dom(SELECTORS.customTtl).exists('shows custom yielded form field');
assert.dom(SELECTORS.maxTtl).exists('shows form-field max ttl'); assert.dom(SELECTORS.maxTtl).exists('shows form-field max ttl');
@ -54,7 +58,7 @@ module('Integration | Component | pki-role-form', function (hooks) {
test('it should save a new pki role with various options selected', async function (assert) { test('it should save a new pki role with various options selected', async function (assert) {
// Key usage, Key params and Not valid after options are tested in their respective component tests // Key usage, Key params and Not valid after options are tested in their respective component tests
assert.expect(9); assert.expect(9);
this.server.post(`/${this.model.backend}/roles/test-role`, (schema, req) => { this.server.post(`/${this.role.backend}/roles/test-role`, (schema, req) => {
assert.ok(true, 'Request made to save role'); assert.ok(true, 'Request made to save role');
const request = JSON.parse(req.requestBody); const request = JSON.parse(req.requestBody);
const allowedDomainsTemplate = request.allowed_domains_template; const allowedDomainsTemplate = request.allowed_domains_template;
@ -78,7 +82,8 @@ module('Integration | Component | pki-role-form', function (hooks) {
await render( await render(
hbs` hbs`
<PkiRoleForm <PkiRoleForm
@model={{this.model}} @role={{this.role}}
@issuers={{this.issuers}}
@onCancel={{this.onCancel}} @onCancel={{this.onCancel}}
@onSave={{this.onSave}} @onSave={{this.onSave}}
/> />
@ -87,6 +92,7 @@ module('Integration | Component | pki-role-form', function (hooks) {
); );
await click(SELECTORS.roleCreateButton); await click(SELECTORS.roleCreateButton);
assert assert
.dom(SELECTORS.roleName) .dom(SELECTORS.roleName)
.hasClass('has-error-border', 'shows border error on role name field when no role name is submitted'); .hasClass('has-error-border', 'shows border error on role name field when no role name is submitted');
@ -120,6 +126,7 @@ module('Integration | Component | pki-role-form', function (hooks) {
test('it should update attributes on the model on update', async function (assert) { test('it should update attributes on the model on update', async function (assert) {
assert.expect(1); assert.expect(1);
this.store.pushPayload('pki/role', { this.store.pushPayload('pki/role', {
modelName: 'pki/role', modelName: 'pki/role',
name: 'test-role', name: 'test-role',
@ -127,21 +134,22 @@ module('Integration | Component | pki-role-form', function (hooks) {
id: 'role-id', id: 'role-id',
}); });
this.model = this.store.peekRecord('pki/role', 'role-id'); this.role = this.store.peekRecord('pki/role', 'role-id');
await render( await render(
hbs` hbs`
<PkiRoleForm <PkiRoleForm
@model={{this.model}} @role={{this.role}}
@issuers={{this.issuers}}
@onCancel={{this.onCancel}} @onCancel={{this.onCancel}}
@onSave={{this.onSave}} @onSave={{this.onSave}}
/> />
`, `,
{ owner: this.engine } { owner: this.engine }
); );
await click(SELECTORS.issuerRefToggle);
await fillIn(SELECTORS.issuerRef, 'not-default'); await fillIn(SELECTORS.issuerRefSelect, 'issuer-1');
await click(SELECTORS.roleCreateButton); await click(SELECTORS.roleCreateButton);
assert.strictEqual(this.model.issuerRef, 'not-default', 'Issuer Ref correctly saved on create'); assert.strictEqual(this.role.issuerRef, 'issuer-1', 'Issuer Ref correctly saved on create');
}); });
}); });