KV automatic delete state issue in UI (#13166)

* converts secret-v2-version model to native class -- fixes issues with cached values for deleted prop

* adds changelog entry

* adds disabled state to ToolbarLink component and disables create new version action when users cannot read metadata

* updates secret-edit acceptance test
This commit is contained in:
Jordan Reimer 2021-11-23 14:17:37 -07:00 committed by GitHub
parent 201526e983
commit 516f18f736
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 141 additions and 58 deletions

3
changelog/13166.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ui: Fixes issue with placeholder not displaying for automatically deleted secrets when deletion time has passed
```

View File

@ -1,20 +1,21 @@
import { belongsTo, attr } from '@ember-data/model'; import { belongsTo, attr } from '@ember-data/model';
import Secret from './secret'; import SecretModel from './secret';
import { computed } from '@ember/object';
export default Secret.extend({ export default class SecretV2VersionModel extends SecretModel {
failedServerRead: attr('boolean'), @attr('boolean') failedServerRead;
pathAttr: 'path', @attr('number') version;
version: attr('number'), @attr('string') path;
secret: belongsTo('secret-v2'), @attr('string') deletionTime;
path: attr('string'), @attr('string') createdTime;
deletionTime: attr('string'), @attr('boolean') detroyed;
createdTime: attr('string'), @attr('number') currentVersion;
deleted: computed('deletionTime', function() { @belongsTo('secret-v2') secret;
pathAttr = 'path';
get deleted() {
const deletionTime = new Date(this.deletionTime); const deletionTime = new Date(this.deletionTime);
const now = new Date(); const now = new Date();
return deletionTime <= now; return deletionTime <= now;
}), }
destroyed: attr('boolean'), }
currentVersion: attr('number'),
});

View File

@ -155,12 +155,9 @@ export default Route.extend(UnloadModelRoute, {
try { try {
if (secretModel.failedServerRead) { if (secretModel.failedServerRead) {
// we couldn't read metadata, so we want to directly fetch the version // we couldn't read metadata, so we want to directly fetch the version
versionModel = await this.store.findRecord('secret-v2-version', JSON.stringify(versionId), {
versionModel = reload: true,
this.store.peekRecord('secret-v2-version', JSON.stringify(versionId)) || });
(await this.store.findRecord('secret-v2-version', JSON.stringify(versionId), {
reload: true,
}));
} else { } else {
// we may have previously errored, so roll it back here // we may have previously errored, so roll it back here
version.rollbackAttributes(); version.rollbackAttributes();

View File

@ -84,7 +84,7 @@
color: $black; color: $black;
transition: background-color $speed; transition: background-color $speed;
&:hover { &:hover:not(.disabled) {
background-color: $ui-gray-100; background-color: $ui-gray-100;
border: 0; border: 0;
color: $blue; color: $blue;
@ -98,6 +98,18 @@
height: 2.5rem; height: 2.5rem;
padding: $size-10 $size-8; padding: $size-10 $size-8;
} }
&.disabled {
opacity: 0.5;
cursor: default;
&:focus {
box-shadow: none;
}
&:hover {
background: transparent;
}
}
} }
.toolbar-separator { .toolbar-separator {

View File

@ -101,6 +101,8 @@
@data-test-secret-edit="true" @data-test-secret-edit="true"
@replace={{true}} @replace={{true}}
@type="add" @type="add"
@disabled={{@model.failedServerRead}}
@disabledTooltip="Metadata read access is required to create new version"
> >
Create new version Create new version
</ToolbarLink> </ToolbarLink>

9
ui/jsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"allowJs": true
},
"exclude": [
"node_modules"
]
}

View File

@ -7,15 +7,17 @@
* ```js * ```js
* <Toolbar> * <Toolbar>
* <ToolbarActions> * <ToolbarActions>
* <ToolbarLink @params={{array 'vault.cluster.policies.create'}} @type="add"> * <ToolbarLink @params={{array 'vault.cluster.policies.create'}} @type="add" @disabled={{true}} @disabledTooltip="This link is disabled">
* Create policy * Create policy
* </ToolbarLink> * </ToolbarLink>
* </ToolbarActions> * </ToolbarActions>
* </Toolbar> * </Toolbar>
* ``` * ```
* *
* @param params=''{Array} Array to pass to LinkTo * @param {array} params - Array to pass to LinkTo
* @param type=''{String} Use "add" to change icon * @param {string} type - Use "add" to change icon
* @param {boolean} disabled - pass true to disable link
* @param {string} disabledTooltip - tooltip to display on hover when disabled
*/ */
import Component from '@ember/component'; import Component from '@ember/component';
@ -27,6 +29,8 @@ export default Component.extend({
tagName: '', tagName: '',
supportsDataTestProperties: true, supportsDataTestProperties: true,
type: null, type: null,
disabled: false,
disabledTooltip: null,
glyph: computed('type', function() { glyph: computed('type', function() {
if (this.type == 'add') { if (this.type == 'add') {
return 'plus-plain'; return 'plus-plain';

View File

@ -1,20 +1,36 @@
<LinkTo {{#let (component "link-to"
class="toolbar-link" class="toolbar-link"
@params={{params}} disabled=disabled
data-test-secret-edit={{data-test-secret-edit}} params=params
data-test-secondary-add={{data-test-secondary-add}} data-test-secret-edit=data-test-secret-edit
data-test-configure-link={{data-test-configure-link}} data-test-secondary-add=data-test-secondary-add
data-test-alias-edit-link={{data-test-alias-edit-link}} data-test-configure-link=data-test-configure-link
data-test-entity-edit-link={{data-test-entity-edit-link}} data-test-alias-edit-link=data-test-alias-edit-link
data-test-replication-link={{data-test-replication-link}} data-test-entity-edit-link=data-test-entity-edit-link
data-test-entity-merge-link={{data-test-entity-merge-link}} data-test-replication-link=data-test-replication-link
data-test-backend-view-link={{data-test-backend-view-link}} data-test-entity-merge-link=data-test-entity-merge-link
data-test-entity-create-link={{data-test-entity-create-link}} data-test-backend-view-link=data-test-backend-view-link
data-test-policy-create-link={{data-test-policy-create-link}} data-test-entity-create-link=data-test-entity-create-link
data-test-policy-edit-toggle={{data-test-policy-edit-toggle}} data-test-policy-create-link=data-test-policy-create-link
data-test-secret-backend-configure={{data-test-secret-backend-configure}} data-test-policy-edit-toggle=data-test-policy-edit-toggle
...attributes data-test-secret-backend-configure=data-test-secret-backend-configure
> ) as |LinkToComponent|}}
{{yield}} {{#if (and disabled disabledTooltip)}}
<Icon @glyph={{glyph}} /> <ToolTip @verticalPosition="above" as |T|>
</LinkTo> <T.trigger tabindex="-1">
<LinkToComponent ...attributes>
{{yield}} <Icon @glyph={{glyph}} data-test-icon={{glyph}} />
</LinkToComponent>
</T.trigger>
<T.content @class="tool-tip smaller-font">
<div class="box" data-test-disabled-tooltip>
{{disabledTooltip}}
</div>
</T.content>
</ToolTip>
{{else}}
<LinkToComponent ...attributes>
{{yield}} <Icon @glyph={{glyph}} data-test-icon={{glyph}} />
</LinkToComponent>
{{/if}}
{{/let}}

View File

@ -4,18 +4,21 @@
`ToolbarLink` components style links and buttons for the Toolbar `ToolbarLink` components style links and buttons for the Toolbar
It should only be used inside of `Toolbar`. It should only be used inside of `Toolbar`.
**Params**
| Param | Type | Default | Description | | Param | Type | Description |
| --- | --- | --- | --- | | --- | --- | --- |
| params | <code>Array</code> | <code>&#x27;&#x27;</code> | Array to pass to LinkTo | | params | <code>array</code> | Array to pass to LinkTo |
| type | <code>String</code> | <code>&#x27;&#x27;</code> | Use "add" to change icon | | type | <code>string</code> | Use "add" to change icon |
| disabled | <code>boolean</code> | pass true to disable link |
| disabledTooltip | <code>string</code> | tooltip to display on hover when disabled |
**Example** **Example**
```js ```js
<Toolbar> <Toolbar>
<ToolbarActions> <ToolbarActions>
<ToolbarLink @params={{array 'vault.cluster.policies.create'}} @type="add"> <ToolbarLink @params={{array 'vault.cluster.policies.create'}} @type="add" @disabled={{true}} @disabledTooltip="This link is disabled">
Create policy Create policy
</ToolbarLink> </ToolbarLink>
</ToolbarActions> </ToolbarActions>

View File

@ -1,6 +1,6 @@
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
import { storiesOf } from '@storybook/ember'; import { storiesOf } from '@storybook/ember';
import { withKnobs, select, text } from '@storybook/addon-knobs'; import { withKnobs, select, text, boolean } from '@storybook/addon-knobs';
import notes from './toolbar-link.md'; import notes from './toolbar-link.md';
storiesOf('Toolbar', module) storiesOf('Toolbar', module)
@ -17,6 +17,8 @@ storiesOf('Toolbar', module)
<ToolbarLink <ToolbarLink
@params={{array '#'}} @params={{array '#'}}
@type={{type}} @type={{type}}
@disabled={{disabled}}
@disabledTooltip={{disabledTooltip}}
> >
{{label}} {{label}}
</ToolbarLink> </ToolbarLink>
@ -27,6 +29,8 @@ storiesOf('Toolbar', module)
context: { context: {
type: select('Type', ['', 'add']), type: select('Type', ['', 'add']),
label: text('Button text', 'Edit secret'), label: text('Button text', 'Edit secret'),
disabled: boolean('disabled', false),
disabledTooltip: text('Tooltip to display when disabled', ''),
}, },
}), }),
{ notes } { notes }

View File

@ -617,13 +617,8 @@ module('Acceptance | secrets/secret/create', function(hooks) {
await assert await assert
.dom('[data-test-value-div="secret-key"]') .dom('[data-test-value-div="secret-key"]')
.exists('secret view page and info table row with secret-key value'); .exists('secret view page and info table row with secret-key value');
// check you can create new version // create new version should be disabled with no metadata read access
await click('[data-test-secret-edit="true"]'); assert.dom('[data-test-secret-edit]').hasClass('disabled', 'Create new version action is disabled');
await settled();
await fillIn('[data-test-secret-key]', 'version2');
await editPage.save();
await settled();
assert.dom('[data-test-row-label="version2"]').exists('the current version displayed is the new version');
assert assert
.dom('[data-test-popup-menu-trigger="version"]') .dom('[data-test-popup-menu-trigger="version"]')
.doesNotExist('the version drop down menu does not show'); .doesNotExist('the version drop down menu does not show');

View File

@ -1,6 +1,6 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit'; import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers'; import { render, triggerEvent } from '@ember/test-helpers';
import { isPresent } from 'ember-cli-page-object'; import { isPresent } from 'ember-cli-page-object';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
@ -14,4 +14,41 @@ module('Integration | Component | toolbar-link', function(hooks) {
assert.ok(isPresent('.toolbar-link')); assert.ok(isPresent('.toolbar-link'));
assert.ok(isPresent('.icon')); assert.ok(isPresent('.icon'));
}); });
test('it should render icons', async function(assert) {
assert.expect(2);
await render(hbs`
<ToolbarLink
@params={{array '/secrets'}}
@type={{this.type}}
>
Test Link
</ToolbarLink>
`);
assert.dom('[data-test-icon="chevron-right"]').exists('Default chevron right icon renders');
this.set('type', 'add');
assert.dom('[data-test-icon="plus-plain"]').exists('Icon can be overriden to show plus sign');
});
test('it should disable and show tooltip if provided', async function(assert) {
assert.expect(3);
await render(hbs`
<ToolbarLink
@params={{array '/secrets'}}
@disabled={{true}}
@disabledTooltip={{this.tooltip}}
>
Test Link
</ToolbarLink>
`);
assert.dom('a').hasClass('disabled', 'Link can be disabled');
assert.dom('[data-test-popup-menu-trigger]').doesNotExist('Tooltip is hidden when not provided');
this.set('tooltip', 'Test tooltip');
await triggerEvent('.ember-basic-dropdown-trigger', 'mouseenter');
assert.dom('[data-test-disabled-tooltip]').hasText(this.tooltip, 'Tooltip renders');
});
}); });