@bundle-stats/ui 4.13.0-beta.0 → 4.13.0-beta.2
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/index.js +2 -2
- package/lib/components/bundle-assets/index.js.map +1 -1
- package/lib/components/bundle-modules/index.js +2 -2
- package/lib/components/bundle-modules/index.js.map +1 -1
- package/lib/components/bundle-packages/index.js +2 -2
- package/lib/components/bundle-packages/index.js.map +1 -1
- package/lib/components/metrics-display-selector/metrics-display-selector.js +2 -8
- package/lib/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.js +68 -48
- package/lib/components/metrics-treemap/metrics-treemap.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.module.css +57 -41
- package/lib/hooks/entry-info.js +12 -0
- package/lib/hooks/entry-info.js.map +1 -0
- package/lib/query-state.js +1 -1
- package/lib/query-state.js.map +1 -1
- package/lib/ui/button/button.module.css +1 -0
- package/lib-esm/components/bundle-assets/index.js +3 -3
- package/lib-esm/components/bundle-assets/index.js.map +1 -1
- package/lib-esm/components/bundle-modules/index.js +2 -2
- package/lib-esm/components/bundle-modules/index.js.map +1 -1
- package/lib-esm/components/bundle-packages/index.js +3 -3
- package/lib-esm/components/bundle-packages/index.js.map +1 -1
- package/lib-esm/components/metrics-display-selector/metrics-display-selector.js +2 -8
- package/lib-esm/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.js +71 -50
- package/lib-esm/components/metrics-treemap/metrics-treemap.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.module.css +57 -41
- package/lib-esm/hooks/entry-info.js +8 -0
- package/lib-esm/hooks/entry-info.js.map +1 -0
- package/lib-esm/query-state.js +1 -1
- package/lib-esm/query-state.js.map +1 -1
- package/lib-esm/ui/button/button.module.css +1 -0
- package/package.json +4 -6
- package/src/components/bundle-assets/index.jsx +8 -3
- package/src/components/bundle-modules/index.tsx +2 -2
- package/src/components/bundle-packages/index.jsx +8 -3
- package/src/components/metrics-display-selector/metrics-display-selector.tsx +4 -8
- package/src/components/metrics-treemap/metrics-treemap.module.css +57 -41
- package/src/components/metrics-treemap/metrics-treemap.tsx +142 -105
- package/src/hooks/entry-info.ts +19 -0
- package/src/query-state.tsx +1 -1
- package/src/ui/button/button.module.css +1 -0
- package/types/hooks/entry-info.d.ts +8 -0
- package/lib/hooks/show-entry.js +0 -13
- package/lib/hooks/show-entry.js.map +0 -1
- package/lib-esm/hooks/show-entry.js +0 -9
- package/lib-esm/hooks/show-entry.js.map +0 -1
- package/src/hooks/show-entry.ts +0 -17
- package/types/hooks/show-entry.d.ts +0 -10
|
@@ -6,9 +6,14 @@ import * as webpack from '@bundle-stats/utils/lib-esm/webpack/compare';
|
|
|
6
6
|
import { useRowsFilter } from '../../hooks/rows-filter';
|
|
7
7
|
import { useRowsSort } from '../../hooks/rows-sort';
|
|
8
8
|
import { useSearchParams } from '../../hooks/search-params';
|
|
9
|
-
import {
|
|
9
|
+
import { useEntryInfo } from '../../hooks/entry-info';
|
|
10
10
|
import { BundlePackages as BundlePackagesComponent } from './bundle-packages';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
getDuplicatePackages,
|
|
13
|
+
getAddRowDuplicateFlag,
|
|
14
|
+
getRowFilter,
|
|
15
|
+
getCustomSort,
|
|
16
|
+
} from './bundle-packages.utils';
|
|
12
17
|
|
|
13
18
|
export const BundlePackages = (props) => {
|
|
14
19
|
const { jobs, search, filters, setState, sortBy, direction, ...restProps } = props;
|
|
@@ -55,7 +60,7 @@ export const BundlePackages = (props) => {
|
|
|
55
60
|
getCustomSort,
|
|
56
61
|
});
|
|
57
62
|
|
|
58
|
-
const
|
|
63
|
+
const [hideEntryInfo, showEntryInfo] = useEntryInfo({ setState });
|
|
59
64
|
|
|
60
65
|
return (
|
|
61
66
|
<BundlePackagesComponent
|
|
@@ -25,26 +25,22 @@ export const MetricsDisplaySelector = (
|
|
|
25
25
|
active={value === MetricsDisplayType.TABLE}
|
|
26
26
|
size="small"
|
|
27
27
|
type="button"
|
|
28
|
+
glyph={Icon.ICONS.TABLE}
|
|
28
29
|
onClick={() => onSelect(MetricsDisplayType.TABLE)}
|
|
29
30
|
className={css.button}
|
|
30
31
|
>
|
|
31
|
-
|
|
32
|
-
<Icon glyph={Icon.ICONS.TABLE} className={css.icon} />
|
|
33
|
-
<span>Table</span>
|
|
34
|
-
</FlexStack>
|
|
32
|
+
Table
|
|
35
33
|
</Button>
|
|
36
34
|
<Button
|
|
37
35
|
outline
|
|
38
36
|
active={value === MetricsDisplayType.TREEMAP}
|
|
39
37
|
size="small"
|
|
40
38
|
type="button"
|
|
39
|
+
glyph={Icon.ICONS.TREEMAP}
|
|
41
40
|
onClick={() => onSelect(MetricsDisplayType.TREEMAP)}
|
|
42
41
|
className={cx(css.button)}
|
|
43
42
|
>
|
|
44
|
-
|
|
45
|
-
<Icon glyph={Icon.ICONS.TREEMAP} className={css.icon} />
|
|
46
|
-
<span>Treemap</span>
|
|
47
|
-
</FlexStack>
|
|
43
|
+
Treemap
|
|
48
44
|
</Button>
|
|
49
45
|
</FlexStack>
|
|
50
46
|
);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
height: 100%;
|
|
13
13
|
fill: var(--color-text);
|
|
14
14
|
font-family: var(--font-family);
|
|
15
|
+
background: var(--color-outline);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/** Tile overlay fill background transition */
|
|
@@ -20,29 +21,31 @@
|
|
|
20
21
|
transition-property: opacity;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
.
|
|
24
|
-
fill: var(--color-
|
|
24
|
+
.leafBackground {
|
|
25
|
+
fill: var(--color-background);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.leafBackdrop {
|
|
29
|
+
fill: var(--color-background);
|
|
25
30
|
transition: var(--ui-transition-out);
|
|
26
31
|
transition-property: opacity;
|
|
27
|
-
opacity: 0.
|
|
32
|
+
opacity: 0.2;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
.
|
|
35
|
+
.leaf:hover .leafBackdrop {
|
|
31
36
|
transition: var(--ui-transition-in);
|
|
32
|
-
opacity: 0.
|
|
37
|
+
opacity: 0.5;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
/**
|
|
36
|
-
.
|
|
40
|
+
/** Leaf wrapper button */
|
|
41
|
+
.leafAction {
|
|
37
42
|
appearance: none;
|
|
38
43
|
border: 0;
|
|
39
|
-
border-radius:
|
|
44
|
+
border-radius: 0;
|
|
40
45
|
padding: 0;
|
|
41
46
|
background: none;
|
|
42
|
-
|
|
43
47
|
width: 100%;
|
|
44
48
|
height: 100%;
|
|
45
|
-
padding: var(--space-xxsmall) var(--space-small);
|
|
46
49
|
display: flex;
|
|
47
50
|
align-items: center;
|
|
48
51
|
font-family: var(--font-family-fixed);
|
|
@@ -51,78 +54,90 @@
|
|
|
51
54
|
text-align: left;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
.
|
|
57
|
+
.leafContent {
|
|
55
58
|
width: 100%;
|
|
56
|
-
|
|
59
|
+
height: 100%;
|
|
57
60
|
display: flex;
|
|
58
61
|
flex-flow: column;
|
|
59
|
-
|
|
62
|
+
justify-content: center;
|
|
63
|
+
padding: var(--space-xxsmall) var(--space-xxsmall);
|
|
64
|
+
gap: var(--space-xxsmall);
|
|
65
|
+
line-height: var(--line-height-heading);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Align label to other elements on the first column leafs */
|
|
69
|
+
.leafWrapper[x="0"] .leafContent {
|
|
70
|
+
padding-left: var(--space-small);
|
|
60
71
|
}
|
|
61
72
|
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
max-
|
|
73
|
+
.leadContentLabel {
|
|
74
|
+
flex: 0 1 auto;
|
|
75
|
+
width: 100%;
|
|
76
|
+
min-width: 0;
|
|
77
|
+
max-height: calc(2em * var(--line-height-heading));
|
|
67
78
|
overflow: hidden;
|
|
68
79
|
word-break: break-word;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
.
|
|
82
|
+
.leadContentValue {
|
|
72
83
|
flex: 0 0 auto;
|
|
73
84
|
max-width: calc(100% + var(--space-xxsmall));
|
|
74
|
-
margin-right: calc(0px - var(--space-xxsmall)); /* take more space if necessary */
|
|
75
85
|
white-space: nowrap;
|
|
76
86
|
overflow: hidden;
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
.
|
|
89
|
+
.leadContentMetric {
|
|
80
90
|
font-weight: bold;
|
|
81
91
|
}
|
|
82
92
|
|
|
83
|
-
.
|
|
93
|
+
.leadContentDelta {
|
|
84
94
|
margin-left: var(--space-xxxsmall);
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
/** Tile size variation */
|
|
88
|
-
.
|
|
98
|
+
.leafSmall .leafContent {
|
|
99
|
+
padding: var(--space-xxxsmall) var(--space-xxsmall);
|
|
100
|
+
gap: 0;
|
|
89
101
|
font-size: var(--size-xsmall);
|
|
90
102
|
}
|
|
91
103
|
|
|
92
|
-
.
|
|
104
|
+
.leafSmall .leadContentLabel {
|
|
93
105
|
margin-top: 0;
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
/** Tile diff variation */
|
|
97
|
-
.
|
|
109
|
+
.leaf-NO_CHANGE .leafBackdrop {
|
|
110
|
+
fill: transparent;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.leaf-NO_CHANGE .leafAction {
|
|
98
114
|
color: var(--color-text-light);
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
.
|
|
117
|
+
.leaf-HIGH_NEGATIVE .leafBackdrop {
|
|
102
118
|
fill: var(--color-red-100);
|
|
103
119
|
}
|
|
104
120
|
|
|
105
|
-
.
|
|
121
|
+
.leaf-NEGATIVE .leafBackdrop {
|
|
106
122
|
fill: var(--color-red-50);
|
|
107
123
|
}
|
|
108
124
|
|
|
109
|
-
.
|
|
125
|
+
.leaf-LOW_NEGATIVE .leafBackdrop {
|
|
110
126
|
fill: var(--color-red-25);
|
|
111
127
|
}
|
|
112
128
|
|
|
113
|
-
.
|
|
129
|
+
.leaf-LOW_POSITIVE .leafBackdrop {
|
|
114
130
|
fill: var(--color-green-25);
|
|
115
131
|
}
|
|
116
132
|
|
|
117
|
-
.
|
|
133
|
+
.leaf-POSITIVE .leafBackdrop {
|
|
118
134
|
fill: var(--color-green-50);
|
|
119
135
|
}
|
|
120
136
|
|
|
121
|
-
.
|
|
137
|
+
.leaf-HIGH_POSITIVE .leafBackdrop {
|
|
122
138
|
fill: var(--color-green-100);
|
|
123
139
|
}
|
|
124
140
|
|
|
125
|
-
|
|
126
141
|
/** empty message */
|
|
127
142
|
.emptyMessage {
|
|
128
143
|
position: absolute;
|
|
@@ -140,7 +155,12 @@
|
|
|
140
155
|
text-align: center;
|
|
141
156
|
}
|
|
142
157
|
|
|
143
|
-
/** tooltip
|
|
158
|
+
/** tooltip */
|
|
159
|
+
.leafContentTooltipAnchor {
|
|
160
|
+
width: 100%;
|
|
161
|
+
height: 100%;
|
|
162
|
+
}
|
|
163
|
+
|
|
144
164
|
.tooltip {
|
|
145
165
|
z-index: var(--layer-xxhigh);
|
|
146
166
|
padding: var(--space-xsmall) var(--space-small);
|
|
@@ -154,15 +174,11 @@
|
|
|
154
174
|
font-size: var(--size-small);
|
|
155
175
|
}
|
|
156
176
|
|
|
157
|
-
.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
border-left-color: transparent;
|
|
161
|
-
border-right-color: transparent;
|
|
162
|
-
position: absolute;
|
|
163
|
-
top: 100%;
|
|
177
|
+
.leafTooltipArrow svg {
|
|
178
|
+
display: block;
|
|
179
|
+
fill: var(--color-background) !important;
|
|
164
180
|
}
|
|
165
181
|
|
|
166
|
-
.
|
|
182
|
+
.leafTooltipContentTitle {
|
|
167
183
|
font-size: var(--size-medium);
|
|
168
184
|
}
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import React, { forwardRef, useCallback, useMemo, useRef
|
|
1
|
+
import React, { RefObject, forwardRef, useCallback, useMemo, useRef } from 'react';
|
|
2
|
+
import { useDebounce, useHoverDirty, useMeasure, useMouseHovered } from 'react-use';
|
|
2
3
|
import cx from 'classnames';
|
|
3
|
-
import { useMeasure } from 'react-use';
|
|
4
4
|
import type { ReportMetricRow, MetricRunInfo, MetricRunInfoBaseline } from '@bundle-stats/utils';
|
|
5
|
-
import { hierarchy, treemap, treemapSquarify } from 'd3';
|
|
6
|
-
import {
|
|
7
|
-
FloatingPortal,
|
|
8
|
-
useClientPoint,
|
|
9
|
-
useFloating,
|
|
10
|
-
useHover,
|
|
11
|
-
useInteractions,
|
|
12
|
-
} from '@floating-ui/react';
|
|
13
|
-
import { arrow, offset } from '@floating-ui/react-dom';
|
|
5
|
+
import { hierarchy, treemap, treemapSquarify, window } from 'd3';
|
|
6
|
+
import { Tooltip, TooltipArrow, TooltipAnchor, useTooltipState, useTooltip } from 'ariakit/tooltip';
|
|
14
7
|
|
|
15
8
|
import { Stack } from '../../layout/stack';
|
|
16
9
|
import { FileName } from '../../ui/file-name';
|
|
@@ -36,10 +29,10 @@ const VERTICAL_SPACING = 4;
|
|
|
36
29
|
const LINE_HEIGHT = 16;
|
|
37
30
|
const LINE_HEIGHT_SMALL = 13.3;
|
|
38
31
|
|
|
39
|
-
function
|
|
32
|
+
function resolveLeafSizeDisplay(width: number, height: number): TileSizeDisplay {
|
|
40
33
|
if (
|
|
41
34
|
height > PADDING_TOP + PADDING_BOTTOM + VERTICAL_SPACING + LINE_HEIGHT * 2 &&
|
|
42
|
-
width > PADDING_LEFT + PADDING_RIGHT +
|
|
35
|
+
width > PADDING_LEFT + PADDING_RIGHT + 128
|
|
43
36
|
) {
|
|
44
37
|
return 'default';
|
|
45
38
|
}
|
|
@@ -54,19 +47,20 @@ function resolveTileSizeDisplay(width: number, height: number): TileSizeDisplay
|
|
|
54
47
|
return 'minimal';
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
interface
|
|
58
|
-
|
|
50
|
+
interface LeafTooltipContentProps {
|
|
51
|
+
item: ReportMetricRow;
|
|
59
52
|
}
|
|
60
53
|
|
|
61
|
-
const
|
|
62
|
-
const {
|
|
63
|
-
|
|
64
|
-
const
|
|
54
|
+
const LeafTooltipContent = (props: LeafTooltipContentProps) => {
|
|
55
|
+
const { item } = props;
|
|
56
|
+
|
|
57
|
+
const currentRun = item.runs[0] as MetricRunInfo;
|
|
58
|
+
const baselineRun = item.runs[item.runs.length - 1] as MetricRunInfoBaseline;
|
|
65
59
|
|
|
66
60
|
return (
|
|
67
|
-
<Stack space="small" className={css.
|
|
68
|
-
<h3 className={css.
|
|
69
|
-
<FileName as="code" name={
|
|
61
|
+
<Stack space="small" className={css.leafTooltipContent}>
|
|
62
|
+
<h3 className={css.leafTooltipContentTitle}>
|
|
63
|
+
<FileName as="code" name={item.label} />
|
|
70
64
|
</h3>
|
|
71
65
|
<RunInfo
|
|
72
66
|
current={currentRun.displayValue}
|
|
@@ -78,7 +72,77 @@ const TooltipContent = (props: TooltipContentProps) => {
|
|
|
78
72
|
);
|
|
79
73
|
};
|
|
80
74
|
|
|
81
|
-
|
|
75
|
+
interface LeafContentProps {
|
|
76
|
+
sizeDisplay: 'minimal' | 'small' | 'default';
|
|
77
|
+
item: ReportMetricRow;
|
|
78
|
+
runInfo: MetricRunInfo;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const LeafContent = forwardRef((props: LeafContentProps, ref: React.Ref<HTMLDivElement>) => {
|
|
82
|
+
const { sizeDisplay, item, runInfo } = props;
|
|
83
|
+
|
|
84
|
+
if (sizeDisplay === 'minimal') {
|
|
85
|
+
return <div className={css.leafContent} ref={ref} />;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className={css.leafContent} ref={ref}>
|
|
90
|
+
<p className={css.leadContentLabel}>{item.label}</p>
|
|
91
|
+
{sizeDisplay !== 'small' && (
|
|
92
|
+
<p className={css.leadContentValue}>
|
|
93
|
+
<span className={css.leadContentMetric}>{runInfo.displayValue}</span>
|
|
94
|
+
<Delta
|
|
95
|
+
className={css.leadContentDelta}
|
|
96
|
+
displayValue={runInfo.displayDeltaPercentage}
|
|
97
|
+
deltaType={runInfo.deltaType}
|
|
98
|
+
/>
|
|
99
|
+
</p>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const LeafContentWithTooltip = (props: LeafContentProps & { parentRef: RefObject<Element> }) => {
|
|
106
|
+
const { sizeDisplay, item, runInfo, parentRef } = props;
|
|
107
|
+
|
|
108
|
+
const pointer = useMouseHovered(parentRef, { whenHovered: true });
|
|
109
|
+
|
|
110
|
+
// Return custom rect based on the mouse position
|
|
111
|
+
const getAnchorRect = useCallback(() => {
|
|
112
|
+
// Skip custom component rect area if the pointer position is empty
|
|
113
|
+
if (pointer.docX === 0 && pointer.docY === 0 && pointer.elW === 0 && pointer.elH === 0) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const newRect = {
|
|
118
|
+
x: pointer.docX - (document?.defaultView?.pageXOffset ?? 0),
|
|
119
|
+
y: pointer.docY - (document?.defaultView?.pageYOffset ?? 0),
|
|
120
|
+
w: 1,
|
|
121
|
+
h: 1,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return newRect;
|
|
125
|
+
}, [pointer.docX, pointer.docY]);
|
|
126
|
+
|
|
127
|
+
const tooltipState = useTooltipState({ gutter: 8, getAnchorRect, timeout: 100 });
|
|
128
|
+
|
|
129
|
+
// Update tooltip position when poiner values are changing
|
|
130
|
+
useDebounce(tooltipState.render, 10, [pointer.docX, pointer.docY]);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
<TooltipAnchor state={tooltipState} className={css.leafContentTooltipAnchor}>
|
|
135
|
+
<LeafContent sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
136
|
+
</TooltipAnchor>
|
|
137
|
+
<Tooltip state={tooltipState} className={css.tooltip}>
|
|
138
|
+
<TooltipArrow state={tooltipState} size={16} className={css.leafTooltipArrow} />
|
|
139
|
+
<LeafTooltipContent item={item} />
|
|
140
|
+
</Tooltip>
|
|
141
|
+
</>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const LeafAction = forwardRef((props: React.ComponentProps<'button'>, ref) => (
|
|
82
146
|
// @ts-expect-error
|
|
83
147
|
<button type="button" xmlns="http://www.w3.org/1999/xhtml" ref={ref} {...props} />
|
|
84
148
|
));
|
|
@@ -105,98 +169,61 @@ interface LeafProps {
|
|
|
105
169
|
onClick?: (id: string) => void;
|
|
106
170
|
}
|
|
107
171
|
|
|
108
|
-
const
|
|
172
|
+
const Leaf = (props: LeafProps) => {
|
|
109
173
|
const { x, y, width, height, data, onClick } = props;
|
|
174
|
+
|
|
110
175
|
const { item } = data;
|
|
111
176
|
const runInfo = item.runs?.[0] as MetricRunInfo;
|
|
112
177
|
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const tileSizeDisplay = useMemo(() => resolveTileSizeDisplay(width, height), [width, height]);
|
|
126
|
-
|
|
127
|
-
const handleOnClick = useCallback(() => {
|
|
128
|
-
if (onClick) {
|
|
129
|
-
onClick(item.key);
|
|
130
|
-
}
|
|
131
|
-
}, [item.key]);
|
|
178
|
+
const sizeDisplay = useMemo(() => resolveLeafSizeDisplay(width, height), [width, height]);
|
|
179
|
+
const handleOnClick = useCallback(() => onClick?.(item.key), [item.key]);
|
|
180
|
+
|
|
181
|
+
const leafContentRef = useRef<HTMLElement>(null);
|
|
182
|
+
const hover = useHoverDirty(leafContentRef);
|
|
183
|
+
|
|
184
|
+
const leafClassName = cx(
|
|
185
|
+
css.leaf,
|
|
186
|
+
css[`leaf-${runInfo.deltaType}`],
|
|
187
|
+
sizeDisplay === 'small' && css.leafSmall,
|
|
188
|
+
);
|
|
132
189
|
|
|
133
190
|
return (
|
|
134
|
-
<g
|
|
135
|
-
className={
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
css
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
{...getReferenceProps()}
|
|
149
|
-
>
|
|
150
|
-
{tileSizeDisplay !== 'minimal' && (
|
|
151
|
-
<div className={css.tileContentWrapper}>
|
|
152
|
-
<p className={css.tileContentLabel}>{item.label}</p>
|
|
153
|
-
<p className={css.tileContentValue}>
|
|
154
|
-
<span className={css.tileContentMetric}>{runInfo.displayValue}</span>
|
|
155
|
-
<Delta
|
|
156
|
-
className={css.tileContentDelta}
|
|
157
|
-
displayValue={runInfo.displayDeltaPercentage}
|
|
158
|
-
deltaType={runInfo.deltaType}
|
|
159
|
-
/>
|
|
160
|
-
</p>
|
|
161
|
-
</div>
|
|
191
|
+
<g className={leafClassName}>
|
|
192
|
+
<rect x={x} y={y} width={width} height={height} className={css.leafBackground} />
|
|
193
|
+
<rect x={x} y={y} width={width} height={height} className={css.leafBackdrop} />
|
|
194
|
+
<foreignObject height={height} width={width} x={x} y={y} className={css.leafWrapper}>
|
|
195
|
+
<LeafAction onClick={handleOnClick} ref={leafContentRef} className={css.leafAction}>
|
|
196
|
+
{hover ? (
|
|
197
|
+
<LeafContentWithTooltip
|
|
198
|
+
sizeDisplay={sizeDisplay}
|
|
199
|
+
item={item}
|
|
200
|
+
runInfo={runInfo}
|
|
201
|
+
parentRef={leafContentRef}
|
|
202
|
+
/>
|
|
203
|
+
) : (
|
|
204
|
+
<LeafContent sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
162
205
|
)}
|
|
163
|
-
</
|
|
164
|
-
{isOpen && (
|
|
165
|
-
<FloatingPortal>
|
|
166
|
-
<div
|
|
167
|
-
ref={refs.setFloating}
|
|
168
|
-
style={floatingStyles}
|
|
169
|
-
{...getFloatingProps()}
|
|
170
|
-
className={css.tooltip}
|
|
171
|
-
>
|
|
172
|
-
<div
|
|
173
|
-
ref={arrowRef}
|
|
174
|
-
style={{ left: middlewareData.arrow?.x }}
|
|
175
|
-
className={css.tooltipArrow}
|
|
176
|
-
/>
|
|
177
|
-
<TooltipContent data={item} />
|
|
178
|
-
</div>
|
|
179
|
-
</FloatingPortal>
|
|
180
|
-
)}
|
|
206
|
+
</LeafAction>
|
|
181
207
|
</foreignObject>
|
|
182
208
|
</g>
|
|
183
209
|
);
|
|
184
210
|
};
|
|
185
211
|
|
|
186
|
-
interface
|
|
212
|
+
interface UseMetricsTreemapHierarchyParams {
|
|
187
213
|
items: Array<ReportMetricRow>;
|
|
188
|
-
|
|
189
|
-
|
|
214
|
+
width: number;
|
|
215
|
+
height: number;
|
|
190
216
|
}
|
|
191
217
|
|
|
192
|
-
|
|
193
|
-
const {
|
|
194
|
-
|
|
195
|
-
const [containerRef, { width, height }] = useMeasure<HTMLDivElement>();
|
|
218
|
+
function useMetricsTreemapHierarchy(params: UseMetricsTreemapHierarchyParams) {
|
|
219
|
+
const { items, width, height } = params;
|
|
196
220
|
|
|
197
221
|
const treemapHierarchy = useMemo(() => {
|
|
198
222
|
const children = items.map((item) => {
|
|
199
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Set leaf value to the largest run value to allow to
|
|
225
|
+
* display the impact of deleted / items
|
|
226
|
+
*/
|
|
200
227
|
const values = item.runs.map((run) => run?.value || 0);
|
|
201
228
|
const value = Math.max(...values);
|
|
202
229
|
|
|
@@ -234,29 +261,39 @@ export const MetricsTreemap = (props: MetricsTreemapProps & React.ComponentProps
|
|
|
234
261
|
return tremapLayout.leaves().filter((leaf) => Boolean(leaf.parent));
|
|
235
262
|
}, [height, width, treemapHierarchy]);
|
|
236
263
|
|
|
264
|
+
return cells;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
interface MetricsTreemapProps {
|
|
268
|
+
items: Array<ReportMetricRow>;
|
|
269
|
+
emptyMessage?: React.ReactNode;
|
|
270
|
+
onItemClick?: (entryId: string) => void;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export const MetricsTreemap = (props: MetricsTreemapProps & React.ComponentProps<'div'>) => {
|
|
274
|
+
const { className = '', items, onItemClick, emptyMessage = 'No data', ...restProps } = props;
|
|
275
|
+
|
|
276
|
+
const [containerRef, { width, height }] = useMeasure<HTMLDivElement>();
|
|
277
|
+
const cells = useMetricsTreemapHierarchy({ items, width, height });
|
|
278
|
+
|
|
237
279
|
return (
|
|
238
280
|
<div className={cx(css.root, className)} {...restProps} ref={containerRef}>
|
|
239
281
|
{cells.length > 0 ? (
|
|
240
|
-
<svg
|
|
241
|
-
width={width}
|
|
242
|
-
height={height}
|
|
243
|
-
viewBox={`0 0 ${width} ${height}`}
|
|
244
|
-
className={css.canvas}
|
|
245
|
-
>
|
|
282
|
+
<svg viewBox={`0 0 ${width} ${height}`} className={css.canvas}>
|
|
246
283
|
{cells.map((leaf) => {
|
|
247
284
|
if (!leaf.parent) {
|
|
248
285
|
return null;
|
|
249
286
|
}
|
|
250
287
|
|
|
251
288
|
return (
|
|
252
|
-
<
|
|
289
|
+
<Leaf
|
|
253
290
|
x={leaf.x0}
|
|
254
291
|
y={leaf.y0}
|
|
255
292
|
width={leaf.x1 - leaf.x0}
|
|
256
293
|
height={leaf.y1 - leaf.y0}
|
|
257
294
|
data={leaf.data as TreeNode}
|
|
258
|
-
key={leaf.id}
|
|
259
295
|
onClick={onItemClick}
|
|
296
|
+
key={leaf.id}
|
|
260
297
|
/>
|
|
261
298
|
);
|
|
262
299
|
})}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseEntryInfoParams {
|
|
4
|
+
setState: (data: { entryId: string }) => void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type UseEntryInfo = [() => void, (entryId: string) => void];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Show/hide side panel entry
|
|
11
|
+
*/
|
|
12
|
+
export function useEntryInfo(params: UseEntryInfoParams): UseEntryInfo {
|
|
13
|
+
const { setState } = params;
|
|
14
|
+
|
|
15
|
+
const hideEntryInfo = useCallback(() => setState({ entryId: '' }), [setState]);
|
|
16
|
+
const showEntryInfo = useCallback((entryId: string) => setState({ entryId }), [setState]);
|
|
17
|
+
|
|
18
|
+
return [hideEntryInfo, showEntryInfo];
|
|
19
|
+
}
|
package/src/query-state.tsx
CHANGED
package/lib/hooks/show-entry.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useShowEntry = void 0;
|
|
4
|
-
const react_1 = require("react");
|
|
5
|
-
const useShowEntry = (params) => {
|
|
6
|
-
const { setState } = params;
|
|
7
|
-
return (0, react_1.useMemo)(() => ({
|
|
8
|
-
hideEntryInfo: () => setState({ entryId: '' }),
|
|
9
|
-
showEntryInfo: (entryId) => setState({ entryId }),
|
|
10
|
-
}), [setState]);
|
|
11
|
-
};
|
|
12
|
-
exports.useShowEntry = useShowEntry;
|
|
13
|
-
//# sourceMappingURL=show-entry.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"show-entry.js","sourceRoot":"","sources":["../../src/hooks/show-entry.ts"],"names":[],"mappings":";;;AAAA,iCAAgC;AAMzB,MAAM,YAAY,GAAG,CAAC,MAAwB,EAAE,EAAE;IACvD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IAE5B,OAAO,IAAA,eAAO,EACZ,GAAG,EAAE,CAAC,CAAC;QACL,aAAa,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAC9C,aAAa,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;KAC1D,CAAC,EACF,CAAC,QAAQ,CAAC,CACX,CAAC;AACJ,CAAC,CAAC;AAVW,QAAA,YAAY,gBAUvB"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
export const useShowEntry = (params) => {
|
|
3
|
-
const { setState } = params;
|
|
4
|
-
return useMemo(() => ({
|
|
5
|
-
hideEntryInfo: () => setState({ entryId: '' }),
|
|
6
|
-
showEntryInfo: (entryId) => setState({ entryId }),
|
|
7
|
-
}), [setState]);
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=show-entry.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"show-entry.js","sourceRoot":"","sources":["../../src/hooks/show-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAMhC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAwB,EAAE,EAAE;IACvD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IAE5B,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC;QACL,aAAa,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAC9C,aAAa,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;KAC1D,CAAC,EACF,CAAC,QAAQ,CAAC,CACX,CAAC;AACJ,CAAC,CAAC"}
|
package/src/hooks/show-entry.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
interface UseEntryIdParams {
|
|
4
|
-
setState: (data: { entryId: string }) => void;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export const useShowEntry = (params: UseEntryIdParams) => {
|
|
8
|
-
const { setState } = params;
|
|
9
|
-
|
|
10
|
-
return useMemo(
|
|
11
|
-
() => ({
|
|
12
|
-
hideEntryInfo: () => setState({ entryId: '' }),
|
|
13
|
-
showEntryInfo: (entryId: string) => setState({ entryId }),
|
|
14
|
-
}),
|
|
15
|
-
[setState],
|
|
16
|
-
);
|
|
17
|
-
};
|