From 9d6199a6308664e53b3f8be38fabd4f3b462b34c Mon Sep 17 00:00:00 2001 From: rowa-audil Date: Wed, 15 Jan 2020 15:10:09 +0100 Subject: [PATCH] feat(duration-formatter): Adds formatting modes and improves output configuration. Fixes #444 BREAKING CHANGE: Removed experiementalFormatTime and DtExperimentalFormatTime in favor of dtDuration pipe. --- .../formatters/formatters-demo.component.html | 6 +- libs/barista-components/formatters/README.md | 37 +- libs/barista-components/formatters/index.ts | 9 +- .../duration/duration-formatter-constants.ts | 51 +++ .../convert-to-milliseconds.ts | 43 +++ .../duration-formatter-utils/index.ts | 19 + .../transform-result-precise.ts | 66 ++++ .../transform-result.ts | 62 ++++ .../src/duration/duration-formatter.ts | 75 ++++ .../convert-to-milliseconds.spec.ts | 142 +++++++ .../transform-result-precise.spec.ts | 298 +++++++++++++++ .../transform-result.spec.ts | 281 ++++++++++++++ .../formatters/src/duration/duration.spec.ts | 348 ++++++++++++++++++ .../{time/time.ts => duration/duration.ts} | 29 +- .../formatters/src/formatted-value.ts | 5 +- .../formatters/src/formatters-module.ts | 4 +- .../formatters/src/rate/rate-formatter.ts | 5 + .../formatters/src/time/time-formatter.ts | 133 ------- .../formatters/src/time/time.spec.ts | 172 --------- .../formatters-duration-example.html | 12 + .../formatters-duration-example.ts} | 6 +- .../formatters/formatters-examples.module.ts | 4 +- .../formatters-time-example.html | 7 - 23 files changed, 1462 insertions(+), 352 deletions(-) create mode 100644 libs/barista-components/formatters/src/duration/duration-formatter-constants.ts create mode 100644 libs/barista-components/formatters/src/duration/duration-formatter-utils/convert-to-milliseconds.ts create mode 100644 libs/barista-components/formatters/src/duration/duration-formatter-utils/index.ts create mode 100644 libs/barista-components/formatters/src/duration/duration-formatter-utils/transform-result-precise.ts create mode 100644 libs/barista-components/formatters/src/duration/duration-formatter-utils/transform-result.ts create mode 100644 libs/barista-components/formatters/src/duration/duration-formatter.ts create mode 100644 libs/barista-components/formatters/src/duration/duration-functions-tests/convert-to-milliseconds.spec.ts create mode 100644 libs/barista-components/formatters/src/duration/duration-functions-tests/transform-result-precise.spec.ts create mode 100644 libs/barista-components/formatters/src/duration/duration-functions-tests/transform-result.spec.ts create mode 100644 libs/barista-components/formatters/src/duration/duration.spec.ts rename libs/barista-components/formatters/src/{time/time.ts => duration/duration.ts} (58%) delete mode 100644 libs/barista-components/formatters/src/time/time-formatter.ts delete mode 100644 libs/barista-components/formatters/src/time/time.spec.ts create mode 100644 libs/examples/src/formatters/formatters-duration-example/formatters-duration-example.html rename libs/examples/src/formatters/{formatters-time-example/formatters-time-example.ts => formatters-duration-example/formatters-duration-example.ts} (82%) delete mode 100644 libs/examples/src/formatters/formatters-time-example/formatters-time-example.html diff --git a/apps/dev/src/formatters/formatters-demo.component.html b/apps/dev/src/formatters/formatters-demo.component.html index 2ac75689b7..453b72e549 100644 --- a/apps/dev/src/formatters/formatters-demo.component.html +++ b/apps/dev/src/formatters/formatters-demo.component.html @@ -22,7 +22,9 @@

Rate

per second: {{ exampleValue | dtCount | dtRate: 's' }}

Chaining rate + bytes: {{ exampleValue | dtRate: 's' | dtBytes }}

Chaining bytes + rate: {{ exampleValue | dtBytes | dtRate: 's' }}

+

Duration

+

Default: {{ exampleValue | dtDuration: 'DEFAULT' }}

- InputUnit(Day -> Second): Time Formatter: {{ exampleValue | dtTime: 'd':'s' }} + Format: precise & chaining duration + rate: + {{ exampleValue | dtDuration: 'PRECISE' | dtRate: 'min' }}

-

InputUnit(Ms): Time Formatter: {{ exampleValue | dtTime }}

diff --git a/libs/barista-components/formatters/README.md b/libs/barista-components/formatters/README.md index 13252e6c42..a9d45e2d69 100644 --- a/libs/barista-components/formatters/README.md +++ b/libs/barista-components/formatters/README.md @@ -42,9 +42,9 @@ The `dtRate` pipe provides a way to add a rate info to the value -### Time +### Duration -The `dtTime` pipe provides a way to format a input time to a timestamp +The `dtDuration` pipe provides a way to format an input time to a timestamp @@ -136,18 +136,31 @@ from a previous pipe with a rate. The function takes the following parameters: | `input` | `DtFormattedValue | number` | | numeric value to be transformed by the pipe | | `rateUnit` | `DtRateUnit | string` | | rate unit | -### Time +### Duration -The `formatTime` function converts a number to a timestamp. Default behaviour -will print the first available value/unit and only the next two descending -steps. Optionally you can set the input unit and which unit you want to set as -the lower limit. +The `formatDuration` function converts a number to a duration string and +consumes a formatMethod which configures how the output is built. -| Name | Type | Default | Description | -| ----------- | ------------ | ----------- | -------------------------------------------------------------------------------------------------------------- | -| `input` | `number` | `ms` | numeric value to be transformed by the pipe | -| `inputUnit` | `DtTimeUnit` | `undefined` | Which timeunit is used for the input | -| `toUnit` | `DtTimeUnit` | `undefined` | Which timeunit is the smallest possible output (Pipe disregards toUnit when unit is bigger than the inputUnit) | +- **'DEFAULT':** will look for the first unit that has a value and will only + print the next two descending units as long as they have values. The results + for each time unit will be rounded to a decimal number. +- **'PRECISE':** will only print the unit that it consumed or was set as the + outputUnit. The output value can be real numbers. (e.g. 1.54 s) +- **Custom/Number(1-n):** will tell the formatter to print a custom amount of + units. + +You can specify the following properties on your options: + +| Name | Type | Default | Description | +| -------------- | ------------------------------- | -------------- | ------------------------------------------------ | +| `input` | `number` | | Numeric value to be transformed | +| `formatMethod` | `'DEFAULT | 'PRECISE' | number` | `'DEFAULT'` | Formatting/Precision mode configuring the output | +| `outputUnit` | `DtTimeUnit` | `undefined` | Which unit to transform the input to | +| `inputUnit` | `DtTimeUnit` | `Milliseconds` | Which timeunit is used for the input | + +#### Examples + + ## Special uses (e.g. infographics, tiles) diff --git a/libs/barista-components/formatters/index.ts b/libs/barista-components/formatters/index.ts index 23dd1e6df7..9e09c40f11 100644 --- a/libs/barista-components/formatters/index.ts +++ b/libs/barista-components/formatters/index.ts @@ -14,9 +14,6 @@ * limitations under the License. */ -import { DtTime } from './src/time/time'; -import { formatTime } from './src/time/time-formatter'; - export * from './src/formatters-module'; export * from './src/unit'; export { @@ -37,8 +34,6 @@ export { formatRate } from './src/rate/rate-formatter'; export * from './src/rate/rate'; export * from './src/bits/bits-formatter'; export * from './src/bits/bits'; -export { - formatTime as experimentalFormatTime, - DtTime as DtExperimentalFormatTime, -}; +export * from './src/duration/duration'; +export * from './src/duration/duration-formatter'; export { DtDateRange, dtFormatDateRange } from './src/date/date-range'; diff --git a/libs/barista-components/formatters/src/duration/duration-formatter-constants.ts b/libs/barista-components/formatters/src/duration/duration-formatter-constants.ts new file mode 100644 index 0000000000..88817ea2a8 --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-formatter-constants.ts @@ -0,0 +1,51 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DtTimeUnit } from '../unit'; + +export type DurationMode = 'DEFAULT' | 'PRECISE' | number; + +export function toDurationMode( + formatMethod: string | number, +): DurationMode | undefined { + if (formatMethod === 'DEFAULT') { + return 'DEFAULT'; + } else if (formatMethod === 'PRECISE') { + return 'PRECISE'; + } else if (typeof formatMethod === 'number') { + return formatMethod; + } +} + +// tslint:disable: no-magic-numbers +/** Factorials needed for converting milliseconds to other time units */ +export const CONVERSION_FACTORS_TO_MS = new Map([ + [DtTimeUnit.YEAR, 12 * 30.41666 * 24 * 60 * 60 * 1000], + [DtTimeUnit.MONTH, 30.41666 * 24 * 60 * 60 * 1000], + [DtTimeUnit.DAY, 24 * 60 * 60 * 1000], + [DtTimeUnit.HOUR, 60 * 60 * 1000], + [DtTimeUnit.MINUTE, 60 * 1000], + [DtTimeUnit.SECOND, 1000], + [DtTimeUnit.MILLISECOND, 1], + [DtTimeUnit.MICROSECOND, 1000], // Has to be handled differently because IEEE can't handle floating point numbers very well + [DtTimeUnit.NANOSECOND, 1], // Has to be handled differently because IEEE can't handle floating point numbers very well +]); + +/** Default for the conversionunit when no formatmethod is passed as a number. */ +export const CONVERSIONUNITS = 3; + +/** Use when converting a duration to micro- or nanoseconds */ +export const MOVE_COMMA = 1000000; diff --git a/libs/barista-components/formatters/src/duration/duration-formatter-utils/convert-to-milliseconds.ts b/libs/barista-components/formatters/src/duration/duration-formatter-utils/convert-to-milliseconds.ts new file mode 100644 index 0000000000..6a530d69aa --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-formatter-utils/convert-to-milliseconds.ts @@ -0,0 +1,43 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DtTimeUnit } from '../../unit'; +import { + CONVERSION_FACTORS_TO_MS, + MOVE_COMMA, +} from '../duration-formatter-constants'; + +/** + * Converts any duration to milliseconds + * @param duration numeric time value + * @param inputUnit dtTimeUnit value describing which unit the duration is in + */ +export function dtConvertToMilliseconds( + duration: number, + inputUnit: DtTimeUnit, +): number | undefined { + if (duration >= 0) { + console.log( + duration * CONVERSION_FACTORS_TO_MS.get(inputUnit)! * MOVE_COMMA, + inputUnit, + duration, + ); + return inputUnit === DtTimeUnit.MICROSECOND || + inputUnit === DtTimeUnit.NANOSECOND + ? (duration * CONVERSION_FACTORS_TO_MS.get(inputUnit)!) / MOVE_COMMA + : duration * CONVERSION_FACTORS_TO_MS.get(inputUnit)!; + } +} diff --git a/libs/barista-components/formatters/src/duration/duration-formatter-utils/index.ts b/libs/barista-components/formatters/src/duration/duration-formatter-utils/index.ts new file mode 100644 index 0000000000..e9bfe34892 --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-formatter-utils/index.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './convert-to-milliseconds'; +export * from './transform-result'; +export * from './transform-result-precise'; diff --git a/libs/barista-components/formatters/src/duration/duration-formatter-utils/transform-result-precise.ts b/libs/barista-components/formatters/src/duration/duration-formatter-utils/transform-result-precise.ts new file mode 100644 index 0000000000..1783948eae --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-formatter-utils/transform-result-precise.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DtTimeUnit } from '../../unit'; +import { + CONVERSION_FACTORS_TO_MS, + DurationMode, + MOVE_COMMA, +} from '../duration-formatter-constants'; +import { dtConvertToMilliseconds } from './convert-to-milliseconds'; + +/** + * Calculates the duration precisely. Will convert duration to the inputUnit or to the outputUnit if set. (floating point number for its corelated unit) + * @param duration numeric time value + * @param inputUnit dtTimeUnit value describing which unit the duration is in + * @param outputUnit dtTimeUnit | undefined value describing the unit to which it should format + * @param formatMethod the formatting method + */ + +export function dtTransformResultPrecise( + duration: number, + inputUnit: DtTimeUnit, + outputUnit: DtTimeUnit | undefined, + formatMethod: DurationMode, +): Map | undefined { + const amount = + inputUnit === DtTimeUnit.MILLISECOND + ? duration + : dtConvertToMilliseconds(duration, inputUnit); + return outputUnit !== undefined + ? calcResult(amount!, formatMethod, outputUnit) + : calcResult(amount!, formatMethod, inputUnit); +} + +function calcResult( + amount: number, + formatMethod: DurationMode, + unit: DtTimeUnit, +): Map { + let result = new Map(); + if (formatMethod === 'PRECISE') { + amount = amount / CONVERSION_FACTORS_TO_MS.get(unit)!; + // Need to move the comma since IEEE can't handle floating point numbers very well. + if (unit === DtTimeUnit.MICROSECOND || unit === DtTimeUnit.NANOSECOND) { + amount *= MOVE_COMMA; + } + result.set(unit, amount.toString()); + } else { + amount = Math.trunc(amount / CONVERSION_FACTORS_TO_MS.get(unit)!); + amount < 1 ? result.set(unit, '< 1') : result.set(unit, amount.toString()); + } + return result; +} diff --git a/libs/barista-components/formatters/src/duration/duration-formatter-utils/transform-result.ts b/libs/barista-components/formatters/src/duration/duration-formatter-utils/transform-result.ts new file mode 100644 index 0000000000..d011387073 --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-formatter-utils/transform-result.ts @@ -0,0 +1,62 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DtTimeUnit } from '../../unit'; +import { + CONVERSION_FACTORS_TO_MS, + CONVERSIONUNITS, + DurationMode, + MOVE_COMMA, +} from '../duration-formatter-constants'; + +/** + * Calculates output duration in either "DEFAULT" or "CUSTOM" mode. + * If precision is DEFAULT then displays a maximum of three units, but + * if precision is a number, then displays that amount of units. + * @param duration numeric time value + * @param inputUnit dtTimeUnit value describing which unit the duration is in + * @param formatMethod the formatting method + */ +export function dtTransformResult( + duration: number, + inputUnit: DtTimeUnit, + formatMethod: DurationMode, +): Map | undefined { + const result = new Map(); + + let rest = duration * CONVERSION_FACTORS_TO_MS.get(inputUnit)!; + let displayedUnits = 0; + let unitsToDisplay = + typeof formatMethod === 'number' ? formatMethod : CONVERSIONUNITS; + for (const key of Array.from(CONVERSION_FACTORS_TO_MS.keys())) { + if (key === DtTimeUnit.MICROSECOND) { + rest = Math.round(rest * MOVE_COMMA); // handles IEEE floating point number problem + } + const amount = Math.trunc(rest / CONVERSION_FACTORS_TO_MS.get(key)!); + if (displayedUnits < unitsToDisplay) { + if (amount > 0) { + result.set(key, amount.toString()); + // Only increase when a unit with a value bigger than 0 exists + displayedUnits++; + } else if (displayedUnits > 0) { + // Only increase when a unit with a value is already set + displayedUnits++; + } + } + rest = rest - amount * CONVERSION_FACTORS_TO_MS.get(key)!; + } + return result; +} diff --git a/libs/barista-components/formatters/src/duration/duration-formatter.ts b/libs/barista-components/formatters/src/duration/duration-formatter.ts new file mode 100644 index 0000000000..2bbc2e79e7 --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-formatter.ts @@ -0,0 +1,75 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DtFormattedValue, NO_DATA, SourceData } from '../formatted-value'; +import { DtTimeUnit } from '../unit'; +import { + DurationMode, + CONVERSION_FACTORS_TO_MS, +} from './duration-formatter-constants'; +import { + dtTransformResultPrecise, + dtTransformResult, +} from './duration-formatter-utils'; + +/** + * Formats a numeric value to a duration string + * @param duration numeric time value + * @param formatMethod the formatting method + * @param outputUnit dtTimeUnit | undefined value describing the unit to which it should format e.g to seconds + * @param inputUnit dtTimeUnit value describing which unit the duration is in (default: milliseconds) + */ +export function formatDuration( + duration: number, + formatMethod: DurationMode = 'DEFAULT', + outputUnit?: DtTimeUnit, + inputUnit: DtTimeUnit = DtTimeUnit.MILLISECOND, +): DtFormattedValue | string { + const inputData: SourceData = { + input: duration, + unit: inputUnit, + }; + if (duration <= 0 && formatMethod === 'DEFAULT') { + return new DtFormattedValue(inputData, { + transformedValue: duration, + displayValue: '< 1', + displayUnit: inputUnit, + displayWhiteSpace: false, + }); + } + const result = + outputUnit || formatMethod === 'PRECISE' + ? dtTransformResultPrecise(duration, inputUnit, outputUnit, formatMethod) + : dtTransformResult(duration, inputUnit, formatMethod); + + // Return NO_DATA when inputUnit is invalid + if (CONVERSION_FACTORS_TO_MS.get(inputUnit) === undefined) { + return NO_DATA; + } + if (result === undefined) { + return NO_DATA; + } + let resultString = ''; + result.forEach((value, key) => { + resultString = `${resultString}${value} ${key} `; + }); + resultString = resultString.trim(); + return new DtFormattedValue(inputData, { + transformedValue: inputData.input, + displayValue: resultString, + displayWhiteSpace: false, + }); +} diff --git a/libs/barista-components/formatters/src/duration/duration-functions-tests/convert-to-milliseconds.spec.ts b/libs/barista-components/formatters/src/duration/duration-functions-tests/convert-to-milliseconds.spec.ts new file mode 100644 index 0000000000..c4666fe08f --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-functions-tests/convert-to-milliseconds.spec.ts @@ -0,0 +1,142 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { dtConvertToMilliseconds } from '../duration-formatter-utils'; +import { DtTimeUnit } from '../../unit'; + +describe('DtDurationFormatter', () => { + interface TestCase { + duration: number; + inputUnit: DtTimeUnit; + output: number; + } + + describe('dtConvertToMilliseconds()', () => { + [ + { + duration: 1, + inputUnit: DtTimeUnit.MILLISECOND, + output: 1, + }, + { + duration: 999.99, + inputUnit: DtTimeUnit.MILLISECOND, + output: 999.99, + }, + { + duration: 1, + inputUnit: DtTimeUnit.SECOND, + output: 1000, + }, + { + duration: 1.5, + inputUnit: DtTimeUnit.SECOND, + output: 1500, + }, + { + duration: 1, + inputUnit: DtTimeUnit.MINUTE, + output: 60000, + }, + { + duration: 0.5, + inputUnit: DtTimeUnit.MINUTE, + output: 30000, + }, + { + duration: 0.00005, + inputUnit: DtTimeUnit.MINUTE, + output: 3, + }, + { + duration: 1, + inputUnit: DtTimeUnit.HOUR, + output: 3600000, + }, + { + duration: 1, + inputUnit: DtTimeUnit.DAY, + output: 86400000, + }, + { + duration: 1, + inputUnit: DtTimeUnit.MONTH, + output: 2627999423.9999995, + }, + { + duration: 1, + inputUnit: DtTimeUnit.YEAR, + output: 31535993088, + }, + { + duration: 1, + inputUnit: DtTimeUnit.MICROSECOND, + output: 0.001, + }, + { + duration: 1000, + inputUnit: DtTimeUnit.MICROSECOND, + output: 1, + }, + { + duration: 123.455, + inputUnit: DtTimeUnit.MICROSECOND, + output: 0.123455, + }, + { + duration: 1, + inputUnit: DtTimeUnit.NANOSECOND, + output: 0.000001, + }, + { + duration: 1000000, + inputUnit: DtTimeUnit.NANOSECOND, + output: 1, + }, + { + duration: 123.455, + inputUnit: DtTimeUnit.NANOSECOND, + output: 0.000123455, + }, + { + duration: -123.455, + inputUnit: DtTimeUnit.NANOSECOND, + output: undefined, + }, + { + duration: -0.001, + inputUnit: DtTimeUnit.NANOSECOND, + output: undefined, + }, + { + duration: -1, + inputUnit: DtTimeUnit.NANOSECOND, + output: undefined, + }, + { + duration: 0, + inputUnit: DtTimeUnit.NANOSECOND, + output: 0, + }, + ].forEach((testCase: TestCase) => { + it(`Duration '${testCase.duration}', input unit '${testCase.inputUnit}' should equal to '${testCase.output}'`, () => { + expect( + dtConvertToMilliseconds(testCase.duration, testCase.inputUnit), + ).toBe(testCase.output); + }); + }); + }); +}); diff --git a/libs/barista-components/formatters/src/duration/duration-functions-tests/transform-result-precise.spec.ts b/libs/barista-components/formatters/src/duration/duration-functions-tests/transform-result-precise.spec.ts new file mode 100644 index 0000000000..ee7e4644c6 --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-functions-tests/transform-result-precise.spec.ts @@ -0,0 +1,298 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DtTimeUnit } from '../../unit'; +import { toDurationMode } from '../duration-formatter-constants'; +import { dtTransformResultPrecise } from '../duration-formatter-utils'; + +describe('DtDurationFormatter', () => { + interface Output { + timeUnit: DtTimeUnit; + duration: string; + } + + interface TestCase { + duration: number; + inputUnit: DtTimeUnit; + outputUnit: DtTimeUnit; + formatMethod: string; + outPut: Output[]; + displayedOutPut: string; + } + + describe('dtTransformResultPrecise() with OutputUnit', () => { + [ + { + duration: 1, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + outPut: [ + { + timeUnit: DtTimeUnit.MILLISECOND, + duration: '1', + }, + ], + displayedOutPut: '1 ms', + }, + { + duration: 1500, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.SECOND, + formatMethod: 'DEFAULT', + outPut: [ + { + timeUnit: DtTimeUnit.SECOND, + duration: '1', + }, + ], + displayedOutPut: '1 s', + }, + { + duration: 150000001, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.SECOND, + formatMethod: 'DEFAULT', + outPut: [ + { + timeUnit: DtTimeUnit.SECOND, + duration: '150000', + }, + ], + displayedOutPut: '150000 s', + }, + { + duration: 1, + inputUnit: DtTimeUnit.HOUR, + outputUnit: DtTimeUnit.SECOND, + formatMethod: 'DEFAULT', + outPut: [ + { + timeUnit: DtTimeUnit.SECOND, + duration: '3600', + }, + ], + displayedOutPut: '3600 s', + }, + { + duration: 1, + inputUnit: DtTimeUnit.NANOSECOND, + outputUnit: DtTimeUnit.YEAR, + formatMethod: 'DEFAULT', + outPut: [ + { + timeUnit: DtTimeUnit.YEAR, + duration: '< 1', + }, + ], + displayedOutPut: '1 y', + }, + { + duration: 999, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.SECOND, + formatMethod: 'DEFAULT', + outPut: [ + { + timeUnit: DtTimeUnit.SECOND, + duration: '< 1', + }, + ], + displayedOutPut: '1 s', + }, + { + duration: 1, + inputUnit: DtTimeUnit.MONTH, + outputUnit: DtTimeUnit.YEAR, + formatMethod: 'DEFAULT', + outPut: [ + { + timeUnit: DtTimeUnit.YEAR, + duration: '< 1', + }, + ], + displayedOutPut: '1 y', + }, + ].forEach((testCase: TestCase) => { + it(`Duration '${testCase.duration}', input unit '${testCase.inputUnit}' should equal to '${testCase.displayedOutPut}'`, () => { + const formatMethod = toDurationMode(testCase.formatMethod)!; + const result = Array.from( + dtTransformResultPrecise( + testCase.duration, + testCase.inputUnit, + testCase.outputUnit, + formatMethod, + )!, + ); + testCase.outPut.forEach((output: Output, index) => { + expect(result[index]).toContain(output.timeUnit); + expect(result[index]).toContain(output.duration); + }); + }); + }); + + describe('dtTransformResultPrecise() Mode: Precise', () => { + [ + { + duration: 1500, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.MILLISECOND, + duration: '1500', + }, + ], + displayedOutPut: '1500 ms', + }, + { + duration: 1500, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.SECOND, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.SECOND, + duration: '1.5', + }, + ], + displayedOutPut: '1.5 s', + }, + { + duration: 1.234569, + inputUnit: DtTimeUnit.SECOND, + outputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.MILLISECOND, + duration: '1234.569', + }, + ], + displayedOutPut: '1234.569 ms', + }, + { + duration: 12.525, + inputUnit: DtTimeUnit.MINUTE, + outputUnit: DtTimeUnit.HOUR, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.HOUR, + duration: '0.20875', + }, + ], + displayedOutPut: '0.20875 h', + }, + { + duration: 123.4569, + inputUnit: DtTimeUnit.HOUR, + outputUnit: DtTimeUnit.SECOND, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.SECOND, + duration: '444444.84', + }, + ], + displayedOutPut: '444444.84 s', + }, + { + duration: 1234.569, + inputUnit: DtTimeUnit.DAY, + outputUnit: DtTimeUnit.HOUR, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.HOUR, + duration: '29629.656', + }, + ], + displayedOutPut: '29629.656 h', + }, + { + duration: 12345.5, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.MICROSECOND, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.MICROSECOND, + duration: '12345500', + }, + ], + displayedOutPut: '123455000 µs', + }, + { + duration: 12345.5, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.NANOSECOND, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.NANOSECOND, + duration: '12345500000', + }, + ], + displayedOutPut: '12345500000 ns', + }, + { + duration: 1, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.MICROSECOND, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.MICROSECOND, + duration: '1000', + }, + ], + displayedOutPut: '1000 µs', + }, + { + duration: 1, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.NANOSECOND, + formatMethod: 'PRECISE', + outPut: [ + { + timeUnit: DtTimeUnit.NANOSECOND, + duration: '1000000', + }, + ], + displayedOutPut: '1000000 ns', + }, + ].forEach((testCase: TestCase) => { + it(`Duration '${testCase.duration}', input unit '${testCase.inputUnit}' should equal to '${testCase.displayedOutPut}'`, () => { + const formatMethod = toDurationMode(testCase.formatMethod)!; + const result = Array.from( + dtTransformResultPrecise( + testCase.duration, + testCase.inputUnit, + testCase.outputUnit, + formatMethod, + )!, + ); + testCase.outPut.forEach((output: Output, index) => { + expect(result[index]).toContain(output.timeUnit); + expect(result[index]).toContain(output.duration); + }); + }); + }); + }); + }); +}); diff --git a/libs/barista-components/formatters/src/duration/duration-functions-tests/transform-result.spec.ts b/libs/barista-components/formatters/src/duration/duration-functions-tests/transform-result.spec.ts new file mode 100644 index 0000000000..8cfcdd8734 --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration-functions-tests/transform-result.spec.ts @@ -0,0 +1,281 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { dtTransformResult } from '../duration-formatter-utils'; +import { DtTimeUnit } from '../../unit'; +import { DurationMode, toDurationMode } from '../duration-formatter-constants'; + +describe('DtDurationFormatter', () => { + interface Output { + timeUnit: DtTimeUnit; + duration: string; + } + + interface TestCase { + duration: number; + inputUnit: DtTimeUnit; + formatMethod: string | number; + output: Output[]; + displayedOutput: string; + } + + describe('dtTransformResult() Mode: DEFAULT', () => { + [ + { + duration: 1, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.MILLISECOND, + duration: '1', + }, + ], + displayedOutput: '1 ms', + }, + { + duration: 1500, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.SECOND, + duration: '1', + }, + { + timeUnit: DtTimeUnit.MILLISECOND, + duration: '500', + }, + ], + displayedOutput: '1 s 500 ms', + }, + { + duration: 61500, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.MINUTE, + duration: '1', + }, + { + timeUnit: DtTimeUnit.SECOND, + duration: '1', + }, + { + timeUnit: DtTimeUnit.MILLISECOND, + duration: '500', + }, + ], + displayedOutput: '1 min 1 s 500 ms', + }, + { + duration: 3601500, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.HOUR, + duration: '1', + }, + { + timeUnit: DtTimeUnit.SECOND, + duration: '1', + }, + ], + displayedOutput: '1 h 1 s', + }, + { + duration: 123456789, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.DAY, + duration: '1', + }, + { + timeUnit: DtTimeUnit.HOUR, + duration: '10', + }, + { + timeUnit: DtTimeUnit.MINUTE, + duration: '17', + }, + ], + displayedOutput: '1 d 10 h 17 min', + }, + { + duration: 12.5, + inputUnit: DtTimeUnit.HOUR, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.HOUR, + duration: '12', + }, + { + timeUnit: DtTimeUnit.MINUTE, + duration: '30', + }, + ], + displayedOutput: '12 h 30 min', + }, + { + duration: 10.111, + inputUnit: DtTimeUnit.DAY, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.DAY, + duration: '10', + }, + { + timeUnit: DtTimeUnit.HOUR, + duration: '2', + }, + ], + displayedOutput: '10 d 2 h', + }, + { + duration: 100000000.1, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.DAY, + duration: '1', + }, + ], + displayedOutput: '1 d', + }, + { + duration: 0.000001, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.NANOSECOND, + duration: '1', + }, + ], + displayedOutput: '1 ns', + }, + { + duration: 0.001001, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 'DEFAULT', + output: [ + { + timeUnit: DtTimeUnit.MICROSECOND, + duration: '1', + }, + { + timeUnit: DtTimeUnit.NANOSECOND, + duration: '1', + }, + ], + displayedOutput: '1 µs 1 ns', + }, + ].forEach((testCase: TestCase) => { + // tslint:disable-next-line: dt-no-focused-tests + it.only(`Duration '${testCase.duration}', input unit '${testCase.inputUnit}' should equal to '${testCase.displayedOutput}'`, () => { + const formatMethod: DurationMode = toDurationMode( + testCase.formatMethod, + )!; + const result = Array.from( + dtTransformResult( + testCase.duration, + testCase.inputUnit, + formatMethod, + )!, + ); + expect(result).not.toBeUndefined(); + testCase.output.forEach((output: Output, index) => { + expect(result[index][0]).toBe(output.timeUnit); + expect(result[index][1]).toBe(output.duration); + }); + }); + }); + + describe('dtTransformResult() Mode: Custom', () => { + [ + { + duration: 123456789, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 2, + output: [ + { + timeUnit: DtTimeUnit.DAY, + duration: '1', + }, + { + timeUnit: DtTimeUnit.HOUR, + duration: '10', + }, + ], + displayedOutput: '1 d 10', + }, + { + duration: 123456789, + inputUnit: DtTimeUnit.MILLISECOND, + formatMethod: 5, + output: [ + { + timeUnit: DtTimeUnit.DAY, + duration: '1', + }, + { + timeUnit: DtTimeUnit.HOUR, + duration: '10', + }, + { + timeUnit: DtTimeUnit.MINUTE, + duration: '17', + }, + { + timeUnit: DtTimeUnit.SECOND, + duration: '36', + }, + { + timeUnit: DtTimeUnit.MILLISECOND, + duration: '789', + }, + ], + displayedOutput: '1 d 10 h 17 min 36 s 789 ms', + }, + ].forEach((testCase: TestCase) => { + it(`Duration '${testCase.duration}', input unit '${testCase.inputUnit}' should equal to '${testCase.displayedOutput}'`, () => { + const formatMethod: DurationMode = toDurationMode( + testCase.formatMethod, + )!; + const result = Array.from( + dtTransformResult( + testCase.duration, + testCase.inputUnit, + formatMethod, + )!, + ); + expect(result).not.toBeUndefined(); + testCase.output.forEach((output: Output, index) => { + expect(result[index][0]).toBe(output.timeUnit); + expect(result[index][1]).toBe(output.duration); + }); + }); + }); + }); + }); +}); diff --git a/libs/barista-components/formatters/src/duration/duration.spec.ts b/libs/barista-components/formatters/src/duration/duration.spec.ts new file mode 100644 index 0000000000..8222ce5428 --- /dev/null +++ b/libs/barista-components/formatters/src/duration/duration.spec.ts @@ -0,0 +1,348 @@ +/** + * @license + * Copyright 2020 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// tslint:disable: no-magic-numbers +import { NO_DATA } from '../formatted-value'; +import { DtTimeUnit } from '../unit'; +import { DtDuration } from './duration'; + +describe('DtDurationPipe', () => { + interface TestCase { + input: number; + inputUnit: DtTimeUnit | undefined; + output: string; + } + + interface TestCaseToUnit { + input: number; + inputUnit: DtTimeUnit | undefined; + outputUnit: DtTimeUnit; + output: string; + } + + interface TestCasePrecision { + input: number; + precision: string | number; + inputUnit: DtTimeUnit; + outputUnit: DtTimeUnit | undefined; + output: string; + } + + let pipe: DtDuration; + + beforeEach(() => { + pipe = new DtDuration(); + }); + + describe('Transforming input', () => { + [ + { + input: 1, + inputUnit: undefined, + output: '1 ms', + }, + { + input: 31540000000, + inputUnit: undefined, + output: '1 y', + }, + { + input: 2629738737.72, + inputUnit: undefined, + output: '1 mo', + }, + { + input: 24 * 60 * 60 * 1000, + inputUnit: undefined, + output: '1 d', + }, + { + input: 1.5, + inputUnit: DtTimeUnit.MONTH, + output: '1 mo 15 d 4 h', + }, + { + input: 1.5, + inputUnit: DtTimeUnit.HOUR, + output: '1 h 30 min', + }, + { + input: 1.5, + inputUnit: DtTimeUnit.MINUTE, + output: '1 min 30 s', + }, + { + input: 1.5, + inputUnit: DtTimeUnit.SECOND, + output: '1 s 500 ms', + }, + { + input: -1, + inputUnit: DtTimeUnit.MILLISECOND, + output: '< 1 ms', + }, + { + input: 0, + inputUnit: DtTimeUnit.MILLISECOND, + output: '< 1 ms', + }, + { + input: 0.9, + inputUnit: DtTimeUnit.MILLISECOND, + output: '900 µs', + }, + { + input: 0.4, + inputUnit: DtTimeUnit.MILLISECOND, + output: '400 µs', + }, + ].forEach((testCase: TestCase) => { + it(`should display ${testCase.output} when input is ${testCase.input} and inputUnit is '${testCase.inputUnit}'`, () => { + expect( + pipe + .transform(testCase.input, 'DEFAULT', undefined, testCase.inputUnit) + .toString() + .trim(), + ).toBe(testCase.output); + }); + }); + }); + + describe('Transforming input with outputUnit parameter set', () => { + [ + { + input: 120, + inputUnit: DtTimeUnit.MINUTE, + outputUnit: DtTimeUnit.HOUR, + output: '2 h', + }, + { + input: 2, + inputUnit: DtTimeUnit.HOUR, + outputUnit: DtTimeUnit.MINUTE, + output: '120 min', + }, + { + input: 1600, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.SECOND, + output: '1 s', + }, + { + input: 1400, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.SECOND, + output: '1 s', + }, + { + input: 400, + inputUnit: DtTimeUnit.MILLISECOND, + outputUnit: DtTimeUnit.SECOND, + output: '< 1 s', + }, + { + input: 15, + inputUnit: DtTimeUnit.MINUTE, + outputUnit: DtTimeUnit.HOUR, + output: '< 1 h', + }, + ].forEach((testCaseToUnit: TestCaseToUnit) => { + it(`should display ${testCaseToUnit.output} when input is ${testCaseToUnit.input}, inputUnit is '${testCaseToUnit.inputUnit}' and outputUnit is '${testCaseToUnit.outputUnit}'`, () => { + expect( + pipe + .transform( + testCaseToUnit.input, + 'DEFAULT', + testCaseToUnit.outputUnit, + testCaseToUnit.inputUnit, + ) + .toString() + .trim(), + ).toBe(testCaseToUnit.output); + }); + }); + }); + + describe('Transform by precision mode', () => { + describe('Precision Mode: PRECISE', () => { + [ + { + input: 500, + outputUnit: DtTimeUnit.SECOND, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 'PRECISE', + output: '0.5 s', + }, + { + input: 1.5, + outputUnit: DtTimeUnit.SECOND, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 'PRECISE', + output: '0.0015 s', + }, + { + input: 30000, + outputUnit: DtTimeUnit.MINUTE, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 'PRECISE', + output: '0.5 min', + }, + { + input: 135, + outputUnit: DtTimeUnit.SECOND, + inputUnit: DtTimeUnit.MINUTE, + precision: 'PRECISE', + output: '8100 s', + }, + { + input: 720000, + outputUnit: DtTimeUnit.HOUR, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 'PRECISE', + output: '0.2 h', + }, + { + input: -720000, + outputUnit: DtTimeUnit.HOUR, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 'PRECISE', + output: '-0.2 h', + }, + { + input: -500, + outputUnit: DtTimeUnit.SECOND, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 'PRECISE', + output: '-0.5 s', + }, + { + input: 0, + outputUnit: DtTimeUnit.SECOND, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 'PRECISE', + output: '0 s', + }, + ].forEach((testCasePrecision: TestCasePrecision) => { + it(`should display ${testCasePrecision.output} when input is ${testCasePrecision.input}, inputUnit is '${testCasePrecision.inputUnit}', outputUnit is '${testCasePrecision.outputUnit}' and precision mode is ${testCasePrecision.precision}`, () => { + expect( + pipe + .transform( + testCasePrecision.input, + 'PRECISE', + testCasePrecision.outputUnit, + testCasePrecision.inputUnit, + ) + .toString() + .trim(), + ).toEqual(testCasePrecision.output); + }); + }); + }); + + describe('Precision Mode: CUSTOM (1-n)', () => { + [ + { + input: 450305005, + outputUnit: undefined, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 5, + output: '5 d 5 h 5 min 5 s 5 ms', + }, + { + input: 450305005, + outputUnit: undefined, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 4, + output: '5 d 5 h 5 min 5 s', + }, + { + input: 450305005, + outputUnit: undefined, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 3, + output: '5 d 5 h 5 min', + }, + { + input: 450305005, + outputUnit: undefined, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 2, + output: '5 d 5 h', + }, + { + input: 450305005, + outputUnit: undefined, + inputUnit: DtTimeUnit.MILLISECOND, + precision: 1, + output: '5 d', + }, + ].forEach((testCaseCustom: TestCasePrecision) => { + it(`should display ${testCaseCustom.output} when input is ${testCaseCustom.input}, inputUnit is '${testCaseCustom.inputUnit}', outputUnit is '${testCaseCustom.outputUnit}' and precision mode is '${testCaseCustom.precision}'`, () => { + if (typeof testCaseCustom.precision === 'number') { + expect( + pipe + .transform( + testCaseCustom.input, + testCaseCustom.precision, + testCaseCustom.outputUnit, + testCaseCustom.inputUnit, + ) + .toString() + .trim(), + ).toEqual(testCaseCustom.output); + } + }); + }); + }); + }); + + describe('Empty Values / Invalid Values', () => { + it(`should return '${NO_DATA}' for empty values`, () => { + expect(pipe.transform('', 'DEFAULT', undefined)).toEqual(NO_DATA); + expect(pipe.transform(null, 'DEFAULT', undefined)).toEqual(NO_DATA); + expect(pipe.transform(undefined, 'DEFAULT', undefined)).toEqual(NO_DATA); + }); + it(`should return '${NO_DATA}' for values that cannot be converted to numbers`, () => { + class A {} + expect(pipe.transform([], 'DEFAULT', undefined)).toEqual(NO_DATA); + expect(pipe.transform({}, 'DEFAULT', undefined)).toEqual(NO_DATA); + expect(pipe.transform(() => {}, 'DEFAULT', undefined)).toEqual(NO_DATA); + expect(pipe.transform(A, 'DEFAULT', undefined)).toEqual(NO_DATA); + expect(pipe.transform(new A(), 'DEFAULT', undefined)).toEqual(NO_DATA); + }); + it(`should return '${NO_DATA}' for combined strings`, () => { + expect(pipe.transform('123test', 'DEFAULT', undefined)).toEqual(NO_DATA); + }); + }); + + describe('should handle 0 and negative numbers', () => { + it('should handle 0', () => { + expect(pipe.transform('0', 'DEFAULT', undefined).toString()).toEqual( + '< 1 ms', + ); + }); + it('should handle -1', () => { + expect(pipe.transform('-1', 'DEFAULT', undefined).toString()).toEqual( + '< 1 ms', + ); + }); + it('should handle -123', () => { + expect(pipe.transform('-123', 'DEFAULT', undefined).toString()).toEqual( + '< 1 ms', + ); + }); + }); +}); diff --git a/libs/barista-components/formatters/src/time/time.ts b/libs/barista-components/formatters/src/duration/duration.ts similarity index 58% rename from libs/barista-components/formatters/src/time/time.ts rename to libs/barista-components/formatters/src/duration/duration.ts index b1f01aceeb..ed84280057 100644 --- a/libs/barista-components/formatters/src/time/time.ts +++ b/libs/barista-components/formatters/src/duration/duration.ts @@ -21,27 +21,36 @@ import { isEmpty, isNumber } from '@dynatrace/barista-components/core'; import { DtFormattedValue, NO_DATA } from '../formatted-value'; import { DtTimeUnit } from '../unit'; -import { formatTime } from './time-formatter'; +import { formatDuration } from './duration-formatter'; +import { DurationMode } from './duration-formatter-constants'; /** Pipe used to convert milliseconds to amount of time from years to nanoseconds */ @Pipe({ - name: 'dtTime', + name: 'dtDuration', }) -export class DtTime implements PipeTransform { +export class DtDuration implements PipeTransform { /** - * @param input - The timevalue to be formatted to amount of time from years to nanoseconds + * @param duration The timevalue to be formatted to amount of time from years to nanoseconds + * @param formatMethod Configuration for formatting the output + * @param outputUnit dtTimeUnit | undefined value describing the unit to which it should format + * @param inputUnit dtTimeUnit value describing which unit the duration is in */ - // tslint:disable: no-any transform( - input: any, + duration: any, + formatMethod: DurationMode, + outputUnit: DtTimeUnit | undefined, inputUnit: DtTimeUnit = DtTimeUnit.MILLISECOND, - toUnit: DtTimeUnit | undefined, ): DtFormattedValue | string { - if (isEmpty(input)) { + if (isEmpty(duration)) { return NO_DATA; } - return isNumber(input) - ? formatTime(coerceNumberProperty(input), inputUnit, toUnit) + return isNumber(duration) + ? formatDuration( + coerceNumberProperty(duration), + formatMethod, + outputUnit, + inputUnit, + ) : NO_DATA; } } diff --git a/libs/barista-components/formatters/src/formatted-value.ts b/libs/barista-components/formatters/src/formatted-value.ts index 2e933be7f8..b6c12fdd96 100644 --- a/libs/barista-components/formatters/src/formatted-value.ts +++ b/libs/barista-components/formatters/src/formatted-value.ts @@ -26,6 +26,7 @@ export interface FormattedData { readonly displayValue?: string; readonly displayUnit?: string; readonly displayRateUnit?: string; + readonly displayWhiteSpace?: boolean; } export const NO_DATA = '-'; @@ -54,14 +55,14 @@ export class DtFormattedValue { if (this._formattedData.displayValue === undefined) { return NO_DATA; } - let text = this._formattedData.displayValue; if (this._formattedData.displayUnit !== undefined) { text = `${text} ${this._formattedData.displayUnit}`; } if (this._formattedData.displayRateUnit !== undefined) { text = - this._formattedData.displayUnit !== undefined + this._formattedData.displayUnit !== undefined || + this._formattedData.displayWhiteSpace === false ? `${text}/${this._formattedData.displayRateUnit}` : `${text} /${this._formattedData.displayRateUnit}`; } diff --git a/libs/barista-components/formatters/src/formatters-module.ts b/libs/barista-components/formatters/src/formatters-module.ts index 8d45d2e2fc..a47c3cb136 100644 --- a/libs/barista-components/formatters/src/formatters-module.ts +++ b/libs/barista-components/formatters/src/formatters-module.ts @@ -25,7 +25,7 @@ import { DtCount } from './count/count'; import { DtDateRange } from './date/date-range'; import { DtPercent } from './percent/percent'; import { DtRate } from './rate/rate'; -import { DtTime } from './time/time'; +import { DtDuration } from './duration/duration'; const FORMATTERS = [ DtBytes, @@ -36,7 +36,7 @@ const FORMATTERS = [ DtPercent, DtRate, DtDateRange, - DtTime, + DtDuration, ]; @NgModule({ diff --git a/libs/barista-components/formatters/src/rate/rate-formatter.ts b/libs/barista-components/formatters/src/rate/rate-formatter.ts index c96f10960f..00d5a004c0 100644 --- a/libs/barista-components/formatters/src/rate/rate-formatter.ts +++ b/libs/barista-components/formatters/src/rate/rate-formatter.ts @@ -40,12 +40,17 @@ export function formatRate( input instanceof DtFormattedValue ? input.displayData.displayUnit : undefined; + const displayWhiteSpace = + input instanceof DtFormattedValue + ? input.displayData.displayWhiteSpace + : undefined; const formattedData: FormattedData = { transformedValue: sourceData.input, displayRateUnit: rateUnit, displayUnit, displayValue, + displayWhiteSpace, }; return new DtFormattedValue(sourceData, formattedData); diff --git a/libs/barista-components/formatters/src/time/time-formatter.ts b/libs/barista-components/formatters/src/time/time-formatter.ts deleted file mode 100644 index aca3f01472..0000000000 --- a/libs/barista-components/formatters/src/time/time-formatter.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * @license - * Copyright 2020 Dynatrace LLC - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - DtFormattedValue, - FormattedData, - NO_DATA, - SourceData, -} from '../formatted-value'; -import { DtTimeUnit } from '../unit'; - -// tslint:disable: no-magic-numbers -// Factorials needed in converting milliseconds to other time units -const CONVERSION_FACTORS_TO_MS = new Map([ - [DtTimeUnit.YEAR, 12 * 30.4 * 24 * 60 * 60 * 1000], - [DtTimeUnit.MONTH, 30.4 * 24 * 60 * 60 * 1000], - [DtTimeUnit.DAY, 24 * 60 * 60 * 1000], - [DtTimeUnit.HOUR, 60 * 60 * 1000], - [DtTimeUnit.MINUTE, 60 * 1000], - [DtTimeUnit.SECOND, 1000], - [DtTimeUnit.MILLISECOND, 1], - [DtTimeUnit.MICROSECOND, 0.001], - [DtTimeUnit.NANOSECOND, 0.000001], -]); - -const CONVERSIONUNITS = 2; - -/** - * @param time - numeric time value to be converted to - * amount of time from years to nanoseconds - */ -export function formatTime( - time: number, - inputUnit: DtTimeUnit = DtTimeUnit.MILLISECOND, - toUnit: DtTimeUnit | undefined, -): DtFormattedValue | string { - const inputData: SourceData = { - input: time, - unit: inputUnit, - }; - let formattedData: FormattedData; - if (time <= 0) { - return new DtFormattedValue( - inputData, - (formattedData = { - transformedValue: inputData.input, - displayValue: '0', - displayUnit: inputUnit, - }), - ); - } else { - // Calculates the amount of each timeunit and adds the value and unit into a resultstring - if (CONVERSION_FACTORS_TO_MS.get(inputUnit) === undefined) { - return NO_DATA; - } - const result = buildResultFromTime(time, inputUnit, toUnit); - if (result === undefined) { - return NO_DATA; - } - let resultString = ''; - result.forEach((value, key) => { - resultString = `${resultString}${value}${key} `; - }); - resultString = resultString.trim(); - formattedData = { - transformedValue: inputData.input, - displayValue: resultString, - displayUnit: '', - }; - return new DtFormattedValue(inputData, formattedData); - } -} - -// Converts input to milliseconds -function buildResultFromTime( - time: number, - inputUnit: DtTimeUnit, - toUnit: DtTimeUnit | undefined, -): Map | undefined { - if (!CONVERSION_FACTORS_TO_MS.has(inputUnit)) { - return; - } - let rest = time * CONVERSION_FACTORS_TO_MS.get(inputUnit)!; - const result = new Map(); - let counter = 0; - let canConvert = false; - const conversionFactorKeys = Array.from(CONVERSION_FACTORS_TO_MS.keys()); - if ( - toUnit !== undefined && - conversionFactorKeys.indexOf(toUnit) > - conversionFactorKeys.indexOf(inputUnit) - ) { - canConvert = true; - } - for (const key of conversionFactorKeys) { - const factor = CONVERSION_FACTORS_TO_MS.get(key)!; - const amount = Math.trunc(rest / factor); - if ( - canConvert && - conversionFactorKeys.indexOf(toUnit!) >= conversionFactorKeys.indexOf(key) - ) { - counter = CONVERSIONUNITS; - if (amount > 0) { - result.set(key, amount); - } - } else if (counter < CONVERSIONUNITS) { - if (amount > 0) { - if (counter < CONVERSIONUNITS) { - result.set(key, amount); - } - counter++; - // Only next two units will be displayed. Examples: `1y 1mo 1d` instead of `1y 1mo 1d 1h 1min...` - } else if (counter > 0) { - counter++; - } - } - rest = rest - amount * factor; - } - return result; -} diff --git a/libs/barista-components/formatters/src/time/time.spec.ts b/libs/barista-components/formatters/src/time/time.spec.ts deleted file mode 100644 index 1b08d15881..0000000000 --- a/libs/barista-components/formatters/src/time/time.spec.ts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * @license - * Copyright 2020 Dynatrace LLC - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// tslint:disable: no-magic-numbers -import { NO_DATA } from '../formatted-value'; -import { DtTimeUnit } from '../unit'; -import { DtTime } from './time'; - -describe('DtTimePipe', () => { - interface TestCase { - input: number; - inputFormat: DtTimeUnit | undefined; - output: string; - } - - interface TestCaseToUnit { - input: number; - inputFormat: DtTimeUnit | undefined; - toUnit: DtTimeUnit; - output: string; - } - - let pipe: DtTime; - - beforeEach(() => { - pipe = new DtTime(); - }); - - describe('Transforming input', () => { - [ - { - input: 1, - inputFormat: undefined, - output: '1ms', - }, - { - input: 12 * 30.4 * 24 * 60 * 60 * 1000 + 7, - inputFormat: undefined, - output: '1y', - }, - { - input: 30.4 * 24 * 60 * 60 * 1000, - inputFormat: undefined, - output: '1mo', - }, - { - input: 24 * 60 * 60 * 1000, - inputFormat: undefined, - output: '1d', - }, - { - input: 1.5, - inputFormat: DtTimeUnit.MONTH, - output: '1mo 15d', - }, - { - input: 1.5, - inputFormat: DtTimeUnit.HOUR, - output: '1h 30min', - }, - { - input: 1.5, - inputFormat: DtTimeUnit.MINUTE, - output: '1min 30s', - }, - { - input: 1.5, - inputFormat: DtTimeUnit.SECOND, - output: '1s 500ms', - }, - ].forEach((testCase: TestCase) => { - it(`should display ${testCase.output} when input is ${testCase.input}`, () => { - expect( - pipe - .transform(testCase.input, testCase.inputFormat, undefined) - .toString() - .trim(), - ).toBe(testCase.output); - }); - }); - }); - describe('Transforming input with toUnit parameter set', () => { - [ - { - input: 1.234567, - inputFormat: DtTimeUnit.DAY, - toUnit: DtTimeUnit.SECOND, - output: '1d 5h 37min 46s', - }, - { - input: 1.234567, - inputFormat: DtTimeUnit.DAY, - toUnit: DtTimeUnit.HOUR, - output: '1d 5h', - }, - { - input: 1.234567, - inputFormat: DtTimeUnit.DAY, - toUnit: DtTimeUnit.MINUTE, - output: '1d 5h 37min', - }, - { - input: 1.234567, - inputFormat: DtTimeUnit.DAY, - // should disregard at toUnit set higher than the inputUnit and output three time units - toUnit: DtTimeUnit.YEAR, - output: '1d 5h', - }, - ].forEach((testCaseToUnit: TestCaseToUnit) => { - it(`should display ${testCaseToUnit.output} when input is ${testCaseToUnit.input} and toUnit is set to ${testCaseToUnit.toUnit}`, () => { - expect( - pipe - .transform( - testCaseToUnit.input, - testCaseToUnit.inputFormat, - testCaseToUnit.toUnit, - ) - .toString() - .trim(), - ).toBe(testCaseToUnit.output); - }); - }); - }); - describe('Empty Values / Invalid Values', () => { - it(`should return '${NO_DATA}' for empty values`, () => { - expect(pipe.transform('', undefined, undefined)).toEqual(NO_DATA); - expect(pipe.transform(null, undefined, undefined)).toEqual(NO_DATA); - expect(pipe.transform(undefined, undefined, undefined)).toEqual(NO_DATA); - }); - it(`should return '${NO_DATA}' for values that cannot be converted to numbers`, () => { - class A {} - expect(pipe.transform([], undefined, undefined)).toEqual(NO_DATA); - expect(pipe.transform({}, undefined, undefined)).toEqual(NO_DATA); - expect(pipe.transform(() => {}, undefined, undefined)).toEqual(NO_DATA); - expect(pipe.transform(A, undefined, undefined)).toEqual(NO_DATA); - expect(pipe.transform(new A(), undefined, undefined)).toEqual(NO_DATA); - }); - it(`should return '${NO_DATA}' for combined strings`, () => { - expect(pipe.transform('123test', undefined, undefined)).toEqual(NO_DATA); - }); - }); - describe('should handle 0 and negative numbers', () => { - it('should handle 0', () => { - expect(pipe.transform('0', undefined, undefined).toString()).toEqual( - '0 ms', - ); - }); - it('should handle -1', () => { - expect(pipe.transform('-1', undefined, undefined).toString()).toEqual( - '0 ms', - ); - }); - it('should handle -123', () => { - expect(pipe.transform('-123', undefined, undefined).toString()).toEqual( - '0 ms', - ); - }); - }); -}); diff --git a/libs/examples/src/formatters/formatters-duration-example/formatters-duration-example.html b/libs/examples/src/formatters/formatters-duration-example/formatters-duration-example.html new file mode 100644 index 0000000000..436197b247 --- /dev/null +++ b/libs/examples/src/formatters/formatters-duration-example/formatters-duration-example.html @@ -0,0 +1,12 @@ + + Value to be transformed + + +

DEFAULT {{ exampleValue | dtDuration }}: {{ exampleValue | dtDuration }}

+

+ PRECISE + outputUnit {{ exampleValue | dtDuration: 'PRECISE':'s' }}: {{ + exampleValue | dtDuration: 'PRECISE':'s' }} +

+

+ CUSTOM {{ exampleValue | dtDuration: 5 }}: {{ exampleValue | dtDuration: 5 }} +

diff --git a/libs/examples/src/formatters/formatters-time-example/formatters-time-example.ts b/libs/examples/src/formatters/formatters-duration-example/formatters-duration-example.ts similarity index 82% rename from libs/examples/src/formatters/formatters-time-example/formatters-time-example.ts rename to libs/examples/src/formatters/formatters-duration-example/formatters-duration-example.ts index 8075cef5cd..05526b3fa4 100644 --- a/libs/examples/src/formatters/formatters-time-example/formatters-time-example.ts +++ b/libs/examples/src/formatters/formatters-duration-example/formatters-duration-example.ts @@ -17,9 +17,9 @@ import { Component } from '@angular/core'; @Component({ - selector: 'dt-example-formatters-time-example', - templateUrl: 'formatters-time-example.html', + selector: 'dt-example-formatters-duration-example', + templateUrl: 'formatters-duration-example.html', }) -export class DtExampleFormattersTime { +export class DtExampleFormattersDuration { exampleValue: number; } diff --git a/libs/examples/src/formatters/formatters-examples.module.ts b/libs/examples/src/formatters/formatters-examples.module.ts index 27e2c3ea2b..7399a693e8 100644 --- a/libs/examples/src/formatters/formatters-examples.module.ts +++ b/libs/examples/src/formatters/formatters-examples.module.ts @@ -25,7 +25,7 @@ import { DtExampleFormattersBytes } from './formatters-bytes-example/formatters- import { DtExampleFormattersCount } from './formatters-count-example/formatters-count-example'; import { DtExampleFormattersPercent } from './formatters-percent-example/formatters-percent-example'; import { DtExampleFormattersRate } from './formatters-rate-example/formatters-rate-example'; -import { DtExampleFormattersTime } from './formatters-time-example/formatters-time-example'; +import { DtExampleFormattersDuration } from './formatters-duration-example/formatters-duration-example'; export const DT_FORMATTERS_EXAMPLES = [ DtExampleFormattersBits, @@ -33,7 +33,7 @@ export const DT_FORMATTERS_EXAMPLES = [ DtExampleFormattersCount, DtExampleFormattersPercent, DtExampleFormattersRate, - DtExampleFormattersTime, + DtExampleFormattersDuration, ]; @NgModule({ diff --git a/libs/examples/src/formatters/formatters-time-example/formatters-time-example.html b/libs/examples/src/formatters/formatters-time-example/formatters-time-example.html deleted file mode 100644 index 9dabd7a37a..0000000000 --- a/libs/examples/src/formatters/formatters-time-example/formatters-time-example.html +++ /dev/null @@ -1,7 +0,0 @@ - - Value to be transformed - - -

Ms: {{ exampleValue | dtTime }}

-

Hour: {{ exampleValue | dtTime: 'h' }}

-

Day to Second: {{ exampleValue | dtTime: 'd':'s' }}