From ab1a65b6be8c7a5c34a3111cd2d429e8483fca52 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Fri, 1 Nov 2024 19:59:00 +0100 Subject: [PATCH 01/17] refactor(ui): BundleAssets - combine data extraction --- .../bundle-assets/bundle-assets.utils.js | 65 ++++++++++++------- .../ui/src/components/bundle-assets/index.jsx | 9 +-- packages/ui/src/types.d.ts | 29 ++++++++- 3 files changed, 71 insertions(+), 32 deletions(-) diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js index 41d1fd4690..048f94a3fa 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js +++ b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js @@ -1,26 +1,20 @@ -import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, getFileType } from '@bundle-stats/utils'; - -export const addRowAssetFlags = (row) => { - const { runs } = row; +/** + * @type {import('@bundle-stats/utils').ReportMetricRow} ReportMetricRow + * @type {import('../../types').ReportMetricAssetRow} ReportMetricAssetRow + */ - const isEntry = runs.map((run) => run?.isEntry).includes(true); - const isInitial = runs.map((run) => run?.isInitial).includes(true); - const isChunk = runs.map((run) => run?.isChunk).includes(true); - const isAsset = !(isEntry || isInitial || isChunk); - const fileType = getFileType(row.key); +import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, getFileType } from '@bundle-stats/utils'; - return { - ...row, - isEntry, - isInitial, - isChunk, - isAsset, - fileType, - }; -}; +/** + * Check if the asset cache is not predictive + * + * @param {ReportMetricRow} row + * @returns {boolean} + */ +const getIsNotPredictive = (row) => { + const { key, runs } = row; -export const getIsNotPredictive = (key, runs) => - runs.reduce((agg, current, index) => { + return runs.reduce((agg, current, index) => { if (agg) { return agg; } @@ -41,11 +35,34 @@ export const getIsNotPredictive = (key, runs) => return agg; }, false); +}; + +/** + * Add asset row flags + * + * @param {ReportMetricRow} row + * @returns {ReportMetricAssetRow} + */ +export const addMetricReportAssetRowData = (row) => { + const { runs } = row; + + const isEntry = runs.map((run) => run?.isEntry).includes(true); + const isInitial = runs.map((run) => run?.isInitial).includes(true); + const isChunk = runs.map((run) => run?.isChunk).includes(true); + const isAsset = !(isEntry || isInitial || isChunk); + const isNotPredictive = getIsNotPredictive(row); + const fileType = getFileType(row.key); -export const addRowIsNotPredictive = (row) => ({ - ...row, - isNotPredictive: getIsNotPredictive(row.key, row.runs), -}); + return { + ...row, + isEntry, + isInitial, + isChunk, + isAsset, + isNotPredictive, + fileType, + }; +}; export const getRowFilter = (filters) => (item) => { if (filters[ASSET_FILTERS.CHANGED] && !item.changed) { diff --git a/packages/ui/src/components/bundle-assets/index.jsx b/packages/ui/src/components/bundle-assets/index.jsx index 751d9a79ef..ae7f875681 100644 --- a/packages/ui/src/components/bundle-assets/index.jsx +++ b/packages/ui/src/components/bundle-assets/index.jsx @@ -13,12 +13,7 @@ import { useSearchParams } from '../../hooks/search-params'; import { useEntryInfo } from '../../hooks/entry-info'; import { getJobsChunksData } from '../../utils/jobs'; import { BundleAssets as BundleAssetsComponent } from './bundle-assets'; -import { - addRowAssetFlags, - addRowIsNotPredictive, - getRowFilter, - getCustomSort, -} from './bundle-assets.utils'; +import { addMetricReportAssetRowData, getRowFilter, getCustomSort } from './bundle-assets.utils'; export const BundleAssets = (props) => { const { jobs, filters, search, setState, sortBy, direction, ...restProps } = props; @@ -50,7 +45,7 @@ export const BundleAssets = (props) => { }); const { rows, totalRowCount } = useMemo(() => { - const result = webpack.compareBySection.assets(jobs, [addRowAssetFlags, addRowIsNotPredictive]); + const result = webpack.compareBySection.assets(jobs, [addMetricReportAssetRowData]); return { rows: result, totalRowCount: result.length }; }, [jobs]); diff --git a/packages/ui/src/types.d.ts b/packages/ui/src/types.d.ts index 884f20dc74..79778c70a4 100644 --- a/packages/ui/src/types.d.ts +++ b/packages/ui/src/types.d.ts @@ -1,5 +1,5 @@ import type { ReportMetricRow } from '@bundle-stats/utils'; -import type { Module } from '@bundle-stats/utils/types/webpack/types'; +import type { Asset, Module } from '@bundle-stats/utils/types/webpack/types'; export interface SortAction { field: string; @@ -19,6 +19,33 @@ type FilterGroupFieldData = { type FilterFieldsData = Record; +export type ReportMetricAssetRow = { + /** + * Asset isEntry - at least one run has isEntry truthy + */ + isEntry: boolean; + /** + * Asset isInitial - at least one run has isInitial truthy + */ + isInitial: boolean; + /** + * Asset isChunk - at least one run has isChunk truthy + */ + isChunk: boolean; + /** + * Asset isAsset - at least one run has isAsset truthy + */ + isAsset: boolean; + /** + * Asset name is not predictive + */ + isNotPredictive: boolean; + /** + * Report asset row isEntry - at least one run has isEntry + */ + fileType: string; +} & Omit & { runs: Array<(Asset & ReportMetricRun) | null> }; + export type ReportMetricModuleRow = { thirdParty: boolean; duplicated: boolean; From ea9eb2a1e28bedcfd1beff2ac7f7ba704feb4bc4 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sat, 2 Nov 2024 13:21:57 +0100 Subject: [PATCH 02/17] test(ui): Storybook - add QueryStateProvider to the default decorator --- packages/ui/src/stories/wrapper.jsx | 36 +++++++++++++++++------------ 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/stories/wrapper.jsx b/packages/ui/src/stories/wrapper.jsx index e9b76eba16..964fa63f24 100644 --- a/packages/ui/src/stories/wrapper.jsx +++ b/packages/ui/src/stories/wrapper.jsx @@ -1,19 +1,25 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -export const getWrapperDecorator = (customWrapperStyles = {}) => (storyFn) => { - const wrapperStyles = { - width: '100%', - height: '100%', - padding: '24px', - ...customWrapperStyles, - }; +import { QueryStateProvider } from '../query-state'; + +export const getWrapperDecorator = + (customWrapperStyles = {}) => + (Story) => { + const wrapperStyles = { + width: '100%', + height: '100%', + padding: '24px', + ...customWrapperStyles, + }; - return ( - -
- {storyFn()} -
-
- ); -}; + return ( + + +
+ +
+
+
+ ); + }; From 6b4725a856e156f76793f87252dd5daa9ca84c7b Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sat, 2 Nov 2024 16:35:08 +0100 Subject: [PATCH 03/17] fix(ui): Remove console.log --- .../ui/src/components/metrics-treemap/metrics-treemap.utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/components/metrics-treemap/metrics-treemap.utils.ts b/packages/ui/src/components/metrics-treemap/metrics-treemap.utils.ts index da7c7121d5..5ae402baaf 100644 --- a/packages/ui/src/components/metrics-treemap/metrics-treemap.utils.ts +++ b/packages/ui/src/components/metrics-treemap/metrics-treemap.utils.ts @@ -141,7 +141,6 @@ export function getTreemapNodesGroupedByPath(items: Array): Tre }; const childrenTotal = setTreeNode(treeNodes, baseSlugs, 0, treeNode); - console.log(childrenTotal); total.current += childrenTotal.current; total.baseline = From 1f50498e3b55646d1ffca05fde24e88f190a5baf Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sat, 2 Nov 2024 13:43:14 +0100 Subject: [PATCH 04/17] test(ui): BundleAssets - add utils tests --- .../add-metric-report-asset-row-data.ts | 181 ++++++++++++++++++ .../__tests__/get-is-not-predictive.ts | 119 ++++++++++++ .../bundle-assets/bundle-assets.utils.js | 6 +- 3 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts create mode 100644 packages/ui/src/components/bundle-assets/__tests__/get-is-not-predictive.ts diff --git a/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts b/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts new file mode 100644 index 0000000000..48b48c74ec --- /dev/null +++ b/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts @@ -0,0 +1,181 @@ +import { addMetricReportAssetRowData } from '../bundle-assets.utils'; + +describe('BundleAssets / addMetricReportAssetRowData', () => { + test('should add data to a single run', () => { + expect( + addMetricReportAssetRowData({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }), + ).toEqual({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + fileType: 'JS', + isAsset: false, + isChunk: true, + isEntry: true, + isInitial: true, + isNotPredictive: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }); + }); + + test('should add add data when baseline run is null', () => { + expect( + addMetricReportAssetRowData({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + null, + ], + }), + ).toEqual({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + fileType: 'JS', + isAsset: false, + isChunk: true, + isEntry: true, + isInitial: true, + isNotPredictive: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + null, + ], + }); + }); + + test('should add add data when curent run is null', () => { + expect( + addMetricReportAssetRowData({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: true, + runs: [ + null, + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }), + ).toEqual({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: true, + fileType: 'JS', + isAsset: false, + isChunk: true, + isEntry: true, + isInitial: true, + isNotPredictive: false, + runs: [ + null, + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }); + }); + + test('should add data when baseline is not null', () => { + expect( + addMetricReportAssetRowData({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }), + ).toEqual({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + fileType: 'JS', + isAsset: false, + isChunk: true, + isEntry: true, + isInitial: true, + isNotPredictive: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }); + }); +}); diff --git a/packages/ui/src/components/bundle-assets/__tests__/get-is-not-predictive.ts b/packages/ui/src/components/bundle-assets/__tests__/get-is-not-predictive.ts new file mode 100644 index 0000000000..dd50964644 --- /dev/null +++ b/packages/ui/src/components/bundle-assets/__tests__/get-is-not-predictive.ts @@ -0,0 +1,119 @@ +import { getIsNotPredictive } from '../bundle-assets.utils'; + +describe('BundleAssets / getIsNotPredictive', () => { + test('should return false by default', () => { + expect( + getIsNotPredictive({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }), + ).toBeFalsy(); + + expect( + getIsNotPredictive({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + null, + ], + }), + ).toBeFalsy(); + + expect( + getIsNotPredictive({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }), + ).toBeFalsy(); + + expect( + getIsNotPredictive({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.def456.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + { + name: 'assets/main.abc123.js', + size: 1024 * 11, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }), + ).toBeFalsy(); + }); + + test('should return true when not predictive', () => { + expect( + getIsNotPredictive({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.abc123.js', + size: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + { + name: 'assets/main.abc123.js', + size: 1024 * 11, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }), + ).toBeTruthy(); + }); +}); diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js index 048f94a3fa..d0aa1c6abb 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js +++ b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js @@ -11,7 +11,7 @@ import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, getFileType } from '@ * @param {ReportMetricRow} row * @returns {boolean} */ -const getIsNotPredictive = (row) => { +export const getIsNotPredictive = (row) => { const { key, runs } = row; return runs.reduce((agg, current, index) => { @@ -26,9 +26,9 @@ const getIsNotPredictive = (row) => { if ( current && runs[index + 1] && - current.delta !== 0 && key !== current.name && - current.name === runs[index + 1].name + current.name === runs[index + 1].name && + current.size !== runs[index + 1].size ) { return true; } From 41b206bb55ef120c14f28c509fdb46148ca44ced Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sat, 2 Nov 2024 15:58:13 +0100 Subject: [PATCH 05/17] feat(ui): BundleAssets - view asset meta changes --- .../add-metric-report-asset-row-data.ts | 20 +++--- .../__tests__/get-asset-meta-status.ts | 21 +++++++ .../__tests__/get-is-not-predictive.ts | 16 ++--- .../bundle-assets/bundle-assets.jsx | 63 +++++++++++-------- .../bundle-assets/bundle-assets.module.css | 23 +++++++ .../bundle-assets/bundle-assets.utils.js | 52 +++++++++++++-- packages/ui/src/types.d.ts | 8 ++- 7 files changed, 151 insertions(+), 52 deletions(-) create mode 100644 packages/ui/src/components/bundle-assets/__tests__/get-asset-meta-status.ts diff --git a/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts b/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts index 48b48c74ec..ee6424b0bb 100644 --- a/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts +++ b/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts @@ -11,7 +11,7 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -32,7 +32,7 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -51,7 +51,7 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -73,7 +73,7 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -94,7 +94,7 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { null, { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -116,7 +116,7 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { null, { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -135,14 +135,14 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, }, { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -163,14 +163,14 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, }, { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, diff --git a/packages/ui/src/components/bundle-assets/__tests__/get-asset-meta-status.ts b/packages/ui/src/components/bundle-assets/__tests__/get-asset-meta-status.ts new file mode 100644 index 0000000000..14c492a4e1 --- /dev/null +++ b/packages/ui/src/components/bundle-assets/__tests__/get-asset-meta-status.ts @@ -0,0 +1,21 @@ +import { getAssetMetaStatus } from '../bundle-assets.utils'; + +describe('BundleAssets / getAssetMetaStatus', () => { + test('should return true when all runs meta values are truthy', () => { + expect(getAssetMetaStatus([true])).toBeTruthy(); + expect(getAssetMetaStatus([true, true])).toBeTruthy(); + }); + + test('should return false when all runs meta values are falsy', () => { + expect(getAssetMetaStatus([false])).toBeFalsy(); + expect(getAssetMetaStatus([false, false])).toBeFalsy(); + }); + + test('should return "added" when current is truthy and baseline is falsy', () => { + expect(getAssetMetaStatus([true, false])).toEqual('added'); + }); + + test('should return "removed" when current is false and baseline is truthy', () => { + expect(getAssetMetaStatus([false, true])).toEqual('removed'); + }); +}); diff --git a/packages/ui/src/components/bundle-assets/__tests__/get-is-not-predictive.ts b/packages/ui/src/components/bundle-assets/__tests__/get-is-not-predictive.ts index dd50964644..b390a43d0c 100644 --- a/packages/ui/src/components/bundle-assets/__tests__/get-is-not-predictive.ts +++ b/packages/ui/src/components/bundle-assets/__tests__/get-is-not-predictive.ts @@ -11,7 +11,7 @@ describe('BundleAssets / getIsNotPredictive', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -29,7 +29,7 @@ describe('BundleAssets / getIsNotPredictive', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -48,14 +48,14 @@ describe('BundleAssets / getIsNotPredictive', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, }, { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, @@ -73,14 +73,14 @@ describe('BundleAssets / getIsNotPredictive', () => { runs: [ { name: 'assets/main.def456.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, }, { name: 'assets/main.abc123.js', - size: 1024 * 11, + value: 1024 * 11, isEntry: true, isChunk: true, isInitial: true, @@ -100,14 +100,14 @@ describe('BundleAssets / getIsNotPredictive', () => { runs: [ { name: 'assets/main.abc123.js', - size: 1024 * 10, + value: 1024 * 10, isEntry: true, isChunk: true, isInitial: true, }, { name: 'assets/main.abc123.js', - size: 1024 * 11, + value: 1024 * 11, isEntry: true, isChunk: true, isInitial: true, diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.jsx b/packages/ui/src/components/bundle-assets/bundle-assets.jsx index 28cd768b27..8e5bd310c6 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.jsx +++ b/packages/ui/src/components/bundle-assets/bundle-assets.jsx @@ -94,6 +94,38 @@ const getFilters = ({ compareMode, filters }) => ({ }, }); +const AssetNameTag = (props) => { + const { className = '', title, status } = props; + + const rootClassName = cx( + css.assetNameTag, + status === 'added' && css.assetNameTagAdded, + status === 'removed' && css.assetNameTagRemoved, + className, + ); + + return ( + + ); +}; + +AssetNameTag.propTypes = { + className: PropTypes.string, + title: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, +}; + +AssetNameTag.defaultProps = { + className: '', +}; + +/** + * @param {object} props + * @param {import('../../types').ReportMetricAssetRow} props.row + * @param {import('react').ElementType} props.customComponentLink + * @param {object} props.filters + * @param {string} props.search + */ const RowHeader = ({ row, customComponentLink: CustomComponentLink, filters, search }) => { const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = row; @@ -116,28 +148,13 @@ const RowHeader = ({ row, customComponentLink: CustomComponentLink, filters, sea > {isEntry && ( - + )} {isInitial && ( - + )} {isChunk && ( - + )} @@ -147,15 +164,7 @@ const RowHeader = ({ row, customComponentLink: CustomComponentLink, filters, sea }; RowHeader.propTypes = { - row: PropTypes.shape({ - key: PropTypes.string, - label: PropTypes.string, - isNotPredictive: PropTypes.bool, - isChunk: PropTypes.bool, - isInitial: PropTypes.bool, - isEntry: PropTypes.bool, - runs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types - }).isRequired, + row: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types customComponentLink: PropTypes.elementType.isRequired, filters: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types search: PropTypes.string, diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.module.css b/packages/ui/src/components/bundle-assets/bundle-assets.module.css index d6ad564fff..024a9eb8af 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.module.css +++ b/packages/ui/src/components/bundle-assets/bundle-assets.module.css @@ -31,9 +31,32 @@ } .assetNameTag { + overflow: hidden; + position: relative; vertical-align: middle; } +.assetNameTag::before { + display: block; + padding: 2px; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + font-size: 8px; + line-height: 1; + vertical-align: middle; +} + +.assetNameTagAdded::before { + background: linear-gradient(-45deg, var(--color-danger) 50%, transparent 50%); +} + +.assetNameTagRemoved::before { + background: linear-gradient(135deg, var(--color-danger) 50%, transparent 50%); +} + .assetNameTag + .assetNameTag { margin-left: 2px; } diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js index d0aa1c6abb..af0402e34b 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js +++ b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js @@ -1,6 +1,7 @@ /** * @type {import('@bundle-stats/utils').ReportMetricRow} ReportMetricRow * @type {import('../../types').ReportMetricAssetRow} ReportMetricAssetRow + * @type {import('../../types').ReportMetricAssetRowMetaStatus} ReportMetricAssetRowFlagStatus */ import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, getFileType } from '@bundle-stats/utils'; @@ -28,7 +29,7 @@ export const getIsNotPredictive = (row) => { runs[index + 1] && key !== current.name && current.name === runs[index + 1].name && - current.size !== runs[index + 1].size + current.value !== runs[index + 1].value ) { return true; } @@ -37,6 +38,38 @@ export const getIsNotPredictive = (row) => { }, false); }; +/** + * @param {Array} + * @returns {ReportMetricAssetRowFlagStatus | boolean} + */ +export const getAssetMetaStatus = (values) => { + if (!values.includes(true)) { + return false; + } + + // filter empty runs + const metaValues = values.filter((value) => typeof value !== 'undefined'); + + const current = metaValues[0]; + const metaValuesLength = metaValues.length; + + if (metaValuesLength === 1) { + return Boolean(current); + } + + const baseline = metaValues[metaValuesLength - 1]; + + if (current && !baseline) { + return 'added'; + } + + if (!current && baseline) { + return 'removed'; + } + + return true; +}; + /** * Add asset row flags * @@ -46,9 +79,20 @@ export const getIsNotPredictive = (row) => { export const addMetricReportAssetRowData = (row) => { const { runs } = row; - const isEntry = runs.map((run) => run?.isEntry).includes(true); - const isInitial = runs.map((run) => run?.isInitial).includes(true); - const isChunk = runs.map((run) => run?.isChunk).includes(true); + // Collect meta for each run + const runsEntry = []; + const runsInitial = []; + const runsChunk = []; + + runs.forEach((run) => { + runsEntry.push(run?.isEntry); + runsInitial.push(run?.isInitial); + runsChunk.push(run?.isChunk); + }); + + const isEntry = getAssetMetaStatus(runsEntry); + const isInitial = getAssetMetaStatus(runsInitial); + const isChunk = getAssetMetaStatus(runsChunk); const isAsset = !(isEntry || isInitial || isChunk); const isNotPredictive = getIsNotPredictive(row); const fileType = getFileType(row.key); diff --git a/packages/ui/src/types.d.ts b/packages/ui/src/types.d.ts index 79778c70a4..2a1b3a644f 100644 --- a/packages/ui/src/types.d.ts +++ b/packages/ui/src/types.d.ts @@ -19,19 +19,21 @@ type FilterGroupFieldData = { type FilterFieldsData = Record; +export type ReportMetricAssetRowMetaStatus = 'added' | 'removed'; + export type ReportMetricAssetRow = { /** * Asset isEntry - at least one run has isEntry truthy */ - isEntry: boolean; + isEntry: ReportMetricAssetRowMetaStatus | boolean; /** * Asset isInitial - at least one run has isInitial truthy */ - isInitial: boolean; + isInitial: ReportMetricAssetRowMetaStatus | boolean; /** * Asset isChunk - at least one run has isChunk truthy */ - isChunk: boolean; + isChunk: ReportMetricAssetRowMetaStatus | boolean; /** * Asset isAsset - at least one run has isAsset truthy */ From b32c0c038d7e85e23569a9bbe60a7dc3e6f05710 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sat, 2 Nov 2024 16:43:12 +0100 Subject: [PATCH 06/17] fix(ui): Tag - export prop types --- packages/ui/src/ui/tag/tag.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/ui/tag/tag.tsx b/packages/ui/src/ui/tag/tag.tsx index c5fc4c4aa4..e9d5c13269 100644 --- a/packages/ui/src/ui/tag/tag.tsx +++ b/packages/ui/src/ui/tag/tag.tsx @@ -1,16 +1,17 @@ +import type { ComponentProps } from 'react'; import React from 'react'; import cx from 'classnames'; import { KIND, SIZE } from '../../tokens'; import css from './tag.module.css'; -interface TagProps { +export type TagProps = { as?: React.ElementType; kind?: (typeof KIND)[keyof typeof KIND]; size?: (typeof SIZE)[keyof typeof SIZE]; -} +} & ComponentProps<'span'>; -export const Tag = (props: TagProps & React.ComponentProps<'span'>) => { +export const Tag = (props: TagProps) => { const { className = '', as: Component = 'span', From d51f7d5163dd519f6e96c19e69b38c0271f151b9 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sat, 2 Nov 2024 17:07:02 +0100 Subject: [PATCH 07/17] refactor(ui): Extract AssetName --- .../asset-name/asset-name.module.css | 83 ++++++++++++++++ .../asset-name/asset-name.stories.tsx | 85 +++++++++++++++++ .../src/components/asset-name/asset-name.tsx | 76 +++++++++++++++ .../ui/src/components/asset-name/index.ts | 1 + .../bundle-assets/bundle-assets.jsx | 95 +------------------ .../bundle-assets/bundle-assets.module.css | 87 ----------------- 6 files changed, 248 insertions(+), 179 deletions(-) create mode 100644 packages/ui/src/components/asset-name/asset-name.module.css create mode 100644 packages/ui/src/components/asset-name/asset-name.stories.tsx create mode 100644 packages/ui/src/components/asset-name/asset-name.tsx create mode 100644 packages/ui/src/components/asset-name/index.ts diff --git a/packages/ui/src/components/asset-name/asset-name.module.css b/packages/ui/src/components/asset-name/asset-name.module.css new file mode 100644 index 0000000000..4b413f666d --- /dev/null +++ b/packages/ui/src/components/asset-name/asset-name.module.css @@ -0,0 +1,83 @@ +.root {} + +.notPredictive { + margin-right: var(--space-xxsmall); + display: inline-block; + width: var(--space-small); + height: var(--space-small); +} + +.notPredictiveAnchor {} + +.notPredictiveIcon { + color: var(--color-warning-dark); +} + +.metaTags { + display: inline; + margin-right: var(--space-xxxsmall); +} + +.metaTags:empty { + display: none; +} + +.metaTag { + overflow: hidden; + position: relative; + vertical-align: middle; +} + +.metaTag::before { + display: block; + padding: 2px; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + font-size: 8px; + line-height: 1; + vertical-align: middle; +} + +.metaTagAdded::before { + background: linear-gradient(-45deg, var(--color-danger) 50%, transparent 50%); +} + +.metaTagRemoved::before { + background: linear-gradient(135deg, var(--color-danger) 50%, transparent 50%); +} + +.metaTag + .metaTag { + margin-left: 2px; +} + +.metaTagEntry { + background: var(--color-info-dark); +} + +.metaTagEntry::before { + content: 'e'; +} + +.metaTagInitial { + background: var(--color-info); +} + +.metaTagInitial::before { + content: 'i'; +} + +.metaTagChunk { + background: var(--color-info-light); +} + +.metaTagChunk::before { + content: 'c'; +} + +.nameText { + display: inline-block; + vertical-align: middle; +} diff --git a/packages/ui/src/components/asset-name/asset-name.stories.tsx b/packages/ui/src/components/asset-name/asset-name.stories.tsx new file mode 100644 index 0000000000..d8af6be3d8 --- /dev/null +++ b/packages/ui/src/components/asset-name/asset-name.stories.tsx @@ -0,0 +1,85 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { AssetName } from '.'; + +const ROW = { + key: 'asset/main.js', + label: 'asset/main.js', + changed: true, + isEntry: false, + isChunk: false, + isInitial: false, + isAsset: true, + isNotPredictive: false, + fileType: 'JS', + biggerIsBetter: false, + runs: [ + { name: 'asset/main.abcd1234.js', value: 1024 * 10 }, + { name: 'asset/main.abcd1234.js', value: 1024 * 11 }, + ], +}; + +const meta = { + title: 'Components/AssetName', + component: AssetName, + args: { + customComponentLink: 'span', + filters: {}, + search: '', + }, +} as Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + row: ROW, + }, +}; + +export const MetaDefault: Story = { + args: { + row: { + ...ROW, + isEntry: true, + isChunk: true, + isInitial: true, + isAsset: false, + }, + }, +}; + +export const MetaAdded: Story = { + args: { + row: { + ...ROW, + isEntry: 'added', + isChunk: 'added', + isInitial: 'added', + isAsset: false, + }, + }, +}; + +export const MetaRemoved: Story = { + args: { + row: { + ...ROW, + isEntry: 'removed', + isChunk: 'removed', + isInitial: 'removed', + isAsset: false, + }, + }, +}; + +export const NotPredictive: Story = { + args: { + row: { + ...ROW, + isNotPredictive: true, + }, + }, +}; diff --git a/packages/ui/src/components/asset-name/asset-name.tsx b/packages/ui/src/components/asset-name/asset-name.tsx new file mode 100644 index 0000000000..5dd04274ae --- /dev/null +++ b/packages/ui/src/components/asset-name/asset-name.tsx @@ -0,0 +1,76 @@ +import React, { ElementType } from 'react'; +import cx from 'classnames'; +import { COMPONENT, SECTIONS } from '@bundle-stats/utils'; + +import type { TagProps } from '../../ui/tag'; +import { Icon } from '../../ui/icon'; +import { FileName } from '../../ui/file-name'; +import { HoverCard } from '../../ui/hover-card'; +import { Tag } from '../../ui/tag'; +import { AssetNotPredictive } from '../asset-not-predictive'; + +import css from './asset-name.module.css'; +import { ReportMetricAssetRow, ReportMetricAssetRowMetaStatus } from '../../types'; + +const RUN_TITLE_CURRENT = 'Current'; +const RUN_TITLE_BASELINE = 'Baseline'; +const RUNS_LABELS = [RUN_TITLE_CURRENT, RUN_TITLE_BASELINE]; + +type MetaTagProps = { status: ReportMetricAssetRowMetaStatus | boolean } & TagProps; + +const MetaTag = (props: MetaTagProps) => { + const { className = '', title, status } = props; + + const rootClassName = cx( + css.metaTag, + status === 'added' && css.metaTagAdded, + status === 'removed' && css.metaTagRemoved, + className, + ); + + return ( + + ); +}; + +export type AssetNameProps = { + className?: string; + row: ReportMetricAssetRow; + customComponentLink: ElementType; + filters: object; + search: string; +}; + +export const AssetName = (props: AssetNameProps) => { + const { className = '', customComponentLink: CustomComponentLink, row, filters, search } = props; + const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = row; + + return ( + + {isNotPredictive && ( + } + className={css.notPredictive} + anchorClassName={css.notPredictiveAnchor} + > + + + )} + + + + {isEntry && } + {isInitial && ( + + )} + {isChunk && } + + + + + ); +}; diff --git a/packages/ui/src/components/asset-name/index.ts b/packages/ui/src/components/asset-name/index.ts new file mode 100644 index 0000000000..e42ac8c381 --- /dev/null +++ b/packages/ui/src/components/asset-name/index.ts @@ -0,0 +1 @@ +export * from './asset-name'; diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.jsx b/packages/ui/src/components/bundle-assets/bundle-assets.jsx index 8e5bd310c6..be8a5237d3 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.jsx +++ b/packages/ui/src/components/bundle-assets/bundle-assets.jsx @@ -7,9 +7,7 @@ import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, - COMPONENT, FILE_TYPE_LABELS, - SECTIONS, } from '@bundle-stats/utils'; import config from '../../config.json'; @@ -20,17 +18,12 @@ import { Box } from '../../layout/box'; import { FlexStack } from '../../layout/flex-stack'; import { Stack } from '../../layout/stack'; import { Dialog, useDialogState } from '../../ui/dialog'; -import { Icon } from '../../ui/icon'; import { InputSearch } from '../../ui/input-search'; -import { FileName } from '../../ui/file-name'; -import { HoverCard } from '../../ui/hover-card'; -import { Tag } from '../../ui/tag'; import { Table } from '../../ui/table'; import { Filters } from '../../ui/filters'; import { EmptySet } from '../../ui/empty-set'; import { Toolbar } from '../../ui/toolbar'; import { AssetInfo } from '../asset-info'; -import { AssetNotPredictive } from '../asset-not-predictive'; import { ComponentLink } from '../component-link'; import { MetricsTable } from '../metrics-table'; import { MetricsTableExport } from '../metrics-table-export'; @@ -41,10 +34,7 @@ import { MetricsTableHeader } from '../metrics-table-header'; import { MetricsTreemap, getTreemapNodes, getTreemapNodesGroupedByPath } from '../metrics-treemap'; import { SEARCH_PLACEHOLDER } from './bundle-assets.i18n'; import css from './bundle-assets.module.css'; - -const RUN_TITLE_CURRENT = 'Current'; -const RUN_TITLE_BASELINE = 'Baseline'; -const RUNS_LABELS = [RUN_TITLE_CURRENT, RUN_TITLE_BASELINE]; +import { AssetName } from '../asset-name'; const DISPLAY_TYPE_GROUPS = { [MetricsDisplayType.TREEMAP]: ['folder'], @@ -94,86 +84,6 @@ const getFilters = ({ compareMode, filters }) => ({ }, }); -const AssetNameTag = (props) => { - const { className = '', title, status } = props; - - const rootClassName = cx( - css.assetNameTag, - status === 'added' && css.assetNameTagAdded, - status === 'removed' && css.assetNameTagRemoved, - className, - ); - - return ( - - ); -}; - -AssetNameTag.propTypes = { - className: PropTypes.string, - title: PropTypes.string.isRequired, - status: PropTypes.string.isRequired, -}; - -AssetNameTag.defaultProps = { - className: '', -}; - -/** - * @param {object} props - * @param {import('../../types').ReportMetricAssetRow} props.row - * @param {import('react').ElementType} props.customComponentLink - * @param {object} props.filters - * @param {string} props.search - */ -const RowHeader = ({ row, customComponentLink: CustomComponentLink, filters, search }) => { - const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = row; - - return ( - - {isNotPredictive && ( - } - className={css.notPredictive} - anchorClassName={css.notPredictiveAnchor} - > - - - )} - - - - {isEntry && ( - - )} - {isInitial && ( - - )} - {isChunk && ( - - )} - - - - - ); -}; - -RowHeader.propTypes = { - row: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types - customComponentLink: PropTypes.elementType.isRequired, - filters: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types - search: PropTypes.string, -}; - -RowHeader.defaultProps = { - search: '', -}; - const ViewMetricsTreemap = (props) => { const { metricsTableTitle, @@ -294,11 +204,12 @@ export const BundleAssets = (props) => { const renderRowHeader = useCallback( (row) => ( - ), [CustomComponentLink, filters, search], diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.module.css b/packages/ui/src/components/bundle-assets/bundle-assets.module.css index 024a9eb8af..bc5f90a022 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.module.css +++ b/packages/ui/src/components/bundle-assets/bundle-assets.module.css @@ -3,93 +3,6 @@ max-width: 240px; } -.assetNameWrapper { -} - -.assetName {} - -.notPredictive { - margin-right: var(--space-xxsmall); - display: inline-block; - width: var(--space-small); - height: var(--space-small); -} - -.notPredictiveAnchor {} - -.notPredictiveIcon { - color: var(--color-warning-dark); -} - -.assetNameTags { - display: inline; - margin-right: var(--space-xxxsmall); -} - -.assetNameTags:empty { - display: none; -} - -.assetNameTag { - overflow: hidden; - position: relative; - vertical-align: middle; -} - -.assetNameTag::before { - display: block; - padding: 2px; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - font-size: 8px; - line-height: 1; - vertical-align: middle; -} - -.assetNameTagAdded::before { - background: linear-gradient(-45deg, var(--color-danger) 50%, transparent 50%); -} - -.assetNameTagRemoved::before { - background: linear-gradient(135deg, var(--color-danger) 50%, transparent 50%); -} - -.assetNameTag + .assetNameTag { - margin-left: 2px; -} - -.assetNameTagEntry { - background: var(--color-info-dark); -} - -.assetNameTagEntry::before { - content: 'e'; -} - -.assetNameTagInitial { - background: var(--color-info); -} - -.assetNameTagInitial::before { - content: 'i'; -} - -.assetNameTagChunk { - background: var(--color-info-light); -} - -.assetNameTagChunk::before { - content: 'c'; -} - -.assetNameText { - display: inline-block; - vertical-align: middle; -} - .root .assetName:hover, .root .assetName:active, .root .assetName:focus { From 3f61bd5ab42c2d2e4eece348fe4dc975668beeea Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sun, 3 Nov 2024 16:24:29 +0100 Subject: [PATCH 08/17] refactor(ui): BundleAssets - pass custom component link --- .../ui/src/components/asset-name/asset-name.tsx | 11 ++--------- .../components/bundle-assets/bundle-assets.jsx | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/asset-name/asset-name.tsx b/packages/ui/src/components/asset-name/asset-name.tsx index 5dd04274ae..c76a0c41e4 100644 --- a/packages/ui/src/components/asset-name/asset-name.tsx +++ b/packages/ui/src/components/asset-name/asset-name.tsx @@ -1,6 +1,5 @@ import React, { ElementType } from 'react'; import cx from 'classnames'; -import { COMPONENT, SECTIONS } from '@bundle-stats/utils'; import type { TagProps } from '../../ui/tag'; import { Icon } from '../../ui/icon'; @@ -37,12 +36,10 @@ export type AssetNameProps = { className?: string; row: ReportMetricAssetRow; customComponentLink: ElementType; - filters: object; - search: string; }; export const AssetName = (props: AssetNameProps) => { - const { className = '', customComponentLink: CustomComponentLink, row, filters, search } = props; + const { className = '', customComponentLink: CustomComponentLink, row } = props; const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = row; return ( @@ -57,11 +54,7 @@ export const AssetName = (props: AssetNameProps) => { )} - + {isEntry && } {isInitial && ( diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.jsx b/packages/ui/src/components/bundle-assets/bundle-assets.jsx index be8a5237d3..633bccb0b4 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.jsx +++ b/packages/ui/src/components/bundle-assets/bundle-assets.jsx @@ -7,7 +7,9 @@ import { ASSET_ENTRY_TYPE, ASSET_FILE_TYPE, ASSET_FILTERS, + COMPONENT, FILE_TYPE_LABELS, + SECTIONS, } from '@bundle-stats/utils'; import config from '../../config.json'; @@ -202,13 +204,22 @@ export const BundleAssets = (props) => { [items, totalRowCount], ); + const assetNameCustomComponentLink = useCallback( + ({ entryId: assetEntryId, ...assetNameRestProps }) => ( + + ), + [CustomComponentLink, filters, search], + ); + const renderRowHeader = useCallback( (row) => ( ), From 145f56a7677d04188397d0030f775318dd969afa Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sun, 3 Nov 2024 16:31:23 +0100 Subject: [PATCH 09/17] fix(ui): AssetNotPredictive - adjust layout & text --- .../asset-not-predictive.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx b/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx index 7e0c729a6e..8b5e7233ac 100644 --- a/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx +++ b/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx @@ -1,3 +1,4 @@ +import type { ComponentProps } from 'react'; import React from 'react'; import cx from 'classnames'; import type { Asset } from '@bundle-stats/utils/types/webpack'; @@ -6,19 +7,20 @@ import { Stack } from '../../layout/stack'; import { Table } from '../../ui/table'; import css from './asset-not-predictive.module.css'; -interface AssetNotPredictiveProps { +export type AssetNotPredictiveProps = { runs?: Array; - labels?: Array -} + labels?: Array; +} & ComponentProps<'div'>; -export const AssetNotPredictive = (props: AssetNotPredictiveProps & React.ComponentProps<'div'>) => { +export const AssetNotPredictive = (props: AssetNotPredictiveProps) => { const { className = '', runs = null, labels = null, ...restProps } = props; return ( - -

- Asset file name is the same, but the size has changed. -

+ + +

Asset hash is not predictive

+

File names are equal, but the size is different.

+
{runs && ( From df304a2ef8c52c06a2ab55884c2ddf85056b0577 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sun, 3 Nov 2024 17:25:15 +0100 Subject: [PATCH 10/17] test: Correct fixtures --- fixtures/webpack-stats.baseline.json | 8 ++++---- fixtures/webpack-stats.current.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fixtures/webpack-stats.baseline.json b/fixtures/webpack-stats.baseline.json index 4d32dba876..245e4b72aa 100644 --- a/fixtures/webpack-stats.baseline.json +++ b/fixtures/webpack-stats.baseline.json @@ -94,10 +94,10 @@ "entry": true, "initial": true, "files": [ - "vendors.8b6bb4f.css", - "vendors.176a5f6.js", - "vendors.8b6bb4f.css.map", - "vendors.176a5f6.js.map" + "assets/css/vendors.8b6bb4f.css", + "assets/js/vendors.176a5f6.js", + "assets/css/vendors.8b6bb4f.css.map", + "assets/js/vendors.176a5f6.js.map" ], "names": [ "vendors" diff --git a/fixtures/webpack-stats.current.json b/fixtures/webpack-stats.current.json index 48d4104738..2c27c83cee 100644 --- a/fixtures/webpack-stats.current.json +++ b/fixtures/webpack-stats.current.json @@ -182,10 +182,10 @@ "entry": true, "initial": true, "files": [ - "vendors.5024abb.css", - "vendors.278dc2f.js", - "vendors.5024abb.css.map", - "vendors.278dc2f.js.map" + "assets/css/vendors.5024abb.css", + "assets/js/vendors.278dc2f.js", + "assets/css/vendors.5024abb.css.map", + "assets/js/vendors.278dc2f.js.map" ], "names": [ "vendors" From 0dc2b973de992847bc2a38a87aa651f411f1a171 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sun, 3 Nov 2024 17:50:50 +0100 Subject: [PATCH 11/17] refactor(ui): App - convert stories to tsx --- ...dys.stories.jsx => app-gladys.stories.tsx} | 24 +++--- ...ies.jsx => app-home-assistant.stories.tsx} | 24 +++--- ...ne.stories.jsx => app-outline.stories.tsx} | 24 +++--- .../app/{app.stories.jsx => app.stories.tsx} | 79 ++++++++++--------- 4 files changed, 87 insertions(+), 64 deletions(-) rename packages/ui/src/app/{app-gladys.stories.jsx => app-gladys.stories.tsx} (69%) rename packages/ui/src/app/{app-home-assistant.stories.jsx => app-home-assistant.stories.tsx} (69%) rename packages/ui/src/app/{app-outline.stories.jsx => app-outline.stories.tsx} (69%) rename packages/ui/src/app/{app.stories.jsx => app.stories.tsx} (52%) diff --git a/packages/ui/src/app/app-gladys.stories.jsx b/packages/ui/src/app/app-gladys.stories.tsx similarity index 69% rename from packages/ui/src/app/app-gladys.stories.jsx rename to packages/ui/src/app/app-gladys.stories.tsx index 4ba11124ff..ad80541f2f 100644 --- a/packages/ui/src/app/app-gladys.stories.jsx +++ b/packages/ui/src/app/app-gladys.stories.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; import { createJobs } from '@bundle-stats/utils'; /* eslint-disable import/no-unresolved, import/no-relative-packages */ @@ -20,16 +21,21 @@ const BASELINE_SOURCE = { const JOBS = createJobs([CURRENT_SOURCE, BASELINE_SOURCE]); -export default { +const meta: Meta = { title: 'App/Gladys', component: App, - decorators: [ - (Story) => ( -
- -
- ), - ], + parameters: { + layout: 'fullscreen', + }, + args: { + version: '1.0', + }, }; -export const Default = () => ; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => , +}; diff --git a/packages/ui/src/app/app-home-assistant.stories.jsx b/packages/ui/src/app/app-home-assistant.stories.tsx similarity index 69% rename from packages/ui/src/app/app-home-assistant.stories.jsx rename to packages/ui/src/app/app-home-assistant.stories.tsx index 9b342460fb..fda012c68f 100644 --- a/packages/ui/src/app/app-home-assistant.stories.jsx +++ b/packages/ui/src/app/app-home-assistant.stories.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; import { createJobs } from '@bundle-stats/utils'; /* eslint-disable import/no-unresolved, import/no-relative-packages */ @@ -20,16 +21,21 @@ const BASELINE_SOURCE = { const JOBS = createJobs([CURRENT_SOURCE, BASELINE_SOURCE]); -export default { +const meta: Meta = { title: 'App/HomeAssistant', component: App, - decorators: [ - (Story) => ( -
- -
- ), - ], + parameters: { + layout: 'fullscreen', + }, + args: { + version: '1.0', + }, }; -export const Default = () => ; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => , +}; diff --git a/packages/ui/src/app/app-outline.stories.jsx b/packages/ui/src/app/app-outline.stories.tsx similarity index 69% rename from packages/ui/src/app/app-outline.stories.jsx rename to packages/ui/src/app/app-outline.stories.tsx index 250532001e..20bfd3b721 100644 --- a/packages/ui/src/app/app-outline.stories.jsx +++ b/packages/ui/src/app/app-outline.stories.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; import { createJobs } from '@bundle-stats/utils'; /* eslint-disable import/no-unresolved, import/no-relative-packages */ @@ -20,16 +21,21 @@ const BASELINE_SOURCE = { const JOBS = createJobs([CURRENT_SOURCE, BASELINE_SOURCE]); -export default { +const meta: Meta = { title: 'App/Outline', component: App, - decorators: [ - (Story) => ( -
- -
- ), - ], + parameters: { + layout: 'fullscreen', + }, + args: { + version: '1.0', + }, }; -export const Default = () => ; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => , +}; diff --git a/packages/ui/src/app/app.stories.jsx b/packages/ui/src/app/app.stories.tsx similarity index 52% rename from packages/ui/src/app/app.stories.jsx rename to packages/ui/src/app/app.stories.tsx index 89f8f88751..744ff75aff 100644 --- a/packages/ui/src/app/app.stories.jsx +++ b/packages/ui/src/app/app.stories.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; import { createJobs } from '@bundle-stats/utils'; /* eslint-disable import/no-unresolved, import/no-relative-packages */ @@ -23,17 +24,7 @@ const BASELINE_SOURCE = { }, }; -const JOBS = createJobs([CURRENT_SOURCE, BASELINE_SOURCE], { - webpack: { - budgets: [ - { - metric: 'totalSizeByTypeALL', - value: 1024 * 1024, - }, - ], - }, -}); -const NO_BASELINE_JOBS = createJobs([CURRENT_SOURCE]); +const JOBS = createJobs([CURRENT_SOURCE, BASELINE_SOURCE]); const MULTIPLE_JOBS = createJobs([ CURRENT_SOURCE, @@ -51,38 +42,52 @@ const MULTIPLE_JOBS = createJobs([ const [CURRENT_JOB, BASELINE_JOB] = JOBS; -const EMPTY_BASELINE = createJobs([CURRENT_SOURCE, { webpack: null }]); - -export default { +const meta: Meta = { title: 'App', component: App, - decorators: [ - (Story) => ( -
- -
- ), - ], + parameters: { + layout: 'fullscreen', + }, + args: { + version: '1.0', + }, }; -export const Default = () => ; +export default meta; -export const NoInsights = () => ( - -); +type Story = StoryObj; -export const NoBaseline = () => ; +export const Default: Story = { + render: (args) => , +}; -export const EmptyBaseline = () => ; +export const NoInsights: Story = { + render: (args) => ( + + ), +}; -export const MultipleBaselines = () => ; +export const NoBaseline: Story = { + render: (args) => , +}; + +export const EmptyBaseline: Story = { + render: (args) => , +}; -export const Empty = () => ; +export const MultipleBaselines: Story = { + render: (args) => , +}; + +export const Empty: Story = { + render: (args) => , +}; From 3ffac7fbc0b0934ba2e406be7be7291715dd4d3c Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sun, 3 Nov 2024 18:01:50 +0100 Subject: [PATCH 12/17] test(ui): App - add meta change case --- packages/ui/src/app/app.stories.tsx | 36 ++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/app/app.stories.tsx b/packages/ui/src/app/app.stories.tsx index 744ff75aff..a816744d11 100644 --- a/packages/ui/src/app/app.stories.tsx +++ b/packages/ui/src/app/app.stories.tsx @@ -1,6 +1,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { createJobs } from '@bundle-stats/utils'; +import merge from 'lodash/merge'; /* eslint-disable import/no-unresolved, import/no-relative-packages */ import currentData from '../../../../fixtures/job.current'; @@ -76,8 +77,41 @@ export const NoInsights: Story = { ), }; +export const AssetMetaChanges: Story = { + render: (args) => { + const current = merge({}, CURRENT_SOURCE); + const baseline = merge({}, CURRENT_SOURCE); + + current.webpack.assets.push({ + name: 'assets/js/vendors-auth.1a278dc.js', + size: 26364, + }); + current.webpack.chunks.push({ + id: 100, + entry: false, + initial: false, + files: ['assets/js/vendors-auth.1a278dc.js'], + names: ['vendors-auth'], + }); + + baseline.webpack.assets.push({ + name: 'assets/js/vendors-auth.1a278dc.js', + size: 26364, + }); + baseline.webpack.chunks.push({ + id: 100, + entry: false, + initial: true, + files: ['assets/js/vendors-auth.1a278dc.js'], + names: ['vendors-auth'], + }); + + return ; + }, +}; + export const NoBaseline: Story = { - render: (args) => , + render: (args) => , }; export const EmptyBaseline: Story = { From 49f2b4b06ae53560e11ea1c1ccc1827a936ff3d7 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Mon, 4 Nov 2024 01:30:03 +0100 Subject: [PATCH 13/17] refactor(ui): Extract AssetMetaTag --- .../src/components/asset-info/asset-info.tsx | 25 +++++--- .../asset-meta-tag/asset-meta-tag.module.css | 56 ++++++++++++++++++ .../asset-meta-tag/asset-meta-tag.tsx | 34 +++++++++++ .../ui/src/components/asset-meta-tag/index.ts | 1 + .../asset-name/asset-name.module.css | 57 +------------------ .../src/components/asset-name/asset-name.tsx | 37 +++++------- .../src/components/entry-info/entry-info.tsx | 7 +-- 7 files changed, 126 insertions(+), 91 deletions(-) create mode 100644 packages/ui/src/components/asset-meta-tag/asset-meta-tag.module.css create mode 100644 packages/ui/src/components/asset-meta-tag/asset-meta-tag.tsx create mode 100644 packages/ui/src/components/asset-meta-tag/index.ts diff --git a/packages/ui/src/components/asset-info/asset-info.tsx b/packages/ui/src/components/asset-info/asset-info.tsx index 59c13d551f..c5211f198e 100644 --- a/packages/ui/src/components/asset-info/asset-info.tsx +++ b/packages/ui/src/components/asset-info/asset-info.tsx @@ -12,7 +12,7 @@ import { import { Asset, MetaChunk } from '@bundle-stats/utils/types/webpack'; import { FlexStack } from '../../layout/flex-stack'; -import { Tag } from '../../ui/tag'; +import { AssetMetaTag } from '../asset-meta-tag'; import { ComponentLink } from '../component-link'; import { EntryInfo, EntryInfoMetaLink } from '../entry-info'; import css from './asset-info.module.css'; @@ -89,34 +89,43 @@ export const AssetInfo = (props: AssetInfoProps & React.ComponentProps<'div'>) = return ( {item.isEntry && ( - Entrypoint - + )} {item.isInitial && ( - Initial - + )} {item.isChunk && ( - Chunk - + )} ); diff --git a/packages/ui/src/components/asset-meta-tag/asset-meta-tag.module.css b/packages/ui/src/components/asset-meta-tag/asset-meta-tag.module.css new file mode 100644 index 0000000000..f8fb49d437 --- /dev/null +++ b/packages/ui/src/components/asset-meta-tag/asset-meta-tag.module.css @@ -0,0 +1,56 @@ +.root { + --background-color: var(--color-info); + background: var(--background-color); + overflow: hidden; + position: relative; + vertical-align: middle; +} + +.root::before { + display: block; + content: ' '; + padding: 2px; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + font-size: 8px; + line-height: 1; + vertical-align: middle; +} + +/* tag */ +.entry { + --background-color: var(--color-info-dark); +} + +.entry:empty::before { + content: 'e'; +} + +.initial { + --background-color: var(--color-info); +} + +.initial:empty::before { + content: 'i'; +} + +.chunk { + --background-color: var(--color-info-light); +} + +.chunk:empty::before { + content: 'c'; +} + +/* status */ +.added { + background: linear-gradient(-45deg, var(--color-danger-light) 50%, var(--background-color) 50%) !important; +} + +.removed { + background: linear-gradient(135deg, var(--color-danger-light) 50%, var(--background-color) 50%) !important; +} + diff --git a/packages/ui/src/components/asset-meta-tag/asset-meta-tag.tsx b/packages/ui/src/components/asset-meta-tag/asset-meta-tag.tsx new file mode 100644 index 0000000000..487092ca2c --- /dev/null +++ b/packages/ui/src/components/asset-meta-tag/asset-meta-tag.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import cx from 'classnames'; + +import type { TagProps } from '../../ui/tag'; +import { Tag } from '../../ui/tag'; +import css from './asset-meta-tag.module.css'; +import type { ReportMetricAssetRowMetaStatus } from '../../types'; + +export type AssetMetaTagProps = { + tag: 'entry' | 'initial' | 'chunk'; + status?: ReportMetricAssetRowMetaStatus | boolean; +} & TagProps; + +export const AssetMetaTag = (props: AssetMetaTagProps) => { + const { className = '', title, tag, status, ...restProps } = props; + + const rootClassName = cx( + css.root, + status === 'added' && css.added, + status === 'removed' && css.removed, + css[tag], + className, + ); + + return ( + + ); +}; diff --git a/packages/ui/src/components/asset-meta-tag/index.ts b/packages/ui/src/components/asset-meta-tag/index.ts new file mode 100644 index 0000000000..8a9abc4325 --- /dev/null +++ b/packages/ui/src/components/asset-meta-tag/index.ts @@ -0,0 +1 @@ +export * from './asset-meta-tag'; diff --git a/packages/ui/src/components/asset-name/asset-name.module.css b/packages/ui/src/components/asset-name/asset-name.module.css index 4b413f666d..3af908a9ad 100644 --- a/packages/ui/src/components/asset-name/asset-name.module.css +++ b/packages/ui/src/components/asset-name/asset-name.module.css @@ -18,63 +18,12 @@ margin-right: var(--space-xxxsmall); } -.metaTags:empty { - display: none; -} - -.metaTag { - overflow: hidden; - position: relative; - vertical-align: middle; -} - -.metaTag::before { - display: block; - padding: 2px; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - font-size: 8px; - line-height: 1; - vertical-align: middle; -} - -.metaTagAdded::before { - background: linear-gradient(-45deg, var(--color-danger) 50%, transparent 50%); -} - -.metaTagRemoved::before { - background: linear-gradient(135deg, var(--color-danger) 50%, transparent 50%); -} - .metaTag + .metaTag { - margin-left: 2px; -} - -.metaTagEntry { - background: var(--color-info-dark); -} - -.metaTagEntry::before { - content: 'e'; -} - -.metaTagInitial { - background: var(--color-info); + margin-left: calc(var(--space-xxxsmall) / 2); } -.metaTagInitial::before { - content: 'i'; -} - -.metaTagChunk { - background: var(--color-info-light); -} - -.metaTagChunk::before { - content: 'c'; +.metaTags:empty { + display: none; } .nameText { diff --git a/packages/ui/src/components/asset-name/asset-name.tsx b/packages/ui/src/components/asset-name/asset-name.tsx index c76a0c41e4..bb96015b77 100644 --- a/packages/ui/src/components/asset-name/asset-name.tsx +++ b/packages/ui/src/components/asset-name/asset-name.tsx @@ -1,37 +1,19 @@ import React, { ElementType } from 'react'; import cx from 'classnames'; -import type { TagProps } from '../../ui/tag'; import { Icon } from '../../ui/icon'; import { FileName } from '../../ui/file-name'; import { HoverCard } from '../../ui/hover-card'; -import { Tag } from '../../ui/tag'; import { AssetNotPredictive } from '../asset-not-predictive'; +import { ReportMetricAssetRow } from '../../types'; +import { AssetMetaTag } from '../asset-meta-tag'; import css from './asset-name.module.css'; -import { ReportMetricAssetRow, ReportMetricAssetRowMetaStatus } from '../../types'; const RUN_TITLE_CURRENT = 'Current'; const RUN_TITLE_BASELINE = 'Baseline'; const RUNS_LABELS = [RUN_TITLE_CURRENT, RUN_TITLE_BASELINE]; -type MetaTagProps = { status: ReportMetricAssetRowMetaStatus | boolean } & TagProps; - -const MetaTag = (props: MetaTagProps) => { - const { className = '', title, status } = props; - - const rootClassName = cx( - css.metaTag, - status === 'added' && css.metaTagAdded, - status === 'removed' && css.metaTagRemoved, - className, - ); - - return ( - - ); -}; - export type AssetNameProps = { className?: string; row: ReportMetricAssetRow; @@ -56,11 +38,20 @@ export const AssetName = (props: AssetNameProps) => { - {isEntry && } + {isEntry && ( + + )} {isInitial && ( - + + )} + {isChunk && ( + )} - {isChunk && } diff --git a/packages/ui/src/components/entry-info/entry-info.tsx b/packages/ui/src/components/entry-info/entry-info.tsx index efdb524373..d54c92d411 100644 --- a/packages/ui/src/components/entry-info/entry-info.tsx +++ b/packages/ui/src/components/entry-info/entry-info.tsx @@ -145,12 +145,7 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) = {rowRun?.[runNameSelector] ? ( - + Date: Tue, 5 Nov 2024 18:31:58 +0100 Subject: [PATCH 14/17] refactor: Rename Asset type to AssetMetricRun --- packages/ui/src/components/asset-info/asset-info.tsx | 4 ++-- .../components/asset-not-predictive/asset-not-predictive.tsx | 4 ++-- packages/utils/src/webpack/types.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/asset-info/asset-info.tsx b/packages/ui/src/components/asset-info/asset-info.tsx index c5211f198e..f49ab695e4 100644 --- a/packages/ui/src/components/asset-info/asset-info.tsx +++ b/packages/ui/src/components/asset-info/asset-info.tsx @@ -9,7 +9,7 @@ import { getBundleAssetsFileTypeComponentLink, getModuleFileType, } from '@bundle-stats/utils'; -import { Asset, MetaChunk } from '@bundle-stats/utils/types/webpack'; +import type { AssetMetricRun, MetaChunk } from '@bundle-stats/utils/types/webpack'; import { FlexStack } from '../../layout/flex-stack'; import { AssetMetaTag } from '../asset-meta-tag'; @@ -60,7 +60,7 @@ interface AssetInfoProps { isInitial?: boolean; isNotPredictive?: boolean; fileType?: string; - runs: Array; + runs: Array; }; chunks?: Array; labels: Array; diff --git a/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx b/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx index 8b5e7233ac..ecaa1879c1 100644 --- a/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx +++ b/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx @@ -1,14 +1,14 @@ import type { ComponentProps } from 'react'; import React from 'react'; import cx from 'classnames'; -import type { Asset } from '@bundle-stats/utils/types/webpack'; +import type { AssetMetricRun } from '@bundle-stats/utils/types/webpack'; import { Stack } from '../../layout/stack'; import { Table } from '../../ui/table'; import css from './asset-not-predictive.module.css'; export type AssetNotPredictiveProps = { - runs?: Array; + runs?: Array; labels?: Array; } & ComponentProps<'div'>; diff --git a/packages/utils/src/webpack/types.ts b/packages/utils/src/webpack/types.ts index 4ba4aa0466..0bee3a9a09 100644 --- a/packages/utils/src/webpack/types.ts +++ b/packages/utils/src/webpack/types.ts @@ -29,7 +29,7 @@ export interface MetaChunk { name: string; } -export interface Asset extends MetricRun { +export interface AssetMetricRun extends MetricRun { name: string; isEntry: boolean; isInitial: boolean; @@ -37,7 +37,7 @@ export interface Asset extends MetricRun { chunkId?: string; } -export type Assets = Record; +export type Assets = Record; export interface MetricsAssets { metrics: { From 0b97d483e65cf7c85c1fd86a9923a8148c6585fb Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Tue, 5 Nov 2024 18:41:45 +0100 Subject: [PATCH 15/17] feat(ui): AssetInfo - display run tags --- .../asset-info/asset-info.module.css | 9 +++++++ .../src/components/asset-info/asset-info.tsx | 24 ++++++++++++++++++- .../src/components/entry-info/entry-info.tsx | 24 +++++++++++++------ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/asset-info/asset-info.module.css b/packages/ui/src/components/asset-info/asset-info.module.css index baa8af4122..57a859417c 100644 --- a/packages/ui/src/components/asset-info/asset-info.module.css +++ b/packages/ui/src/components/asset-info/asset-info.module.css @@ -29,3 +29,12 @@ content: ' '; } +.runNameTags { + display: flex; + align-items: center; + gap: calc(var(--space-xxxsmall) / 2); +} + +.runNameTags:empty { + display: block; +} diff --git a/packages/ui/src/components/asset-info/asset-info.tsx b/packages/ui/src/components/asset-info/asset-info.tsx index f49ab695e4..61d33cdcf1 100644 --- a/packages/ui/src/components/asset-info/asset-info.tsx +++ b/packages/ui/src/components/asset-info/asset-info.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import cx from 'classnames'; import noop from 'lodash/noop'; import { @@ -16,6 +16,9 @@ import { AssetMetaTag } from '../asset-meta-tag'; import { ComponentLink } from '../component-link'; import { EntryInfo, EntryInfoMetaLink } from '../entry-info'; import css from './asset-info.module.css'; +import { AssetName } from '../asset-name/asset-name'; +import { FileName } from '../../ui'; +import { ReportMetricAssetRow } from '../../types'; interface ChunkModulesLinkProps { as: React.ElementType; @@ -131,6 +134,24 @@ export const AssetInfo = (props: AssetInfoProps & React.ComponentProps<'div'>) = ); }, [item]); + const RunName = useCallback( + (runNameProps: { children: React.ReactNode; run: ReportMetricAssetRow }) => { + const { run, children } = runNameProps; + + return ( + <> + + {run.isEntry && } + {run.isInitial && } + {run.isChunk && } + + {children} + + ); + }, + [], + ); + const fileTypeLabel = FILE_TYPE_LABELS[item.fileType as keyof typeof FILE_TYPE_LABELS]; return ( @@ -139,6 +160,7 @@ export const AssetInfo = (props: AssetInfoProps & React.ComponentProps<'div'>) = labels={labels} tags={tags} onClose={onClose} + RunName={RunName} className={cx(css.root, className)} > {item.fileType && ( diff --git a/packages/ui/src/components/entry-info/entry-info.tsx b/packages/ui/src/components/entry-info/entry-info.tsx index d54c92d411..eb9ec010f3 100644 --- a/packages/ui/src/components/entry-info/entry-info.tsx +++ b/packages/ui/src/components/entry-info/entry-info.tsx @@ -2,7 +2,7 @@ import type { ElementType, ReactNode } from 'react'; import React from 'react'; import cx from 'classnames'; import { Portal } from 'ariakit/portal'; -import type { MetricRunInfo, ReportMetricRow } from '@bundle-stats/utils'; +import type { MetricRunInfo, ReportMetricRow, ReportMetricRun } from '@bundle-stats/utils'; import { METRIC_TYPE_CONFIGS, getMetricRunInfo } from '@bundle-stats/utils'; import { Box } from '../../layout/box'; @@ -72,6 +72,12 @@ function defaultRenderRunInfo(item: ReportMetricRow) { ); } +export type RenderRunNameProps = { + className?: string; + run: T; + runSelector: string; +}; + interface EntryInfoProps { itemTitle?: React.ReactNode; item: ReportMetricRow; @@ -82,6 +88,7 @@ interface EntryInfoProps { tags?: React.ReactNode; onClose: () => void; renderRunInfo?: (item: ReportMetricRow) => React.ReactNode; + RunName?: React.ElementType; } export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) => { @@ -93,10 +100,11 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) = runNameSelector = 'name', runNameLabel = I18N.PATH, runSizeLabel = I18N.SIZE, - children, tags = null, onClose, renderRunInfo = defaultRenderRunInfo, + RunName = React.Fragment, + children, } = props; return ( @@ -146,11 +154,13 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) = {rowRun?.[runNameSelector] ? ( - + + + ) : ( From 5030e78c1bb37f6157819854930547e5d31dd47d Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Tue, 5 Nov 2024 18:52:55 +0100 Subject: [PATCH 16/17] feat(ui): BundleAssets - flag assets as changed when meta changed --- .../src/components/asset-info/asset-info.tsx | 55 ++++++++++--------- .../add-metric-report-asset-row-data.ts | 54 ++++++++++++++++++ .../bundle-assets/bundle-assets.utils.js | 10 +++- 3 files changed, 91 insertions(+), 28 deletions(-) diff --git a/packages/ui/src/components/asset-info/asset-info.tsx b/packages/ui/src/components/asset-info/asset-info.tsx index 61d33cdcf1..db33d5e303 100644 --- a/packages/ui/src/components/asset-info/asset-info.tsx +++ b/packages/ui/src/components/asset-info/asset-info.tsx @@ -1,4 +1,5 @@ -import React, { useCallback, useMemo } from 'react'; +import type { ComponentProps, ElementType, ReactNode } from 'react'; +import React, { useMemo } from 'react'; import cx from 'classnames'; import noop from 'lodash/noop'; import { @@ -11,17 +12,15 @@ import { } from '@bundle-stats/utils'; import type { AssetMetricRun, MetaChunk } from '@bundle-stats/utils/types/webpack'; +import type { ReportMetricAssetRow } from '../../types'; import { FlexStack } from '../../layout/flex-stack'; import { AssetMetaTag } from '../asset-meta-tag'; import { ComponentLink } from '../component-link'; import { EntryInfo, EntryInfoMetaLink } from '../entry-info'; import css from './asset-info.module.css'; -import { AssetName } from '../asset-name/asset-name'; -import { FileName } from '../../ui'; -import { ReportMetricAssetRow } from '../../types'; interface ChunkModulesLinkProps { - as: React.ElementType; + as: ElementType; chunks: Array; chunkId: string; name: string; @@ -33,7 +32,7 @@ const ChunkModulesLink = ({ chunkId, name, onClick, -}: ChunkModulesLinkProps & React.ComponentProps<'a'>) => { +}: ChunkModulesLinkProps & ComponentProps<'a'>) => { const chunk = chunks?.find(({ id }) => id === chunkId); if (!chunk) { @@ -54,6 +53,26 @@ const ChunkModulesLink = ({ ); }; +type AssetRunNameProps = { + run: ReportMetricAssetRow; + children: ReactNode; +}; + +const AssetRunName = (props: AssetRunNameProps) => { + const { run, children } = props; + + return ( + <> + + {run.isEntry && } + {run.isInitial && } + {run.isChunk && } + + {children} + + ); +}; + interface AssetInfoProps { item: { label: string; @@ -67,11 +86,11 @@ interface AssetInfoProps { }; chunks?: Array; labels: Array; - customComponentLink?: React.ElementType; + customComponentLink?: ElementType; onClose: () => void; } -export const AssetInfo = (props: AssetInfoProps & React.ComponentProps<'div'>) => { +export const AssetInfo = (props: AssetInfoProps & ComponentProps<'div'>) => { const { className = '', chunks = null, @@ -134,24 +153,6 @@ export const AssetInfo = (props: AssetInfoProps & React.ComponentProps<'div'>) = ); }, [item]); - const RunName = useCallback( - (runNameProps: { children: React.ReactNode; run: ReportMetricAssetRow }) => { - const { run, children } = runNameProps; - - return ( - <> - - {run.isEntry && } - {run.isInitial && } - {run.isChunk && } - - {children} - - ); - }, - [], - ); - const fileTypeLabel = FILE_TYPE_LABELS[item.fileType as keyof typeof FILE_TYPE_LABELS]; return ( @@ -160,7 +161,7 @@ export const AssetInfo = (props: AssetInfoProps & React.ComponentProps<'div'>) = labels={labels} tags={tags} onClose={onClose} - RunName={RunName} + RunName={AssetRunName} className={cx(css.root, className)} > {item.fileType && ( diff --git a/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts b/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts index ee6424b0bb..cb409f7f72 100644 --- a/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts +++ b/packages/ui/src/components/bundle-assets/__tests__/add-metric-report-asset-row-data.ts @@ -178,4 +178,58 @@ describe('BundleAssets / addMetricReportAssetRowData', () => { ], }); }); + + test('should flag as changed when only meta changes', () => { + expect( + addMetricReportAssetRowData({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: false, + runs: [ + { + name: 'assets/main.abc123.js', + value: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: false, + }, + { + name: 'assets/main.abc123.js', + value: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }), + ).toEqual({ + key: 'assets/main.js', + label: 'assets/main.js', + biggerIsBetter: false, + changed: true, + fileType: 'JS', + isAsset: false, + isChunk: true, + isEntry: true, + isInitial: 'removed', + isNotPredictive: false, + runs: [ + { + name: 'assets/main.abc123.js', + value: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: false, + }, + { + name: 'assets/main.abc123.js', + value: 1024 * 10, + isEntry: true, + isChunk: true, + isInitial: true, + }, + ], + }); + }); }); diff --git a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js index af0402e34b..185e2d5d5d 100644 --- a/packages/ui/src/components/bundle-assets/bundle-assets.utils.js +++ b/packages/ui/src/components/bundle-assets/bundle-assets.utils.js @@ -77,7 +77,7 @@ export const getAssetMetaStatus = (values) => { * @returns {ReportMetricAssetRow} */ export const addMetricReportAssetRowData = (row) => { - const { runs } = row; + const { changed, runs } = row; // Collect meta for each run const runsEntry = []; @@ -97,8 +97,16 @@ export const addMetricReportAssetRowData = (row) => { const isNotPredictive = getIsNotPredictive(row); const fileType = getFileType(row.key); + // Flag asset as changed if name and value are identical, if one of the meta tags is changed + const assetChanged = + changed || + typeof isEntry !== 'boolean' || + typeof isInitial !== 'boolean' || + typeof isChunk !== 'boolean'; + return { ...row, + changed: assetChanged, isEntry, isInitial, isChunk, From 43d68e55f8f4c32a48511703abc485dc0f9bd8e0 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Tue, 5 Nov 2024 19:10:59 +0100 Subject: [PATCH 17/17] fix(ui): AssetNotPredictive - improve hovercard --- .../components/asset-name/asset-name.module.css | 8 ++++---- .../ui/src/components/asset-name/asset-name.tsx | 7 +++---- .../asset-not-predictive.module.css | 7 ++++++- .../asset-not-predictive/asset-not-predictive.tsx | 14 ++++++++------ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/components/asset-name/asset-name.module.css b/packages/ui/src/components/asset-name/asset-name.module.css index 3af908a9ad..a7a983123c 100644 --- a/packages/ui/src/components/asset-name/asset-name.module.css +++ b/packages/ui/src/components/asset-name/asset-name.module.css @@ -1,5 +1,3 @@ -.root {} - .notPredictive { margin-right: var(--space-xxsmall); display: inline-block; @@ -7,12 +5,14 @@ height: var(--space-small); } -.notPredictiveAnchor {} - .notPredictiveIcon { color: var(--color-warning-dark); } +.notPredictiveHoverCard { + max-width: 480px; +} + .metaTags { display: inline; margin-right: var(--space-xxxsmall); diff --git a/packages/ui/src/components/asset-name/asset-name.tsx b/packages/ui/src/components/asset-name/asset-name.tsx index bb96015b77..c005798ab1 100644 --- a/packages/ui/src/components/asset-name/asset-name.tsx +++ b/packages/ui/src/components/asset-name/asset-name.tsx @@ -1,12 +1,11 @@ import React, { ElementType } from 'react'; -import cx from 'classnames'; import { Icon } from '../../ui/icon'; import { FileName } from '../../ui/file-name'; import { HoverCard } from '../../ui/hover-card'; import { AssetNotPredictive } from '../asset-not-predictive'; -import { ReportMetricAssetRow } from '../../types'; +import type { ReportMetricAssetRow } from '../../types'; import { AssetMetaTag } from '../asset-meta-tag'; import css from './asset-name.module.css'; @@ -25,12 +24,12 @@ export const AssetName = (props: AssetNameProps) => { const { label, isNotPredictive, runs, isChunk, isEntry, isInitial } = row; return ( - + {isNotPredictive && ( } className={css.notPredictive} - anchorClassName={css.notPredictiveAnchor} + hoverCardClassName={css.notPredictiveHoverCard} > diff --git a/packages/ui/src/components/asset-not-predictive/asset-not-predictive.module.css b/packages/ui/src/components/asset-not-predictive/asset-not-predictive.module.css index 46bc5db490..bace631b9b 100644 --- a/packages/ui/src/components/asset-not-predictive/asset-not-predictive.module.css +++ b/packages/ui/src/components/asset-not-predictive/asset-not-predictive.module.css @@ -1,3 +1,8 @@ -.tableValue { +.colName { + width: 100%; + overflow: hidden; +} + +.colValue { text-align: right; } diff --git a/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx b/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx index ecaa1879c1..ada855b372 100644 --- a/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx +++ b/packages/ui/src/components/asset-not-predictive/asset-not-predictive.tsx @@ -6,6 +6,7 @@ import type { AssetMetricRun } from '@bundle-stats/utils/types/webpack'; import { Stack } from '../../layout/stack'; import { Table } from '../../ui/table'; import css from './asset-not-predictive.module.css'; +import { Alert } from '../../ui'; export type AssetNotPredictiveProps = { runs?: Array; @@ -17,10 +18,11 @@ export const AssetNotPredictive = (props: AssetNotPredictiveProps) => { return ( - -

Asset hash is not predictive

-

File names are equal, but the size is different.

-
+

Asset hash is not predictive

+ + File names are identical, but the size is different. Content changes without a hash change + can cause runtime errors. + {runs && (
@@ -34,8 +36,8 @@ export const AssetNotPredictive = (props: AssetNotPredictiveProps) => { {runs.map(({ name, value }, index) => ( {labels?.[index]} - {name} - {value} + {name} + {value} ))}