From e5626c94b56a24c49b6004517152340ab4884737 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stephan=20M=C3=BCller?= Date: Thu, 26 Apr 2018 15:25:04 +0200 Subject: [PATCH] mgr/dashboard: Refactored formatter service MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Added tests which caused a refactorization of the service. Signed-off-by: Stephan Müller --- .../app/shared/pipes/dimless-binary.pipe.ts | 6 +- .../shared/services/formatter.service.spec.ts | 92 +++++++++++++------ .../app/shared/services/formatter.service.ts | 83 ++++------------- 3 files changed, 86 insertions(+), 95 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless-binary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless-binary.pipe.ts index 13686a1a1971c..b1784fcf16b35 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless-binary.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/dimless-binary.pipe.ts @@ -15,9 +15,9 @@ export class DimlessBinaryPipe implements PipeTransform { 'GiB', 'TiB', 'PiB', - 'Eib', - 'Zib', - 'Yib' + 'EiB', + 'ZiB', + 'YiB' ]); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts index f32f92889ce68..42f64e9b20d10 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.spec.ts @@ -1,51 +1,91 @@ import { TestBed } from '@angular/core/testing'; +import { DimlessBinaryPipe } from '../pipes/dimless-binary.pipe'; import { FormatterService } from './formatter.service'; describe('FormatterService', () => { let service: FormatterService; + let dimlessBinaryPipe: DimlessBinaryPipe; + + const convertToBytesAndBack = (value: string, newValue?: string) => { + expect(dimlessBinaryPipe.transform(service.toBytes(value))).toBe(newValue || value); + }; + beforeEach(() => { TestBed.configureTestingModule({ - providers: [FormatterService] + providers: [FormatterService, DimlessBinaryPipe] }); service = new FormatterService(); + dimlessBinaryPipe = new DimlessBinaryPipe(service); }); it('should be created', () => { expect(service).toBeTruthy(); }); - it('should not convert 10xyz to bytes (failure)', () => { - const bytes = service.toBytes('10xyz'); - expect(bytes).toBeNull(); - }); + describe('truncate', () => { + it('should do test integer values', () => { + expect(service.truncate('1234', 8)).toBe('1234'); + expect(service.truncate(1234, 8)).toBe('1234'); + }); - it('should not convert 1.1.1KiB to bytes (failure)', () => { - const bytes = service.toBytes('1.1.1KiB'); - expect(bytes).toBeNull(); + it('should do test floating values', () => { + const value = '1234.567899000'; + expect(service.truncate(value, 0)).toBe('1235'); + expect(service.truncate(value, 1)).toBe('1234.6'); + expect(service.truncate(value, 3)).toBe('1234.568'); + expect(service.truncate(value, 4)).toBe('1234.5679'); + expect(service.truncate(value, 5)).toBe('1234.5679'); + expect(service.truncate(value, 6)).toBe('1234.567899'); + expect(service.truncate(value, 7)).toBe('1234.567899'); + expect(service.truncate(value, 10)).toBe('1234.567899'); + expect(service.truncate(100.00, 4)).toBe('100'); + }); }); - it('should convert 4815162342 to bytes', () => { - const bytes = service.toBytes('4815162342'); - expect(bytes).toEqual(jasmine.any(Number)); - expect(bytes).toBe(4815162342); - }); + describe('format_number', () => { + const formats = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; - it('should convert 100M to bytes', () => { - const bytes = service.toBytes('100M'); - expect(bytes).toEqual(jasmine.any(Number)); - expect(bytes).toBe(104857600); - }); + it('should return minus for unsupported values', () => { + expect(service.format_number(service, 1024, formats)).toBe('-'); + expect(service.format_number(undefined, 1024, formats)).toBe('-'); + expect(service.format_number(null, 1024, formats)).toBe('-'); + expect(service.format_number('0', 1024, formats)).toBe('-'); + }); - it('should convert 1.532KiB to bytes', () => { - const bytes = service.toBytes('1.532KiB'); - expect(bytes).toEqual(jasmine.any(Number)); - expect(bytes).toBe(Math.floor(1.532 * 1024)); + it('should test some values', () => { + expect(service.format_number('1', 1024, formats)).toBe('1B'); + expect(service.format_number('1024', 1024, formats)).toBe('1KiB'); + expect(service.format_number(23.45678 * Math.pow(1024, 3), 1024, formats)).toBe('23.4568GiB'); + }); }); - it('should convert 0.000000000001TiB to bytes', () => { - const bytes = service.toBytes('0.000000000001TiB'); - expect(bytes).toEqual(jasmine.any(Number)); - expect(bytes).toBe(1); + describe('toBytes', () => { + it('should not convert wrong values', () => { + expect(service.toBytes('10xyz')).toBeNull(); + expect(service.toBytes('1.1.1KiB')).toBeNull(); + expect(service.toBytes('1.1 KiloByte')).toBeNull(); + expect(service.toBytes('1.1 kib')).toBeNull(); + expect(service.toBytes('1.kib')).toBeNull(); + expect(service.toBytes('1 ki')).toBeNull(); + }); + + it('should convert values to bytes', () => { + expect(service.toBytes('4815162342')).toBe(4815162342); + expect(service.toBytes('100M')).toBe(104857600); + expect(service.toBytes('100 M')).toBe(104857600); + expect(service.toBytes('100 mIb')).toBe(104857600); + expect(service.toBytes('100 mb')).toBe(104857600); + expect(service.toBytes('100MIB')).toBe(104857600); + expect(service.toBytes('1.532KiB')).toBe(Math.round(1.532 * 1024)); + expect(service.toBytes('0.000000000001TiB')).toBe(1); + }); + + it('should convert values to human readable again', () => { + convertToBytesAndBack('1.1MiB'); + convertToBytesAndBack('1.0MiB', '1MiB'); + convertToBytesAndBack('8.9GiB'); + convertToBytesAndBack('123.456EiB'); + }); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.ts index 7c6a95d3048a8..341e1ee25231a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/formatter.service.ts @@ -6,48 +6,26 @@ import * as _ from 'lodash'; export class FormatterService { constructor() {} - truncate(n, maxWidth) { - const stringized = n.toString(); - const parts = stringized.split('.'); + truncate(n: number | string, decimals: number): string { + const value = n.toString(); + const parts = value.split('.'); if (parts.length === 1) { - // Just an int - return stringized; + return value; // integer } else { - const fractionalDigits = maxWidth - parts[0].length - 1; - if (fractionalDigits <= 0) { - // No width available for the fractional part, drop - // it and the decimal point - return parts[0]; - } else { - return stringized.substring(0, maxWidth); - } + return Number.parseFloat(value).toPrecision(decimals + parts[0].length) + .toString().replace(/0+$/, ''); } } - format_number(n, divisor, units) { - const width = 4; - let unit = 0; - - if (n == null) { - // People shouldn't really be passing null, but let's - // do something sensible instead of barfing. - return '-'; - } - - while (Math.floor(n / divisor ** unit).toString().length > width - 1) { - unit = unit + 1; + format_number(n: any, divisor: number, units: string[], decimals: number = 4): string { + if (_.isString(n)) { + n = Number(n); } - - let truncatedFloat; - if (unit > 0) { - truncatedFloat = this.truncate( - (n / Math.pow(divisor, unit)).toString(), - width - ); - } else { - truncatedFloat = this.truncate(n, width); + if (!(_.isNumber(n)) || n === 0) { + return '-'; } - + const unit = Math.floor(Math.log(n) / Math.log(divisor)); + const truncatedFloat = this.truncate((n / Math.pow(divisor, unit)), decimals); return truncatedFloat === '' ? '-' : (truncatedFloat + units[unit]); } @@ -59,42 +37,15 @@ export class FormatterService { */ toBytes(value: string): number | null { const base = 1024; - const units = { - 'b': 1, - 'k': Math.pow(base, 1), - 'kb': Math.pow(base, 1), - 'kib': Math.pow(base, 1), - 'm': Math.pow(base, 2), - 'mb': Math.pow(base, 2), - 'mib': Math.pow(base, 2), - 'g': Math.pow(base, 3), - 'gb': Math.pow(base, 3), - 'gib': Math.pow(base, 3), - 't': Math.pow(base, 4), - 'tb': Math.pow(base, 4), - 'tib': Math.pow(base, 4), - 'p': Math.pow(base, 5), - 'pb': Math.pow(base, 5), - 'pib': Math.pow(base, 5), - 'e': Math.pow(base, 6), - 'eb': Math.pow(base, 6), - 'eib': Math.pow(base, 6), - 'z': Math.pow(base, 7), - 'zb': Math.pow(base, 7), - 'zib': Math.pow(base, 7), - 'y': Math.pow(base, 8), - 'yb': Math.pow(base, 8), - 'yib': Math.pow(base, 8) - }; - const m = RegExp('^(\\d+(\.\\d+)?)\\s*(B|K(B|iB)?|M(B|iB)?|G(B|iB)?|T(B|iB)?|P(B|iB)?|' + - 'E(B|iB)?|Z(B|iB)?|Y(B|iB)?)?$', 'i').exec(value); + const units = ['b', 'k', 'm', 'g', 't', 'p', 'e', 'z', 'y']; + const m = RegExp('^(\\d+(\.\\d+)?) ?(\[' + units.join('') + '\](b|ib)?)?$', 'i').exec(value); if (m === null) { return null; } let bytes = parseFloat(m[1]); if (_.isString(m[3])) { - bytes = bytes * units[m[3].toLowerCase()]; + bytes = bytes * Math.pow(base, units.indexOf(m[3].toLowerCase()[0])); } - return Math.floor(bytes); + return Math.round(bytes); } } -- 2.47.3