UI: TTL picker cleanup (#18114)
This commit is contained in:
parent
826e87884e
commit
0ea02992b7
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: update TTL picker for consistency
|
||||
```
|
|
@ -6,9 +6,9 @@ import { action } from '@ember/object';
|
|||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <ConfigureAwsSecret
|
||||
@model={{model}}
|
||||
@tab={{tab}}
|
||||
* <ConfigureAwsSecret
|
||||
@model={{model}}
|
||||
@tab={{tab}}
|
||||
@accessKey={{accessKey}}
|
||||
@secretKey={{secretKey}}
|
||||
@region={{region}}
|
||||
|
@ -27,7 +27,7 @@ import { action } from '@ember/object';
|
|||
* @param {string} stsEndpoint - Sts endpoint
|
||||
* @param {Function} saveAWSRoot - parent action which saves AWS root credentials
|
||||
* @param {Function} saveAWSLease - parent action which updates AWS lease information
|
||||
*
|
||||
*
|
||||
*/
|
||||
export default class ConfigureAwsSecretComponent extends Component {
|
||||
@action
|
||||
|
@ -41,4 +41,11 @@ export default class ConfigureAwsSecretComponent extends Component {
|
|||
event.preventDefault();
|
||||
this.args.saveAWSLease(data);
|
||||
}
|
||||
|
||||
@action
|
||||
handleTtlChange(name, ttlObj) {
|
||||
// lease values cannot be undefined, set to 0 to use default
|
||||
const valueToSet = ttlObj.enabled ? ttlObj.goSafeTimeString : 0;
|
||||
this.args.model.set(name, valueToSet);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<div class="field">
|
||||
<TtlPicker
|
||||
@label="Wrap response"
|
||||
@helperTextDisabled="Will not wrap response"
|
||||
@helperTextEnabled="Will wrap response with a lease of"
|
||||
@initialEnabled={{true}}
|
||||
@initialValue="30m"
|
||||
@onChange={{this.changedValue}}
|
||||
@changeOnInit={{true}}
|
||||
data-test-wrap-ttl-picker
|
||||
/>
|
||||
</div>
|
|
@ -1,49 +1,26 @@
|
|||
import { assert } from '@ember/debug';
|
||||
import Component from '@ember/component';
|
||||
import { set, computed } from '@ember/object';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default Component.extend({
|
||||
// passed from outside
|
||||
onChange: null,
|
||||
wrapResponse: true,
|
||||
export default class WrapTtlComponent extends Component {
|
||||
@tracked
|
||||
wrapResponse = true;
|
||||
|
||||
ttl: '30m',
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
assert('`onChange` handler is a required attr in `' + this.toString() + '`.', this.args.onChange);
|
||||
}
|
||||
|
||||
wrapTTL: computed('wrapResponse', 'ttl', function () {
|
||||
get wrapTTL() {
|
||||
const { wrapResponse, ttl } = this;
|
||||
return wrapResponse ? ttl : null;
|
||||
}),
|
||||
}
|
||||
|
||||
didRender() {
|
||||
this._super(...arguments);
|
||||
this.onChange(this.wrapTTL);
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
assert('`onChange` handler is a required attr in `' + this.toString() + '`.', this.onChange);
|
||||
},
|
||||
|
||||
layout: hbs`
|
||||
<div class="field">
|
||||
{{ttl-picker2
|
||||
data-test-wrap-ttl-picker=true
|
||||
label='Wrap response'
|
||||
helperTextDisabled='Will not wrap response'
|
||||
helperTextEnabled='Will wrap response with a lease of'
|
||||
enableTTL=this.wrapResponse
|
||||
initialValue=this.ttl
|
||||
onChange=(action 'changedValue')
|
||||
}}
|
||||
</div>
|
||||
`,
|
||||
|
||||
actions: {
|
||||
changedValue(ttlObj) {
|
||||
set(this, 'wrapResponse', ttlObj.enabled);
|
||||
set(this, 'ttl', `${ttlObj.seconds}s`);
|
||||
this.onChange(this.wrapTTL);
|
||||
},
|
||||
},
|
||||
});
|
||||
@action
|
||||
changedValue(ttlObj) {
|
||||
this.wrapResponse = ttlObj.enabled;
|
||||
this.ttl = ttlObj.goSafeTimeString;
|
||||
this.args.onChange(this.wrapTTL);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,3 +13,13 @@
|
|||
// Font comes from npm package: https://www.npmjs.com/package/text-security
|
||||
// We took the font we wanted and moved it into the ui/fonts folder
|
||||
@include font-face('text-security-square');
|
||||
|
||||
.sr-only {
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
@import './components/tool-tip';
|
||||
@import './components/transform-edit.scss';
|
||||
@import './components/transit-card';
|
||||
@import './components/ttl-picker2';
|
||||
@import './components/ttl-picker';
|
||||
@import './components/unseal-warning';
|
||||
@import './components/ui-wizard';
|
||||
@import './components/vault-loading';
|
||||
|
|
|
@ -34,8 +34,18 @@
|
|||
If you do not supply lease settings, we will use the default values in AWS.
|
||||
</p>
|
||||
</div>
|
||||
<TtlPicker @labelText="Lease" @initialValue={{@model.lease}} @onChange={{action (mut @model.lease)}} />
|
||||
<TtlPicker @labelText="Maximum Lease" @initialValue={{@model.leaseMax}} @onChange={{action (mut @model.leaseMax)}} />
|
||||
<TtlPicker
|
||||
@label="Lease"
|
||||
@initialValue={{@model.lease}}
|
||||
@initialEnabled={{@model.lease}}
|
||||
@onChange={{fn this.handleTtlChange "lease"}}
|
||||
/>
|
||||
<TtlPicker
|
||||
@label="Maximum Lease"
|
||||
@initialValue={{@model.leaseMax}}
|
||||
@initialEnabled={{@model.leaseMax}}
|
||||
@onChange={{fn this.handleTtlChange "leaseMax"}}
|
||||
/>
|
||||
<div class="box is-bottomless is-fullwidth">
|
||||
<button data-test-aws-input="lease-save" type="submit" class="button is-primary">
|
||||
Save
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{{else if (eq @attr.options.editType "ttl")}}
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
@initialValue={{or (get @model @attr.name) @attr.options.defaultValue}}
|
||||
@initialEnabled={{or (get @model @attr.name) false}}
|
||||
@label={{or @attr.options.label (humanize (dasherize @attr.name))}}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<MessageError @model={{this.config}} @errors={{this.errors}} />
|
||||
<form {{action "save" this.section on="submit"}} class="box is-shadowless is-marginless is-fullwidth has-slim-padding">
|
||||
{{#if (eq this.section "crl")}}
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
data-test-input="expiry"
|
||||
@onChange={{action "handleCrlTtl"}}
|
||||
@label={{if (get this.config "disable") "CRL building disabled" "CRL building enabled"}}
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
@label="Wrap TTL"
|
||||
@initialValue="30m"
|
||||
@onChange={{action "updateTtl"}}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<Input id="key-name" @value={{@key.name}} class="input" data-test-transit-key-name={{true}} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
@initialValue="30d"
|
||||
@initialEnabled={{false}}
|
||||
@label="Auto-rotation period"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
@initialValue={{or @key.autoRotatePeriod "30d"}}
|
||||
@initialEnabled={{not (eq @key.autoRotatePeriod "0s")}}
|
||||
@label="Auto-rotation period"
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<div class="box is-shadowless" data-test-lease-renew-picker={{true}}>
|
||||
<h2 class="title is-6">Renew Lease</h2>
|
||||
<form {{action "renewLease" this.model this.increment on="submit"}}>
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
@label="Increment"
|
||||
@helperTextEnabled="Lease will expire after"
|
||||
@helperTextDisabled="Vault will use the default lease duration"
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
{{! TTL Picker }}
|
||||
<div class="field">
|
||||
{{#let (or (get @model this.valuePath) @attr.options.setDefault) as |initialValue|}}
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
data-test-input={{@attr.name}}
|
||||
@onChange={{this.setAndBroadcastTtl}}
|
||||
@label={{this.labelString}}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
data-test-radio-button="ttl"
|
||||
/>
|
||||
<label class="has-left-margin-xs">
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
data-test-input="ttl"
|
||||
@onChange={{this.setAndBroadcastTtl}}
|
||||
@label="TTL"
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
class={{this.inputClasses}}
|
||||
disabled={{this.disabled}}
|
||||
data-test-toggle-input={{this.name}}
|
||||
...attributes
|
||||
/>
|
||||
<label data-test-toggle-label={{this.name}} for={{this.safeId}} class="toggle-label">
|
||||
<label data-test-toggle-label={{this.name}} for={{this.safeId}} class="toggle-label {{if this.hideLabel 'sr-only'}}">
|
||||
{{#if (has-block)}}
|
||||
{{yield}}
|
||||
{{else}}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
/**
|
||||
* @module TtlForm
|
||||
* TtlForm components are used to enter a Time To Live (TTL) input.
|
||||
* This component does not include a label and is designed to take
|
||||
* a time and unit, and pass an object including seconds and
|
||||
* timestring when those two values are changed.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <TtlForm @onChange={{action handleChange}} @unit="m"/>
|
||||
* ```
|
||||
* @param {function} onChange - This function will be called when the user changes the value. An object will be passed in as a parameter with values seconds{number}, timeString{string}
|
||||
* @param {number} [time] - Time is the value that will be passed into the value input. Can be null/undefined to start if input is required.
|
||||
* @param {unit} [unit="s"] - This is the unit key which will show by default on the form. Can be one of `s` (seconds), `m` (minutes), `h` (hours), `d` (days)
|
||||
* @param {number} [recalculationTimeout=5000] - This is the time, in milliseconds, that `recalculateSeconds` will be be true after time is updated
|
||||
*/
|
||||
|
||||
import Ember from 'ember';
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { task, timeout } from 'ember-concurrency';
|
||||
import layout from '../templates/components/ttl-form';
|
||||
|
||||
const secondsMap = {
|
||||
s: 1,
|
||||
m: 60,
|
||||
h: 3600,
|
||||
d: 86400,
|
||||
};
|
||||
const convertToSeconds = (time, unit) => {
|
||||
return time * secondsMap[unit];
|
||||
};
|
||||
const convertFromSeconds = (seconds, unit) => {
|
||||
return seconds / secondsMap[unit];
|
||||
};
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
time: '',
|
||||
unit: 's',
|
||||
|
||||
/* Used internally */
|
||||
recalculationTimeout: 5000,
|
||||
recalculateSeconds: false,
|
||||
errorMessage: null,
|
||||
unitOptions: computed(function () {
|
||||
return [
|
||||
{ label: 'seconds', value: 's' },
|
||||
{ label: 'minutes', value: 'm' },
|
||||
{ label: 'hours', value: 'h' },
|
||||
{ label: 'days', value: 'd' },
|
||||
];
|
||||
}),
|
||||
handleChange() {
|
||||
const { time, unit, seconds } = this;
|
||||
const ttl = {
|
||||
seconds,
|
||||
timeString: time + unit,
|
||||
};
|
||||
this.onChange(ttl);
|
||||
},
|
||||
keepSecondsRecalculate(newUnit) {
|
||||
const newTime = convertFromSeconds(this.seconds, newUnit);
|
||||
this.setProperties({
|
||||
time: newTime,
|
||||
unit: newUnit,
|
||||
});
|
||||
},
|
||||
updateTime: task(function* (newTime) {
|
||||
this.set('errorMessage', '');
|
||||
const parsedTime = parseInt(newTime, 10);
|
||||
if (!newTime) {
|
||||
this.set('errorMessage', 'This field is required');
|
||||
return;
|
||||
} else if (Number.isNaN(parsedTime)) {
|
||||
this.set('errorMessage', 'Value must be a number');
|
||||
return;
|
||||
}
|
||||
this.set('time', parsedTime);
|
||||
this.handleChange();
|
||||
if (Ember.testing) {
|
||||
return;
|
||||
}
|
||||
this.set('recalculateSeconds', true);
|
||||
yield timeout(this.recalculationTimeout);
|
||||
this.set('recalculateSeconds', false);
|
||||
}).restartable(),
|
||||
|
||||
seconds: computed('time', 'unit', function () {
|
||||
return convertToSeconds(this.time, this.unit);
|
||||
}),
|
||||
|
||||
actions: {
|
||||
updateUnit(newUnit) {
|
||||
if (this.recalculateSeconds) {
|
||||
this.set('unit', newUnit);
|
||||
} else {
|
||||
this.keepSecondsRecalculate(newUnit);
|
||||
}
|
||||
this.handleChange();
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,139 @@
|
|||
<div class="has-bottom-margin-m" ...attributes>
|
||||
{{#if @hideToggle}}
|
||||
<fieldset class="field is-grouped is-marginless is-borderless">
|
||||
{{#if (has-block)}}
|
||||
{{! Allow label override }}
|
||||
{{yield}}
|
||||
{{else}}
|
||||
<legend>
|
||||
<span class="ttl-picker-label is-large" data-test-ttl-form-label={{this.label}}>{{this.label}}</span><br />
|
||||
{{#if this.helperText}}
|
||||
<div class="sub-text">
|
||||
<span data-test-ttl-form-subtext>{{this.helperText}}</span>
|
||||
{{#if @description}}
|
||||
<ToolTip @verticalPosition="below" as |T|>
|
||||
<T.Trigger data-test-tooltip-trigger tabindex="-1">
|
||||
<Icon @name="info" aria-label="description" />
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box" data-test-hover-copy-tooltip-text>
|
||||
{{this.description}}
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</legend>
|
||||
{{/if}}
|
||||
<div class="control is-marginless" data-test-ttl-inputs>
|
||||
<label for="time-{{this.elementId}}" class="sr-only">Number of units</label>
|
||||
<Input
|
||||
id="time-{{this.elementId}}"
|
||||
@value={{this.time}}
|
||||
@type="text"
|
||||
name="time"
|
||||
class="input {{if this.errorMessage 'has-error'}}"
|
||||
oninput={{perform this.updateTime value="target.value"}}
|
||||
pattern="[0-9]*"
|
||||
data-test-ttl-value={{this.label}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<label for="unit-{{this.elementId}}" class="sr-only">Unit for TTL</label>
|
||||
<Select
|
||||
id="unit-{{this.elementId}}"
|
||||
@name="ttl-unit"
|
||||
@options={{this.unitOptions}}
|
||||
@onChange={{this.updateUnit}}
|
||||
@selectedValue={{this.unit}}
|
||||
data-test-ttl-unit={{this.label}}
|
||||
/>
|
||||
</div>
|
||||
{{#if this.errorMessage}}
|
||||
<div class="columns is-mobile is-variable is-1 ttl-value-error">
|
||||
<div class="is-narrow message-icon">
|
||||
<Icon @name="x-square-fill" class="has-text-danger" />
|
||||
</div>
|
||||
<div class="has-text-danger">
|
||||
{{this.errorMessage}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
{{else}}
|
||||
<Toggle
|
||||
@name={{this.label}}
|
||||
@status="success"
|
||||
@size="small"
|
||||
@onChange={{action "toggleEnabled"}}
|
||||
@checked={{this.enableTTL}}
|
||||
@hideLabel={{true}}
|
||||
data-test-ttl-toggle={{this.label}}
|
||||
>
|
||||
<fieldset class="field is-grouped is-marginless is-borderless">
|
||||
{{#if (has-block)}}
|
||||
{{! Allow label override }}
|
||||
{{yield}}
|
||||
{{else}}
|
||||
<legend>
|
||||
<span class="ttl-picker-label is-large" data-test-ttl-form-label={{this.label}}>{{this.label}}</span><br />
|
||||
{{#if this.helperText}}
|
||||
<div class="sub-text">
|
||||
<span data-test-ttl-form-subtext>{{this.helperText}}</span>
|
||||
{{#if @description}}
|
||||
<ToolTip @verticalPosition="below" as |T|>
|
||||
<T.Trigger data-test-tooltip-trigger tabindex="-1">
|
||||
<Icon @name="info" aria-label="description" />
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box" data-test-hover-copy-tooltip-text>
|
||||
{{this.description}}
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</legend>
|
||||
{{/if}}
|
||||
{{#if (or this.enableTTL @hideToggle)}}
|
||||
<div class="control is-marginless" data-test-ttl-inputs>
|
||||
<label for="time-{{this.elementId}}" class="sr-only">Number of units</label>
|
||||
<Input
|
||||
id="time-{{this.elementId}}"
|
||||
@value={{this.time}}
|
||||
@type="text"
|
||||
name="time"
|
||||
class="input {{if this.errorMessage 'has-error'}}"
|
||||
oninput={{perform this.updateTime value="target.value"}}
|
||||
pattern="[0-9]*"
|
||||
data-test-ttl-value={{this.label}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<label for="unit-{{this.elementId}}" class="sr-only">Unit for TTL</label>
|
||||
<Select
|
||||
id="unit-{{this.elementId}}"
|
||||
@name="ttl-unit"
|
||||
@options={{this.unitOptions}}
|
||||
@onChange={{this.updateUnit}}
|
||||
@selectedValue={{this.unit}}
|
||||
data-test-ttl-unit={{this.label}}
|
||||
/>
|
||||
</div>
|
||||
{{#if this.errorMessage}}
|
||||
<div class="columns is-mobile is-variable is-1 ttl-value-error">
|
||||
<div class="is-narrow message-icon">
|
||||
<Icon @name="x-square-fill" class="has-text-danger" />
|
||||
</div>
|
||||
<div class="has-text-danger">
|
||||
{{this.errorMessage}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
</Toggle>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,124 +1,188 @@
|
|||
import { typeOf } from '@ember/utils';
|
||||
import EmberError from '@ember/error';
|
||||
import Component from '@ember/component';
|
||||
import { set, computed } from '@ember/object';
|
||||
import Duration from '@icholy/duration';
|
||||
import layout from '../templates/components/ttl-picker';
|
||||
|
||||
const ERROR_MESSAGE = 'TTLs must be specified in whole number increments, please enter a whole number.';
|
||||
|
||||
/**
|
||||
* @module TtlPicker
|
||||
* `TtlPicker` components are used to set the 'time to live'.
|
||||
* This version is being deprecated and replaced by `TtlPicker2` which is an automatic-width version that
|
||||
* automatically recalculates the time value when unit is updated unless time has been changed recently.
|
||||
* Once all instances of TtlPicker are replaced with TtlPicker2, this component will be removed and
|
||||
* TtlPicker2 will be renamed to TtlPicker.
|
||||
* TtlPicker components are used to enable and select duration values such as TTL.
|
||||
* This component renders a toggle by default, and passes all relevant attributes
|
||||
* to TtlForm. Please see that component for additional arguments
|
||||
* - allows TTL to be enabled or disabled
|
||||
* - recalculates the time when the unit is changed by the user (eg 60s -> 1m)
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
<TtlPicker @labelText="Lease" @initialValue={{lease}} @onChange={{action (mut lease)}} />
|
||||
* <TtlPicker @onChange={{this.handleChange}} @initialEnabled={{@model.myAttribute}} @initialValue={{@model.myAttribute}}/>
|
||||
* ```
|
||||
*
|
||||
* @param labelClass="" {String} - A CSS class to add to the label.
|
||||
* @param labelText="TTL" {String} - The text content of the label associated with the widget.
|
||||
* @param initialValue=null {Number} - The starting value of the TTL;
|
||||
* @param setDefaultValue=true {Boolean} - If true, the component will trigger onChange on the initial
|
||||
* render, causing a value to be set.
|
||||
* @param onChange=Function.prototype{Function} - The function to call when the value of the ttl changes.
|
||||
* @param outputSeconds=false{Boolean} - If true, the component will trigger onChange with a value
|
||||
* converted to seconds instead of a Golang duration string.
|
||||
* @param onChange {Function} - This function will be passed a TTL object, which includes enabled{bool}, seconds{number}, timeString{string}, goSafeTimeString{string}.
|
||||
* @param initialEnabled=false {Boolean} - Set this value if you want the toggle on when component is mounted
|
||||
* @param label="Time to live (TTL)" {String} - Label is the main label that lives next to the toggle. Yielded values will replace the label
|
||||
* @param helperTextEnabled="" {String} - This helper text is shown under the label when the toggle is switched on
|
||||
* @param helperTextDisabled="" {String} - This helper text is shown under the label when the toggle is switched off
|
||||
* @param initialValue=null {string} - InitialValue is the duration value which will be shown when the component is loaded. If it can't be parsed, will default to 0.
|
||||
* @param changeOnInit=false {boolean} - if true, calls the onChange hook when component is initialized
|
||||
* @param hideToggle=false {Boolean} - set this value if you'd like to hide the toggle and just leverage the input field
|
||||
*/
|
||||
export default Component.extend({
|
||||
layout,
|
||||
'data-test-component': 'ttl-picker',
|
||||
attributeBindings: ['data-test-component'],
|
||||
classNames: 'field',
|
||||
|
||||
onChange: () => {},
|
||||
setDefaultValue: true,
|
||||
labelText: 'TTL',
|
||||
labelClass: '',
|
||||
ouputSeconds: false,
|
||||
import Component from '@glimmer/component';
|
||||
import { typeOf } from '@ember/utils';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import Duration from '@icholy/duration';
|
||||
import { guidFor } from '@ember/object/internals';
|
||||
import Ember from 'ember';
|
||||
import { restartableTask, timeout } from 'ember-concurrency';
|
||||
|
||||
time: 30,
|
||||
unit: 'm',
|
||||
initialValue: null,
|
||||
errorMessage: null,
|
||||
unitOptions: computed(function () {
|
||||
export const secondsMap = {
|
||||
s: 1,
|
||||
m: 60,
|
||||
h: 3600,
|
||||
d: 86400,
|
||||
};
|
||||
const convertToSeconds = (time, unit) => {
|
||||
return time * secondsMap[unit];
|
||||
};
|
||||
const convertFromSeconds = (seconds, unit) => {
|
||||
return seconds / secondsMap[unit];
|
||||
};
|
||||
const goSafeConvertFromSeconds = (seconds, unit) => {
|
||||
// Go only accepts s, m, or h units
|
||||
const u = unit === 'd' ? 'h' : unit;
|
||||
return convertFromSeconds(seconds, u) + u;
|
||||
};
|
||||
const largestUnitFromSeconds = (seconds) => {
|
||||
let unit = 's';
|
||||
if (seconds === 0) return unit;
|
||||
// get largest unit with no remainder
|
||||
if (seconds % secondsMap.d === 0) {
|
||||
unit = 'd';
|
||||
} else if (seconds % secondsMap.h === 0) {
|
||||
unit = 'h';
|
||||
} else if (seconds % secondsMap.m === 0) {
|
||||
unit = 'm';
|
||||
}
|
||||
return unit;
|
||||
};
|
||||
export default class TtlPickerComponent extends Component {
|
||||
@tracked enableTTL = false;
|
||||
@tracked recalculateSeconds = false;
|
||||
@tracked time = ''; // if defaultValue is NOT set, then do not display a defaultValue.
|
||||
@tracked unit = 's';
|
||||
@tracked recalculateSeconds = false;
|
||||
@tracked errorMessage = '';
|
||||
|
||||
/* Used internally */
|
||||
recalculationTimeout = 5000;
|
||||
elementId = 'ttl-' + guidFor(this);
|
||||
|
||||
get label() {
|
||||
return this.args.label || 'Time to live (TTL)';
|
||||
}
|
||||
get helperText() {
|
||||
return this.enableTTL || this.args.hideToggle
|
||||
? this.args.helperTextEnabled
|
||||
: this.args.helperTextDisabled;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
const enable = this.args.initialEnabled;
|
||||
|
||||
let setEnable = !!this.args.hideToggle;
|
||||
if (!!enable || typeOf(enable) === 'boolean') {
|
||||
// This allows non-boolean values passed in to be evaluated for truthiness
|
||||
setEnable = !!enable;
|
||||
}
|
||||
|
||||
this.enableTTL = setEnable;
|
||||
this.initializeTtl();
|
||||
}
|
||||
|
||||
initializeTtl() {
|
||||
const initialValue = this.args.initialValue;
|
||||
let seconds = 0;
|
||||
if (typeof initialValue === 'number') {
|
||||
// if the passed value is a number, assume unit is seconds
|
||||
seconds = initialValue;
|
||||
} else {
|
||||
try {
|
||||
seconds = Duration.parse(initialValue).seconds();
|
||||
} catch (e) {
|
||||
// if parsing fails leave it empty
|
||||
return;
|
||||
}
|
||||
}
|
||||
const unit = largestUnitFromSeconds(seconds);
|
||||
this.time = convertFromSeconds(seconds, unit);
|
||||
this.unit = unit;
|
||||
|
||||
if (this.args.changeOnInit) {
|
||||
this.handleChange();
|
||||
}
|
||||
}
|
||||
|
||||
get seconds() {
|
||||
return convertToSeconds(this.time, this.unit);
|
||||
}
|
||||
get unitOptions() {
|
||||
return [
|
||||
{ label: 'seconds', value: 's' },
|
||||
{ label: 'minutes', value: 'm' },
|
||||
{ label: 'hours', value: 'h' },
|
||||
{ label: 'days', value: 'd' },
|
||||
];
|
||||
}),
|
||||
}
|
||||
|
||||
convertToSeconds(time, unit) {
|
||||
const toSeconds = {
|
||||
s: 1,
|
||||
m: 60,
|
||||
h: 3600,
|
||||
};
|
||||
|
||||
return time * toSeconds[unit];
|
||||
},
|
||||
|
||||
TTL: computed('outputSeconds', 'time', 'unit', function () {
|
||||
let { time, unit, outputSeconds } = this;
|
||||
//convert to hours
|
||||
if (unit === 'd') {
|
||||
time = time * 24;
|
||||
unit = 'h';
|
||||
keepSecondsRecalculate(newUnit) {
|
||||
const newTime = convertFromSeconds(this.seconds, newUnit);
|
||||
if (Number.isInteger(newTime)) {
|
||||
// Only recalculate if time is whole number
|
||||
this.time = newTime;
|
||||
}
|
||||
const timeString = time + unit;
|
||||
return outputSeconds ? this.convertToSeconds(time, unit) : timeString;
|
||||
}),
|
||||
this.unit = newUnit;
|
||||
}
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
if (this.setDefaultValue === false) {
|
||||
handleChange() {
|
||||
const { time, unit, seconds, enableTTL } = this;
|
||||
const ttl = {
|
||||
enabled: this.args.hideToggle || enableTTL,
|
||||
seconds,
|
||||
timeString: time + unit,
|
||||
goSafeTimeString: goSafeConvertFromSeconds(seconds, unit),
|
||||
};
|
||||
this.args.onChange(ttl);
|
||||
}
|
||||
|
||||
@action
|
||||
toggleEnabled() {
|
||||
this.enableTTL = !this.enableTTL;
|
||||
this.handleChange();
|
||||
}
|
||||
|
||||
@restartableTask
|
||||
*updateTime(newTime) {
|
||||
this.errorMessage = '';
|
||||
const parsedTime = parseInt(newTime, 10);
|
||||
if (!newTime) {
|
||||
this.errorMessage = 'This field is required';
|
||||
return;
|
||||
} else if (Number.isNaN(parsedTime)) {
|
||||
this.errorMessage = 'Value must be a number';
|
||||
return;
|
||||
}
|
||||
this.onChange(this.TTL);
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
if (!this.onChange) {
|
||||
throw new EmberError('`onChange` handler is a required attr in `' + this.toString() + '`.');
|
||||
this.time = parsedTime;
|
||||
this.handleChange();
|
||||
if (Ember.testing) {
|
||||
return;
|
||||
}
|
||||
if (this.initialValue != undefined) {
|
||||
this.parseAndSetTime();
|
||||
this.recalculateSeconds = true;
|
||||
yield timeout(this.recalculationTimeout);
|
||||
this.recalculateSeconds = false;
|
||||
}
|
||||
|
||||
@action
|
||||
updateUnit(newUnit) {
|
||||
if (this.recalculateSeconds) {
|
||||
this.unit = newUnit;
|
||||
} else {
|
||||
this.keepSecondsRecalculate(newUnit);
|
||||
}
|
||||
},
|
||||
|
||||
parseAndSetTime() {
|
||||
const value = this.initialValue;
|
||||
let seconds = typeOf(value) === 'number' ? value : 30;
|
||||
try {
|
||||
seconds = Duration.parse(value).seconds();
|
||||
} catch (e) {
|
||||
// if parsing fails leave as default 30
|
||||
}
|
||||
|
||||
this.set('time', seconds);
|
||||
this.set('unit', 's');
|
||||
},
|
||||
|
||||
actions: {
|
||||
changedValue(key, value) {
|
||||
if (value && key === 'time') {
|
||||
value = parseInt(value, 10);
|
||||
if (Number.isNaN(value)) {
|
||||
this.set('errorMessage', ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.set('errorMessage', null);
|
||||
|
||||
set(this, key, value);
|
||||
this.onChange(this.TTL);
|
||||
},
|
||||
},
|
||||
});
|
||||
this.handleChange();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
/**
|
||||
* @module TtlPicker2
|
||||
* TtlPicker2 components are used to enable and select 'time to live' values. Use this TtlPicker2 instead of TtlPicker if you:
|
||||
* - Want the TTL to be enabled or disabled
|
||||
* - Want to have the time recalculated by default when the unit changes (eg 60s -> 1m)
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <TtlPicker2 @onChange={{handleChange}} @time={{defaultTime}} @unit={{defaultUnit}}/>
|
||||
* ```
|
||||
* @param onChange {Function} - This function will be passed a TTL object, which includes enabled{bool}, seconds{number}, timeString{string}, goSafeTimeString{string}.
|
||||
* @param label="Time to live (TTL)" {String} - Label is the main label that lives next to the toggle.
|
||||
* @param helperTextDisabled="Allow tokens to be used indefinitely" {String} - This helper text is shown under the label when the toggle is switched off
|
||||
* @param helperTextEnabled="Disable the use of the token after" {String} - This helper text is shown under the label when the toggle is switched on
|
||||
* @param description="Longer description about this value, what it does, and why it is useful. Shows up in tooltip next to helpertext"
|
||||
* @param time='' {Number} - The time (in the default units) which will be adjustable by the user of the form
|
||||
* @param unit="s" {String} - This is the unit key which will show by default on the form. Can be one of `s` (seconds), `m` (minutes), `h` (hours), `d` (days)
|
||||
* @param recalculationTimeout=5000 {Number} - This is the time, in milliseconds, that `recalculateSeconds` will be be true after time is updated
|
||||
* @param initialValue=null {String} - This is the value set initially (particularly from a string like '30h')
|
||||
* @param initialEnabled=null {Boolean} - Set this value if you want the toggle on when component is mounted
|
||||
* @param changeOnInit=false {Boolean} - set this value if you'd like the passed onChange function to be called on component initialization
|
||||
* @param hideToggle=false {Boolean} - set this value if you'd like to hide the toggle and just leverage the input field
|
||||
*/
|
||||
|
||||
import { computed } from '@ember/object';
|
||||
import { typeOf } from '@ember/utils';
|
||||
import Duration from '@icholy/duration';
|
||||
import TtlForm from './ttl-form';
|
||||
import layout from '../templates/components/ttl-picker2';
|
||||
|
||||
const secondsMap = {
|
||||
s: 1,
|
||||
m: 60,
|
||||
h: 3600,
|
||||
d: 86400,
|
||||
};
|
||||
const convertFromSeconds = (seconds, unit) => {
|
||||
return seconds / secondsMap[unit];
|
||||
};
|
||||
const goSafeConvertFromSeconds = (seconds, unit) => {
|
||||
// Go only accepts s, m, or h units
|
||||
const u = unit === 'd' ? 'h' : unit;
|
||||
return convertFromSeconds(seconds, u) + u;
|
||||
};
|
||||
|
||||
export default TtlForm.extend({
|
||||
layout,
|
||||
enableTTL: false,
|
||||
label: 'Time to live (TTL)',
|
||||
helperTextDisabled: 'Allow tokens to be used indefinitely',
|
||||
helperTextEnabled: 'Disable the use of the token after',
|
||||
description: '',
|
||||
time: '', // if defaultValue is NOT set, then do not display a defaultValue.
|
||||
unit: 's',
|
||||
initialValue: null,
|
||||
changeOnInit: false,
|
||||
hideToggle: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
const value = this.initialValue;
|
||||
const enable = this.initialEnabled;
|
||||
const changeOnInit = this.changeOnInit;
|
||||
// if initial value is unset use params passed in as defaults
|
||||
// and if no defaultValue is passed in display no time
|
||||
if (!value && value !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let time = 30;
|
||||
let unit = 's';
|
||||
let setEnable = this.hideToggle || this.enableTTL;
|
||||
if (!!enable || typeOf(enable) === 'boolean') {
|
||||
// This allows non-boolean values passed in to be evaluated for truthiness
|
||||
setEnable = !!enable;
|
||||
}
|
||||
|
||||
if (typeOf(value) === 'number') {
|
||||
// if the passed value is a number, assume unit is seconds
|
||||
// then check if the value can be converted into a larger unit
|
||||
if (value % secondsMap.d === 0) {
|
||||
unit = 'd';
|
||||
} else if (value % secondsMap.h === 0) {
|
||||
unit = 'h';
|
||||
} else if (value % secondsMap.m === 0) {
|
||||
unit = 'm';
|
||||
}
|
||||
time = convertFromSeconds(value, unit);
|
||||
} else {
|
||||
try {
|
||||
const seconds = Duration.parse(value).seconds();
|
||||
time = seconds;
|
||||
// get largest unit with no remainder
|
||||
if (seconds % secondsMap.d === 0) {
|
||||
unit = 'd';
|
||||
} else if (seconds % secondsMap.h === 0) {
|
||||
unit = 'h';
|
||||
} else if (seconds % secondsMap.m === 0) {
|
||||
unit = 'm';
|
||||
}
|
||||
|
||||
if (unit !== 's') {
|
||||
time = convertFromSeconds(seconds, unit);
|
||||
}
|
||||
} catch (e) {
|
||||
// if parsing fails leave as default 30s
|
||||
}
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
time,
|
||||
unit,
|
||||
enableTTL: setEnable,
|
||||
});
|
||||
|
||||
if (changeOnInit) {
|
||||
this.handleChange();
|
||||
}
|
||||
},
|
||||
|
||||
unitOptions: computed(function () {
|
||||
return [
|
||||
{ label: 'seconds', value: 's' },
|
||||
{ label: 'minutes', value: 'm' },
|
||||
{ label: 'hours', value: 'h' },
|
||||
{ label: 'days', value: 'd' },
|
||||
];
|
||||
}),
|
||||
handleChange() {
|
||||
const { time, unit, enableTTL, seconds } = this;
|
||||
const ttl = {
|
||||
enabled: this.hideToggle || enableTTL,
|
||||
seconds,
|
||||
timeString: time + unit,
|
||||
goSafeTimeString: goSafeConvertFromSeconds(seconds, unit),
|
||||
};
|
||||
this.onChange(ttl);
|
||||
},
|
||||
|
||||
helperText: computed(
|
||||
'enableTTL',
|
||||
'helperTextDisabled',
|
||||
'helperTextEnabled',
|
||||
'helperTextSet',
|
||||
'helperTextUnset',
|
||||
'hideToggle',
|
||||
function () {
|
||||
return this.enableTTL || this.hideToggle ? this.helperTextEnabled : this.helperTextDisabled;
|
||||
}
|
||||
),
|
||||
|
||||
recalculateSeconds: false,
|
||||
actions: {
|
||||
toggleEnabled() {
|
||||
this.toggleProperty('enableTTL');
|
||||
this.handleChange();
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
{{this.yeild}}
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<input
|
||||
data-test-ttlform-value
|
||||
value={{this.time}}
|
||||
id="time-foobar"
|
||||
type="text"
|
||||
name="time"
|
||||
class="input"
|
||||
pattern="[0-9]*"
|
||||
oninput={{perform this.updateTime value="target.value"}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<Select
|
||||
data-test-ttlform-unit
|
||||
@name="ttl-unit"
|
||||
@options={{this.unitOptions}}
|
||||
@onChange={{action "updateUnit"}}
|
||||
@selectedValue={{this.unit}}
|
||||
@isFullwidth={{true}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{#if this.errorMessage}}
|
||||
<div class="columns is-mobile is-variable is-1 ttl-value-error">
|
||||
<div class="is-narrow message-icon">
|
||||
<Icon @name="x-square-fill" class="has-text-danger" />
|
||||
</div>
|
||||
<div class="has-text-danger">
|
||||
{{this.errorMessage}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -1,25 +0,0 @@
|
|||
<label for="time-{{this.elementId}}" class="is-label {{this.labelClass}}">{{this.labelText}}</label>
|
||||
<MessageError @errorMessage={{this.errorMessage}} data-test-ttl-error />
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<input
|
||||
data-test-ttl-value
|
||||
value={{this.time}}
|
||||
id="time-{{this.elementId}}"
|
||||
type="text"
|
||||
name="time"
|
||||
class="input"
|
||||
oninput={{action (action "changedValue" "time") value="target.value"}}
|
||||
pattern="[0-9]*"
|
||||
/>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<Select
|
||||
@name="ttl-unit"
|
||||
@options={{this.unitOptions}}
|
||||
@onChange={{action "changedValue" "unit"}}
|
||||
@selectedValue={{this.unit.value}}
|
||||
@isFullwidth={{true}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
|
@ -1,83 +0,0 @@
|
|||
{{#if this.hideToggle}}
|
||||
<span class="ttl-picker-label">{{this.label}}</span><br />
|
||||
<div class="sub-text">
|
||||
<span>{{this.helperText}}</span>
|
||||
{{#if this.description}}
|
||||
<ToolTip @verticalPosition="below" as |T|>
|
||||
<T.Trigger data-test-tooltip-trigger tabindex="-1">
|
||||
<Icon @name="info" aria-label="description" />
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box" data-test-hover-copy-tooltip-text>
|
||||
{{this.description}}
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<Toggle
|
||||
@name={{this.label}}
|
||||
@status="success"
|
||||
@size="small"
|
||||
@onChange={{action "toggleEnabled"}}
|
||||
@checked={{this.enableTTL}}
|
||||
data-test-ttl-toggle
|
||||
>
|
||||
<span class="ttl-picker-label is-large">{{this.label}}</span><br />
|
||||
<div class="description has-text-grey">
|
||||
<span>{{this.helperText}}</span>
|
||||
{{#if this.description}}
|
||||
<ToolTip @verticalPosition="below" as |T|>
|
||||
<T.Trigger data-test-tooltip-trigger tabindex="-1">
|
||||
<Icon @name="info" aria-label="description" />
|
||||
</T.Trigger>
|
||||
<T.Content @defaultClass="tool-tip">
|
||||
<div class="box" data-test-hover-copy-tooltip-text>
|
||||
{{this.description}}
|
||||
</div>
|
||||
</T.Content>
|
||||
</ToolTip>
|
||||
{{/if}}
|
||||
</div>
|
||||
</Toggle>
|
||||
{{/if}}
|
||||
{{#if (or this.enableTTL this.hideToggle)}}
|
||||
<div class={{unless this.hideToggle "ttl-show-picker"}} data-test-ttl-picker-group={{this.label}}>
|
||||
<div class="field is-grouped is-marginless">
|
||||
<div class="control is-marginless">
|
||||
<input
|
||||
data-test-ttl-value={{this.label}}
|
||||
value={{this.time}}
|
||||
id="time-{{this.elementId}}"
|
||||
type="text"
|
||||
name="time"
|
||||
class="input{{if this.errorMessage ' has-error'}}"
|
||||
oninput={{perform this.updateTime value="target.value"}}
|
||||
pattern="[0-9]*"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<Select
|
||||
data-test-ttl-unit={{this.label}}
|
||||
@name="ttl-unit"
|
||||
@options={{this.unitOptions}}
|
||||
@onChange={{action "updateUnit"}}
|
||||
@selectedValue={{this.unit}}
|
||||
@isFullwidth={{true}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{#if this.errorMessage}}
|
||||
<div class="columns is-mobile is-variable is-1 ttl-value-error">
|
||||
<div class="is-narrow message-icon">
|
||||
<Icon @name="x-square-fill" class="has-text-danger" />
|
||||
</div>
|
||||
<div class="has-text-danger">
|
||||
{{this.errorMessage}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{yield}}
|
||||
{{/if}}
|
|
@ -1 +0,0 @@
|
|||
export { default } from 'core/components/ttl-form';
|
|
@ -1 +0,0 @@
|
|||
export { default } from 'core/components/ttl-picker2';
|
|
@ -28,7 +28,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<TtlPicker2
|
||||
<TtlPicker
|
||||
@initialValue="30m"
|
||||
@label="Time to Live (TTL) for generated secondary token"
|
||||
@helperTextDisabled="If not set, the default value (30 minutes) will be used"
|
||||
|
|
|
@ -183,6 +183,7 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
|
|||
await fillIn('[data-test-input="maxVersions"]', maxVersion);
|
||||
await click('[data-test-input="casRequired"]');
|
||||
await click('[data-test-toggle-label="Automate secret deletion"]');
|
||||
await fillIn('[data-test-select="ttl-unit"]', 's');
|
||||
await fillIn('[data-test-ttl-value="Automate secret deletion"]', '1');
|
||||
await click('[data-test-mount-submit="true"]');
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
const selectors = {
|
||||
ttlFormGroup: '[data-test-ttl-inputs]',
|
||||
toggle: '[data-test-ttl-toggle]',
|
||||
toggleByLabel: (label) => `[data-test-ttl-toggle="${label}"]`,
|
||||
label: '[data-test-ttl-form-label]',
|
||||
subtext: '[data-test-ttl-form-subtext]',
|
||||
tooltipTrigger: `[data-test-tooltip-trigger]`,
|
||||
ttlValue: '[data-test-ttl-value]',
|
||||
ttlUnit: '[data-test-select="ttl-unit"]',
|
||||
valueInputByLabel: (label) => `[data-test-ttl-value="${label}"]`,
|
||||
unitInputByLabel: (label) => `[data-test-ttl-unit="${label}"] [data-test-select="ttl-unit"]`,
|
||||
};
|
||||
|
||||
export default selectors;
|
|
@ -63,7 +63,7 @@ module('Integration | Component | mfa-method-form', function (hooks) {
|
|||
assert.expect(3);
|
||||
|
||||
this.model.issuer = 'Vault';
|
||||
this.model.period = '30';
|
||||
this.model.period = '30s';
|
||||
this.model.algorithm = 'SHA512';
|
||||
|
||||
await render(hbs`
|
||||
|
|
|
@ -54,22 +54,25 @@ module('Integration | Component | config pki', function (hooks) {
|
|||
'renders form subtext'
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-toggle-label]')
|
||||
.hasText('CRL building enabled The CRL will expire after', 'renders enabled field title and subtext');
|
||||
.dom('[data-test-ttl-form-label="CRL building enabled"]')
|
||||
.hasText('CRL building enabled', 'renders enabled field title');
|
||||
assert
|
||||
.dom('[data-test-ttl-form-subtext]')
|
||||
.hasText('The CRL will expire after', 'renders enabled field subtext');
|
||||
assert.dom('[data-test-input="expiry"] input').isChecked('defaults to enabling CRL build');
|
||||
assert.dom('[data-test-ttl-value="CRL building enabled"]').hasValue('3', 'default value is 3 (72h)');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('d', 'default unit value is days');
|
||||
await click('[data-test-input="expiry"] input');
|
||||
assert
|
||||
.dom('[data-test-toggle-label]')
|
||||
.hasText('CRL building disabled The CRL will not be built.', 'renders disabled text when toggled off');
|
||||
.dom('[data-test-ttl-form-subtext]')
|
||||
.hasText('The CRL will not be built.', 'renders disabled text when toggled off');
|
||||
|
||||
// assert 'disable' attr on pki-config model updates with toggle
|
||||
assert.true(this.config.disable, 'when toggled off, sets CRL config to disable=true');
|
||||
await click('[data-test-input="expiry"] input');
|
||||
assert
|
||||
.dom('[data-test-toggle-label]')
|
||||
.hasText('CRL building enabled The CRL will expire after', 'toggles back to enabled text');
|
||||
.dom('[data-test-ttl-form-subtext]')
|
||||
.hasText('The CRL will expire after', 'toggles back to enabled text');
|
||||
assert.false(this.config.disable, 'CRL config toggles back to disable=false');
|
||||
});
|
||||
|
||||
|
|
|
@ -33,14 +33,13 @@ module('Integration | Component | radio-select-ttl-or-string', function (hooks)
|
|||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
assert.dom('[data-test-input="ttl"]').exists('shows the TTL component');
|
||||
const inputValue = document.querySelector('[data-test-ttl-value="TTL"]').value;
|
||||
assert.strictEqual(inputValue, '', 'default TTL is empty');
|
||||
assert.dom('[data-test-ttl-inputs]').exists('shows the TTL component');
|
||||
assert.dom('[data-test-ttl-value]').hasValue('', 'default TTL is empty');
|
||||
assert.dom('[data-test-radio-button="ttl"]').isChecked('ttl is selected by default');
|
||||
});
|
||||
|
||||
test('it should set the model properties ttl or notAfter based on the radio button selections', async function (assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(7);
|
||||
await render(
|
||||
hbs`
|
||||
<div class="has-top-margin-xxl">
|
||||
|
@ -68,23 +67,21 @@ module('Integration | Component | radio-select-ttl-or-string', function (hooks)
|
|||
'sets the model property notAfter when this value is selected and filled in.'
|
||||
);
|
||||
|
||||
await fillIn('[data-test-ttl-value="TTL"]', ttlDate);
|
||||
assert.strictEqual(this.model.ttl, '', 'No ttl is set because the radio button was not selected.');
|
||||
|
||||
await click('[data-test-radio-button="ttl"]');
|
||||
assert.strictEqual(
|
||||
this.model.notAfter,
|
||||
'',
|
||||
'The notAfter is cleared on the model because the radio button was selected.'
|
||||
);
|
||||
await fillIn('[data-test-ttl-value="TTL"]', ttlDate);
|
||||
assert.strictEqual(
|
||||
this.model.ttl,
|
||||
ttlDate,
|
||||
'1s',
|
||||
'The ttl is now saved on the model because the radio button was selected.'
|
||||
);
|
||||
|
||||
await click('[data-test-radio-button="not_after"]');
|
||||
assert.strictEqual(this.model.ttl, '', 'Both ttl and notAfter are cleared.');
|
||||
assert.strictEqual(this.model.notAfter, '', 'Both ttl and notAfter are cleared.');
|
||||
assert.strictEqual(this.model.ttl, '', 'TTL is cleared after radio select.');
|
||||
assert.strictEqual(this.model.notAfter, '', 'notAfter is cleared after radio select.');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, fillIn } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Integration | Component | ttl-form', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.changeSpy = sinon.spy();
|
||||
this.set('onChange', this.changeSpy);
|
||||
});
|
||||
|
||||
test('it shows no initial time and initial unit of s when not time or unit passed in', async function (assert) {
|
||||
await render(hbs`<TtlForm @onChange={{this.onChange}} />`);
|
||||
assert.dom('[data-test-ttlform-value]').hasValue('');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('s');
|
||||
});
|
||||
|
||||
test('it calls the change fn with the correct values', async function (assert) {
|
||||
await render(hbs`<TtlForm @onChange={{this.onChange}} @unit="m" />`);
|
||||
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('m', 'unit value initially shows m (minutes)');
|
||||
await fillIn('[data-test-ttlform-value]', '10');
|
||||
await assert.ok(this.changeSpy.calledOnce, 'it calls the passed onChange');
|
||||
assert.ok(
|
||||
this.changeSpy.calledWith({
|
||||
seconds: 600,
|
||||
timeString: '10m',
|
||||
}),
|
||||
'Passes the default values back to onChange'
|
||||
);
|
||||
});
|
||||
|
||||
test('it correctly shows initial unit', async function (assert) {
|
||||
const changeSpy = sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlForm
|
||||
@unit="h"
|
||||
@time="3"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('h', 'unit value initially shows as h (hours)');
|
||||
});
|
||||
});
|
|
@ -1,30 +1,390 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, fillIn } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import { render, click, fillIn } from '@ember/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import selectors from 'vault/tests/helpers/components/ttl-picker';
|
||||
|
||||
module('Integration | Component | ttl picker', function (hooks) {
|
||||
module('Integration | Component | ttl-picker', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.changeSpy = sinon.spy();
|
||||
this.set('onChange', this.changeSpy);
|
||||
this.set('onChange', sinon.spy());
|
||||
this.set('label', 'Foobar');
|
||||
});
|
||||
|
||||
test('it renders error on non-number input', async function (assert) {
|
||||
await render(hbs`<TtlPicker @onChange={{this.onChange}} />`);
|
||||
module('without toggle', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.set('hideToggle', true);
|
||||
});
|
||||
|
||||
const callCount = this.changeSpy.callCount;
|
||||
await fillIn('[data-test-ttl-value]', 'foo');
|
||||
assert.strictEqual(this.changeSpy.callCount, callCount, "it didn't call onChange again");
|
||||
assert.dom('[data-test-ttl-error]').includesText('Error', 'renders the error box');
|
||||
await fillIn('[data-test-ttl-value]', '33');
|
||||
assert.dom('[data-test-ttl-error]').doesNotExist('removes the error box');
|
||||
test('it shows correct time and value when no initialValue set', async function (assert) {
|
||||
await render(hbs`<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@onChange={{this.onChange}} />`);
|
||||
assert.dom(selectors.ttlFormGroup).exists('TTL Form fields exist');
|
||||
assert.dom(selectors.ttlValue).hasValue('');
|
||||
assert.dom(selectors.ttlUnit).hasValue('s');
|
||||
});
|
||||
|
||||
test('it calls the change fn with the correct values', async function (assert) {
|
||||
const changeSpy = sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="30m" />
|
||||
`);
|
||||
assert.dom(selectors.ttlUnit).hasValue('m', 'unit value shows m (minutes)');
|
||||
await fillIn(selectors.ttlValue, '10');
|
||||
await assert.ok(changeSpy.calledOnce, 'it calls the passed onChange');
|
||||
assert.ok(
|
||||
changeSpy.calledWithExactly({
|
||||
enabled: true,
|
||||
seconds: 600,
|
||||
timeString: '10m',
|
||||
goSafeTimeString: '10m',
|
||||
}),
|
||||
'Passes the values back to onChange'
|
||||
);
|
||||
});
|
||||
|
||||
test('it correctly shows initial time and unit', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@initialValue="3h"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom(selectors.ttlUnit).hasValue('h', 'unit value initially shows as h (hours)');
|
||||
assert.dom(selectors.ttlValue).hasValue('3', 'time value initially shows as 3');
|
||||
});
|
||||
|
||||
test('it fails gracefully when initialValue is not parseable', async function (assert) {
|
||||
const changeSpy = sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@initialValue="foobar"
|
||||
@onChange={{this.onChange}}
|
||||
@changeOnInit={{true}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom(selectors.ttlValue).hasValue('', 'time value initially shows as empty');
|
||||
assert.dom(selectors.ttlUnit).hasValue('s', 'unit value initially shows as s (seconds)');
|
||||
assert.ok(changeSpy.notCalled, 'onChange is not called on init');
|
||||
});
|
||||
|
||||
test('it recalculates time when unit is changed', async function (assert) {
|
||||
const changeSpy = sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@initialValue="1h"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom(selectors.ttlUnit).hasValue('h', 'unit value initially shows as h (hours)');
|
||||
assert.dom(selectors.ttlValue).hasValue('1', 'time value initially shows as 1');
|
||||
await fillIn(selectors.ttlUnit, 'm');
|
||||
assert.dom(selectors.ttlUnit).hasValue('m', 'unit value changed to m (minutes)');
|
||||
assert.dom(selectors.ttlValue).hasValue('60', 'time value recalculates to fit unit');
|
||||
assert.ok(
|
||||
changeSpy.calledWithExactly({
|
||||
enabled: true,
|
||||
seconds: 3600,
|
||||
timeString: '60m',
|
||||
goSafeTimeString: '60m',
|
||||
}),
|
||||
'Passes the values back to onChange'
|
||||
);
|
||||
});
|
||||
|
||||
test('it skips recalculating time when unit is changed if time is not whole number', async function (assert) {
|
||||
const changeSpy = sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@initialValue="30s"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom(selectors.ttlUnit).hasValue('s', 'unit value starts as s (seconds)');
|
||||
assert.dom(selectors.ttlValue).hasValue('30', 'time value starts as 30');
|
||||
await fillIn(selectors.ttlUnit, 'm');
|
||||
assert.dom(selectors.ttlUnit).hasValue('m', 'unit value changed to m (minutes)');
|
||||
assert.dom(selectors.ttlValue).hasValue('30', 'time value is still 30');
|
||||
assert.ok(
|
||||
changeSpy.calledWithExactly({
|
||||
enabled: true,
|
||||
seconds: 1800,
|
||||
timeString: '30m',
|
||||
goSafeTimeString: '30m',
|
||||
}),
|
||||
'Passes the values back to onChange'
|
||||
);
|
||||
});
|
||||
|
||||
test('it calls onChange on init when changeOnInit is true', async function (assert) {
|
||||
const changeSpy = sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@initialValue="10m"
|
||||
@changeOnInit={{true}}
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.ok(changeSpy.calledOnce, 'it calls the passed onChange when rendered');
|
||||
assert.ok(
|
||||
changeSpy.calledWithExactly({
|
||||
enabled: true,
|
||||
seconds: 600,
|
||||
timeString: '10m',
|
||||
goSafeTimeString: '10m',
|
||||
}),
|
||||
'Passes the values back to onChange'
|
||||
);
|
||||
});
|
||||
|
||||
test('it shows a label when passed', async function (assert) {
|
||||
this.set('label', 'My Label');
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-ttl-form-label]').hasText('My Label', 'Renders label correctly');
|
||||
assert.dom('[data-test-ttl-form-subtext]').doesNotExist('Subtext not rendered');
|
||||
assert.dom('[data-test-tooltip-trigger]').doesNotExist('Description tooltip not rendered');
|
||||
});
|
||||
|
||||
test('it shows subtext and description when passed', async function (assert) {
|
||||
this.set('label', 'My Label');
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@helperTextEnabled="Subtext"
|
||||
@description="Description"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-ttl-form-label]').hasText('My Label', 'Renders label correctly');
|
||||
assert.dom('[data-test-ttl-form-subtext]').hasText('Subtext', 'Renders subtext when present');
|
||||
assert
|
||||
.dom('[data-test-tooltip-trigger]')
|
||||
.exists({ count: 1 }, 'Description tooltip icon shows when description present');
|
||||
});
|
||||
|
||||
test('it yields in place of label if block is present', async function (assert) {
|
||||
this.set('label', 'My Label');
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@hideToggle={{this.hideToggle}}
|
||||
@helperTextEnabled="Subtext"
|
||||
@description="Description"
|
||||
@onChange={{this.onChange}}
|
||||
>
|
||||
<legend data-test-custom>Different Label</legend>
|
||||
</TtlPicker>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-custom]').hasText('Different Label', 'custom block is rendered');
|
||||
assert.dom('[data-test-ttl-form-label]').doesNotExist('Label not rendered');
|
||||
});
|
||||
});
|
||||
|
||||
test('it shows 30s for invalid duration initialValue input', async function (assert) {
|
||||
await render(hbs`<TtlPicker @onChange={{this.onChange}} @initialValue="invalid" />`);
|
||||
assert.dom('[data-test-ttl-value]').hasValue('30', 'sets 30 as the default');
|
||||
module('with toggle', function () {
|
||||
test('it has toggle off by default', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
assert.dom(selectors.toggle).isNotChecked('Toggle is unchecked by default');
|
||||
assert.dom(selectors.ttlFormGroup).doesNotExist('TTL Form is not rendered');
|
||||
});
|
||||
|
||||
test('it shows time and unit inputs when initialEnabled', async function (assert) {
|
||||
const changeSpy = sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@onChange={{this.onChange}}
|
||||
@initialEnabled={{true}}
|
||||
@changeOnInit={{true}}
|
||||
/>
|
||||
`);
|
||||
assert.dom(selectors.toggle).isChecked('Toggle is checked when initialEnabled is true');
|
||||
assert.dom(selectors.ttlFormGroup).exists('TTL Form is rendered');
|
||||
assert.ok(changeSpy.notCalled, 'onChange not called because initialValue not parsed');
|
||||
});
|
||||
|
||||
test('it sets initial value to initialValue', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="2h"
|
||||
@initialEnabled={{true}}
|
||||
/>
|
||||
`);
|
||||
assert.dom(selectors.ttlValue).hasValue('2', 'time value is 2');
|
||||
assert.dom(selectors.ttlUnit).hasValue('h', 'unit is hours');
|
||||
assert.ok(
|
||||
this.onChange.notCalled,
|
||||
'it does not call onChange after render when changeOnInit is not set'
|
||||
);
|
||||
});
|
||||
|
||||
test('it passes the appropriate data to onChange when toggled on', async function (assert) {
|
||||
const changeSpy = sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="clicktest"
|
||||
@initialValue="10m"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
await click(selectors.toggle);
|
||||
assert.ok(changeSpy.calledOnce, 'it calls the passed onChange');
|
||||
assert.ok(
|
||||
changeSpy.calledWith({
|
||||
enabled: true,
|
||||
seconds: 600,
|
||||
timeString: '10m',
|
||||
goSafeTimeString: '10m',
|
||||
}),
|
||||
'Passes the values back to onChange'
|
||||
);
|
||||
});
|
||||
|
||||
test('inputs reflect initial value when toggled on', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
/>
|
||||
`);
|
||||
assert.dom(selectors.toggle).isNotChecked('Toggle is off');
|
||||
assert.dom(selectors.ttlFormGroup).doesNotExist('TTL Form not shown on mount');
|
||||
await click(selectors.toggle);
|
||||
assert.dom(selectors.ttlValue).hasValue('100', 'time after toggle is 100');
|
||||
assert.dom(selectors.ttlUnit).hasValue('m', 'Unit is minutes after toggle');
|
||||
});
|
||||
|
||||
test('it is enabled on init if initialEnabled is true', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
@initialEnabled={{true}}
|
||||
/>
|
||||
`);
|
||||
assert.dom(selectors.toggle).isChecked('Toggle is on');
|
||||
assert.dom(selectors.ttlFormGroup).exists();
|
||||
assert.dom(selectors.ttlValue).hasValue('100', 'time is shown on mount');
|
||||
assert.dom(selectors.ttlUnit).hasValue('m', 'Unit is shown on mount');
|
||||
await click(selectors.toggle);
|
||||
assert.dom(selectors.toggle).isNotChecked('Toggle is off');
|
||||
assert.dom(selectors.ttlFormGroup).doesNotExist('TTL Form no longer shows after toggle');
|
||||
});
|
||||
|
||||
test('it is enabled on init if initialEnabled evals to truthy', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
@initialEnabled="100m"
|
||||
/>
|
||||
`);
|
||||
assert.dom(selectors.toggle).isChecked('Toggle is enabled');
|
||||
assert.dom(selectors.ttlValue).hasValue('100', 'time value is shown on mount');
|
||||
assert.dom(selectors.ttlUnit).hasValue('m', 'Unit matches what is passed in');
|
||||
});
|
||||
|
||||
test('it converts days to go safe time', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="clicktest"
|
||||
@initialValue="2d"
|
||||
@onChange={{this.onChange}}
|
||||
/>
|
||||
`);
|
||||
await click(selectors.toggle);
|
||||
assert.ok(this.onChange.calledOnce, 'it calls the passed onChange');
|
||||
assert.ok(
|
||||
this.onChange.calledWith({
|
||||
enabled: true,
|
||||
seconds: 172800,
|
||||
timeString: '2d',
|
||||
goSafeTimeString: '48h',
|
||||
}),
|
||||
'Converts day unit to go safe time'
|
||||
);
|
||||
});
|
||||
|
||||
test('it converts to the largest round unit on init', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="convertunits"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="60000s"
|
||||
@initialEnabled="true"
|
||||
/>
|
||||
`);
|
||||
assert.dom(selectors.ttlValue).hasValue('1000', 'time value is converted');
|
||||
assert.dom(selectors.ttlUnit).hasValue('m', 'unit value is m (minutes)');
|
||||
});
|
||||
|
||||
test('it converts to the largest round unit on init when no unit provided', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker
|
||||
@label={{this.label}}
|
||||
@label="convertunits"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue={{86400}}
|
||||
@initialEnabled="true"
|
||||
/>
|
||||
`);
|
||||
assert.dom(selectors.ttlValue).hasValue('1', 'time value is converted');
|
||||
assert.dom(selectors.ttlUnit).hasValue('d', 'unit value is d (days)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,248 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, click, fillIn } from '@ember/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | ttl-picker2', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.set('onChange', sinon.spy());
|
||||
});
|
||||
|
||||
test('it renders time and unit inputs when TTL enabled', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@onChange={{this.onChange}}
|
||||
@enableTTL={{true}}
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-ttl-value]').exists('TTL Picker time input exists');
|
||||
assert.dom('[data-test-ttl-unit]').exists('TTL Picker unit select exists');
|
||||
});
|
||||
|
||||
test('it does not show time and unit inputs when TTL disabled', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@onChange={{this.onChange}}
|
||||
@enableTTL={{false}}
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-ttl-value]').doesNotExist('TTL Picker time input exists');
|
||||
assert.dom('[data-test-ttl-unit]').doesNotExist('TTL Picker unit select exists');
|
||||
});
|
||||
|
||||
test('it passes the appropriate data to onChange when toggled on', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="clicktest"
|
||||
@unit="m"
|
||||
@time="10"
|
||||
@onChange={{this.onChange}}
|
||||
@enableTTL={{false}}
|
||||
/>
|
||||
`);
|
||||
await click('[data-test-toggle-input="clicktest"]');
|
||||
assert.ok(this.onChange.calledOnce, 'it calls the passed onChange');
|
||||
assert.ok(
|
||||
this.onChange.calledWith({
|
||||
enabled: true,
|
||||
seconds: 600,
|
||||
timeString: '10m',
|
||||
goSafeTimeString: '10m',
|
||||
}),
|
||||
'Passes the default values back to onChange'
|
||||
);
|
||||
});
|
||||
|
||||
test('it keeps seconds value when unit is changed', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="clicktest"
|
||||
@unit="s"
|
||||
@time="360"
|
||||
@onChange={{this.onChange}}
|
||||
@enableTTL={{false}}
|
||||
/>
|
||||
`);
|
||||
await click('[data-test-toggle-input="clicktest"]');
|
||||
assert.ok(this.onChange.calledOnce, 'it calls the passed onChange');
|
||||
assert.ok(
|
||||
this.onChange.calledWith({
|
||||
enabled: true,
|
||||
seconds: 360,
|
||||
timeString: '360s',
|
||||
goSafeTimeString: '360s',
|
||||
}),
|
||||
'Changes enabled to true on click'
|
||||
);
|
||||
await fillIn('[data-test-select="ttl-unit"]', 'm');
|
||||
assert.ok(
|
||||
this.onChange.calledWith({
|
||||
enabled: true,
|
||||
seconds: 360,
|
||||
timeString: '6m',
|
||||
goSafeTimeString: '6m',
|
||||
}),
|
||||
'Units and time update without changing seconds value'
|
||||
);
|
||||
assert.dom('[data-test-ttl-value]').hasValue('6', 'time value shows as 6');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('m', 'unit value shows as m (minutes)');
|
||||
});
|
||||
|
||||
test('it recalculates seconds when unit is changed and recalculateSeconds is on', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="clicktest"
|
||||
@unit="s"
|
||||
@time="120"
|
||||
@onChange={{this.onChange}}
|
||||
@enableTTL={{true}}
|
||||
@recalculateSeconds={{true}}
|
||||
/>
|
||||
`);
|
||||
await fillIn('[data-test-select="ttl-unit"]', 'm');
|
||||
assert.ok(
|
||||
this.onChange.calledWith({
|
||||
enabled: true,
|
||||
seconds: 7200,
|
||||
timeString: '120m',
|
||||
goSafeTimeString: '120m',
|
||||
}),
|
||||
'Seconds value is recalculated based on time and unit'
|
||||
);
|
||||
});
|
||||
|
||||
test('it sets default value to time and unit passed', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="2h"
|
||||
@enableTTL={{true}}
|
||||
@time=4
|
||||
@unit="d"
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-ttl-value]').hasValue('2', 'time value is 2');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('h', 'unit is hours');
|
||||
assert.ok(this.onChange.notCalled, 'it does not call onChange after render when changeOnInit is not set');
|
||||
});
|
||||
|
||||
test('it is disabled on init if initialEnabled is false', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
@initialEnabled={{false}}
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-ttl-value]').doesNotExist('Value is not shown on mount');
|
||||
assert.dom('[data-test-ttl-unit]').doesNotExist('Unit is not shown on mount');
|
||||
await click('[data-test-toggle-input="inittest"]');
|
||||
assert.dom('[data-test-ttl-value]').hasValue('100', 'time after toggle is 100');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('m', 'Unit is minutes after toggle');
|
||||
});
|
||||
|
||||
test('it is enabled on init if initialEnabled is true', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
@initialEnabled={{true}}
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-ttl-value]').hasValue('100', 'time is shown on mount');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('m', 'Unit is shown on mount');
|
||||
await click('[data-test-toggle-input="inittest"]');
|
||||
assert.dom('[data-test-ttl-value]').doesNotExist('Value no longer shows after toggle');
|
||||
assert.dom('[data-test-ttl-unit]').doesNotExist('Unit no longer shows after toggle');
|
||||
});
|
||||
|
||||
test('it is enabled on init if initialEnabled evals to truthy', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="inittest"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
@initialEnabled="true"
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-ttl-value]').hasValue('100', 'time value is shown on mount');
|
||||
assert.dom('[data-test-ttl-unit]').exists('Unit is shown on mount');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('m', 'Unit matches what is passed in');
|
||||
});
|
||||
|
||||
test('it calls onChange', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="clicktest"
|
||||
@unit="d"
|
||||
@time="2"
|
||||
@onChange={{this.onChange}}
|
||||
@enableTTL={{false}}
|
||||
/>
|
||||
`);
|
||||
await click('[data-test-toggle-input="clicktest"]');
|
||||
assert.ok(this.onChange.calledOnce, 'it calls the passed onChange');
|
||||
assert.ok(
|
||||
this.onChange.calledWith({
|
||||
enabled: true,
|
||||
seconds: 172800,
|
||||
timeString: '2d',
|
||||
goSafeTimeString: '48h',
|
||||
}),
|
||||
'Converts day unit to go safe time'
|
||||
);
|
||||
});
|
||||
|
||||
test('it calls onChange on init when rendered if changeOnInit is true', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="changeOnInitTest"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="100m"
|
||||
@initialEnabled="true"
|
||||
@changeOnInit={{true}}
|
||||
/>
|
||||
`);
|
||||
assert.ok(
|
||||
this.onChange.calledWith({
|
||||
enabled: true,
|
||||
seconds: 6000,
|
||||
timeString: '100m',
|
||||
goSafeTimeString: '100m',
|
||||
}),
|
||||
'Seconds value is recalculated based on time and unit'
|
||||
);
|
||||
assert.ok(this.onChange.calledOnce, 'it calls the passed onChange after render');
|
||||
});
|
||||
|
||||
test('it converts to the largest round unit on init', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="convertunits"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue="60000s"
|
||||
@initialEnabled="true"
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-ttl-value]').hasValue('1000', 'time value is converted');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('m', 'unit value is m (minutes)');
|
||||
});
|
||||
|
||||
test('it converts to the largest round unit on init when no unit provided', async function (assert) {
|
||||
await render(hbs`
|
||||
<TtlPicker2
|
||||
@label="convertunits"
|
||||
@onChange={{this.onChange}}
|
||||
@initialValue={{86400}}
|
||||
@initialEnabled="true"
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-ttl-value]').hasValue('1', 'time value is converted');
|
||||
assert.dom('[data-test-select="ttl-unit"]').hasValue('d', 'unit value is d (days)');
|
||||
});
|
||||
});
|
|
@ -1,4 +1,5 @@
|
|||
import { module, test } from 'qunit';
|
||||
import Sinon from 'sinon';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, click, fillIn } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
@ -7,38 +8,37 @@ import waitForError from 'vault/tests/helpers/wait-for-error';
|
|||
module('Integration | Component | wrap ttl', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.lastOnChangeCall = null;
|
||||
this.set('onChange', (val) => {
|
||||
this.lastOnChangeCall = val;
|
||||
});
|
||||
});
|
||||
|
||||
test('it requires `onChange`', async function (assert) {
|
||||
const promise = waitForError();
|
||||
render(hbs`{{wrap-ttl}}`);
|
||||
render(hbs`<WrapTtl />`);
|
||||
const err = await promise;
|
||||
assert.ok(err.message.includes('`onChange` handler is a required attr in'), 'asserts without onChange');
|
||||
});
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`{{wrap-ttl onChange=(action this.onChange)}}`);
|
||||
assert.strictEqual(this.lastOnChangeCall, '30m', 'calls onChange with 30m default on first render');
|
||||
assert.dom('label[for="toggle-Wrapresponse"] .ttl-picker-label').hasText('Wrap response');
|
||||
const changeSpy = Sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`<WrapTtl @onChange={{this.onChange}} />`);
|
||||
assert.ok(changeSpy.calledWithExactly('30m'), 'calls onChange with 30m default on render');
|
||||
assert.dom('[data-test-ttl-form-label]').hasText('Wrap response');
|
||||
});
|
||||
|
||||
test('it nulls out value when you uncheck wrapResponse', async function (assert) {
|
||||
await render(hbs`{{wrap-ttl onChange=(action this.onChange)}}`);
|
||||
await click('[data-test-toggle-label="Wrap response"]');
|
||||
assert.strictEqual(this.lastOnChangeCall, null, 'calls onChange with null');
|
||||
const changeSpy = Sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`<WrapTtl @onChange={{this.onChange}} />`);
|
||||
await click('[data-test-ttl-form-label]');
|
||||
assert.ok(changeSpy.calledWithExactly(null), 'calls onChange with null');
|
||||
});
|
||||
|
||||
test('it sends value changes to onChange handler', async function (assert) {
|
||||
await render(hbs`{{wrap-ttl onChange=(action this.onChange)}}`);
|
||||
const changeSpy = Sinon.spy();
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`<WrapTtl @onChange={{this.onChange}} />`);
|
||||
// for testing purposes we need to input unit first because it keeps seconds value
|
||||
await fillIn('[data-test-select="ttl-unit"]', 'h');
|
||||
assert.strictEqual(this.lastOnChangeCall, '1800s', 'calls onChange correctly on time input');
|
||||
await fillIn('[data-test-ttl-value="Wrap response"]', '20');
|
||||
assert.strictEqual(this.lastOnChangeCall, '72000s', 'calls onChange correctly on unit change');
|
||||
assert.ok(changeSpy.calledWithExactly('30h'), 'calls onChange correctly on time input');
|
||||
await fillIn('[data-test-ttl-value]', '20');
|
||||
assert.ok(changeSpy.calledWithExactly('20h'), 'calls onChange correctly on unit change');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue