diff --git a/package-lock.json b/package-lock.json index d896ddc9653..6346a2298ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48287,7 +48287,6 @@ "mongodb-collection-model": "^5.35.3", "mongodb-ns": "^3.0.1", "mongodb-schema": "^12.6.3", - "numeral": "^2.0.6", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", @@ -48338,15 +48337,6 @@ "node": ">=0.3.1" } }, - "packages/compass-collection/node_modules/numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "packages/compass-collection/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -49156,7 +49146,6 @@ "mongodb-data-service": "^22.34.3", "mongodb-ns": "^3.0.1", "mongodb-query-parser": "^4.3.0", - "numeral": "^2.0.6", "react": "^17.0.2", "reflux": "^0.4.1", "semver": "^7.6.3" @@ -49274,14 +49263,6 @@ "path-to-regexp": "^6.2.1" } }, - "packages/compass-crud/node_modules/numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==", - "engines": { - "node": "*" - } - }, "packages/compass-crud/node_modules/path-to-regexp": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", @@ -50754,7 +50735,6 @@ "mongodb-mql-engines": "^0.0.4", "mongodb-ns": "^3.0.1", "mongodb-query-parser": "^4.3.0", - "numeral": "^2.0.6", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", @@ -50767,7 +50747,6 @@ "@mongodb-js/prettier-config-compass": "^1.2.9", "@mongodb-js/testing-library-compass": "^1.3.17", "@mongodb-js/tsconfig-compass": "^1.2.12", - "@types/numeral": "^2.0.5", "chai": "^4.2.0", "depcheck": "^1.4.1", "electron": "^37.6.1", @@ -50819,14 +50798,6 @@ "bson": "^4.6.3 || ^5 || ^6" } }, - "packages/compass-indexes/node_modules/numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==", - "engines": { - "node": "*" - } - }, "packages/compass-indexes/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -51433,6 +51404,7 @@ "@types/leaflet": "^1.9.8", "@types/leaflet-draw": "^1.0.11", "@types/mocha": "^9.0.0", + "@types/numeral": "^2.0.5", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.10", "chai": "^4.3.4", @@ -62331,7 +62303,6 @@ "mongodb-collection-model": "^5.35.3", "mongodb-ns": "^3.0.1", "mongodb-schema": "^12.6.3", - "numeral": "^2.0.6", "nyc": "^15.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -62357,11 +62328,6 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, - "numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==" - }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -63082,7 +63048,6 @@ "mongodb-instance-model": "^12.51.0", "mongodb-ns": "^3.0.1", "mongodb-query-parser": "^4.3.0", - "numeral": "^2.0.6", "nyc": "^15.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -63167,11 +63132,6 @@ "path-to-regexp": "^6.2.1" } }, - "numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==" - }, "path-to-regexp": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", @@ -64060,7 +64020,6 @@ "@mongodb-js/shell-bson-parser": "^1.2.0", "@mongodb-js/testing-library-compass": "^1.3.17", "@mongodb-js/tsconfig-compass": "^1.2.12", - "@types/numeral": "^2.0.5", "bson": "^6.10.4", "chai": "^4.2.0", "compass-preferences-model": "^2.59.0", @@ -64075,7 +64034,6 @@ "mongodb-mql-engines": "^0.0.4", "mongodb-ns": "^3.0.1", "mongodb-query-parser": "^4.3.0", - "numeral": "^2.0.6", "nyc": "^15.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -64115,11 +64073,6 @@ "lodash": "^4.17.21" } }, - "numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==" - }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -64534,6 +64487,7 @@ "@types/leaflet": "^1.9.8", "@types/leaflet-draw": "^1.0.11", "@types/mocha": "^9.0.0", + "@types/numeral": "^2.0.5", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.10", "bson": "^6.10.4", diff --git a/packages/compass-collection/package.json b/packages/compass-collection/package.json index ea5a8c795fb..a755c17a9ca 100644 --- a/packages/compass-collection/package.json +++ b/packages/compass-collection/package.json @@ -68,7 +68,6 @@ "mongodb-collection-model": "^5.35.3", "mongodb-ns": "^3.0.1", "mongodb-schema": "^12.6.3", - "numeral": "^2.0.6", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx index 1a44fe569eb..f17816f34c8 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx @@ -1,5 +1,6 @@ import { Body, + compactBytes, css, palette, spacing, @@ -9,7 +10,6 @@ import React, { useMemo } from 'react'; import { connect } from 'react-redux'; import type { CollectionState } from '../../modules/collection-tab'; import type { SchemaAnalysisState } from '../../schema-analysis-types'; -import numeral from 'numeral'; import { DEFAULT_DOCUMENT_COUNT, MAX_DOCUMENT_COUNT } from './constants'; const BYTE_PRECISION_THRESHOLD = 1000; @@ -40,8 +40,8 @@ const boldStyles = css({ }); const formatBytes = (bytes: number) => { - const precision = bytes <= BYTE_PRECISION_THRESHOLD ? '0' : '0.0'; - return numeral(bytes).format(precision + 'b'); + const decimals = bytes <= BYTE_PRECISION_THRESHOLD ? 0 : 1; + return compactBytes(bytes, true, decimals); }; type ErrorState = @@ -101,7 +101,7 @@ const DocumentCountScreen = ({ } }; - return schemaAnalysisState.status === 'complete' ? ( + return (
Specify Number of Documents to Generate @@ -130,9 +130,6 @@ const DocumentCountScreen = ({
- ) : ( - // Not reachable since schema analysis must be finished before the modal can be opened -
We are analyzing your collection.
); }; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx index 8a786a36197..96492f25e3f 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx @@ -811,10 +811,10 @@ describe('MockDataGeneratorModal', () => { ); userEvent.clear(documentCountInput); userEvent.type(documentCountInput, '1000'); - expect(screen.getByText('100.0KB')).to.exist; + expect(screen.getByText('100.0 kB')).to.exist; userEvent.clear(documentCountInput); userEvent.type(documentCountInput, '2000'); - expect(screen.getByText('200.0KB')).to.exist; + expect(screen.getByText('200.0 kB')).to.exist; }); }); diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index e37b137ec16..83b5dadba23 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -218,6 +218,7 @@ export { } from './components/links/link'; export { ChevronCollapse } from './components/chevron-collapse-icon'; export { formatDate } from './utils/format-date'; +export { compactBytes, compactNumber } from './utils/format'; export { VirtualList, type VirtualListRef, diff --git a/packages/compass-components/src/utils/format.ts b/packages/compass-components/src/utils/format.ts new file mode 100644 index 00000000000..77f8caf2ff8 --- /dev/null +++ b/packages/compass-components/src/utils/format.ts @@ -0,0 +1,43 @@ +/** + * Format bytes into a human-readable string with appropriate units. + * + * @param bytes - The number of bytes to format + * @param si - Use SI units (1000-based) if true, binary units (1024-based) if false + * @param decimals - Number of decimal places to show + * @returns Formatted string with units (e.g., "1.5 MB", "2.0 KiB", "-1.5 MB") + */ +export function compactBytes(bytes: number, si = true, decimals = 2): string { + const isNegative = bytes < 0; + const absBytes = Math.abs(bytes); + + if (absBytes === 0) { + return '0 B'; + } + + const threshold = si ? 1000 : 1024; + const units = si + ? ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + : ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + let i = Math.floor(Math.log(absBytes) / Math.log(threshold)); + if (i >= units.length) { + i = units.length - 1; + } + const num = absBytes / Math.pow(threshold, i); + return `${isNegative ? '-' : ''}${num.toFixed(decimals)} ${units[i]}`; +} + +/** + * Format a number into a compact notation with appropriate suffix. + * + * @param number - The number to format + * @returns Formatted string with compact notation (e.g., "1.5K", "2M") + */ +export function compactNumber(number: number): string { + return new Intl.NumberFormat('en', { + notation: 'compact', + }) + .formatToParts(number) + .reduce((acc, part) => { + return `${acc}${part.value}`; + }, ''); +} diff --git a/packages/compass-crud/package.json b/packages/compass-crud/package.json index a1b3a3d7847..b5e17cfd733 100644 --- a/packages/compass-crud/package.json +++ b/packages/compass-crud/package.json @@ -98,7 +98,6 @@ "mongodb-data-service": "^22.34.3", "mongodb-ns": "^3.0.1", "mongodb-query-parser": "^4.3.0", - "numeral": "^2.0.6", "react": "^17.0.2", "reflux": "^0.4.1", "semver": "^7.6.3" diff --git a/packages/compass-crud/src/plugin-title.tsx b/packages/compass-crud/src/plugin-title.tsx index 8d5f8665404..91bba381fa0 100644 --- a/packages/compass-crud/src/plugin-title.tsx +++ b/packages/compass-crud/src/plugin-title.tsx @@ -1,6 +1,12 @@ import React, { useMemo } from 'react'; -import numeral from 'numeral'; -import { css, Tooltip, Badge, spacing } from '@mongodb-js/compass-components'; +import { + css, + Tooltip, + Badge, + spacing, + compactBytes, + compactNumber, +} from '@mongodb-js/compass-components'; import type { CrudStore } from './stores/crud-store'; import { usePreference } from 'compass-preferences-model/provider'; @@ -22,12 +28,14 @@ const isNumber = (val: any): val is number => { return typeof val === 'number' && !isNaN(val); }; -const format = (value: any, format = 'a') => { +const format = (value: any, formatType: 'number' | 'bytes' = 'number') => { if (!isNumber(value)) { return INVALID; } - const precision = value <= 1000 ? '0' : '0.0'; - return numeral(value).format(precision + format); + const decimals = value <= 1000 ? 0 : 1; + return formatType === 'bytes' + ? compactBytes(value, true, decimals) + : compactNumber(value); }; type CollectionStatsProps = { @@ -84,9 +92,9 @@ export const CrudTabTitle = ({ avg_document_size = NaN, } = collectionStats ?? {}; return { - documentCount: format(document_count), - storageSize: format(storage_size - free_storage_size, 'b'), - avgDocumentSize: format(avg_document_size, 'b'), + documentCount: format(document_count, 'number'), + storageSize: format(storage_size - free_storage_size, 'bytes'), + avgDocumentSize: format(avg_document_size, 'bytes'), }; }, [collectionStats]); const enableDbAndCollStats = usePreference('enableDbAndCollStats'); diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index 868090a363f..c60cf72b5f0 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -53,7 +53,6 @@ "@mongodb-js/prettier-config-compass": "^1.2.9", "@mongodb-js/testing-library-compass": "^1.3.17", "@mongodb-js/tsconfig-compass": "^1.2.12", - "@types/numeral": "^2.0.5", "chai": "^4.2.0", "depcheck": "^1.4.1", "electron": "^37.6.1", @@ -88,7 +87,6 @@ "mongodb-mql-engines": "^0.0.4", "mongodb-ns": "^3.0.1", "mongodb-query-parser": "^4.3.0", - "numeral": "^2.0.6", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", diff --git a/packages/compass-indexes/src/components/regular-indexes-table/size-field.spec.tsx b/packages/compass-indexes/src/components/regular-indexes-table/size-field.spec.tsx index 6ed53c69927..7c5d1fd597a 100644 --- a/packages/compass-indexes/src/components/regular-indexes-table/size-field.spec.tsx +++ b/packages/compass-indexes/src/components/regular-indexes-table/size-field.spec.tsx @@ -17,8 +17,8 @@ describe('SizeField', function () { describe('SizeField functions', function () { it('formats size', function () { expect(formatSize(908)).to.equal('908 B'); - expect(formatSize(2020)).to.equal('2.0 KB'); - expect(formatSize(202020)).to.equal('202.0 KB'); + expect(formatSize(2020)).to.equal('2.0 kB'); + expect(formatSize(202020)).to.equal('202.0 kB'); }); it('returns correct tooltip', function () { diff --git a/packages/compass-indexes/src/components/regular-indexes-table/size-field.tsx b/packages/compass-indexes/src/components/regular-indexes-table/size-field.tsx index 4038c9ab3b8..7b8f195810b 100644 --- a/packages/compass-indexes/src/components/regular-indexes-table/size-field.tsx +++ b/packages/compass-indexes/src/components/regular-indexes-table/size-field.tsx @@ -1,6 +1,5 @@ -import numeral from 'numeral'; import React from 'react'; -import { Body, Tooltip } from '@mongodb-js/compass-components'; +import { Body, Tooltip, compactBytes } from '@mongodb-js/compass-components'; type SizeFieldProps = { size: number; @@ -8,8 +7,8 @@ type SizeFieldProps = { }; export const formatSize = (size: number) => { - const precision = size <= 1000 ? '0' : '0.0'; - return numeral(size).format(precision + ' b'); + const decimals = size <= 1000 ? 0 : 1; + return compactBytes(size, true, decimals); }; export const getSizeTooltip = (relativeSize: number): string => { diff --git a/packages/compass-indexes/src/plugin-title.tsx b/packages/compass-indexes/src/plugin-title.tsx index 8cc0cd433d9..83a6e69a304 100644 --- a/packages/compass-indexes/src/plugin-title.tsx +++ b/packages/compass-indexes/src/plugin-title.tsx @@ -1,8 +1,14 @@ import React, { useMemo } from 'react'; import { connect } from 'react-redux'; import type { RootState } from './modules'; -import { Badge, css, spacing, Tooltip } from '@mongodb-js/compass-components'; -import numeral from 'numeral'; +import { + Badge, + css, + spacing, + Tooltip, + compactBytes, + compactNumber, +} from '@mongodb-js/compass-components'; import { usePreference } from 'compass-preferences-model/provider'; const containerStyles = css({ @@ -30,12 +36,14 @@ const isNumber = (val: any): val is number => { return typeof val === 'number' && !isNaN(val); }; -const format = (value: any, format = 'a') => { +const format = (value: any, formatType: 'number' | 'bytes' = 'number') => { if (!isNumber(value)) { return INVALID; } - const precision = value <= 1000 ? '0' : '0.0'; - return numeral(value).format(precision + format); + const decimals = value <= 1000 ? 0 : 1; + return formatType === 'bytes' + ? compactBytes(value, true, decimals) + : compactNumber(value); }; type CollectionStatsProps = { @@ -85,9 +93,9 @@ const TabTitle = ({ const { indexCount, totalIndexSize, avgIndexSize } = useMemo(() => { const { index_count = NaN, index_size = NaN } = collectionStats ?? {}; return { - indexCount: format(index_count), - totalIndexSize: format(index_size, 'b'), - avgIndexSize: format(avg(index_size, index_count), 'b'), + indexCount: format(index_count, 'number'), + totalIndexSize: format(index_size, 'bytes'), + avgIndexSize: format(avg(index_size, index_count), 'bytes'), }; }, [collectionStats]); diff --git a/packages/compass-schema/package.json b/packages/compass-schema/package.json index 723ad718efd..7be3896b51a 100644 --- a/packages/compass-schema/package.json +++ b/packages/compass-schema/package.json @@ -58,6 +58,7 @@ "@types/leaflet": "^1.9.8", "@types/leaflet-draw": "^1.0.11", "@types/mocha": "^9.0.0", + "@types/numeral": "^2.0.5", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.10", "chai": "^4.3.4", diff --git a/packages/databases-collections-list/src/collections.tsx b/packages/databases-collections-list/src/collections.tsx index 5c8e0e199fa..4281490f1aa 100644 --- a/packages/databases-collections-list/src/collections.tsx +++ b/packages/databases-collections-list/src/collections.tsx @@ -1,6 +1,10 @@ import React from 'react'; -import { css, spacing } from '@mongodb-js/compass-components'; -import { compactBytes, compactNumber } from './format'; +import { + css, + spacing, + compactBytes, + compactNumber, +} from '@mongodb-js/compass-components'; import type { BadgeProp } from './namespace-card'; import { NamespaceItemCard } from './namespace-card'; import { ItemsGrid } from './items-grid'; diff --git a/packages/databases-collections-list/src/databases.tsx b/packages/databases-collections-list/src/databases.tsx index 8e9bad3cbdb..f9e6c2ebce1 100644 --- a/packages/databases-collections-list/src/databases.tsx +++ b/packages/databases-collections-list/src/databases.tsx @@ -1,7 +1,11 @@ /* eslint-disable react/prop-types */ import React from 'react'; -import { PerformanceSignals, spacing } from '@mongodb-js/compass-components'; -import { compactBytes, compactNumber } from './format'; +import { + PerformanceSignals, + spacing, + compactBytes, + compactNumber, +} from '@mongodb-js/compass-components'; import { NamespaceItemCard } from './namespace-card'; import { ItemsGrid } from './items-grid'; import type { DatabaseProps } from 'mongodb-database-model'; diff --git a/packages/databases-collections-list/src/format.ts b/packages/databases-collections-list/src/format.ts deleted file mode 100644 index c9c8b36ddd4..00000000000 --- a/packages/databases-collections-list/src/format.ts +++ /dev/null @@ -1,25 +0,0 @@ -export function compactBytes(bytes: number, si = true, decimals = 2): string { - const threshold = si ? 1000 : 1024; - if (bytes === 0) { - return `${bytes} B`; - } - const units = si - ? ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - : ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; - const i = Math.floor(Math.log(bytes) / Math.log(threshold)); - const num = bytes / Math.pow(threshold, i); - return `${num.toFixed(decimals)} ${units[i]}`; -} - -export function compactNumber(number: number): string { - return new Intl.NumberFormat('en', { - notation: 'compact', - }) - .formatToParts(number) - .reduce((acc, part) => { - if (part.type === 'compact') { - return `${acc} ${part.value}`; - } - return `${acc}${part.value}`; - }, ''); -}