@parca/profile 0.19.60 → 0.19.62
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 +8 -0
- package/dist/MatchersInput/SuggestionsList.d.ts +2 -1
- package/dist/MatchersInput/SuggestionsList.d.ts.map +1 -1
- package/dist/MatchersInput/SuggestionsList.js +24 -3
- package/dist/MatchersInput/index.d.ts +1 -0
- package/dist/MatchersInput/index.d.ts.map +1 -1
- package/dist/MatchersInput/index.js +13 -7
- package/dist/ProfileSelector/QueryControls.d.ts.map +1 -1
- package/dist/ProfileSelector/QueryControls.js +4 -1
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts +4 -0
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +2 -4
- package/dist/ProfileView/components/Toolbars/index.d.ts +4 -0
- package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +2 -2
- package/dist/ProfileView/hooks/useVisualizationState.d.ts +2 -0
- package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useVisualizationState.js +19 -1
- package/dist/ProfileView/index.d.ts.map +1 -1
- package/dist/ProfileView/index.js +2 -2
- package/dist/SimpleMatchers/Select.d.ts +3 -0
- package/dist/SimpleMatchers/Select.d.ts.map +1 -1
- package/dist/SimpleMatchers/Select.js +29 -6
- package/dist/SimpleMatchers/index.d.ts +1 -0
- package/dist/SimpleMatchers/index.d.ts.map +1 -1
- package/dist/SimpleMatchers/index.js +92 -9
- package/dist/ViewMatchers/index.js +14 -14
- package/dist/contexts/MatchersInputLabelsContext.d.ts +1 -0
- package/dist/contexts/MatchersInputLabelsContext.d.ts.map +1 -1
- package/dist/contexts/MatchersInputLabelsContext.js +3 -1
- package/dist/contexts/SimpleMatchersLabelContext.d.ts +1 -0
- package/dist/contexts/SimpleMatchersLabelContext.d.ts.map +1 -1
- package/dist/contexts/SimpleMatchersLabelContext.js +19 -2
- package/dist/styles.css +1 -1
- package/dist/useGrpcQuery/index.d.ts +2 -1
- package/dist/useGrpcQuery/index.d.ts.map +1 -1
- package/dist/useGrpcQuery/index.js +2 -1
- package/package.json +8 -8
- package/src/MatchersInput/SuggestionsList.tsx +119 -40
- package/src/MatchersInput/index.tsx +14 -5
- package/src/ProfileSelector/QueryControls.tsx +5 -1
- package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +10 -5
- package/src/ProfileView/components/Toolbars/index.tsx +12 -0
- package/src/ProfileView/hooks/useVisualizationState.ts +34 -1
- package/src/ProfileView/index.tsx +7 -0
- package/src/SimpleMatchers/Select.tsx +128 -60
- package/src/SimpleMatchers/index.tsx +109 -12
- package/src/ViewMatchers/index.tsx +15 -15
- package/src/contexts/MatchersInputLabelsContext.tsx +8 -7
- package/src/contexts/SimpleMatchersLabelContext.tsx +28 -2
- package/src/useGrpcQuery/index.ts +3 -1
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
import {Fragment, useCallback, useEffect, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {Transition} from '@headlessui/react';
|
|
17
|
+
import {Icon} from '@iconify/react';
|
|
18
|
+
import cx from 'classnames';
|
|
17
19
|
import {usePopper} from 'react-popper';
|
|
18
20
|
|
|
19
21
|
import {useParcaContext} from '@parca/components';
|
|
@@ -53,6 +55,7 @@ interface Props {
|
|
|
53
55
|
isLabelNamesLoading: boolean;
|
|
54
56
|
isLabelValuesLoading: boolean;
|
|
55
57
|
shouldTrimPrefix: boolean;
|
|
58
|
+
refetchLabelValues: () => void;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
const LoadingSpinner = (): JSX.Element => {
|
|
@@ -75,6 +78,7 @@ const SuggestionsList = ({
|
|
|
75
78
|
isLabelNamesLoading,
|
|
76
79
|
isLabelValuesLoading,
|
|
77
80
|
shouldTrimPrefix = false,
|
|
81
|
+
refetchLabelValues,
|
|
78
82
|
}: Props): JSX.Element => {
|
|
79
83
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
80
84
|
const {styles, attributes} = usePopper(inputRef, popperElement, {
|
|
@@ -82,6 +86,18 @@ const SuggestionsList = ({
|
|
|
82
86
|
});
|
|
83
87
|
const [highlightedSuggestionIndex, setHighlightedSuggestionIndex] = useState<number>(-1);
|
|
84
88
|
const [showSuggest, setShowSuggest] = useState(true);
|
|
89
|
+
const [isRefetching, setIsRefetching] = useState(false);
|
|
90
|
+
|
|
91
|
+
const handleRefetch = useCallback(async () => {
|
|
92
|
+
if (isRefetching) return;
|
|
93
|
+
|
|
94
|
+
setIsRefetching(true);
|
|
95
|
+
try {
|
|
96
|
+
await refetchLabelValues();
|
|
97
|
+
} finally {
|
|
98
|
+
setIsRefetching(false);
|
|
99
|
+
}
|
|
100
|
+
}, [refetchLabelValues, isRefetching]);
|
|
85
101
|
|
|
86
102
|
const suggestionsLength =
|
|
87
103
|
suggestions.literals.length + suggestions.labelNames.length + suggestions.labelValues.length;
|
|
@@ -231,43 +247,106 @@ const SuggestionsList = ({
|
|
|
231
247
|
style={{width: inputRef?.offsetWidth}}
|
|
232
248
|
className="absolute z-10 mt-1 max-h-[400px] overflow-auto rounded-md bg-gray-50 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-900 sm:text-sm"
|
|
233
249
|
>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
250
|
+
<div className="relative pb-12">
|
|
251
|
+
{isLabelNamesLoading ? (
|
|
252
|
+
<LoadingSpinner />
|
|
253
|
+
) : (
|
|
254
|
+
<>
|
|
255
|
+
{suggestions.labelNames.map((l, i) => (
|
|
256
|
+
<SuggestionItem
|
|
257
|
+
isHighlighted={highlightedSuggestionIndex === i}
|
|
258
|
+
onHighlight={() => setHighlightedSuggestionIndex(i)}
|
|
259
|
+
onApplySuggestion={() => applySuggestionWithIndex(i)}
|
|
260
|
+
onResetHighlight={() => resetHighlight()}
|
|
261
|
+
value={transformLabelsForSuggestions(l.value, shouldTrimPrefix)}
|
|
262
|
+
key={transformLabelsForSuggestions(l.value, shouldTrimPrefix)}
|
|
263
|
+
/>
|
|
264
|
+
))}
|
|
265
|
+
</>
|
|
266
|
+
)}
|
|
267
|
+
|
|
268
|
+
{suggestions.literals.map((l, i) => (
|
|
269
|
+
<SuggestionItem
|
|
270
|
+
isHighlighted={highlightedSuggestionIndex === i + suggestions.labelNames.length}
|
|
271
|
+
onHighlight={() =>
|
|
272
|
+
setHighlightedSuggestionIndex(i + suggestions.labelNames.length)
|
|
273
|
+
}
|
|
274
|
+
onApplySuggestion={() =>
|
|
275
|
+
applySuggestionWithIndex(i + suggestions.labelNames.length)
|
|
276
|
+
}
|
|
277
|
+
onResetHighlight={() => resetHighlight()}
|
|
278
|
+
value={l.value}
|
|
279
|
+
key={l.value}
|
|
280
|
+
/>
|
|
281
|
+
))}
|
|
282
|
+
|
|
283
|
+
{isLabelValuesLoading ? (
|
|
284
|
+
<LoadingSpinner />
|
|
285
|
+
) : suggestions.labelNames.length === 0 && suggestions.literals.length === 0 ? (
|
|
286
|
+
<>
|
|
287
|
+
{suggestions.labelValues.length === 0 ? (
|
|
288
|
+
<div
|
|
289
|
+
className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 text-center"
|
|
290
|
+
data-testid="suggestions-no-results"
|
|
291
|
+
>
|
|
292
|
+
No label values found
|
|
293
|
+
</div>
|
|
294
|
+
) : (
|
|
295
|
+
suggestions.labelValues.map((l, i) => (
|
|
296
|
+
<SuggestionItem
|
|
297
|
+
isHighlighted={
|
|
298
|
+
highlightedSuggestionIndex ===
|
|
299
|
+
i + suggestions.labelNames.length + suggestions.literals.length
|
|
300
|
+
}
|
|
301
|
+
onHighlight={() =>
|
|
302
|
+
setHighlightedSuggestionIndex(
|
|
303
|
+
i + suggestions.labelNames.length + suggestions.literals.length
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
onApplySuggestion={() =>
|
|
307
|
+
applySuggestionWithIndex(
|
|
308
|
+
i + suggestions.labelNames.length + suggestions.literals.length
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
onResetHighlight={() => resetHighlight()}
|
|
312
|
+
value={l.value}
|
|
313
|
+
key={l.value}
|
|
314
|
+
/>
|
|
315
|
+
))
|
|
316
|
+
)}
|
|
317
|
+
<div className="absolute w-full flex items-center justify-center bottom-0 px-3 py-2 bg-gray-50 dark:bg-gray-800">
|
|
318
|
+
<button
|
|
319
|
+
onClick={e => {
|
|
320
|
+
e.preventDefault();
|
|
321
|
+
e.stopPropagation();
|
|
322
|
+
void handleRefetch();
|
|
323
|
+
}}
|
|
324
|
+
disabled={isRefetching}
|
|
325
|
+
className={cx(
|
|
326
|
+
'p-1 flex items-center gap-1 rounded-full transition-all duration-200 w-auto justify-center',
|
|
327
|
+
isRefetching
|
|
328
|
+
? 'cursor-wait opacity-50'
|
|
329
|
+
: 'hover:bg-gray-200 dark:hover:bg-gray-700 cursor-pointer'
|
|
330
|
+
)}
|
|
331
|
+
title="Refresh label values"
|
|
332
|
+
type="button"
|
|
333
|
+
data-testid="suggestions-refresh-button"
|
|
334
|
+
>
|
|
335
|
+
<Icon
|
|
336
|
+
icon="system-uicons:reset"
|
|
337
|
+
className={cx(
|
|
338
|
+
'w-3 h-3 text-gray-500 dark:text-gray-400',
|
|
339
|
+
isRefetching && 'animate-spin'
|
|
340
|
+
)}
|
|
341
|
+
/>
|
|
342
|
+
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
343
|
+
Refresh results
|
|
344
|
+
</span>
|
|
345
|
+
</button>
|
|
346
|
+
</div>
|
|
347
|
+
</>
|
|
348
|
+
) : (
|
|
349
|
+
suggestions.labelValues.map((l, i) => (
|
|
271
350
|
<SuggestionItem
|
|
272
351
|
isHighlighted={
|
|
273
352
|
highlightedSuggestionIndex ===
|
|
@@ -287,9 +366,9 @@ const SuggestionsList = ({
|
|
|
287
366
|
value={l.value}
|
|
288
367
|
key={l.value}
|
|
289
368
|
/>
|
|
290
|
-
))
|
|
291
|
-
|
|
292
|
-
|
|
369
|
+
))
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
293
372
|
</div>
|
|
294
373
|
</Transition>
|
|
295
374
|
</div>
|
|
@@ -73,7 +73,6 @@ export const useLabelNames = (
|
|
|
73
73
|
},
|
|
74
74
|
options: {
|
|
75
75
|
enabled: profileType !== undefined && profileType !== '',
|
|
76
|
-
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
77
76
|
keepPreviousData: false,
|
|
78
77
|
},
|
|
79
78
|
});
|
|
@@ -87,6 +86,7 @@ interface UseLabelValues {
|
|
|
87
86
|
error?: Error;
|
|
88
87
|
};
|
|
89
88
|
loading: boolean;
|
|
89
|
+
refetch: () => void;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export const useLabelValues = (
|
|
@@ -98,7 +98,7 @@ export const useLabelValues = (
|
|
|
98
98
|
): UseLabelValues => {
|
|
99
99
|
const metadata = useGrpcMetadata();
|
|
100
100
|
|
|
101
|
-
const {data, isLoading, error} = useGrpcQuery<string[]>({
|
|
101
|
+
const {data, isLoading, error, refetch} = useGrpcQuery<string[]>({
|
|
102
102
|
key: ['labelValues', labelName, profileType, start, end],
|
|
103
103
|
queryFn: async signal => {
|
|
104
104
|
const request: ValuesRequest = {labelName, match: [], profileType};
|
|
@@ -115,12 +115,17 @@ export const useLabelValues = (
|
|
|
115
115
|
profileType !== '' &&
|
|
116
116
|
labelName !== undefined &&
|
|
117
117
|
labelName !== '',
|
|
118
|
-
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
119
118
|
keepPreviousData: false,
|
|
120
119
|
},
|
|
121
120
|
});
|
|
122
121
|
|
|
123
|
-
return {
|
|
122
|
+
return {
|
|
123
|
+
result: {response: data ?? [], error: error as Error},
|
|
124
|
+
loading: isLoading,
|
|
125
|
+
refetch: () => {
|
|
126
|
+
void refetch();
|
|
127
|
+
},
|
|
128
|
+
};
|
|
124
129
|
};
|
|
125
130
|
|
|
126
131
|
export const useFetchUtilizationLabelValues = (
|
|
@@ -130,8 +135,10 @@ export const useFetchUtilizationLabelValues = (
|
|
|
130
135
|
const {data} = useQuery({
|
|
131
136
|
queryKey: ['utilizationLabelValues', labelName],
|
|
132
137
|
queryFn: async () => {
|
|
133
|
-
|
|
138
|
+
const result = await utilizationLabels?.utilizationFetchLabelValues?.(labelName);
|
|
139
|
+
return result ?? [];
|
|
134
140
|
},
|
|
141
|
+
enabled: utilizationLabels?.utilizationFetchLabelValues != null && labelName !== '',
|
|
135
142
|
});
|
|
136
143
|
|
|
137
144
|
return data ?? [];
|
|
@@ -155,6 +162,7 @@ const MatchersInput = ({
|
|
|
155
162
|
currentLabelName,
|
|
156
163
|
setCurrentLabelName,
|
|
157
164
|
shouldHandlePrefixes,
|
|
165
|
+
refetchLabelValues,
|
|
158
166
|
} = useLabels();
|
|
159
167
|
|
|
160
168
|
const value = currentQuery.matchersString();
|
|
@@ -327,6 +335,7 @@ const MatchersInput = ({
|
|
|
327
335
|
focusedInput={focusedInput}
|
|
328
336
|
isLabelValuesLoading={isLabelValuesLoading && lastCompleted.type === 'literal'}
|
|
329
337
|
shouldTrimPrefix={shouldHandlePrefixes}
|
|
338
|
+
refetchLabelValues={refetchLabelValues}
|
|
330
339
|
/>
|
|
331
340
|
</div>
|
|
332
341
|
);
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
+
import {useState} from 'react';
|
|
15
|
+
|
|
14
16
|
import {Switch} from '@headlessui/react';
|
|
15
17
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
16
18
|
import Select, {type SelectInstance} from 'react-select';
|
|
@@ -93,6 +95,7 @@ export function QueryControls({
|
|
|
93
95
|
profileTypesError,
|
|
94
96
|
}: QueryControlsProps): JSX.Element {
|
|
95
97
|
const {timezone} = useParcaContext();
|
|
98
|
+
const [searchExecutedTimestamp, setSearchExecutedTimestamp] = useState<number>(0);
|
|
96
99
|
|
|
97
100
|
return (
|
|
98
101
|
<div
|
|
@@ -180,7 +183,6 @@ export function QueryControls({
|
|
|
180
183
|
/>
|
|
181
184
|
) : (
|
|
182
185
|
<SimpleMatchers
|
|
183
|
-
key={query.toString()}
|
|
184
186
|
setMatchersString={setMatchersString}
|
|
185
187
|
runQuery={setQueryExpression}
|
|
186
188
|
currentQuery={query}
|
|
@@ -189,6 +191,7 @@ export function QueryControls({
|
|
|
189
191
|
queryClient={queryClient}
|
|
190
192
|
start={timeRangeSelection.getFromMs()}
|
|
191
193
|
end={timeRangeSelection.getToMs()}
|
|
194
|
+
searchExecutedTimestamp={searchExecutedTimestamp}
|
|
192
195
|
/>
|
|
193
196
|
)}
|
|
194
197
|
</div>
|
|
@@ -261,6 +264,7 @@ export function QueryControls({
|
|
|
261
264
|
disabled={searchDisabled}
|
|
262
265
|
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
|
263
266
|
e.preventDefault();
|
|
267
|
+
setSearchExecutedTimestamp(Date.now());
|
|
264
268
|
setQueryExpression(true);
|
|
265
269
|
}}
|
|
266
270
|
id="h-matcher-search-button"
|
|
@@ -187,6 +187,10 @@ interface MultiLevelDropdownProps {
|
|
|
187
187
|
groupBy: string[];
|
|
188
188
|
toggleGroupBy: (key: string) => void;
|
|
189
189
|
isTableVizOnly: boolean;
|
|
190
|
+
alignFunctionName: string;
|
|
191
|
+
setAlignFunctionName: (align: string) => void;
|
|
192
|
+
colorBy: string;
|
|
193
|
+
setColorBy: (colorBy: string) => void;
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
@@ -195,6 +199,10 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
195
199
|
groupBy,
|
|
196
200
|
toggleGroupBy,
|
|
197
201
|
isTableVizOnly,
|
|
202
|
+
alignFunctionName,
|
|
203
|
+
setAlignFunctionName,
|
|
204
|
+
colorBy,
|
|
205
|
+
setColorBy,
|
|
198
206
|
}) => {
|
|
199
207
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
200
208
|
const [shouldOpenLeft, setShouldOpenLeft] = useState(false);
|
|
@@ -202,7 +210,6 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
202
210
|
defaultValue: FIELD_FUNCTION_NAME,
|
|
203
211
|
});
|
|
204
212
|
const [colorStackLegend, setStoreColorStackLegend] = useURLState('color_stack_legend');
|
|
205
|
-
const [colorBy, setColorBy] = useURLState('color_by');
|
|
206
213
|
const [hiddenBinaries, setHiddenBinaries] = useURLState('hidden_binaries', {
|
|
207
214
|
defaultValue: [],
|
|
208
215
|
alwaysReturnArray: true,
|
|
@@ -212,9 +219,7 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
212
219
|
USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key
|
|
213
220
|
);
|
|
214
221
|
const isColorStackLegendEnabled = colorStackLegend === 'true';
|
|
215
|
-
|
|
216
|
-
const [alignFunctionName, setAlignFunctionName] = useURLState('align_function_name');
|
|
217
|
-
const isLeftAligned = alignFunctionName === 'left' || alignFunctionName === undefined;
|
|
222
|
+
const isLeftAligned = alignFunctionName === 'left';
|
|
218
223
|
|
|
219
224
|
// By default, we want delta profiles (CPU) to be relatively compared.
|
|
220
225
|
// For non-delta profiles, like goroutines or memory, we want the profiles to be compared absolutely.
|
|
@@ -421,7 +426,7 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
421
426
|
closeDropdown={close}
|
|
422
427
|
activeValueForSortBy={storeSortBy as string}
|
|
423
428
|
activeValueForColorBy={
|
|
424
|
-
colorBy === undefined || colorBy === '' ? 'binary' :
|
|
429
|
+
colorBy === undefined || colorBy === '' ? 'binary' : colorBy
|
|
425
430
|
}
|
|
426
431
|
activeValuesForLevel={groupBy}
|
|
427
432
|
renderAsDiv={item.renderAsDiv}
|
|
@@ -50,6 +50,10 @@ export interface VisualisationToolbarProps {
|
|
|
50
50
|
setGroupByLabels: (labels: string[]) => void;
|
|
51
51
|
showVisualizationSelector?: boolean;
|
|
52
52
|
sandwichFunctionName?: string;
|
|
53
|
+
alignFunctionName: string;
|
|
54
|
+
setAlignFunctionName: (align: string) => void;
|
|
55
|
+
colorBy: string;
|
|
56
|
+
setColorBy: (colorBy: string) => void;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
export interface TableToolbarProps {
|
|
@@ -139,6 +143,10 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
|
|
|
139
143
|
total,
|
|
140
144
|
filtered,
|
|
141
145
|
showVisualizationSelector = true,
|
|
146
|
+
alignFunctionName,
|
|
147
|
+
setAlignFunctionName,
|
|
148
|
+
colorBy,
|
|
149
|
+
setColorBy,
|
|
142
150
|
}) => {
|
|
143
151
|
const {dashboardItems} = useDashboard();
|
|
144
152
|
|
|
@@ -183,6 +191,10 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
|
|
|
183
191
|
profileType={profileType}
|
|
184
192
|
onSelect={() => {}}
|
|
185
193
|
isTableVizOnly={isTableVizOnly}
|
|
194
|
+
alignFunctionName={alignFunctionName}
|
|
195
|
+
setAlignFunctionName={setAlignFunctionName}
|
|
196
|
+
colorBy={colorBy}
|
|
197
|
+
setColorBy={setColorBy}
|
|
186
198
|
/>
|
|
187
199
|
|
|
188
200
|
<ShareButton
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import {useCallback, useMemo} from 'react';
|
|
15
15
|
|
|
16
16
|
import {JSONParser, JSONSerializer, useURLState, useURLStateCustom} from '@parca/components';
|
|
17
|
+
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
17
18
|
|
|
18
19
|
import {
|
|
19
20
|
FIELD_FUNCTION_FILE_NAME,
|
|
@@ -38,14 +39,28 @@ export const useVisualizationState = (): {
|
|
|
38
39
|
sandwichFunctionName: string | undefined;
|
|
39
40
|
setSandwichFunctionName: (sandwichFunctionName: string | undefined) => void;
|
|
40
41
|
resetSandwichFunctionName: () => void;
|
|
42
|
+
alignFunctionName: string;
|
|
43
|
+
setAlignFunctionName: (align: string) => void;
|
|
41
44
|
} => {
|
|
45
|
+
const [colorByPreference, setColorByPreference] = useUserPreference<string>(
|
|
46
|
+
USER_PREFERENCES.COLOR_BY.key
|
|
47
|
+
);
|
|
48
|
+
const [alignFunctionNamePreference, setAlignFunctionNamePreference] = useUserPreference<string>(
|
|
49
|
+
USER_PREFERENCES.ALIGN_FUNCTION_NAME.key
|
|
50
|
+
);
|
|
51
|
+
|
|
42
52
|
const [curPathArrow, setCurPathArrow] = useURLStateCustom<CurrentPathFrame[]>('cur_path', {
|
|
43
53
|
parse: JSONParser<CurrentPathFrame[]>,
|
|
44
54
|
stringify: JSONSerializer,
|
|
45
55
|
defaultValue: '[]',
|
|
46
56
|
});
|
|
47
57
|
const [colorStackLegend] = useURLState<string | undefined>('color_stack_legend');
|
|
48
|
-
const [colorBy,
|
|
58
|
+
const [colorBy, setStoreColorBy] = useURLState('color_by', {
|
|
59
|
+
defaultValue: colorByPreference,
|
|
60
|
+
});
|
|
61
|
+
const [alignFunctionName, setStoreAlignFunctionName] = useURLState('align_function_name', {
|
|
62
|
+
defaultValue: alignFunctionNamePreference,
|
|
63
|
+
});
|
|
49
64
|
const [groupBy, setStoreGroupBy] = useURLState<string[]>('group_by', {
|
|
50
65
|
defaultValue: [FIELD_FUNCTION_NAME],
|
|
51
66
|
alwaysReturnArray: true,
|
|
@@ -99,6 +114,22 @@ export const useVisualizationState = (): {
|
|
|
99
114
|
setSandwichFunctionName(undefined);
|
|
100
115
|
}, [setSandwichFunctionName]);
|
|
101
116
|
|
|
117
|
+
const setColorBy = useCallback(
|
|
118
|
+
(value: string): void => {
|
|
119
|
+
setStoreColorBy(value);
|
|
120
|
+
setColorByPreference(value);
|
|
121
|
+
},
|
|
122
|
+
[setStoreColorBy, setColorByPreference]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const setAlignFunctionName = useCallback(
|
|
126
|
+
(value: string): void => {
|
|
127
|
+
setStoreAlignFunctionName(value);
|
|
128
|
+
setAlignFunctionNamePreference(value);
|
|
129
|
+
},
|
|
130
|
+
[setStoreAlignFunctionName, setAlignFunctionNamePreference]
|
|
131
|
+
);
|
|
132
|
+
|
|
102
133
|
return {
|
|
103
134
|
curPathArrow,
|
|
104
135
|
setCurPathArrow,
|
|
@@ -112,5 +143,7 @@ export const useVisualizationState = (): {
|
|
|
112
143
|
sandwichFunctionName,
|
|
113
144
|
setSandwichFunctionName,
|
|
114
145
|
resetSandwichFunctionName,
|
|
146
|
+
alignFunctionName: (alignFunctionName as string) ?? 'left',
|
|
147
|
+
setAlignFunctionName,
|
|
115
148
|
};
|
|
116
149
|
};
|
|
@@ -60,11 +60,14 @@ export const ProfileView = ({
|
|
|
60
60
|
setCurPathArrow,
|
|
61
61
|
colorStackLegend,
|
|
62
62
|
colorBy,
|
|
63
|
+
setColorBy,
|
|
63
64
|
groupBy,
|
|
64
65
|
toggleGroupBy,
|
|
65
66
|
setGroupByLabels,
|
|
66
67
|
sandwichFunctionName,
|
|
67
68
|
resetSandwichFunctionName,
|
|
69
|
+
alignFunctionName,
|
|
70
|
+
setAlignFunctionName,
|
|
68
71
|
} = useVisualizationState();
|
|
69
72
|
|
|
70
73
|
const {colorMappings} = useProfileMetadata({
|
|
@@ -148,6 +151,10 @@ export const ProfileView = ({
|
|
|
148
151
|
setGroupByLabels={setGroupByLabels}
|
|
149
152
|
showVisualizationSelector={showVisualizationSelector}
|
|
150
153
|
sandwichFunctionName={sandwichFunctionName}
|
|
154
|
+
alignFunctionName={alignFunctionName}
|
|
155
|
+
setAlignFunctionName={setAlignFunctionName}
|
|
156
|
+
colorBy={colorBy}
|
|
157
|
+
setColorBy={setColorBy}
|
|
151
158
|
/>
|
|
152
159
|
|
|
153
160
|
{isColorStackLegendEnabled && (
|