ui: VAULT-8673 Add empty states to certificates and roles (#18702)

* Add empty states to certificates and roles

* Add tests for empty state

* Fix naming of model properties
This commit is contained in:
Kianna 2023-01-17 13:30:14 -08:00 committed by GitHub
parent ef3e3eace2
commit 66ea2c0b18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 99 additions and 76 deletions

View File

@ -1,7 +1,8 @@
import Route from '@ember/routing/route';
import PkiOverviewRoute from '../overview';
import { inject as service } from '@ember/service';
import { hash } from 'rsvp';
export default class PkiCertificatesIndexRoute extends Route {
export default class PkiCertificatesIndexRoute extends PkiOverviewRoute {
@service store;
@service secretMountPath;
@service pathHelp;
@ -11,18 +12,23 @@ export default class PkiCertificatesIndexRoute extends Route {
return this.pathHelp.getNewModel('pki/certificate', this.secretMountPath.currentPath);
}
async fetchCertificates() {
try {
return await this.store.query('pki/certificate', { backend: this.secretMountPath.currentPath });
} catch (e) {
if (e.httpStatus === 404) {
return { parentModel: this.modelFor('certificates') };
} else {
throw e;
}
}
}
model() {
return this.store
.query('pki/certificate', { backend: this.secretMountPath.currentPath })
.then((certificateModel) => {
return { certificateModel, parentModel: this.modelFor('certificates') };
})
.catch((err) => {
if (err.httpStatus === 404) {
return { parentModel: this.modelFor('certificates') };
} else {
throw err;
}
});
return hash({
hasConfig: this.hasConfig(),
certificates: this.fetchCertificates(),
parentModel: this.modelFor('certificates'),
});
}
}

View File

@ -1,7 +1,8 @@
import Route from '@ember/routing/route';
import PkiOverviewRoute from '../overview';
import { inject as service } from '@ember/service';
import { hash } from 'rsvp';
export default class PkiRolesIndexRoute extends Route {
export default class PkiRolesIndexRoute extends PkiOverviewRoute {
@service store;
@service secretMountPath;
@service pathHelp;
@ -12,18 +13,23 @@ export default class PkiRolesIndexRoute extends Route {
return this.pathHelp.getNewModel('pki/role', this.secretMountPath.currentPath);
}
async fetchRoles() {
try {
return await this.store.query('pki/role', { backend: this.secretMountPath.currentPath });
} catch (e) {
if (e.httpStatus === 404) {
return { parentModel: this.modelFor('roles') };
} else {
throw e;
}
}
}
model() {
return this.store
.query('pki/role', { backend: this.secretMountPath.currentPath })
.then((roleModel) => {
return { roleModel, parentModel: this.modelFor('roles') };
})
.catch((err) => {
if (err.httpStatus === 404) {
return { parentModel: this.modelFor('roles') };
} else {
throw err;
}
});
return hash({
hasConfig: this.hasConfig(),
roles: this.fetchRoles(),
parentModel: this.modelFor('roles'),
});
}
}

View File

@ -10,14 +10,15 @@
/>
{{outlet}}
<Toolbar>
{{#if this.model.certificateModel.length}}
{{#if this.model.certificates.length}}
<ToolbarFilters>
{{! ARG TODO glimmerize the NavigateInput and refactor so you can use it in an engine }}
</ToolbarFilters>
{{/if}}
</Toolbar>
{{#if this.model.certificateModel.length}}
{{#each this.model.certificateModel as |pkiCertificate|}}
{{#if this.model.hasConfig}}
{{#each this.model.certificates as |pkiCertificate|}}
<LinkedBlock
class="list-item-row"
@params={{array "certificates.certificate.details" pkiCertificate.id}}
@ -56,7 +57,7 @@
{{else}}
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<LinkTo @route="configuration.create">
{{! ARG TODO if configuration of engine not setup then direct toward setting that up otherwise replace with new design language }}
Configure PKI
</LinkTo>
</EmptyState>
{{/if}}

View File

@ -16,50 +16,58 @@
</ToolbarActions>
</Toolbar>
{{#if this.model.roleModel.length}}
{{#each this.model.roleModel as |pkiRole|}}
<LinkedBlock class="list-item-row" @params={{array "roles.role.details" pkiRole.id}} @linkPrefix={{this.mountPoint}}>
<div class="level is-mobile">
<div class="level-left">
<div>
<Icon @name="user" class="has-text-grey-light" />
<span class="has-text-weight-semibold is-underline">
{{pkiRole.id}}
</span>
{{#if this.model.hasConfig}}
{{#if this.model.roles.length}}
{{#each this.model.roles as |pkiRole|}}
<LinkedBlock class="list-item-row" @params={{array "roles.role.details" pkiRole.id}} @linkPrefix={{this.mountPoint}}>
<div class="level is-mobile">
<div class="level-left">
<div>
<Icon @name="user" class="has-text-grey-light" />
<span class="has-text-weight-semibold is-underline">
{{pkiRole.id}}
</span>
</div>
</div>
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu>
<nav class="menu">
<ul class="menu-list">
<li>
<LinkTo @route="roles.role.details" @model={{pkiRole.id}}>
Details
</LinkTo>
</li>
<li>
<LinkTo @route="roles.role.edit" @model={{pkiRole.id}}>
Edit
</LinkTo>
</li>
</ul>
</nav>
</PopupMenu>
</div>
</div>
</div>
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu>
<nav class="menu">
<ul class="menu-list">
<li>
<LinkTo @route="roles.role.details" @model={{pkiRole.id}}>
Details
</LinkTo>
</li>
<li>
<LinkTo @route="roles.role.edit" @model={{pkiRole.id}}>
Edit
</LinkTo>
</li>
</ul>
</nav>
</PopupMenu>
</div>
</LinkedBlock>
{{/each}}
{{else}}
<EmptyState @title="No roles yet">
<div>
<p>When created, roles will be listed here. Create a role to start generating certificates.</p>
<div class="has-top-margin-m">
<LinkTo @route="roles.create">
Create role
</LinkTo>
</div>
</div>
</LinkedBlock>
{{/each}}
</EmptyState>
{{/if}}
{{else}}
<EmptyState @title="No roles yet">
<div>
<p>When created, roles will be listed here. Create a role to start generating certificates.</p>
<div class="has-top-margin-m">
<LinkTo @route="roles.create">
Create role
</LinkTo>
</div>
</div>
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
<LinkTo @route="configuration.create">
Configure PKI
</LinkTo>
</EmptyState>
{{/if}}

View File

@ -64,7 +64,7 @@ module('Acceptance | pki workflow', function (hooks) {
});
test('empty state messages are correct when PKI not configured', async function (assert) {
assert.expect(10);
assert.expect(17);
const assertEmptyState = (assert, resource) => {
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/${resource}`);
assert
@ -73,6 +73,7 @@ module('Acceptance | pki workflow', function (hooks) {
'PKI not configured',
`${resource} index renders correct empty state title when PKI not configured`
);
assert.dom(SELECTORS.emptyStateLink).hasText('Configure PKI');
assert
.dom(SELECTORS.emptyStateMessage)
.hasText(
@ -84,9 +85,8 @@ module('Acceptance | pki workflow', function (hooks) {
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
// TODO comment in when roles index empty state updated & update assert.expect() number
// await click(SELECTORS.rolesTab);
// assertEmptyState(assert, 'roles');
await click(SELECTORS.rolesTab);
assertEmptyState(assert, 'roles');
await click(SELECTORS.issuersTab);
assertEmptyState(assert, 'issuers');
@ -109,6 +109,7 @@ module('Acceptance | pki workflow', function (hooks) {
allow_subdomains=true \
max_ttl="720h"`,
]);
await runCommands([`write ${this.mountPath}/root/generate/internal common_name="Hashicorp Test"`]);
const pki_admin_policy = adminPolicy(this.mountPath, 'roles');
const pki_reader_policy = readerPolicy(this.mountPath, 'roles');
const pki_editor_policy = updatePolicy(this.mountPath, 'roles');

View File

@ -10,6 +10,7 @@ export const SELECTORS = {
pageTitle: '[data-test-pki-role-page-title]',
alertBanner: '[data-test-alert-banner="alert"]',
emptyStateTitle: '[data-test-empty-state-title]',
emptyStateLink: '.empty-state-actions a',
emptyStateMessage: '[data-test-empty-state-message]',
// TABS
overviewTab: '[data-test-secret-list-tab="Overview"]',