@kbn/config-schema: Add support for multi-unit duration (#187888)

This commit is contained in:
Alejandro Fernández Haro 2024-07-11 11:49:15 +02:00 committed by GitHub
parent 5e0a52c4a2
commit 9a28fde4a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 36 additions and 9 deletions

View file

@ -461,8 +461,12 @@ const valueSchema = schema.duration({ defaultValue: '70ms' });
```
__Notes:__
* The string value for `schema.duration()` supports the following optional suffixes: `ms`, `s`, `m`, `h`, `d`, `w`, `M` and `Y`. The default suffix is `ms`.
* The string value for `schema.duration()` supports the following optional suffixes: `ms`, `s`, `m`, `h`, `d`, `w`, `M` and `y`. The default suffix is `ms`.
* The number value is treated as a number of milliseconds and hence should be a positive integer, e.g. `100` is equal to `'100ms'`.
* Multi-unit duration strings are supported (`1m30s`).
* Spaces are not allowed.
* It allows any order in the units (`1m30s1d`).
* It allows the same unit to be specified multiple times (`1m30s50m` is the same as `51m30s`).
#### `schema.conditional()`

View file

@ -6,29 +6,36 @@
* Side Public License, v 1.
*/
import { Duration, duration as momentDuration, DurationInputArg2, isDuration } from 'moment';
import { Duration, duration as momentDuration, isDuration } from 'moment';
export type { Duration };
export { isDuration };
const timeFormatRegex = /^(0|[1-9][0-9]*)(ms|s|m|h|d|w|M|Y)$/;
const timeFormatRegex = /^(0|[1-9][0-9]*)(ms|s|m|h|d|w|M|y|Y)(.*)$/;
type TimeUnitString = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y' | 'Y'; // Moment officially supports lowercased 'y', but keeping 'Y' for BWC
function stringToDuration(text: string) {
function stringToDuration(text: string): Duration {
const result = timeFormatRegex.exec(text);
if (!result) {
const number = Number(text);
if (typeof number !== 'number' || isNaN(number)) {
throw new Error(
`Failed to parse value as time value. Value must be a duration in milliseconds, or follow the format ` +
`<count>[ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer.`
`<count>[ms|s|m|h|d|w|M|y] (e.g. '70ms', '5s', '3d', '1y', '1m30s'), where the duration is a safe positive integer.`
);
}
return numberToDuration(number);
}
const count = parseInt(result[1], 10);
const unit = result[2] as DurationInputArg2;
const unit = result[2] as TimeUnitString;
const rest = result[3];
return momentDuration(count, unit);
const duration = momentDuration(count, unit as Exclude<TimeUnitString, 'Y'>); // Moment still supports capital 'Y', but officially (and type-wise), it doesn't.
if (rest) {
return duration.add(stringToDuration(rest));
}
return duration;
}
function numberToDuration(numberMs: number) {

View file

@ -24,6 +24,22 @@ test('handles number', () => {
expect(duration().validate(123000)).toEqual(momentDuration(123000));
});
test('handles multi-unit', () => {
expect(duration().validate('1m30s')).toEqual(momentDuration(90000));
expect(duration().validate('1m30s70ms')).toEqual(momentDuration(90070));
});
test.each([60000, '60000', '60000ms', '60s', '1m', '1m0s'])(
'multiple ways of introducing 1 minute: %p',
(d) => {
expect(duration().validate(d)).toEqual(momentDuration(60000));
}
);
test('it supports years as Y and y', () => {
expect(duration().validate('1y')).toEqual(duration().validate('1Y'));
});
test('is required by default', () => {
expect(() => duration().validate(undefined)).toThrowErrorMatchingInlineSnapshot(
`"expected value of type [moment.Duration] but got [undefined]"`
@ -184,10 +200,10 @@ test('returns error when not valid string or non-safe positive integer', () => {
);
expect(() => duration().validate('123foo')).toThrowErrorMatchingInlineSnapshot(
`"Failed to parse value as time value. Value must be a duration in milliseconds, or follow the format <count>[ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer."`
`"Failed to parse value as time value. Value must be a duration in milliseconds, or follow the format <count>[ms|s|m|h|d|w|M|y] (e.g. '70ms', '5s', '3d', '1y', '1m30s'), where the duration is a safe positive integer."`
);
expect(() => duration().validate('123 456')).toThrowErrorMatchingInlineSnapshot(
`"Failed to parse value as time value. Value must be a duration in milliseconds, or follow the format <count>[ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer."`
`"Failed to parse value as time value. Value must be a duration in milliseconds, or follow the format <count>[ms|s|m|h|d|w|M|y] (e.g. '70ms', '5s', '3d', '1y', '1m30s'), where the duration is a safe positive integer."`
);
});