open-vault/ui/lib/core/addon/components/ttl-picker.js
claire bontempo 76f1971126
UI: pki configuration edit form (#20245)
* setup routing, move queries in ConfigurationIndex to parent resource route

* finish building out form, add model attrs build ttls

* add types

* update model attribute values, fix default ttl states

* remove defaults and use openApi, group with booleans

* add model to application route"

* add save functionality

* add error banner

* add transition after save

* use defaults from open api

* fix empty state language

* pass engine data

* change model attrs to ttl objects

* update types

* add invalid form alert to error block

* move data manipulation to serialize

* fix serializer, add comments

* add test for serializer

* edit configuration details view

* update details test

* change to updateRecord so POST request is made

* config/urls use POST instead of PUT

* add edit tests, update details

* add model hooks back to routes

* rearrange to remove dif

* remove createRecord for urls

* update comment

* wip sample ttl transform

* Revert "wip sample ttl transform"

This reverts commit 59fc179b5cd2994c4258e553e56667e29b3d6b72.

* revert changes, move model updates back to component

* simplify model fetches

* address comments;

* update pki/urls test

* update adapter test
2023-04-25 21:50:19 +00:00

173 lines
5.5 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
/**
* @module 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 @onChange={{this.handleChange}} @initialEnabled={{@model.myAttribute}} @initialValue={{@model.myAttribute}}/>
* ```
* @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 labelDisabled=Label to display when TTL is toggled off
* @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
*/
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';
import {
convertFromSeconds,
convertToSeconds,
goSafeConvertFromSeconds,
largestUnitFromSeconds,
} from 'core/utils/duration-utils';
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() {
if (this.args.label && this.args.labelDisabled) {
return this.enableTTL ? this.args.label : this.args.labelDisabled;
}
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' },
];
}
keepSecondsRecalculate(newUnit) {
const newTime = convertFromSeconds(this.seconds, newUnit);
if (Number.isInteger(newTime)) {
// Only recalculate if time is whole number
this.time = newTime;
}
this.unit = newUnit;
}
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.time = parsedTime;
this.handleChange();
if (Ember.testing) {
return;
}
this.recalculateSeconds = true;
yield timeout(this.recalculationTimeout);
this.recalculateSeconds = false;
}
@action
updateUnit(newUnit) {
if (this.recalculateSeconds) {
this.unit = newUnit;
} else {
this.keepSecondsRecalculate(newUnit);
}
this.handleChange();
}
}