Skip to content

Commit

Permalink
feat(formatting): Add memory units adaptive formatter to format bytes (
Browse files Browse the repository at this point in the history
  • Loading branch information
mkopec87 authored Oct 11, 2024
1 parent 47c1e09 commit 0e9c0f6
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export const D3_FORMAT_OPTIONS: [string, string][] = [
...d3Formatted,
['DURATION', t('Duration in ms (66000 => 1m 6s)')],
['DURATION_SUB', t('Duration in ms (1.40008 => 1ms 400µs 80ns)')],
['MEMORY_DECIMAL', t('Memory in bytes - decimal (1024B => 1.024kB)')],
['MEMORY_BINARY', t('Memory in bytes - binary (1024B => 1KiB)')],
];

export const D3_TIME_FORMAT_DOCS = t(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 NumberFormatter from '../NumberFormatter';

export default function createMemoryFormatter(
config: {
description?: string;
id?: string;
label?: string;
binary?: boolean;
decimals?: number;
} = {},
) {
const { description, id, label, binary, decimals = 2 } = config;

return new NumberFormatter({
description,
formatFunc: value => {
if (value === 0) return '0B';

const sign = value > 0 ? '' : '-';
const absValue = Math.abs(value);

const suffixes = binary
? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
: ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'RB', 'QB'];
const base = binary ? 1024 : 1000;

const i = Math.min(
suffixes.length - 1,
Math.floor(Math.log(absValue) / Math.log(base)),
);
return `${sign}${parseFloat((absValue / Math.pow(base, i)).toFixed(decimals))}${suffixes[i]}`;
},
id: id ?? 'memory_format',
label: label ?? `Memory formatter`,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ export {
export { default as NumberFormatterRegistry } from './NumberFormatterRegistry';
export { default as createD3NumberFormatter } from './factories/createD3NumberFormatter';
export { default as createDurationFormatter } from './factories/createDurationFormatter';
export { default as createMemoryFormatter } from './factories/createMemoryFormatter';
export { default as createSiAtMostNDigitFormatter } from './factories/createSiAtMostNDigitFormatter';
export { default as createSmartNumberFormatter } from './factories/createSmartNumberFormatter';
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 { NumberFormatter, createMemoryFormatter } from '@superset-ui/core';

test('creates an instance of MemoryFormatter', () => {
const formatter = createMemoryFormatter();
expect(formatter).toBeInstanceOf(NumberFormatter);
});

test('formats bytes in human readable format with default options', () => {
const formatter = createMemoryFormatter();
expect(formatter(0)).toBe('0B');
expect(formatter(50)).toBe('50B');
expect(formatter(555)).toBe('555B');
expect(formatter(1000)).toBe('1kB');
expect(formatter(1111)).toBe('1.11kB');
expect(formatter(1024)).toBe('1.02kB');
expect(formatter(1337)).toBe('1.34kB');
expect(formatter(1999)).toBe('2kB');
expect(formatter(10 * 1000)).toBe('10kB');
expect(formatter(100 * 1000)).toBe('100kB');
expect(formatter(Math.pow(1000, 2))).toBe('1MB');
expect(formatter(Math.pow(1000, 3))).toBe('1GB');
expect(formatter(Math.pow(1000, 4))).toBe('1TB');
expect(formatter(Math.pow(1000, 5))).toBe('1PB');
expect(formatter(Math.pow(1000, 6))).toBe('1EB');
expect(formatter(Math.pow(1000, 7))).toBe('1ZB');
expect(formatter(Math.pow(1000, 8))).toBe('1YB');
expect(formatter(Math.pow(1000, 9))).toBe('1RB');
expect(formatter(Math.pow(1000, 10))).toBe('1QB');
expect(formatter(Math.pow(1000, 11))).toBe('1000QB');
expect(formatter(Math.pow(1000, 12))).toBe('1000000QB');
});

test('formats negative bytes in human readable format with default options', () => {
const formatter = createMemoryFormatter();
expect(formatter(-50)).toBe('-50B');
});

test('formats float bytes in human readable format with default options', () => {
const formatter = createMemoryFormatter();
expect(formatter(10.666)).toBe('10.67B');
expect(formatter(1200.666)).toBe('1.2kB');
});

test('formats bytes in human readable format with additional binary option', () => {
const formatter = createMemoryFormatter({ binary: true });
expect(formatter(0)).toBe('0B');
expect(formatter(50)).toBe('50B');
expect(formatter(555)).toBe('555B');
expect(formatter(1000)).toBe('1000B');
expect(formatter(1111)).toBe('1.08KiB');
expect(formatter(1024)).toBe('1KiB');
expect(formatter(1337)).toBe('1.31KiB');
expect(formatter(2047)).toBe('2KiB');
expect(formatter(10 * 1024)).toBe('10KiB');
expect(formatter(100 * 1024)).toBe('100KiB');
expect(formatter(Math.pow(1024, 2))).toBe('1MiB');
expect(formatter(Math.pow(1024, 3))).toBe('1GiB');
expect(formatter(Math.pow(1024, 4))).toBe('1TiB');
expect(formatter(Math.pow(1024, 5))).toBe('1PiB');
expect(formatter(Math.pow(1024, 6))).toBe('1EiB');
expect(formatter(Math.pow(1024, 7))).toBe('1ZiB');
expect(formatter(Math.pow(1024, 8))).toBe('1YiB');
expect(formatter(Math.pow(1024, 9))).toBe('1024YiB');
expect(formatter(Math.pow(1024, 10))).toBe('1048576YiB');
});

test('formats bytes in human readable format with additional decimals option', () => {
const formatter0decimals = createMemoryFormatter({ decimals: 0 });
expect(formatter0decimals(0)).toBe('0B');
expect(formatter0decimals(1111)).toBe('1kB');

const formatter3decimals = createMemoryFormatter({ decimals: 3 });
expect(formatter3decimals(0)).toBe('0B');
expect(formatter3decimals(1111)).toBe('1.111kB');
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
createD3NumberFormatter,
createDurationFormatter,
createSiAtMostNDigitFormatter,
createMemoryFormatter,
formatNumber,
getNumberFormatter,
getNumberFormatterRegistry,
Expand All @@ -35,6 +36,7 @@ describe('index', () => {
createD3NumberFormatter,
createDurationFormatter,
createSiAtMostNDigitFormatter,
createMemoryFormatter,
formatNumber,
getNumberFormatter,
getNumberFormatterRegistry,
Expand Down
5 changes: 4 additions & 1 deletion superset-frontend/src/setup/setupFormatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
createSmartDateFormatter,
createSmartDateVerboseFormatter,
createSmartDateDetailedFormatter,
createMemoryFormatter,
} from '@superset-ui/core';
import { FormatLocaleDefinition } from 'd3-format';
import { TimeLocaleDefinition } from 'd3-time-format';
Expand Down Expand Up @@ -76,7 +77,9 @@ export default function setupFormatters(
.registerValue(
'DURATION_SUB',
createDurationFormatter({ formatSubMilliseconds: true }),
);
)
.registerValue('MEMORY_DECIMAL', createMemoryFormatter({ binary: false }))
.registerValue('MEMORY_BINARY', createMemoryFormatter({ binary: true }));

const timeFormatterRegistry = getTimeFormatterRegistry();

Expand Down

0 comments on commit 0e9c0f6

Please sign in to comment.