mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-13 00:28:49 +02:00
* Add tests for chart helper * Ensure the decimal places are 4 for credential units total * Add new chart-helpers method * Add jsdoc comment Co-authored-by: Kianna <30884335+kiannaquach@users.noreply.github.com>
This commit is contained in:
parent
417d3dbcb0
commit
72c3492cef
@ -5,7 +5,7 @@
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { toLabel } from 'core/helpers/to-label';
|
||||
import { calculateSum } from 'vault/utils/chart-helpers';
|
||||
import { calculateSum, toFixedDisplay } from 'vault/utils/chart-helpers';
|
||||
import { NormalizedBillingMetrics } from 'vault/utils/metrics-helpers';
|
||||
|
||||
interface Args {
|
||||
@ -18,6 +18,16 @@ export default class MetricCard extends Component<Args> {
|
||||
|
||||
get total() {
|
||||
const sums = Object.values(this.args.metrics).filter((metric) => metric !== undefined);
|
||||
if (this.args.title === 'Credential units') {
|
||||
const totalCredentialUnits = calculateSum(sums, 4);
|
||||
|
||||
if (typeof totalCredentialUnits === 'number') {
|
||||
return toFixedDisplay(totalCredentialUnits, 4);
|
||||
}
|
||||
|
||||
return totalCredentialUnits;
|
||||
}
|
||||
|
||||
return calculateSum(sums);
|
||||
}
|
||||
|
||||
|
||||
@ -7,13 +7,14 @@ import Component from '@glimmer/component';
|
||||
import { toLabel } from 'core/helpers/to-label';
|
||||
import { NormalizedMetricsData } from 'vault/vault/billing/overview';
|
||||
import { NormalizedBillingMetrics } from 'vault/utils/metrics-helpers';
|
||||
import { toFixedDisplay } from 'vault/utils/chart-helpers';
|
||||
|
||||
interface SummaryMetricInfo {
|
||||
label: string;
|
||||
tooltipText?: string;
|
||||
count?: number;
|
||||
showBadge?: boolean;
|
||||
total?: number | boolean | undefined;
|
||||
total?: number | string | boolean | undefined;
|
||||
}
|
||||
interface Args {
|
||||
title: string;
|
||||
@ -58,7 +59,13 @@ export default class SummaryCard extends Component<Args> {
|
||||
|
||||
summaryMetric = (key: string): SummaryMetricInfo => {
|
||||
if (this.summaryMetricMap?.[key]) {
|
||||
this.summaryMetricMap[key].total = this.args.normalizedMetricData[key];
|
||||
const value = this.args.normalizedMetricData[key];
|
||||
// Format CREDENTIAL_UNITS_TOTAL with 4 decimal places to preserve trailing zeros
|
||||
if (key === NormalizedBillingMetrics.CREDENTIAL_UNITS_TOTAL && typeof value === 'number') {
|
||||
this.summaryMetricMap[key].total = toFixedDisplay(value, 4);
|
||||
} else {
|
||||
this.summaryMetricMap[key].total = value;
|
||||
}
|
||||
}
|
||||
|
||||
return this.summaryMetricMap[key] || { label: toLabel([key]) };
|
||||
|
||||
@ -39,9 +39,65 @@ export function calculateAverage(dataset, objectKey) {
|
||||
return checkIntegers ? Math.round(mean(integers)) : null;
|
||||
}
|
||||
|
||||
export function calculateSum(integerArray) {
|
||||
/**
|
||||
* Calculates the sum of an array of numbers with optional decimal precision.
|
||||
* This function fixes floating-point arithmetic errors by rounding to a specified
|
||||
* number of decimal places. For example, 48.7888 + 0.0112 = 48.800000000000004
|
||||
* in JavaScript, but with fixedDecimalPlaces=4, it returns 48.8.
|
||||
*
|
||||
* @param {number[]} integerArray - Array of numbers to sum
|
||||
* @param {number} [fixedDecimalPlaces] - Optional number of decimal places for precision.
|
||||
* If provided, the sum is rounded to this precision.
|
||||
* @returns {number|null} Returns the sum as a number, or null if invalid input.
|
||||
* When fixedDecimalPlaces is provided, returns a number rounded
|
||||
* to that precision (e.g., 48.8 instead of 48.800000000000004).
|
||||
*
|
||||
* @example
|
||||
* calculateSum([2, 3]) // Returns 5
|
||||
* calculateSum([48.7888, 0.0112], 4) // Returns 48.8 (fixes floating-point error)
|
||||
* calculateSum([73.1832, 0.0168], 4) // Returns 73.2
|
||||
* calculateSum([10, 20, 30], 4) // Returns 60
|
||||
* calculateSum(['one', 2]) // Returns null (invalid input)
|
||||
*/
|
||||
export function calculateSum(integerArray, fixedDecimalPlaces) {
|
||||
if (!Array.isArray(integerArray) || integerArray.some((n) => typeof n !== 'number')) {
|
||||
return null;
|
||||
}
|
||||
return integerArray.reduce((a, b) => a + b, 0);
|
||||
|
||||
const sum = integerArray.reduce((a, b) => a + b, 0);
|
||||
|
||||
if (typeof fixedDecimalPlaces === 'number' && fixedDecimalPlaces >= 0) {
|
||||
return parseFloat(sum.toFixed(fixedDecimalPlaces));
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a number for display with a fixed number of decimal places.
|
||||
* This helper function converts numbers to strings with trailing zeros preserved,
|
||||
* which is useful for displaying values like "48.8000" instead of "48.8".
|
||||
*
|
||||
* @param {number} number - The number to format
|
||||
* @param {number} decimalPlaces - The number of decimal places to display
|
||||
* @returns {number|string} Returns the original number if invalid inputs,
|
||||
* returns 0 as-is for zero values,
|
||||
* otherwise returns a string with fixed decimal places
|
||||
*
|
||||
* @example
|
||||
* toFixedDisplay(48.8, 4) // Returns "48.8000"
|
||||
* toFixedDisplay(73.2, 4) // Returns "73.2000"
|
||||
* toFixedDisplay(0, 4) // Returns 0 (not "0.0000")
|
||||
* toFixedDisplay(100, 2) // Returns "100.00"
|
||||
*/
|
||||
export function toFixedDisplay(number, decimalPlaces) {
|
||||
if (typeof number !== 'number' || typeof decimalPlaces !== 'number' || decimalPlaces < 0) {
|
||||
return number;
|
||||
}
|
||||
|
||||
if (number === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return number.toFixed(decimalPlaces);
|
||||
}
|
||||
|
||||
@ -89,11 +89,8 @@ export function normalizeMetricData(metric: Month | null | undefined) {
|
||||
typeof normalized[NormalizedBillingMetrics.ID_TOKEN_UNITS_TOTAL] === 'number'
|
||||
? normalized[NormalizedBillingMetrics.ID_TOKEN_UNITS_TOTAL]
|
||||
: 0;
|
||||
normalized[NormalizedBillingMetrics.CREDENTIAL_UNITS_TOTAL] = calculateSum([
|
||||
sshUnitsTotal,
|
||||
pkiUnitsTotal,
|
||||
idTokenUnitsTotal,
|
||||
]);
|
||||
normalized[NormalizedBillingMetrics.CREDENTIAL_UNITS_TOTAL] =
|
||||
calculateSum([sshUnitsTotal, pkiUnitsTotal, idTokenUnitsTotal], 4) ?? 0;
|
||||
|
||||
// Explicitly set any missing metric keys to 0.
|
||||
for (const metricsKey of Object.values(NormalizedBillingMetrics)) {
|
||||
|
||||
@ -3,7 +3,12 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { numericalAxisLabel, calculateAverage, calculateSum } from 'vault/utils/chart-helpers';
|
||||
import {
|
||||
numericalAxisLabel,
|
||||
calculateAverage,
|
||||
calculateSum,
|
||||
toFixedDisplay,
|
||||
} from 'vault/utils/chart-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
const SMALL_NUMBERS = [0, 7, 27, 103, 999];
|
||||
@ -65,4 +70,67 @@ module('Unit | Utility | chart-helpers', function () {
|
||||
assert.strictEqual(calculateSum(['one', 2]), null, 'returns null if array contains non-integers');
|
||||
assert.strictEqual(calculateSum('not an array'), null, 'returns null if an array is not passed');
|
||||
});
|
||||
|
||||
test('calculateSum with fixedDecimalPlaces parameter', function (assert) {
|
||||
assert.strictEqual(calculateSum([2.5, 3.7], 1), 6.2, 'rounds sum to 1 decimal place');
|
||||
assert.strictEqual(calculateSum([2.5555, 3.7777], 2), 6.33, 'rounds sum to 2 decimal places');
|
||||
assert.strictEqual(
|
||||
calculateSum([48.7888, 0.0112], 4),
|
||||
48.8,
|
||||
'handles floating-point precision issues with 4 decimal places'
|
||||
);
|
||||
assert.strictEqual(
|
||||
calculateSum([73.1832, 0.0168], 4),
|
||||
73.2,
|
||||
'correctly sums and rounds to 4 decimal places'
|
||||
);
|
||||
assert.strictEqual(
|
||||
calculateSum([10, 20, 30], 4),
|
||||
60,
|
||||
'works with whole numbers when fixedDecimalPlaces is provided'
|
||||
);
|
||||
assert.strictEqual(
|
||||
calculateSum([1.11111, 2.22222, 3.33333], 4),
|
||||
6.6667,
|
||||
'rounds sum of multiple numbers to 4 decimal places'
|
||||
);
|
||||
assert.strictEqual(calculateSum([0.1, 0.2], 4), 0.3, 'handles classic floating-point issue (0.1 + 0.2)');
|
||||
assert.strictEqual(calculateSum([2, 3], 0), 5, 'rounds to 0 decimal places (whole number)');
|
||||
});
|
||||
|
||||
test('toFixedDisplay formats numbers with fixed decimal places', function (assert) {
|
||||
assert.strictEqual(toFixedDisplay(48.8, 4), '48.8000', 'formats number with trailing zeros');
|
||||
assert.strictEqual(toFixedDisplay(73.2, 4), '73.2000', 'preserves 4 decimal places');
|
||||
assert.strictEqual(toFixedDisplay(100, 2), '100.00', 'formats whole number with decimals');
|
||||
assert.strictEqual(toFixedDisplay(0, 4), 0, 'returns 0 as number, not formatted string');
|
||||
assert.strictEqual(toFixedDisplay(1.23456, 2), '1.23', 'rounds to specified decimal places');
|
||||
assert.strictEqual(toFixedDisplay('not a number', 4), 'not a number', 'returns non-number as-is');
|
||||
assert.strictEqual(toFixedDisplay(5.5, -1), 5.5, 'returns number as-is for negative decimal places');
|
||||
});
|
||||
|
||||
test('calculateSum and toFixedDisplay work together', function (assert) {
|
||||
const sum1 = calculateSum([48.7888, 0.0112], 4);
|
||||
assert.strictEqual(sum1, 48.8, 'calculateSum returns number with fixed precision');
|
||||
assert.strictEqual(
|
||||
toFixedDisplay(sum1, 4),
|
||||
'48.8000',
|
||||
'toFixedDisplay formats for display with trailing zeros'
|
||||
);
|
||||
|
||||
const sum2 = calculateSum([73.1832, 0.0168], 4);
|
||||
assert.strictEqual(sum2, 73.2, 'calculateSum handles floating-point precision');
|
||||
assert.strictEqual(toFixedDisplay(sum2, 4), '73.2000', 'toFixedDisplay preserves trailing zeros');
|
||||
|
||||
const sum3 = calculateSum([10, 20, 30], 4);
|
||||
assert.strictEqual(sum3, 60, 'calculateSum works with whole numbers');
|
||||
assert.strictEqual(
|
||||
toFixedDisplay(sum3, 4),
|
||||
'60.0000',
|
||||
'toFixedDisplay adds decimal places to whole numbers'
|
||||
);
|
||||
|
||||
const sum4 = calculateSum([0, 0, 0], 4);
|
||||
assert.strictEqual(sum4, 0, 'calculateSum returns 0 for zero sum');
|
||||
assert.strictEqual(toFixedDisplay(sum4, 4), 0, 'toFixedDisplay returns 0 as-is, not formatted');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user