@bundle-stats/ui 4.13.0-beta.19 → 4.13.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/bundle-assets-totals/bundle-assets-totals.js +1 -1
- package/lib/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.i18n.js +6 -0
- package/lib/components/metrics-treemap/metrics-treemap.i18n.js.map +1 -0
- package/lib/components/metrics-treemap/metrics-treemap.js +24 -12
- package/lib/components/metrics-treemap/metrics-treemap.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.module.css +70 -25
- package/lib/components/metrics-treemap/metrics-treemap.utils.js +44 -5
- package/lib/components/metrics-treemap/metrics-treemap.utils.js.map +1 -1
- package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js +1 -1
- package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.i18n.js +3 -0
- package/lib-esm/components/metrics-treemap/metrics-treemap.i18n.js.map +1 -0
- package/lib-esm/components/metrics-treemap/metrics-treemap.js +25 -12
- package/lib-esm/components/metrics-treemap/metrics-treemap.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.module.css +70 -25
- package/lib-esm/components/metrics-treemap/metrics-treemap.utils.js +44 -4
- package/lib-esm/components/metrics-treemap/metrics-treemap.utils.js.map +1 -1
- package/package.json +3 -3
- package/src/components/bundle-assets-totals/bundle-assets-totals.jsx +0 -1
- package/src/components/metrics-treemap/__tests__/metrics-treemap.utils.ts +59 -14
- package/src/components/metrics-treemap/metrics-treemap.constants.ts +9 -0
- package/src/components/metrics-treemap/metrics-treemap.i18n.ts +2 -0
- package/src/components/metrics-treemap/metrics-treemap.module.css +70 -25
- package/src/components/metrics-treemap/metrics-treemap.tsx +55 -21
- package/src/components/metrics-treemap/metrics-treemap.utils.ts +63 -8
- package/types/components/metrics-treemap/metrics-treemap.constants.d.ts +5 -0
- package/types/components/metrics-treemap/metrics-treemap.i18n.d.ts +2 -0
- package/types/components/metrics-treemap/metrics-treemap.utils.d.ts +2 -1
|
@@ -13,19 +13,27 @@ import { useDebounce, useHoverDirty, useMeasure, useMouseHovered } from 'react-u
|
|
|
13
13
|
import cx from 'classnames';
|
|
14
14
|
import { HierarchyRectangularNode, hierarchy, treemap, treemapSquarify } from 'd3';
|
|
15
15
|
import { Tooltip, TooltipArrow, TooltipAnchor, useTooltipState } from 'ariakit/tooltip';
|
|
16
|
-
import
|
|
16
|
+
import {
|
|
17
|
+
type ReportMetricRow,
|
|
18
|
+
type MetricRunInfo,
|
|
19
|
+
type MetricRunInfoBaseline,
|
|
20
|
+
getMetricRunInfo,
|
|
21
|
+
METRIC_TYPE_CONFIGS,
|
|
22
|
+
} from '@bundle-stats/utils';
|
|
17
23
|
|
|
18
24
|
import { Stack } from '../../layout/stack';
|
|
19
25
|
import { FileName } from '../../ui/file-name';
|
|
20
26
|
import { Delta } from '../delta';
|
|
21
27
|
import { RunInfo } from '../run-info';
|
|
22
28
|
import type { TreeLeaf, TreeNode, Tree } from './metrics-treemap.constants';
|
|
29
|
+
import * as I18N from './metrics-treemap.i18n';
|
|
23
30
|
import css from './metrics-treemap.module.css';
|
|
31
|
+
import { resolveGroupDeltaType } from './metrics-treemap.utils';
|
|
24
32
|
|
|
25
|
-
const SQUARIFY_RATIO = 1.
|
|
33
|
+
const SQUARIFY_RATIO = 1.66;
|
|
26
34
|
const PADDING_OUTER = 1;
|
|
27
35
|
const PADDING_INNER = 1;
|
|
28
|
-
const GROUPED_PADDING_TOP =
|
|
36
|
+
const GROUPED_PADDING_TOP = 18;
|
|
29
37
|
|
|
30
38
|
/**
|
|
31
39
|
* Resolve the tile's size using predefined values to avoid
|
|
@@ -33,7 +41,7 @@ const GROUPED_PADDING_TOP = 16;
|
|
|
33
41
|
*/
|
|
34
42
|
const PADDING_TOP = 4;
|
|
35
43
|
const PADDING_BOTTOM = 2; // substracted to allow longer texts to appear
|
|
36
|
-
const PADDING_LEFT =
|
|
44
|
+
const PADDING_LEFT = 2;
|
|
37
45
|
const PADDING_RIGHT = 4; // substracted to allow longer texts to not break
|
|
38
46
|
const VERTICAL_SPACING = 4;
|
|
39
47
|
const LINE_HEIGHT = 16;
|
|
@@ -195,7 +203,7 @@ const Tile = (props: TileProps) => {
|
|
|
195
203
|
const runInfo = item.runs?.[0] as MetricRunInfo;
|
|
196
204
|
|
|
197
205
|
const sizeDisplay = useMemo(() => resolveTileSizeDisplay(width, height), [width, height]);
|
|
198
|
-
const handleOnClick: MouseEventHandler<
|
|
206
|
+
const handleOnClick: MouseEventHandler<HTMLDivElement> = useCallback(
|
|
199
207
|
(event) => {
|
|
200
208
|
event.stopPropagation();
|
|
201
209
|
onClick?.(item.key);
|
|
@@ -203,7 +211,7 @@ const Tile = (props: TileProps) => {
|
|
|
203
211
|
[item.key, onClick],
|
|
204
212
|
);
|
|
205
213
|
|
|
206
|
-
const contentRef = useRef<
|
|
214
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
207
215
|
const hover = useHoverDirty(contentRef);
|
|
208
216
|
|
|
209
217
|
const className = cx(
|
|
@@ -214,10 +222,12 @@ const Tile = (props: TileProps) => {
|
|
|
214
222
|
left === 0 && css.tileFirstCol,
|
|
215
223
|
);
|
|
216
224
|
|
|
225
|
+
/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */
|
|
217
226
|
return (
|
|
218
|
-
<
|
|
219
|
-
type="button"
|
|
227
|
+
<div
|
|
220
228
|
onClick={handleOnClick}
|
|
229
|
+
role="button"
|
|
230
|
+
aria-label={I18N.TILE_LABEL}
|
|
221
231
|
ref={contentRef}
|
|
222
232
|
style={{ left, top, width, height }}
|
|
223
233
|
className={className}
|
|
@@ -233,9 +243,8 @@ const Tile = (props: TileProps) => {
|
|
|
233
243
|
) : (
|
|
234
244
|
<TileContent label={label} sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
235
245
|
)}
|
|
236
|
-
</
|
|
246
|
+
</div>
|
|
237
247
|
);
|
|
238
|
-
/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */
|
|
239
248
|
};
|
|
240
249
|
|
|
241
250
|
interface TileGroupProps extends ComponentProps<'div'> {
|
|
@@ -243,6 +252,10 @@ interface TileGroupProps extends ComponentProps<'div'> {
|
|
|
243
252
|
* Node title
|
|
244
253
|
*/
|
|
245
254
|
title?: string;
|
|
255
|
+
/**
|
|
256
|
+
* Group sum
|
|
257
|
+
*/
|
|
258
|
+
total?: Tree['total'];
|
|
246
259
|
/**
|
|
247
260
|
* Node id
|
|
248
261
|
*/
|
|
@@ -282,6 +295,7 @@ interface TileGroupProps extends ComponentProps<'div'> {
|
|
|
282
295
|
const TileGroup = (props: TileGroupProps) => {
|
|
283
296
|
const {
|
|
284
297
|
title = '',
|
|
298
|
+
total,
|
|
285
299
|
id,
|
|
286
300
|
childNodes,
|
|
287
301
|
onItemClick,
|
|
@@ -307,13 +321,19 @@ const TileGroup = (props: TileGroupProps) => {
|
|
|
307
321
|
[width, height],
|
|
308
322
|
);
|
|
309
323
|
|
|
324
|
+
const metricRunInfo =
|
|
325
|
+
total &&
|
|
326
|
+
getMetricRunInfo(METRIC_TYPE_CONFIGS.METRIC_TYPE_FILE_SIZE, total.current, total.baseline);
|
|
327
|
+
const groupDeltaType = resolveGroupDeltaType(metricRunInfo);
|
|
328
|
+
const rootClassName = cx(css.tileGroup, css[`tileGroup--${groupDeltaType}`]);
|
|
329
|
+
|
|
310
330
|
if (title && displaySize === 'minimal') {
|
|
311
331
|
return (
|
|
312
332
|
<div
|
|
313
333
|
onClick={onClick}
|
|
314
334
|
role="button"
|
|
315
|
-
aria-label=
|
|
316
|
-
className={
|
|
335
|
+
aria-label={I18N.TILE_GROUP_LABEL}
|
|
336
|
+
className={rootClassName}
|
|
317
337
|
style={{ left, top, width, height }}
|
|
318
338
|
/>
|
|
319
339
|
);
|
|
@@ -324,9 +344,9 @@ const TileGroup = (props: TileGroupProps) => {
|
|
|
324
344
|
<div
|
|
325
345
|
onClick={onClick}
|
|
326
346
|
role="button"
|
|
327
|
-
aria-label=
|
|
347
|
+
aria-label={I18N.TILE_GROUP_LABEL}
|
|
328
348
|
style={{ left, top, width, height }}
|
|
329
|
-
className={
|
|
349
|
+
className={rootClassName}
|
|
330
350
|
>
|
|
331
351
|
<div className={css.tileGroupTitle}>{title}</div>
|
|
332
352
|
</div>
|
|
@@ -337,17 +357,30 @@ const TileGroup = (props: TileGroupProps) => {
|
|
|
337
357
|
<div
|
|
338
358
|
onClick={onClick}
|
|
339
359
|
role="button"
|
|
340
|
-
aria-label=
|
|
341
|
-
className={cx(
|
|
360
|
+
aria-label={I18N.TILE_GROUP_LABEL}
|
|
361
|
+
className={cx(rootClassName, css.tileGroupSizeDefault)}
|
|
342
362
|
style={{ left, top, width, height }}
|
|
343
363
|
>
|
|
344
|
-
{title &&
|
|
364
|
+
{title && (
|
|
365
|
+
<div className={css.tileGroupTitle}>
|
|
366
|
+
{title}
|
|
367
|
+
{metricRunInfo && (
|
|
368
|
+
<span className={css.tileGroupTitleTotal}>
|
|
369
|
+
{`${metricRunInfo.displayValue}`}
|
|
370
|
+
{'displayDelta' in metricRunInfo && ` (${metricRunInfo.displayDeltaPercentage})`}
|
|
371
|
+
</span>
|
|
372
|
+
)}
|
|
373
|
+
</div>
|
|
374
|
+
)}
|
|
345
375
|
{childNodes?.map((childNode) => {
|
|
346
376
|
if ('children' in childNode) {
|
|
377
|
+
const groupData = childNode.data as Tree;
|
|
378
|
+
|
|
347
379
|
return (
|
|
348
380
|
<TileGroup
|
|
349
|
-
title={
|
|
350
|
-
|
|
381
|
+
title={groupData.label}
|
|
382
|
+
total={groupData.total}
|
|
383
|
+
id={groupData.id}
|
|
351
384
|
childNodes={childNode.children}
|
|
352
385
|
left={childNode.x0 - absoluteLeft}
|
|
353
386
|
top={childNode.y0 - absoluteTop}
|
|
@@ -357,7 +390,7 @@ const TileGroup = (props: TileGroupProps) => {
|
|
|
357
390
|
height={childNode.y1 - childNode.y0}
|
|
358
391
|
onItemClick={onItemClick}
|
|
359
392
|
onGroupClick={onGroupClick}
|
|
360
|
-
key={
|
|
393
|
+
key={groupData.id}
|
|
361
394
|
/>
|
|
362
395
|
);
|
|
363
396
|
}
|
|
@@ -446,10 +479,11 @@ export const MetricsTreemap = (props: MetricsTreemapProps & ComponentProps<'div'
|
|
|
446
479
|
{...restProps}
|
|
447
480
|
ref={containerRef}
|
|
448
481
|
>
|
|
449
|
-
{rootNode.children && rootNode.children
|
|
482
|
+
{rootNode.children && rootNode.children?.length > 0 ? (
|
|
450
483
|
<div className={css.canvas}>
|
|
451
484
|
<TileGroup
|
|
452
485
|
title={nested ? rootNode.data.label : undefined}
|
|
486
|
+
total={(rootNode.data as Tree).total}
|
|
453
487
|
id={rootNode.data.id}
|
|
454
488
|
childNodes={rootNode.children}
|
|
455
489
|
onItemClick={onItemClick}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
DeltaType,
|
|
3
|
+
type MetricRunInfoBaseline,
|
|
4
|
+
type MetricRunInfo,
|
|
5
|
+
type ReportMetricRow,
|
|
6
|
+
} from '@bundle-stats/utils';
|
|
2
7
|
|
|
3
|
-
import type { TreeLeaf, TreeNodeChildren, Tree } from './metrics-treemap.constants';
|
|
8
|
+
import type { TreeLeaf, TreeNodeChildren, Tree, TreeTotal } from './metrics-treemap.constants';
|
|
4
9
|
|
|
5
10
|
const ROOT_LABEL = '(root)';
|
|
6
11
|
|
|
@@ -32,6 +37,20 @@ export function getTreemapNodes(items: Array<ReportMetricRow>): Tree {
|
|
|
32
37
|
};
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
function getItemValues(runs: ReportMetricRow['runs']): TreeTotal {
|
|
41
|
+
const runCount = runs?.length;
|
|
42
|
+
const current = runs[0]?.value || 0;
|
|
43
|
+
|
|
44
|
+
if (runCount < 2) {
|
|
45
|
+
return { current, baseline: 0 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
current,
|
|
50
|
+
baseline: runs[runCount - 1]?.value || 0,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
/**
|
|
36
55
|
* Recursively set treemap nodes
|
|
37
56
|
*/
|
|
@@ -40,14 +59,14 @@ function setTreeNode(
|
|
|
40
59
|
slugs: Array<string>,
|
|
41
60
|
currentSlugId: number,
|
|
42
61
|
newNode: TreeLeaf,
|
|
43
|
-
):
|
|
62
|
+
): TreeTotal {
|
|
44
63
|
const baseSlugs = slugs.slice(0, currentSlugId);
|
|
45
64
|
const [currentSlug, ...restSlug] = slugs.slice(currentSlugId);
|
|
46
65
|
|
|
47
66
|
// Add to current nodes if there are no child nodes
|
|
48
67
|
if (!currentSlug) {
|
|
49
68
|
nodes.push(newNode);
|
|
50
|
-
return;
|
|
69
|
+
return getItemValues(newNode.item.runs);
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
const currentNodePath = [...baseSlugs, currentSlug].join('/');
|
|
@@ -62,18 +81,30 @@ function setTreeNode(
|
|
|
62
81
|
label: currentSlug,
|
|
63
82
|
value: 0,
|
|
64
83
|
children: [],
|
|
84
|
+
total: {
|
|
85
|
+
current: 0,
|
|
86
|
+
baseline: 0,
|
|
87
|
+
},
|
|
65
88
|
};
|
|
66
89
|
|
|
67
90
|
nodes.push(parentNode);
|
|
68
91
|
}
|
|
69
92
|
|
|
93
|
+
// accumulate children values
|
|
94
|
+
const nodeValues = getItemValues(newNode?.item?.runs);
|
|
95
|
+
|
|
96
|
+
parentNode.total = {
|
|
97
|
+
current: (parentNode.total?.current || 0) + nodeValues.current,
|
|
98
|
+
baseline: (parentNode.total?.baseline || 0) + (nodeValues.baseline || 0),
|
|
99
|
+
};
|
|
100
|
+
|
|
70
101
|
// If there are no other slugs, the new node is as leaf and we add it to the parent
|
|
71
102
|
if (restSlug.length === 0) {
|
|
72
103
|
parentNode.children.push(newNode);
|
|
73
|
-
return;
|
|
104
|
+
return getItemValues(newNode.item.runs);
|
|
74
105
|
}
|
|
75
106
|
|
|
76
|
-
setTreeNode(parentNode.children, slugs, currentSlugId + 1, newNode);
|
|
107
|
+
return setTreeNode(parentNode.children, slugs, currentSlugId + 1, newNode);
|
|
77
108
|
}
|
|
78
109
|
|
|
79
110
|
/**
|
|
@@ -81,9 +112,9 @@ function setTreeNode(
|
|
|
81
112
|
*/
|
|
82
113
|
export function getTreemapNodesGroupedByPath(items: Array<ReportMetricRow>): Tree {
|
|
83
114
|
const treeNodes: TreeNodeChildren = [];
|
|
115
|
+
const total = { current: 0, baseline: 0 };
|
|
84
116
|
|
|
85
117
|
items.forEach((item) => {
|
|
86
|
-
// const normalizedPath = item.key.replace(/^\.?\//, ''); // replace './' or '/' prefix
|
|
87
118
|
const slugs = item.key.split('/');
|
|
88
119
|
const baseSlugs = slugs.slice(0, -1);
|
|
89
120
|
const baseName = slugs.slice(-1)[0];
|
|
@@ -95,7 +126,10 @@ export function getTreemapNodesGroupedByPath(items: Array<ReportMetricRow>): Tre
|
|
|
95
126
|
item,
|
|
96
127
|
};
|
|
97
128
|
|
|
98
|
-
setTreeNode(treeNodes, baseSlugs, 0, treeNode);
|
|
129
|
+
const childrenTotal = setTreeNode(treeNodes, baseSlugs, 0, treeNode);
|
|
130
|
+
|
|
131
|
+
total.current += childrenTotal.current || 0;
|
|
132
|
+
total.baseline += childrenTotal.baseline || 0;
|
|
99
133
|
});
|
|
100
134
|
|
|
101
135
|
return {
|
|
@@ -103,5 +137,26 @@ export function getTreemapNodesGroupedByPath(items: Array<ReportMetricRow>): Tre
|
|
|
103
137
|
label: ROOT_LABEL,
|
|
104
138
|
value: 0,
|
|
105
139
|
children: treeNodes,
|
|
140
|
+
total,
|
|
106
141
|
};
|
|
107
142
|
}
|
|
143
|
+
|
|
144
|
+
export function resolveGroupDeltaType(
|
|
145
|
+
metricRunInfo?: MetricRunInfo | MetricRunInfoBaseline,
|
|
146
|
+
): DeltaType.NO_CHANGE | DeltaType.NEGATIVE | DeltaType.POSITIVE {
|
|
147
|
+
if (!metricRunInfo) {
|
|
148
|
+
return DeltaType.NO_CHANGE;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const deltaType = 'displayDelta' in metricRunInfo ? metricRunInfo.deltaType : undefined;
|
|
152
|
+
|
|
153
|
+
if (deltaType?.match(/NEGATIVE/)) {
|
|
154
|
+
return DeltaType.NEGATIVE;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (deltaType?.match(/POSITIVE/)) {
|
|
158
|
+
return DeltaType.POSITIVE;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return DeltaType.NO_CHANGE;
|
|
162
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ReportMetricRow } from '@bundle-stats/utils';
|
|
2
|
+
export interface TreeTotal {
|
|
3
|
+
current: number;
|
|
4
|
+
baseline: number;
|
|
5
|
+
}
|
|
2
6
|
export interface TreeLeaf {
|
|
3
7
|
id: string;
|
|
4
8
|
label: string;
|
|
@@ -10,6 +14,7 @@ export interface Tree {
|
|
|
10
14
|
label: string;
|
|
11
15
|
value: number;
|
|
12
16
|
children: TreeNodeChildren;
|
|
17
|
+
total?: TreeTotal;
|
|
13
18
|
}
|
|
14
19
|
export type TreeNode = Tree | TreeLeaf;
|
|
15
20
|
export type TreeNodeChildren = Array<TreeNode>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { DeltaType, type MetricRunInfoBaseline, type MetricRunInfo, type ReportMetricRow } from '@bundle-stats/utils';
|
|
2
2
|
import type { Tree } from './metrics-treemap.constants';
|
|
3
3
|
export declare function getTreemapNodes(items: Array<ReportMetricRow>): Tree;
|
|
4
4
|
export declare function getTreemapNodesGroupedByPath(items: Array<ReportMetricRow>): Tree;
|
|
5
|
+
export declare function resolveGroupDeltaType(metricRunInfo?: MetricRunInfo | MetricRunInfoBaseline): DeltaType.NO_CHANGE | DeltaType.NEGATIVE | DeltaType.POSITIVE;
|