@parca/profile 0.14.31 → 0.15.0
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/CHANGELOG.md +69 -0
- package/package.json +6 -5
- package/src/IcicleGraph.tsx +12 -12
- package/src/ProfileIcicleGraph.tsx +1 -1
- package/src/ProfileView.tsx +57 -10
- package/src/ProfileViewWithData.tsx +17 -1
- package/src/TopTable.tsx +20 -5
- package/src/components/DiffLegend.tsx +4 -4
- package/src/components/ProfileShareButton/ResultBox.tsx +2 -2
- package/src/components/ProfileShareButton/index.tsx +4 -4
- package/src/testdata/link_data.json +56 -0
- package/src/testdata/tabular.json +30 -0
- package/src/testdata/test_graph.json +53 -0
- package/src/useDelayedLoader.ts +1 -1
- package/src/utils.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,75 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [0.15.0](https://github.com/parca-dev/parca/compare/ui-v0.14.37...ui-v0.15.0) (2022-08-24)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.14.32](https://github.com/parca-dev/parca/compare/ui-v0.14.30...ui-v0.14.32) (2022-08-18)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.14.30](https://github.com/parca-dev/parca/compare/ui-v0.14.24...ui-v0.14.30) (2022-08-17)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* implement callgraph report api ([83c81c6](https://github.com/parca-dev/parca/commit/83c81c67a140b4b723094ee0d6c2308b112b800c))
|
|
20
|
+
* tooltip for callgraph ([c1d0195](https://github.com/parca-dev/parca/commit/c1d0195e45a5aa065a89c936861646f1455336ff))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## [0.14.16](https://github.com/parca-dev/parca/compare/ui-v0.14.11...ui-v0.14.16) (2022-08-03)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## [0.14.3-alpha.0](https://github.com/parca-dev/parca/compare/ui-v0.14.1...ui-v0.14.3-alpha.0) (2022-07-26)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## [0.13.14](https://github.com/parca-dev/parca/compare/ui-v0.13.12...ui-v0.13.14) (2022-06-29)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Features
|
|
36
|
+
|
|
37
|
+
* add tooltip to callgraph ([b261a48](https://github.com/parca-dev/parca/commit/b261a48a92739d5fb957957da929495cb206417e))
|
|
38
|
+
* connect callgraph component to actual data ([b7dd5d8](https://github.com/parca-dev/parca/commit/b7dd5d8b0851d11010f66a3a5ca03d50902636a1))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## [0.13.10](https://github.com/parca-dev/parca/compare/ui-v0.13.2...ui-v0.13.10) (2022-06-22)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## [0.13.2](https://github.com/parca-dev/parca/compare/ui-v0.13.1...ui-v0.13.2) (2022-05-31)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## [0.14.37](https://github.com/parca-dev/parca/compare/ui-v0.14.36...ui-v0.14.37) (2022-08-24)
|
|
53
|
+
|
|
54
|
+
**Note:** Version bump only for package @parca/profile
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## [0.14.36](https://github.com/parca-dev/parca/compare/ui-v0.14.35...ui-v0.14.36) (2022-08-24)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Bug Fixes
|
|
64
|
+
|
|
65
|
+
* **ui:** enforce @typescript-eslint/explicit-function-return-type ([22a18fd](https://github.com/parca-dev/parca/commit/22a18fd3f7befef5cc175131d8c997c3967a5713))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## 0.14.32 (2022-08-18)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
6
75
|
## [0.14.31](https://github.com/parca-dev/parca/compare/ui-v0.14.30...ui-v0.14.31) (2022-08-18)
|
|
7
76
|
|
|
8
77
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@iconify/react": "^3.2.2",
|
|
7
|
-
"@parca/client": "^0.
|
|
8
|
-
"@parca/dynamicsize": "^0.
|
|
9
|
-
"@parca/
|
|
7
|
+
"@parca/client": "^0.15.0",
|
|
8
|
+
"@parca/dynamicsize": "^0.15.0",
|
|
9
|
+
"@parca/functions": "^0.15.0",
|
|
10
|
+
"@parca/parser": "^0.15.0",
|
|
10
11
|
"d3-scale": "^4.0.2",
|
|
11
12
|
"d3-selection": "3.0.0",
|
|
12
13
|
"react-copy-to-clipboard": "^5.1.0"
|
|
@@ -22,5 +23,5 @@
|
|
|
22
23
|
"access": "public",
|
|
23
24
|
"registry": "https://registry.npmjs.org/"
|
|
24
25
|
},
|
|
25
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "9bba12f8523b8d5763d9fb6d81c9df971a83f6fa"
|
|
26
27
|
}
|
package/src/IcicleGraph.tsx
CHANGED
|
@@ -17,7 +17,7 @@ import {throttle} from 'lodash';
|
|
|
17
17
|
import {pointer} from 'd3-selection';
|
|
18
18
|
import {scaleLinear} from 'd3-scale';
|
|
19
19
|
import {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
|
|
20
|
-
import {
|
|
20
|
+
import {GraphTooltip} from '@parca/components';
|
|
21
21
|
import {getLastItem, diffColor, isSearchMatch} from '@parca/functions';
|
|
22
22
|
import {useAppSelector, selectDarkMode, selectSearchNodeString} from '@parca/store';
|
|
23
23
|
|
|
@@ -91,7 +91,7 @@ function IcicleRect({
|
|
|
91
91
|
onMouseLeave,
|
|
92
92
|
onClick,
|
|
93
93
|
curPath,
|
|
94
|
-
}: IcicleRectProps) {
|
|
94
|
+
}: IcicleRectProps): JSX.Element {
|
|
95
95
|
const currentSearchString = useAppSelector(selectSearchNodeString);
|
|
96
96
|
const isFaded = curPath.length > 0 && name !== curPath[curPath.length - 1];
|
|
97
97
|
const styles = isFaded ? fadedIcicleRectStyles : icicleRectStyles;
|
|
@@ -158,7 +158,7 @@ export function IcicleGraphNodes({
|
|
|
158
158
|
path,
|
|
159
159
|
setCurPath,
|
|
160
160
|
curPath,
|
|
161
|
-
}: IcicleGraphNodesProps) {
|
|
161
|
+
}: IcicleGraphNodesProps): JSX.Element {
|
|
162
162
|
const isDarkMode = useAppSelector(selectDarkMode);
|
|
163
163
|
|
|
164
164
|
const nodes =
|
|
@@ -189,7 +189,7 @@ export function IcicleGraphNodes({
|
|
|
189
189
|
|
|
190
190
|
const color = diffColor(diff, cumulative, isDarkMode);
|
|
191
191
|
|
|
192
|
-
const onClick = () => {
|
|
192
|
+
const onClick = (): void => {
|
|
193
193
|
setCurPath(nextPath);
|
|
194
194
|
};
|
|
195
195
|
|
|
@@ -199,8 +199,8 @@ export function IcicleGraphNodes({
|
|
|
199
199
|
? scaleLinear().domain([0, cumulative]).range([0, totalWidth])
|
|
200
200
|
: xScale;
|
|
201
201
|
|
|
202
|
-
const onMouseEnter = () => setHoveringNode(d);
|
|
203
|
-
const onMouseLeave = () => setHoveringNode(undefined);
|
|
202
|
+
const onMouseEnter = (): void => setHoveringNode(d);
|
|
203
|
+
const onMouseLeave = (): void => setHoveringNode(undefined);
|
|
204
204
|
|
|
205
205
|
return (
|
|
206
206
|
<React.Fragment key={`node-${key}`}>
|
|
@@ -250,16 +250,16 @@ export function IcicleGraphRootNode({
|
|
|
250
250
|
setHoveringNode,
|
|
251
251
|
setCurPath,
|
|
252
252
|
curPath,
|
|
253
|
-
}: IcicleGraphRootNodeProps) {
|
|
253
|
+
}: IcicleGraphRootNodeProps): JSX.Element {
|
|
254
254
|
const isDarkMode = useAppSelector(selectDarkMode);
|
|
255
255
|
|
|
256
256
|
const cumulative = parseFloat(node.cumulative);
|
|
257
257
|
const diff = parseFloat(node.diff);
|
|
258
258
|
const color = diffColor(diff, cumulative, isDarkMode);
|
|
259
259
|
|
|
260
|
-
const onClick = () => setCurPath([]);
|
|
261
|
-
const onMouseEnter = () => setHoveringNode(node);
|
|
262
|
-
const onMouseLeave = () => setHoveringNode(undefined);
|
|
260
|
+
const onClick = (): void => setCurPath([]);
|
|
261
|
+
const onMouseEnter = (): void => setHoveringNode(node);
|
|
262
|
+
const onMouseLeave = (): void => setHoveringNode(undefined);
|
|
263
263
|
const path = [];
|
|
264
264
|
|
|
265
265
|
return (
|
|
@@ -301,7 +301,7 @@ export default function IcicleGraph({
|
|
|
301
301
|
setCurPath,
|
|
302
302
|
curPath,
|
|
303
303
|
sampleUnit,
|
|
304
|
-
}: IcicleGraphProps) {
|
|
304
|
+
}: IcicleGraphProps): JSX.Element {
|
|
305
305
|
const [hoveringNode, setHoveringNode] = useState<
|
|
306
306
|
FlamegraphNode | FlamegraphRootNode | undefined
|
|
307
307
|
>();
|
|
@@ -331,7 +331,7 @@ export default function IcicleGraph({
|
|
|
331
331
|
|
|
332
332
|
return (
|
|
333
333
|
<div onMouseLeave={() => setHoveringNode(undefined)}>
|
|
334
|
-
<
|
|
334
|
+
<GraphTooltip
|
|
335
335
|
unit={sampleUnit}
|
|
336
336
|
total={total}
|
|
337
337
|
x={pos[0]}
|
|
@@ -31,7 +31,7 @@ const ProfileIcicleGraph = ({
|
|
|
31
31
|
curPath,
|
|
32
32
|
setNewCurPath,
|
|
33
33
|
sampleUnit,
|
|
34
|
-
}: ProfileIcicleGraphProps) => {
|
|
34
|
+
}: ProfileIcicleGraphProps): JSX.Element => {
|
|
35
35
|
const compareMode = useAppSelector(selectCompareMode);
|
|
36
36
|
const {ref, dimensions} = useContainerDimensions();
|
|
37
37
|
|
package/src/ProfileView.tsx
CHANGED
|
@@ -14,8 +14,17 @@
|
|
|
14
14
|
import React, {useEffect, useMemo, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {parseParams} from '@parca/functions';
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
17
|
+
import useUIFeatureFlag from '@parca/functions/useUIFeatureFlag';
|
|
18
|
+
import {QueryServiceClient, Flamegraph, Top, Callgraph} from '@parca/client';
|
|
19
|
+
import {
|
|
20
|
+
Button,
|
|
21
|
+
Card,
|
|
22
|
+
SearchNodes,
|
|
23
|
+
useGrpcMetadata,
|
|
24
|
+
useParcaTheme,
|
|
25
|
+
Callgraph as CallgraphComponent,
|
|
26
|
+
} from '@parca/components';
|
|
27
|
+
import {useContainerDimensions} from '@parca/dynamicsize';
|
|
19
28
|
|
|
20
29
|
import ProfileShareButton from './components/ProfileShareButton';
|
|
21
30
|
import ProfileIcicleGraph from './ProfileIcicleGraph';
|
|
@@ -40,7 +49,13 @@ interface TopTableData {
|
|
|
40
49
|
error?: any;
|
|
41
50
|
}
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
interface CallgraphData {
|
|
53
|
+
loading: boolean;
|
|
54
|
+
data?: Callgraph;
|
|
55
|
+
error?: any;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type VisualizationType = 'icicle' | 'table' | 'callgraph' | 'both';
|
|
44
59
|
|
|
45
60
|
interface ProfileVisState {
|
|
46
61
|
currentView: VisualizationType;
|
|
@@ -50,6 +65,7 @@ interface ProfileVisState {
|
|
|
50
65
|
interface ProfileViewProps {
|
|
51
66
|
flamegraphData?: FlamegraphData;
|
|
52
67
|
topTableData?: TopTableData;
|
|
68
|
+
callgraphData?: CallgraphData;
|
|
53
69
|
sampleUnit: string;
|
|
54
70
|
profileVisState: ProfileVisState;
|
|
55
71
|
profileSource?: ProfileSource;
|
|
@@ -58,7 +74,7 @@ interface ProfileViewProps {
|
|
|
58
74
|
compare?: boolean;
|
|
59
75
|
}
|
|
60
76
|
|
|
61
|
-
function arrayEquals(a, b): boolean {
|
|
77
|
+
function arrayEquals<T>(a: T[], b: T[]): boolean {
|
|
62
78
|
return (
|
|
63
79
|
Array.isArray(a) &&
|
|
64
80
|
Array.isArray(b) &&
|
|
@@ -79,15 +95,19 @@ export const useProfileVisState = (): ProfileVisState => {
|
|
|
79
95
|
export const ProfileView = ({
|
|
80
96
|
flamegraphData,
|
|
81
97
|
topTableData,
|
|
98
|
+
callgraphData,
|
|
82
99
|
sampleUnit,
|
|
83
100
|
profileSource,
|
|
84
101
|
queryClient,
|
|
85
102
|
navigateTo,
|
|
86
103
|
profileVisState,
|
|
87
104
|
}: ProfileViewProps): JSX.Element => {
|
|
105
|
+
const {ref, dimensions} = useContainerDimensions();
|
|
88
106
|
const [curPath, setCurPath] = useState<string[]>([]);
|
|
89
107
|
const {currentView, setCurrentView} = profileVisState;
|
|
90
108
|
|
|
109
|
+
const [callgraphEnabled] = useUIFeatureFlag('callgraph');
|
|
110
|
+
|
|
91
111
|
const metadata = useGrpcMetadata();
|
|
92
112
|
const {loader} = useParcaTheme();
|
|
93
113
|
|
|
@@ -100,6 +120,9 @@ export const ProfileView = ({
|
|
|
100
120
|
if (currentView === 'icicle') {
|
|
101
121
|
return Boolean(flamegraphData?.loading);
|
|
102
122
|
}
|
|
123
|
+
if (currentView === 'callgraph') {
|
|
124
|
+
return !!callgraphData?.loading;
|
|
125
|
+
}
|
|
103
126
|
if (currentView === 'table') {
|
|
104
127
|
return Boolean(topTableData?.loading);
|
|
105
128
|
}
|
|
@@ -107,7 +130,7 @@ export const ProfileView = ({
|
|
|
107
130
|
return Boolean(flamegraphData?.loading) || Boolean(topTableData?.loading);
|
|
108
131
|
}
|
|
109
132
|
return false;
|
|
110
|
-
}, [currentView, flamegraphData?.loading, topTableData?.loading]);
|
|
133
|
+
}, [currentView, callgraphData?.loading, flamegraphData?.loading, topTableData?.loading]);
|
|
111
134
|
|
|
112
135
|
const isLoaderVisible = useDelayedLoader(isLoading);
|
|
113
136
|
|
|
@@ -124,7 +147,7 @@ export const ProfileView = ({
|
|
|
124
147
|
);
|
|
125
148
|
}
|
|
126
149
|
|
|
127
|
-
const downloadPProf = async (e: React.MouseEvent<HTMLElement>) => {
|
|
150
|
+
const downloadPProf = async (e: React.MouseEvent<HTMLElement>): Promise<void> => {
|
|
128
151
|
e.preventDefault();
|
|
129
152
|
if (profileSource == null || queryClient == null) {
|
|
130
153
|
return;
|
|
@@ -141,15 +164,15 @@ export const ProfileView = ({
|
|
|
141
164
|
}
|
|
142
165
|
};
|
|
143
166
|
|
|
144
|
-
const resetIcicleGraph = () => setCurPath([]);
|
|
167
|
+
const resetIcicleGraph = (): void => setCurPath([]);
|
|
145
168
|
|
|
146
|
-
const setNewCurPath = (path: string[]) => {
|
|
169
|
+
const setNewCurPath = (path: string[]): void => {
|
|
147
170
|
if (!arrayEquals(curPath, path)) {
|
|
148
171
|
setCurPath(path);
|
|
149
172
|
}
|
|
150
173
|
};
|
|
151
174
|
|
|
152
|
-
const switchProfileView = (view: VisualizationType) => {
|
|
175
|
+
const switchProfileView = (view: VisualizationType): void => {
|
|
153
176
|
if (view == null) {
|
|
154
177
|
return;
|
|
155
178
|
}
|
|
@@ -196,6 +219,18 @@ export const ProfileView = ({
|
|
|
196
219
|
</Button>
|
|
197
220
|
</div>
|
|
198
221
|
|
|
222
|
+
{callgraphEnabled ? (
|
|
223
|
+
<div className="mr-3">
|
|
224
|
+
<Button
|
|
225
|
+
variant={`${currentView === 'callgraph' ? 'primary' : 'neutral'}`}
|
|
226
|
+
onClick={() => switchProfileView('callgraph')}
|
|
227
|
+
className="whitespace-nowrap text-ellipsis"
|
|
228
|
+
>
|
|
229
|
+
Call Graph
|
|
230
|
+
</Button>
|
|
231
|
+
</div>
|
|
232
|
+
) : null}
|
|
233
|
+
|
|
199
234
|
<Button
|
|
200
235
|
variant={`${currentView === 'table' ? 'primary' : 'neutral'}`}
|
|
201
236
|
className="items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
|
|
@@ -222,7 +257,7 @@ export const ProfileView = ({
|
|
|
222
257
|
</div>
|
|
223
258
|
</div>
|
|
224
259
|
|
|
225
|
-
<div className="flex space-x-4 justify-between">
|
|
260
|
+
<div ref={ref} className="flex space-x-4 justify-between w-full">
|
|
226
261
|
{currentView === 'icicle' && flamegraphData?.data != null && (
|
|
227
262
|
<div className="w-full">
|
|
228
263
|
<ProfileIcicleGraph
|
|
@@ -234,6 +269,18 @@ export const ProfileView = ({
|
|
|
234
269
|
</div>
|
|
235
270
|
)}
|
|
236
271
|
|
|
272
|
+
{currentView === 'callgraph' && callgraphData?.data != null && (
|
|
273
|
+
<div className="w-full">
|
|
274
|
+
{dimensions?.width && (
|
|
275
|
+
<CallgraphComponent
|
|
276
|
+
graph={callgraphData.data}
|
|
277
|
+
sampleUnit={sampleUnit}
|
|
278
|
+
width={dimensions?.width}
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
)}
|
|
283
|
+
|
|
237
284
|
{currentView === 'table' && topTableData != null && (
|
|
238
285
|
<div className="w-full">
|
|
239
286
|
<TopTable data={topTableData.data} sampleUnit={sampleUnit} />
|
|
@@ -30,7 +30,7 @@ export const ProfileViewWithData = ({
|
|
|
30
30
|
queryClient,
|
|
31
31
|
profileSource,
|
|
32
32
|
navigateTo,
|
|
33
|
-
}: ProfileViewWithDataProps) => {
|
|
33
|
+
}: ProfileViewWithDataProps): JSX.Element => {
|
|
34
34
|
const profileVisState = useProfileVisState();
|
|
35
35
|
const {currentView} = profileVisState;
|
|
36
36
|
const {
|
|
@@ -49,6 +49,14 @@ export const ProfileViewWithData = ({
|
|
|
49
49
|
skip: currentView !== 'table' && currentView !== 'both',
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
const {
|
|
53
|
+
isLoading: callgraphLoading,
|
|
54
|
+
response: callgraphResponse,
|
|
55
|
+
error: callgraphError,
|
|
56
|
+
} = useQuery(queryClient, profileSource, QueryRequest_ReportType.CALLGRAPH, {
|
|
57
|
+
skip: currentView != 'callgraph',
|
|
58
|
+
});
|
|
59
|
+
|
|
52
60
|
const sampleUnit = profileSource.ProfileType().sampleUnit;
|
|
53
61
|
|
|
54
62
|
return (
|
|
@@ -67,6 +75,14 @@ export const ProfileViewWithData = ({
|
|
|
67
75
|
topTableResponse?.report.oneofKind === 'top' ? topTableResponse.report.top : undefined,
|
|
68
76
|
error: topTableError,
|
|
69
77
|
}}
|
|
78
|
+
callgraphData={{
|
|
79
|
+
loading: callgraphLoading,
|
|
80
|
+
data:
|
|
81
|
+
callgraphResponse?.report.oneofKind === 'callgraph'
|
|
82
|
+
? callgraphResponse?.report?.callgraph
|
|
83
|
+
: undefined,
|
|
84
|
+
error: callgraphError,
|
|
85
|
+
}}
|
|
70
86
|
profileVisState={profileVisState}
|
|
71
87
|
sampleUnit={sampleUnit}
|
|
72
88
|
profileSource={profileSource}
|
package/src/TopTable.tsx
CHANGED
|
@@ -26,7 +26,7 @@ interface TopTableProps {
|
|
|
26
26
|
sampleUnit: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const Arrow = ({direction}: {direction: string | undefined}) => {
|
|
29
|
+
const Arrow = ({direction}: {direction: string | undefined}): JSX.Element => {
|
|
30
30
|
return (
|
|
31
31
|
<svg
|
|
32
32
|
className={`${direction !== undefined ? 'fill-[#161616] dark:fill-[#ffffff]' : ''}`}
|
|
@@ -41,7 +41,22 @@ const Arrow = ({direction}: {direction: string | undefined}) => {
|
|
|
41
41
|
);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const useSortableData = (
|
|
44
|
+
const useSortableData = (
|
|
45
|
+
top?: Top,
|
|
46
|
+
config = {key: 'cumulative', direction: 'desc'}
|
|
47
|
+
): {
|
|
48
|
+
items:
|
|
49
|
+
| Array<{
|
|
50
|
+
diff: number;
|
|
51
|
+
cumulative: number;
|
|
52
|
+
flat: number;
|
|
53
|
+
name: string | undefined;
|
|
54
|
+
meta?: TopNodeMeta | undefined;
|
|
55
|
+
}>
|
|
56
|
+
| undefined;
|
|
57
|
+
requestSort: (key: string) => void;
|
|
58
|
+
sortConfig: {key: string; direction: string} | null;
|
|
59
|
+
} => {
|
|
45
60
|
const [sortConfig, setSortConfig] = React.useState<{key: string; direction: string} | null>(
|
|
46
61
|
config
|
|
47
62
|
);
|
|
@@ -74,7 +89,7 @@ const useSortableData = (top?: Top, config = {key: 'cumulative', direction: 'des
|
|
|
74
89
|
return sortableItems;
|
|
75
90
|
}, [items, sortConfig]);
|
|
76
91
|
|
|
77
|
-
const requestSort = (key: string) => {
|
|
92
|
+
const requestSort = (key: string): void => {
|
|
78
93
|
let direction = 'desc';
|
|
79
94
|
if (sortConfig != null && sortConfig.key === key && sortConfig.direction === 'desc') {
|
|
80
95
|
direction = 'asc';
|
|
@@ -112,14 +127,14 @@ export const TopTable = ({data: top, sampleUnit}: TopTableProps): JSX.Element =>
|
|
|
112
127
|
const total = top != null ? top.list.length : 0;
|
|
113
128
|
if (total === 0) return <>Profile has no samples</>;
|
|
114
129
|
|
|
115
|
-
const getClassNamesFor = (name: string) => {
|
|
130
|
+
const getClassNamesFor = (name: string): string | undefined => {
|
|
116
131
|
if (sortConfig == null) {
|
|
117
132
|
return;
|
|
118
133
|
}
|
|
119
134
|
return sortConfig.key === name ? sortConfig.direction : undefined;
|
|
120
135
|
};
|
|
121
136
|
|
|
122
|
-
const addPlusSign = (num: string) => {
|
|
137
|
+
const addPlusSign = (num: string): string => {
|
|
123
138
|
if (num.charAt(0) === '0' || num.charAt(0) === '-') {
|
|
124
139
|
return num;
|
|
125
140
|
}
|
|
@@ -25,7 +25,7 @@ const DiffLegendBar = ({
|
|
|
25
25
|
}: {
|
|
26
26
|
onMouseEnter: () => void;
|
|
27
27
|
onMouseLeave: () => void;
|
|
28
|
-
}) => {
|
|
28
|
+
}): JSX.Element => {
|
|
29
29
|
const isDarkMode = useAppSelector(selectDarkMode);
|
|
30
30
|
|
|
31
31
|
return (
|
|
@@ -54,7 +54,7 @@ const DiffLegendBar = ({
|
|
|
54
54
|
);
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
const DiffLegend = () => {
|
|
57
|
+
const DiffLegend = (): JSX.Element => {
|
|
58
58
|
const [showLegendTooltip, setShowLegendTooltip] = useState(false);
|
|
59
59
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
60
60
|
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null);
|
|
@@ -64,10 +64,10 @@ const DiffLegend = () => {
|
|
|
64
64
|
strategy: 'absolute',
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
const handleMouseEnter = () => {
|
|
67
|
+
const handleMouseEnter = (): void => {
|
|
68
68
|
setShowLegendTooltip(true);
|
|
69
69
|
};
|
|
70
|
-
const handleMouseLeave = () => {
|
|
70
|
+
const handleMouseLeave = (): void => {
|
|
71
71
|
setShowLegendTooltip(false);
|
|
72
72
|
};
|
|
73
73
|
|
|
@@ -24,10 +24,10 @@ interface Props {
|
|
|
24
24
|
|
|
25
25
|
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
|
26
26
|
|
|
27
|
-
const ResultBox = ({value, className = ''}: Props) => {
|
|
27
|
+
const ResultBox = ({value, className = ''}: Props): JSX.Element => {
|
|
28
28
|
const [isCopied, setIsCopied] = useState<boolean>(false);
|
|
29
29
|
|
|
30
|
-
const onCopy = () => {
|
|
30
|
+
const onCopy = (): void => {
|
|
31
31
|
setIsCopied(true);
|
|
32
32
|
(window.document?.activeElement as HTMLElement)?.blur();
|
|
33
33
|
if (timeoutHandle != null) {
|
|
@@ -34,14 +34,14 @@ const ProfileShareModal = ({
|
|
|
34
34
|
closeModal,
|
|
35
35
|
queryRequest,
|
|
36
36
|
queryClient,
|
|
37
|
-
}: ProfileShareModalProps) => {
|
|
37
|
+
}: ProfileShareModalProps): JSX.Element => {
|
|
38
38
|
const [isShared, setIsShared] = useState(false);
|
|
39
39
|
const [loading, setLoading] = useState<boolean>(false);
|
|
40
40
|
const [error, setError] = useState<string>('');
|
|
41
41
|
const [description, setDescription] = useState<string>('');
|
|
42
42
|
const [sharedLink, setSharedLink] = useState<string>('');
|
|
43
43
|
const metadata = useGrpcMetadata();
|
|
44
|
-
const isFormDataValid = () => true;
|
|
44
|
+
const isFormDataValid = (): boolean => true;
|
|
45
45
|
|
|
46
46
|
const handleSubmit: () => Promise<void> = async () => {
|
|
47
47
|
try {
|
|
@@ -60,7 +60,7 @@ const ProfileShareModal = ({
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const onClose = () => {
|
|
63
|
+
const onClose = (): void => {
|
|
64
64
|
setLoading(false);
|
|
65
65
|
setError('');
|
|
66
66
|
setDescription('');
|
|
@@ -113,7 +113,7 @@ const ProfileShareModal = ({
|
|
|
113
113
|
);
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
const ProfileShareButton = ({queryRequest, queryClient}: Props) => {
|
|
116
|
+
const ProfileShareButton = ({queryRequest, queryClient}: Props): JSX.Element => {
|
|
117
117
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
118
118
|
|
|
119
119
|
return (
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"total": "4358676",
|
|
3
|
+
"unit": "count",
|
|
4
|
+
"height": 27,
|
|
5
|
+
"data": {
|
|
6
|
+
"nodes": {
|
|
7
|
+
"root": {
|
|
8
|
+
"id": "root",
|
|
9
|
+
"cumulative": "10",
|
|
10
|
+
"diff": "0"
|
|
11
|
+
},
|
|
12
|
+
"1": {
|
|
13
|
+
"id": "1",
|
|
14
|
+
"cumulative": "2",
|
|
15
|
+
"diff": "0"
|
|
16
|
+
},
|
|
17
|
+
"2": {
|
|
18
|
+
"id": "2",
|
|
19
|
+
"cumulative": "9",
|
|
20
|
+
"diff": "0"
|
|
21
|
+
},
|
|
22
|
+
"3": {
|
|
23
|
+
"id": "3",
|
|
24
|
+
"cumulative": "8"
|
|
25
|
+
},
|
|
26
|
+
"4": {
|
|
27
|
+
"id": "4",
|
|
28
|
+
"cumulative": "2"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"links": [
|
|
32
|
+
{
|
|
33
|
+
"source": "root",
|
|
34
|
+
"target": "1"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"source": "root",
|
|
38
|
+
"target": "4"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"source": "1",
|
|
42
|
+
"target": "2"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"source": "2",
|
|
46
|
+
"target": "3"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"reversedLinks": [
|
|
50
|
+
{
|
|
51
|
+
"source": "3",
|
|
52
|
+
"target": "1"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"total": "4358676",
|
|
3
|
+
"unit": "count",
|
|
4
|
+
"height": 27,
|
|
5
|
+
"data": [
|
|
6
|
+
{
|
|
7
|
+
"id": "root"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "child_1",
|
|
11
|
+
"parentIds": ["root", "loop_child"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "child_2",
|
|
15
|
+
"parentIds": ["root"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "grandchild_1",
|
|
19
|
+
"parentIds": ["child_1"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "grandchild_2",
|
|
23
|
+
"parentIds": ["child_1"]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "loop_child",
|
|
27
|
+
"parentIds": ["grandchild_1"]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"total": "4358676",
|
|
3
|
+
"unit": "count",
|
|
4
|
+
"height": 27,
|
|
5
|
+
"root": {
|
|
6
|
+
"id": "root",
|
|
7
|
+
"cumulative": "4358676",
|
|
8
|
+
"diff": "0",
|
|
9
|
+
"children": [
|
|
10
|
+
{
|
|
11
|
+
"id": "child_1",
|
|
12
|
+
"cumulative": "1",
|
|
13
|
+
"diff": "0",
|
|
14
|
+
"children": [
|
|
15
|
+
{
|
|
16
|
+
"id": "child_2",
|
|
17
|
+
"cumulative": "1",
|
|
18
|
+
"diff": "0",
|
|
19
|
+
"children": [
|
|
20
|
+
{
|
|
21
|
+
"id": "root",
|
|
22
|
+
"cumulative": "4358676",
|
|
23
|
+
"diff": "0",
|
|
24
|
+
"children": [
|
|
25
|
+
{
|
|
26
|
+
"id": "child_1",
|
|
27
|
+
"cumulative": "1",
|
|
28
|
+
"diff": "0",
|
|
29
|
+
"children": [
|
|
30
|
+
{
|
|
31
|
+
"id": "child_2",
|
|
32
|
+
"cumulative": "1",
|
|
33
|
+
"diff": "0",
|
|
34
|
+
"children": [
|
|
35
|
+
{
|
|
36
|
+
"id": "child_1",
|
|
37
|
+
"cumulative": "1",
|
|
38
|
+
"diff": "0",
|
|
39
|
+
"children": []
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/useDelayedLoader.ts
CHANGED
|
@@ -17,7 +17,7 @@ interface DelayedLoaderOptions {
|
|
|
17
17
|
delay?: number;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const useDelayedLoader = (isLoading = false, options?: DelayedLoaderOptions) => {
|
|
20
|
+
const useDelayedLoader = (isLoading = false, options?: DelayedLoaderOptions): boolean => {
|
|
21
21
|
const {delay = 500} = options ?? {};
|
|
22
22
|
const [isLoaderVisible, setIsLoaderVisible] = useState<boolean>(false);
|
|
23
23
|
useEffect(() => {
|
package/src/utils.ts
CHANGED