This is an automated email from the ASF dual-hosted git repository.

villebro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 0e9c0f621a feat(formatting): Add memory units adaptive formatter to 
format bytes (#30559)
0e9c0f621a is described below

commit 0e9c0f621ac9ddbcf889045f3d4772b1ee213e8d
Author: Mateusz Kopeć <[email protected]>
AuthorDate: Sat Oct 12 01:02:03 2024 +0200

    feat(formatting): Add memory units adaptive formatter to format bytes 
(#30559)
---
 .../src/utils/D3Formatting.ts                      |  2 +
 .../factories/createMemoryFormatter.ts             | 55 +++++++++++++
 .../superset-ui-core/src/number-format/index.ts    |  1 +
 .../factories/createMemoryFormatter.test.ts        | 94 ++++++++++++++++++++++
 .../test/number-format/index.test.ts               |  2 +
 superset-frontend/src/setup/setupFormatters.ts     |  5 +-
 6 files changed, 158 insertions(+), 1 deletion(-)

diff --git 
a/superset-frontend/packages/superset-ui-chart-controls/src/utils/D3Formatting.ts
 
b/superset-frontend/packages/superset-ui-chart-controls/src/utils/D3Formatting.ts
index a8fd6312cb..5541c4a4b4 100644
--- 
a/superset-frontend/packages/superset-ui-chart-controls/src/utils/D3Formatting.ts
+++ 
b/superset-frontend/packages/superset-ui-chart-controls/src/utils/D3Formatting.ts
@@ -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(
diff --git 
a/superset-frontend/packages/superset-ui-core/src/number-format/factories/createMemoryFormatter.ts
 
b/superset-frontend/packages/superset-ui-core/src/number-format/factories/createMemoryFormatter.ts
new file mode 100644
index 0000000000..8d62948939
--- /dev/null
+++ 
b/superset-frontend/packages/superset-ui-core/src/number-format/factories/createMemoryFormatter.ts
@@ -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`,
+  });
+}
diff --git 
a/superset-frontend/packages/superset-ui-core/src/number-format/index.ts 
b/superset-frontend/packages/superset-ui-core/src/number-format/index.ts
index c65537552e..b9835d332d 100644
--- a/superset-frontend/packages/superset-ui-core/src/number-format/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/number-format/index.ts
@@ -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';
diff --git 
a/superset-frontend/packages/superset-ui-core/test/number-format/factories/createMemoryFormatter.test.ts
 
b/superset-frontend/packages/superset-ui-core/test/number-format/factories/createMemoryFormatter.test.ts
new file mode 100644
index 0000000000..e4dc37d77a
--- /dev/null
+++ 
b/superset-frontend/packages/superset-ui-core/test/number-format/factories/createMemoryFormatter.test.ts
@@ -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');
+});
diff --git 
a/superset-frontend/packages/superset-ui-core/test/number-format/index.test.ts 
b/superset-frontend/packages/superset-ui-core/test/number-format/index.test.ts
index 09395e722e..103f5e44a9 100644
--- 
a/superset-frontend/packages/superset-ui-core/test/number-format/index.test.ts
+++ 
b/superset-frontend/packages/superset-ui-core/test/number-format/index.test.ts
@@ -21,6 +21,7 @@ import {
   createD3NumberFormatter,
   createDurationFormatter,
   createSiAtMostNDigitFormatter,
+  createMemoryFormatter,
   formatNumber,
   getNumberFormatter,
   getNumberFormatterRegistry,
@@ -35,6 +36,7 @@ describe('index', () => {
       createD3NumberFormatter,
       createDurationFormatter,
       createSiAtMostNDigitFormatter,
+      createMemoryFormatter,
       formatNumber,
       getNumberFormatter,
       getNumberFormatterRegistry,
diff --git a/superset-frontend/src/setup/setupFormatters.ts 
b/superset-frontend/src/setup/setupFormatters.ts
index e18aeba9dc..384b1be9e3 100644
--- a/superset-frontend/src/setup/setupFormatters.ts
+++ b/superset-frontend/src/setup/setupFormatters.ts
@@ -28,6 +28,7 @@ import {
   createSmartDateFormatter,
   createSmartDateVerboseFormatter,
   createSmartDateDetailedFormatter,
+  createMemoryFormatter,
 } from '@superset-ui/core';
 import { FormatLocaleDefinition } from 'd3-format';
 import { TimeLocaleDefinition } from 'd3-time-format';
@@ -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();
 

Reply via email to