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>
<div class="field">
<p class="control has-icons-left has-icons-right">
<span class="input has-text-grey-light">{{or @placeholder "Search"}}</span>
<Icon @name="search" class="search-icon has-text-grey-light" />
</p>
</div>
</div>

View File

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

View File

@ -7,6 +7,7 @@ import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
/**
* @module PkiRoleForm
@ -18,7 +19,8 @@ import { tracked } from '@glimmer/tracking';
* ```
* @callback onCancel
* @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 {onSave} onSave - Callback triggered on save success.
*/
@ -31,6 +33,19 @@ export default class PkiRoleForm extends Component {
@tracked errorBanner;
@tracked invalidFormAlert;
@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() {
const crumbs = [
@ -38,8 +53,8 @@ export default class PkiRoleForm extends Component {
{ label: this.secretMountPath.currentPath, route: 'overview' },
{ label: 'roles', route: 'roles.index' },
];
if (!this.args.model.isNew) {
crumbs.push({ label: this.args.model.id, route: 'roles.role.details' }, { label: 'edit' });
if (!this.args.role.isNew) {
crumbs.push({ label: this.args.role.id, route: 'roles.role.details' }, { label: 'edit' });
}
return crumbs;
}
@ -48,12 +63,12 @@ export default class PkiRoleForm extends Component {
*save(event) {
event.preventDefault();
try {
const { isValid, state, invalidFormMessage } = this.args.model.validate();
const { isValid, state, invalidFormMessage } = this.args.role.validate();
this.modelValidations = isValid ? null : state;
this.invalidFormAlert = invalidFormMessage;
if (isValid) {
const { isNew, name } = this.args.model;
yield this.args.model.save();
const { isNew, name } = this.args.role;
yield this.args.role.save();
this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} the role ${name}.`);
this.args.onSave();
}
@ -63,4 +78,13 @@ export default class PkiRoleForm extends Component {
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 { inject as service } from '@ember/service';
import { withConfirmLeave } from 'core/decorators/confirm-leave';
import { hash } from 'rsvp';
@withConfirmLeave()
@withConfirmLeave('model.role', ['model.issuers'])
export default class PkiRolesCreateRoute extends Route {
@service store;
@service secretMountPath;
model() {
return this.store.createRecord('pki/role', {
backend: this.secretMountPath.currentPath,
const 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 { inject as service } from '@ember/service';
import { withConfirmLeave } from 'core/decorators/confirm-leave';
import { hash } from 'rsvp';
@withConfirmLeave()
@withConfirmLeave('model.role', ['model.issuers'])
export default class PkiRoleEditRoute extends Route {
@service store;
@service secretMountPath;
model() {
const { role } = this.paramsFor('roles/role');
return this.store.queryRecord('pki/role', {
backend: this.secretMountPath.currentPath,
const backend = this.secretMountPath.currentPath;
return hash({
role: this.store.queryRecord('pki/role', {
backend,
id: role,
}),
issuers: this.store.query('pki/issuer', { backend }),
});
}
setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
const { id } = resolvedModel;
const {
role: { id },
} = resolvedModel;
controller.breadcrumbs = [
{ label: 'secrets', route: 'secrets', linkExternal: true },
{ label: this.secretMountPath.currentPath, route: 'overview' },

View File

@ -10,7 +10,9 @@
</PageHeader>
<PkiRoleForm
@role={{this.model.role}}
@issuers={{this.model.issuers}}
@model={{this.model}}
@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>
</PageHeader>
<PkiRoleForm
@model={{this.model}}
@onCancel={{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.id}}
@role={{this.model.role}}
@issuers={{this.model.issuers}}
@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
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);
assert.true(role.hasDirtyAttributes, 'Role has dirty attrs');
// Exit page via cancel button
@ -153,7 +154,8 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
// Edit again
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);
assert.true(role.hasDirtyAttributes, 'Role has dirty attrs');
// Exit page via breadcrumbs

View File

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

View File

@ -19,8 +19,11 @@ module('Integration | Component | pki-role-form', function (hooks) {
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.model = this.store.createRecord('pki/role');
this.model.backend = 'pki';
this.role = this.store.createRecord('pki/role');
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();
});
@ -29,14 +32,15 @@ module('Integration | Component | pki-role-form', function (hooks) {
await render(
hbs`
<PkiRoleForm
@model={{this.model}}
@role={{this.role}}
@issuers={{this.issuers}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}
/>
`,
{ 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.customTtl).exists('shows custom yielded form field');
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) {
// Key usage, Key params and Not valid after options are tested in their respective component tests
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');
const request = JSON.parse(req.requestBody);
const allowedDomainsTemplate = request.allowed_domains_template;
@ -78,7 +82,8 @@ module('Integration | Component | pki-role-form', function (hooks) {
await render(
hbs`
<PkiRoleForm
@model={{this.model}}
@role={{this.role}}
@issuers={{this.issuers}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}
/>
@ -87,6 +92,7 @@ module('Integration | Component | pki-role-form', function (hooks) {
);
await click(SELECTORS.roleCreateButton);
assert
.dom(SELECTORS.roleName)
.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) {
assert.expect(1);
this.store.pushPayload('pki/role', {
modelName: 'pki/role',
name: 'test-role',
@ -127,21 +134,22 @@ module('Integration | Component | pki-role-form', function (hooks) {
id: 'role-id',
});
this.model = this.store.peekRecord('pki/role', 'role-id');
this.role = this.store.peekRecord('pki/role', 'role-id');
await render(
hbs`
<PkiRoleForm
@model={{this.model}}
@role={{this.role}}
@issuers={{this.issuers}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}
/>
`,
{ owner: this.engine }
);
await fillIn(SELECTORS.issuerRef, 'not-default');
await click(SELECTORS.issuerRefToggle);
await fillIn(SELECTORS.issuerRefSelect, 'issuer-1');
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');
});
});