@parca/profile 0.19.83 → 0.19.85

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/MatchersInput/index.d.ts +2 -34
  3. package/dist/MatchersInput/index.d.ts.map +1 -1
  4. package/dist/MatchersInput/index.js +14 -91
  5. package/dist/MetricsGraph/index.d.ts.map +1 -1
  6. package/dist/MetricsGraph/index.js +13 -1
  7. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  8. package/dist/ProfileMetricsGraph/index.js +6 -29
  9. package/dist/ProfileSelector/MetricsGraphSection.d.ts +8 -10
  10. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  11. package/dist/ProfileSelector/MetricsGraphSection.js +8 -41
  12. package/dist/ProfileSelector/index.d.ts +1 -29
  13. package/dist/ProfileSelector/index.d.ts.map +1 -1
  14. package/dist/ProfileSelector/index.js +14 -10
  15. package/dist/QueryControls/index.d.ts +46 -0
  16. package/dist/QueryControls/index.d.ts.map +1 -0
  17. package/dist/{ProfileSelector/QueryControls.js → QueryControls/index.js} +16 -13
  18. package/dist/SimpleMatchers/Select.js +1 -1
  19. package/dist/SimpleMatchers/index.d.ts +6 -10
  20. package/dist/SimpleMatchers/index.d.ts.map +1 -1
  21. package/dist/SimpleMatchers/index.js +30 -45
  22. package/dist/ViewMatchers/index.d.ts +0 -9
  23. package/dist/ViewMatchers/index.d.ts.map +1 -1
  24. package/dist/ViewMatchers/index.js +20 -12
  25. package/dist/contexts/LabelsQueryProvider.d.ts +35 -0
  26. package/dist/contexts/LabelsQueryProvider.d.ts.map +1 -0
  27. package/dist/contexts/LabelsQueryProvider.js +70 -0
  28. package/dist/contexts/UnifiedLabelsContext.d.ts +37 -0
  29. package/dist/contexts/UnifiedLabelsContext.d.ts.map +1 -0
  30. package/dist/contexts/UnifiedLabelsContext.js +88 -0
  31. package/dist/contexts/utils.d.ts +10 -0
  32. package/dist/contexts/utils.d.ts.map +1 -0
  33. package/dist/contexts/utils.js +31 -0
  34. package/dist/hooks/useLabels.d.ts +23 -0
  35. package/dist/hooks/useLabels.d.ts.map +1 -0
  36. package/dist/hooks/useLabels.js +75 -0
  37. package/dist/hooks/useQueryState.d.ts +3 -1
  38. package/dist/hooks/useQueryState.d.ts.map +1 -1
  39. package/dist/hooks/useQueryState.js +19 -12
  40. package/dist/index.d.ts +9 -3
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +9 -3
  43. package/dist/styles.css +1 -1
  44. package/dist/useSumBy.js +1 -1
  45. package/package.json +2 -2
  46. package/src/MatchersInput/index.tsx +17 -163
  47. package/src/MetricsGraph/index.tsx +17 -1
  48. package/src/ProfileMetricsGraph/index.tsx +17 -98
  49. package/src/ProfileSelector/MetricsGraphSection.tsx +25 -119
  50. package/src/ProfileSelector/index.tsx +115 -109
  51. package/src/{ProfileSelector/QueryControls.tsx → QueryControls/index.tsx} +76 -84
  52. package/src/SimpleMatchers/Select.tsx +1 -1
  53. package/src/SimpleMatchers/index.tsx +46 -84
  54. package/src/ViewMatchers/index.tsx +22 -30
  55. package/src/contexts/LabelsQueryProvider.tsx +142 -0
  56. package/src/contexts/UnifiedLabelsContext.tsx +155 -0
  57. package/src/contexts/utils.ts +43 -0
  58. package/src/hooks/useLabels.ts +121 -0
  59. package/src/hooks/useQueryState.ts +27 -16
  60. package/src/index.tsx +29 -3
  61. package/src/useSumBy.ts +1 -1
  62. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts +0 -29
  63. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts.map +0 -1
  64. package/dist/MetricsGraph/UtilizationMetrics/Throughput.js +0 -175
  65. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +0 -28
  66. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +0 -1
  67. package/dist/MetricsGraph/UtilizationMetrics/index.js +0 -186
  68. package/dist/ProfileSelector/QueryControls.d.ts +0 -43
  69. package/dist/ProfileSelector/QueryControls.d.ts.map +0 -1
  70. package/dist/contexts/MatchersInputLabelsContext.d.ts +0 -29
  71. package/dist/contexts/MatchersInputLabelsContext.d.ts.map +0 -1
  72. package/dist/contexts/MatchersInputLabelsContext.js +0 -79
  73. package/dist/contexts/SimpleMatchersLabelContext.d.ts +0 -25
  74. package/dist/contexts/SimpleMatchersLabelContext.d.ts.map +0 -1
  75. package/dist/contexts/SimpleMatchersLabelContext.js +0 -115
  76. package/dist/contexts/UtilizationLabelsContext.d.ts +0 -15
  77. package/dist/contexts/UtilizationLabelsContext.d.ts.map +0 -1
  78. package/dist/contexts/UtilizationLabelsContext.js +0 -25
  79. package/src/MetricsGraph/UtilizationMetrics/Throughput.tsx +0 -405
  80. package/src/MetricsGraph/UtilizationMetrics/index.tsx +0 -426
  81. package/src/contexts/MatchersInputLabelsContext.tsx +0 -141
  82. package/src/contexts/SimpleMatchersLabelContext.tsx +0 -189
  83. package/src/contexts/UtilizationLabelsContext.tsx +0 -45
@@ -11,32 +11,28 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useCallback, useEffect, useMemo, useState} from 'react';
14
+ import {useCallback, useEffect, useState} from 'react';
15
15
 
16
16
  import {Icon} from '@iconify/react';
17
17
  import {useQueryClient} from '@tanstack/react-query';
18
18
  import cx from 'classnames';
19
19
 
20
- import {QueryServiceClient} from '@parca/client';
21
- import {useGrpcMetadata} from '@parca/components';
20
+ import {useGrpcMetadata, useParcaContext} from '@parca/components';
22
21
  import {Query} from '@parca/parser';
23
22
  import {TEST_IDS, testId} from '@parca/test-utils';
24
23
  import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
25
24
 
26
- import {LabelProvider, useLabels} from '../contexts/SimpleMatchersLabelContext';
27
- import {useUtilizationLabels} from '../contexts/UtilizationLabelsContext';
25
+ import {QuerySelection} from '../ProfileSelector';
26
+ import {useUnifiedLabels} from '../contexts/UnifiedLabelsContext';
27
+ import {transformLabelName} from '../contexts/utils';
28
28
  import Select, {type SelectItem} from './Select';
29
29
 
30
30
  interface Props {
31
- queryClient: QueryServiceClient;
32
- setMatchersString: (arg: string) => void;
33
- runQuery: () => void;
34
- currentQuery: Query;
35
- profileType: string;
36
31
  queryBrowserRef: React.RefObject<HTMLDivElement>;
37
- start?: number;
38
- end?: number;
39
32
  searchExecutedTimestamp?: number;
33
+ draftSelection: QuerySelection;
34
+ setDraftMatchers: (selection: string) => void;
35
+ draftParsedQuery?: Query | null;
40
36
  }
41
37
 
42
38
  interface QueryRow {
@@ -47,22 +43,12 @@ interface QueryRow {
47
43
  isLoading: boolean;
48
44
  }
49
45
 
50
- const trimOtelPrefix = (labelName: string): string => {
51
- if (labelName.startsWith('attributes_resource.')) {
52
- return labelName.replace('attributes_resource.', '');
53
- }
54
- if (labelName.startsWith('attributes.')) {
55
- return labelName.replace('attributes.', '');
56
- }
57
- return labelName;
58
- };
59
-
60
46
  export const transformLabelsForSelect = (labelNames: string[]): SelectItem[] => {
61
47
  return labelNames.map(labelName => ({
62
48
  key: labelName,
63
49
  element: {
64
- active: <>{trimOtelPrefix(labelName)}</>,
65
- expanded: <>{trimOtelPrefix(labelName)}</>,
50
+ active: <>{transformLabelName(labelName)}</>,
51
+ expanded: <>{transformLabelName(labelName)}</>,
66
52
  },
67
53
  }));
68
54
  };
@@ -115,25 +101,28 @@ const operatorOptions = [
115
101
  ];
116
102
 
117
103
  const SimpleMatchers = ({
118
- queryClient,
119
- setMatchersString,
120
- currentQuery,
121
- profileType,
122
104
  queryBrowserRef,
123
- start,
124
- end,
125
105
  searchExecutedTimestamp,
106
+ draftSelection,
107
+ setDraftMatchers,
108
+ draftParsedQuery,
126
109
  }: Props): JSX.Element => {
127
- const utilizationLabels = useUtilizationLabels();
128
110
  const [queryRows, setQueryRows] = useState<QueryRow[]>([
129
111
  {labelName: '', operator: '=', labelValue: '', labelValues: [], isLoading: false},
130
112
  ]);
131
113
  const reactQueryClient = useQueryClient();
132
114
  const metadata = useGrpcMetadata();
115
+ const {queryServiceClient: parcaQueryClient} = useParcaContext();
133
116
 
134
117
  const [showAll, setShowAll] = useState(false);
135
118
  const [isActivelyEditing, setIsActivelyEditing] = useState(false);
136
119
 
120
+ const {
121
+ labelNameMappingsForSimpleMatchers: labelNameOptions,
122
+ isLabelNamesLoading: labelNamesLoading,
123
+ refetchLabelNames,
124
+ } = useUnifiedLabels();
125
+
137
126
  // Reset editing mode when search is executed
138
127
  useEffect(() => {
139
128
  if (searchExecutedTimestamp !== undefined && searchExecutedTimestamp > 0) {
@@ -147,18 +136,25 @@ const SimpleMatchers = ({
147
136
 
148
137
  const maxWidthInPixels = `max-w-[${queryBrowserRef.current?.offsetWidth.toString() as string}px]`;
149
138
 
150
- const currentMatchers = currentQuery.matchersString();
139
+ const currentMatchers = draftParsedQuery?.matchersString();
140
+ const profileType = draftParsedQuery?.profileType().toString();
141
+ const start = draftSelection.from;
142
+ const end = draftSelection.to;
151
143
 
152
144
  const fetchLabelValues = useCallback(
153
145
  async (labelName: string): Promise<string[]> => {
154
- if (labelName == null || labelName === '' || profileType == null || profileType === '') {
146
+ if (labelName == null || labelName === '') {
147
+ return [];
148
+ }
149
+
150
+ if (profileType == null || profileType === '') {
155
151
  return [];
156
152
  }
157
153
  try {
158
154
  const values = await reactQueryClient.fetchQuery(
159
155
  [labelName, profileType, start, end],
160
156
  async () => {
161
- const response = await queryClient.values(
157
+ const response = await parcaQueryClient.values(
162
158
  {
163
159
  labelName,
164
160
  match: [],
@@ -183,14 +179,7 @@ const SimpleMatchers = ({
183
179
  return [];
184
180
  }
185
181
  },
186
- [queryClient, metadata, profileType, reactQueryClient, start, end]
187
- );
188
-
189
- const fetchLabelValuesUtilization = useCallback(
190
- async (labelName: string): Promise<string[]> => {
191
- return (await utilizationLabels?.utilizationFetchLabelValues?.(labelName)) ?? [];
192
- },
193
- [utilizationLabels]
182
+ [parcaQueryClient, metadata, profileType, reactQueryClient, start, end]
194
183
  );
195
184
 
196
185
  const updateMatchersString = useCallback(
@@ -199,18 +188,11 @@ const SimpleMatchers = ({
199
188
  .filter(row => row.labelName.length > 0 && row.labelValue)
200
189
  .map(row => `${row.labelName}${row.operator}"${row.labelValue}"`)
201
190
  .join(',');
202
- setMatchersString(matcherString);
191
+ setDraftMatchers(matcherString);
203
192
  },
204
- [setMatchersString]
193
+ [setDraftMatchers]
205
194
  );
206
195
 
207
- const {
208
- labelNameOptions,
209
- isLoading: labelNamesLoading,
210
- refetchLabelValues,
211
- refetchLabelNames,
212
- } = useLabels();
213
-
214
196
  // Helper to ensure selected label name is in the options (for page load before API returns)
215
197
  const getLabelNameOptionsWithSelected = useCallback(
216
198
  (selectedLabelName: string): typeof labelNameOptions => {
@@ -276,20 +258,13 @@ const SimpleMatchers = ({
276
258
 
277
259
  const fetchLabelValuesUnified = useCallback(
278
260
  async (labelName: string): Promise<string[]> => {
279
- const labelType = labelNameOptions.find(option =>
280
- option.values.some(e => e.key === labelName)
281
- )?.type;
282
- const labelValues =
283
- labelType === 'gpu'
284
- ? await fetchLabelValuesUtilization(labelName)
285
- : await fetchLabelValues(labelName);
286
- return labelValues;
261
+ return await fetchLabelValues(labelName);
287
262
  },
288
- [fetchLabelValues, fetchLabelValuesUtilization, labelNameOptions]
263
+ [fetchLabelValues]
289
264
  );
290
265
 
291
266
  useEffect(() => {
292
- if (currentMatchers === '') {
267
+ if (currentMatchers === '' || currentMatchers === undefined) {
293
268
  const defaultRow = {
294
269
  labelName: '',
295
270
  operator: '=',
@@ -306,7 +281,7 @@ const SimpleMatchers = ({
306
281
  const fetchAndSetQueryRows = async (): Promise<void> => {
307
282
  const newRows = await Promise.all(
308
283
  currentMatchers.split(',').map(async matcher => {
309
- const match = matcher.match(/([^=!~]+)([=!~]{1,2})(.+)/);
284
+ const match = matcher.match(/^([^=!~]+)([=!~]{1,2})(.+)$/);
310
285
  if (match === null) return null;
311
286
 
312
287
  const [, labelName, operator, labelValue] = match;
@@ -440,6 +415,13 @@ const SimpleMatchers = ({
440
415
 
441
416
  const isRowRegex = (row: QueryRow): boolean => row.operator === '=~' || row.operator === '!~';
442
417
 
418
+ const handleRefetchForLabelValues = useCallback(
419
+ async (labelName: string): Promise<void> => {
420
+ await fetchLabelValuesUnified(labelName);
421
+ },
422
+ [fetchLabelValuesUnified]
423
+ );
424
+
443
425
  return (
444
426
  <div
445
427
  className={`flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`}
@@ -485,7 +467,7 @@ const SimpleMatchers = ({
485
467
  onButtonClick={() => handleLabelValueClick(index)}
486
468
  editable={isRowRegex(row)}
487
469
  {...testId(TEST_IDS.LABEL_VALUE_SELECT)}
488
- refetchValues={async () => await refetchLabelValues(row.labelName)}
470
+ refetchValues={async () => await handleRefetchForLabelValues(row.labelName)}
489
471
  showLoadingInButton={true}
490
472
  />
491
473
  <button
@@ -530,24 +512,4 @@ const SimpleMatchers = ({
530
512
  );
531
513
  };
532
514
 
533
- export default function SimpleMathersWithProvider(props: Props): JSX.Element {
534
- const labelNameFromMatchers = useMemo(() => {
535
- if (props.currentQuery === undefined) return [];
536
-
537
- const matchers = props.currentQuery.matchers;
538
-
539
- return matchers.map(matcher => matcher.key);
540
- }, [props.currentQuery]);
541
-
542
- return (
543
- <LabelProvider
544
- queryClient={props.queryClient}
545
- profileType={props.profileType}
546
- labelNameFromMatchers={labelNameFromMatchers}
547
- start={props.start}
548
- end={props.end}
549
- >
550
- <SimpleMatchers {...props} />
551
- </LabelProvider>
552
- );
553
- }
515
+ export default SimpleMatchers;
@@ -16,40 +16,32 @@ import React, {useCallback, useEffect, useRef, useState} from 'react';
16
16
  import {Icon} from '@iconify/react';
17
17
  import cx from 'classnames';
18
18
 
19
- import {QueryServiceClient} from '@parca/client';
20
- import {useGrpcMetadata} from '@parca/components';
21
- import {Query} from '@parca/parser';
19
+ import {useGrpcMetadata, useParcaContext} from '@parca/components';
22
20
  import {TEST_IDS, testId} from '@parca/test-utils';
23
21
  import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
24
22
 
25
23
  import CustomSelect, {SelectItem} from '../SimpleMatchers/Select';
24
+ import {useUnifiedLabels} from '../contexts/UnifiedLabelsContext';
25
+ import {useQueryState} from '../hooks/useQueryState';
26
26
 
27
27
  interface Props {
28
28
  labelNames: string[];
29
- profileType: string;
30
- runQuery: () => void;
31
- currentQuery: Query;
32
- queryClient: QueryServiceClient;
33
- setMatchersString: (arg: string) => void;
34
- start?: number;
35
- end?: number;
36
29
  }
37
30
 
38
- const ViewMatchers: React.FC<Props> = ({
39
- labelNames,
40
- profileType,
41
- queryClient,
42
- runQuery,
43
- setMatchersString,
44
- start,
45
- end,
46
- currentQuery,
47
- }) => {
31
+ const ViewMatchers: React.FC<Props> = ({labelNames}) => {
48
32
  const [labelValuesMap, setLabelValuesMap] = useState<Record<string, string[]>>({});
49
33
  const [isLoading, setIsLoading] = useState<Record<string, boolean>>({});
50
34
  const metadata = useGrpcMetadata();
35
+ const {queryServiceClient: parcaQueryClient} = useParcaContext();
51
36
 
52
- const currentMatchers = currentQuery.matchersString();
37
+ const {suffix} = useUnifiedLabels();
38
+
39
+ const {draftSelection, setDraftMatchers, commitDraft, draftParsedQuery} = useQueryState({suffix});
40
+
41
+ const currentMatchers = draftParsedQuery?.matchersString();
42
+ const profileType = draftParsedQuery?.profileType().toString();
43
+ const start = draftSelection.from;
44
+ const end = draftSelection.to;
53
45
 
54
46
  const parseCurrentMatchers = useCallback((matchersString: string): Record<string, string> => {
55
47
  const matches = matchersString.match(/(\w+)="([^"]+)"/g);
@@ -68,15 +60,15 @@ const ViewMatchers: React.FC<Props> = ({
68
60
  );
69
61
  }, []);
70
62
 
71
- const initialSelections = parseCurrentMatchers(currentMatchers);
63
+ const initialSelections = parseCurrentMatchers(currentMatchers ?? '');
72
64
  const selectionsRef = useRef<Record<string, string | null>>(initialSelections);
73
65
 
74
- const runQueryRef = useRef(runQuery);
66
+ const commitDraftRef = useRef(commitDraft);
75
67
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
76
68
 
77
69
  useEffect(() => {
78
- runQueryRef.current = runQuery;
79
- }, [runQuery]);
70
+ commitDraftRef.current = commitDraft;
71
+ }, [commitDraft]);
80
72
 
81
73
  useEffect(() => {
82
74
  selectionsRef.current = initialSelections;
@@ -85,7 +77,7 @@ const ViewMatchers: React.FC<Props> = ({
85
77
  const fetchLabelValues = useCallback(
86
78
  async (labelName: string): Promise<string[]> => {
87
79
  try {
88
- const response = await queryClient.values(
80
+ const response = await parcaQueryClient.values(
89
81
  {
90
82
  labelName,
91
83
  match: [],
@@ -105,7 +97,7 @@ const ViewMatchers: React.FC<Props> = ({
105
97
  return [];
106
98
  }
107
99
  },
108
- [queryClient, metadata, profileType, start, end]
100
+ [parcaQueryClient, metadata, profileType, start, end]
109
101
  );
110
102
 
111
103
  const fetchAllLabelValues = useCallback(async (): Promise<void> => {
@@ -135,16 +127,16 @@ const ViewMatchers: React.FC<Props> = ({
135
127
  .map(([ln, v]) => `${ln}="${v as string}"`);
136
128
 
137
129
  const matcherString = matcherParts.join(',');
138
- setMatchersString(matcherString);
130
+ setDraftMatchers(matcherString);
139
131
 
140
132
  if (timeoutRef.current !== null) {
141
133
  clearTimeout(timeoutRef.current);
142
134
  }
143
135
 
144
136
  timeoutRef.current = setTimeout(() => {
145
- runQueryRef.current();
137
+ commitDraftRef.current();
146
138
  }, 300);
147
- }, [setMatchersString]);
139
+ }, [setDraftMatchers]);
148
140
 
149
141
  const handleSelection = useCallback(
150
142
  (labelName: string, value: string | null): void => {
@@ -0,0 +1,142 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ /**
15
+ * LabelsQueryProvider - Data Fetching Layer
16
+ *
17
+ * This context provider is responsible for fetching label data from the Parca API
18
+ * and making it available to child components through React Context.
19
+ *
20
+ * Purpose:
21
+ * - Fetches label names and values from the Parca profiling API
22
+ * - Manages loading states for label data
23
+ * - Provides refetch functions for manual data refresh
24
+ * - Acts as the primary data source in the label provider architecture
25
+ *
26
+ * Architecture Pattern:
27
+ * This is the first layer in a three-layer architecture:
28
+ * 1. LabelsQueryProvider (this file) - Fetches data from API
29
+ * 2. LabelsSource (in ProfileSelector) - Transforms/merges data
30
+ * 3. UnifiedLabelsProvider - Provides unified interface to UI components
31
+ *
32
+ * Consumer Hook: useLabelsQueryProvider()
33
+ */
34
+
35
+ import {createContext, useContext, useState} from 'react';
36
+
37
+ import {QueryServiceClient} from '@parca/client';
38
+ import {Query} from '@parca/parser';
39
+
40
+ import {useLabelNames, useLabelValues} from '../hooks/useLabels';
41
+ import {useExtractedLabelNames} from './utils';
42
+
43
+ export interface LabelsQueryProviderContextType {
44
+ isLabelNamesLoading: boolean;
45
+ isLabelValuesLoading: boolean;
46
+ currentLabelName: string | null;
47
+ setCurrentLabelName: (name: string | null) => void;
48
+ refetchLabelValues: () => Promise<void>;
49
+ refetchLabelNames: () => Promise<void>;
50
+
51
+ labelNames: string[];
52
+ labelValues: string[];
53
+
54
+ queryClient: QueryServiceClient;
55
+ setMatchersString: (arg: string) => void;
56
+ runQuery: () => void;
57
+ currentQuery: Query;
58
+ profileType: string;
59
+ start?: number;
60
+ end?: number;
61
+
62
+ suffix?: '_a' | '_b';
63
+ }
64
+
65
+ const LabelsQueryProviderContext = createContext<LabelsQueryProviderContextType | null>(null);
66
+
67
+ interface LabelsQueryProviderProps {
68
+ children: React.ReactNode;
69
+
70
+ queryClient: QueryServiceClient;
71
+ setMatchersString: (arg: string) => void;
72
+ runQuery: () => void;
73
+ currentQuery: Query;
74
+ profileType: string;
75
+ start?: number;
76
+ end?: number;
77
+
78
+ suffix?: '_a' | '_b';
79
+ }
80
+
81
+ export function LabelsQueryProvider({
82
+ children,
83
+ queryClient,
84
+ setMatchersString,
85
+ runQuery,
86
+ currentQuery,
87
+ profileType,
88
+ start,
89
+ end,
90
+ suffix,
91
+ }: LabelsQueryProviderProps): JSX.Element {
92
+ const [currentLabelName, setCurrentLabelName] = useState<string | null>(null);
93
+
94
+ const {
95
+ result: labelNamesResponse,
96
+ loading: isLabelNamesLoading,
97
+ refetch: labelNamesRefetch,
98
+ } = useLabelNames(queryClient, profileType, start, end);
99
+
100
+ const labelNames = useExtractedLabelNames(labelNamesResponse.response, labelNamesResponse.error);
101
+
102
+ const {
103
+ result: labelValuesOriginal,
104
+ loading: isLabelValuesLoading,
105
+ refetch: labelValuesRefetch,
106
+ } = useLabelValues(queryClient, currentLabelName ?? '', profileType, start, end);
107
+
108
+ const labelValues = labelValuesOriginal.response;
109
+
110
+ const value = {
111
+ labelNames,
112
+ labelValues,
113
+ isLabelNamesLoading,
114
+ isLabelValuesLoading,
115
+ refetchLabelValues: labelValuesRefetch,
116
+ refetchLabelNames: labelNamesRefetch,
117
+ queryClient,
118
+ setMatchersString,
119
+ runQuery,
120
+ currentQuery,
121
+ profileType,
122
+ start,
123
+ end,
124
+ setCurrentLabelName,
125
+ currentLabelName,
126
+ suffix,
127
+ };
128
+
129
+ return (
130
+ <LabelsQueryProviderContext.Provider value={value}>
131
+ {children}
132
+ </LabelsQueryProviderContext.Provider>
133
+ );
134
+ }
135
+
136
+ export function useLabelsQueryProvider(): LabelsQueryProviderContextType {
137
+ const context = useContext(LabelsQueryProviderContext);
138
+ if (context === null) {
139
+ throw new Error('useLabelsQueryProvider must be used within a LabelsQueryProvider');
140
+ }
141
+ return context;
142
+ }
@@ -0,0 +1,155 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ /**
15
+ * UnifiedLabelsProvider - UI Presentation Layer
16
+ *
17
+ * This context provider transforms raw label data into a format optimized for
18
+ * UI components (QueryControls, SimpleMatchers, etc.).
19
+ *
20
+ * Purpose:
21
+ * - Transforms label arrays into structured formats for dropdowns and selectors
22
+ * - Groups labels by type (e.g., 'cpu', 'gpu') for organized display
23
+ * - Handles label name prefixes and mappings for user-friendly display
24
+ * - Provides a unified interface regardless of data source(s)
25
+ *
26
+ * Architecture Pattern:
27
+ * This is the final layer in a three-layer architecture:
28
+ * 1. LabelsQueryProvider - Fetches data from API
29
+ * 2. LabelsSource (in ProfileSelector) - Transforms/merges data
30
+ * 3. UnifiedLabelsProvider (this file) - Presents data to UI components
31
+ *
32
+ * Consumer Hook: useUnifiedLabels()
33
+ */
34
+
35
+ import {createContext, useContext} from 'react';
36
+
37
+ import {transformLabelsForSelect} from '../SimpleMatchers';
38
+ import type {SelectItem} from '../SimpleMatchers/Select';
39
+ import {useLabelNameMappings, type LabelNameMapping} from './utils';
40
+
41
+ interface LabelNameSection {
42
+ type: string;
43
+ values: SelectItem[];
44
+ }
45
+
46
+ interface UnifiedLabelsContextType {
47
+ labelNameMappingsForMatchersInput: LabelNameMapping[];
48
+ labelNameMappingsForSimpleMatchers: LabelNameSection[];
49
+ labelNames: string[];
50
+ labelValues: string[];
51
+
52
+ isLabelNamesLoading: boolean;
53
+ isLabelValuesLoading: boolean;
54
+ currentLabelName: string | null;
55
+ setCurrentLabelName: (name: string | null) => void;
56
+ shouldHandlePrefixes: boolean;
57
+ refetchLabelValues: () => Promise<void>;
58
+ refetchLabelNames: () => Promise<void>;
59
+ labelNameFromMatchers: string[];
60
+
61
+ suffix?: '_a' | '_b';
62
+ }
63
+
64
+ const UnifiedLabelsContext = createContext<UnifiedLabelsContextType | null>(null);
65
+
66
+ interface UnifiedLabelsProviderProps {
67
+ children: React.ReactNode;
68
+
69
+ currentLabelName: string | null;
70
+ setCurrentLabelName: (name: string | null) => void;
71
+
72
+ labelNames: string[];
73
+ labelValues: string[];
74
+ isLabelNamesLoading: boolean;
75
+ isLabelValuesLoading: boolean;
76
+
77
+ refetchLabelValues: () => Promise<void>;
78
+ refetchLabelNames: () => Promise<void>;
79
+
80
+ suffix?: '_a' | '_b';
81
+ }
82
+
83
+ export function UnifiedLabelsProvider({
84
+ children,
85
+ labelNames,
86
+ isLabelNamesLoading,
87
+ isLabelValuesLoading,
88
+ refetchLabelValues,
89
+ refetchLabelNames,
90
+ currentLabelName,
91
+ setCurrentLabelName,
92
+ labelValues,
93
+ suffix,
94
+ }: UnifiedLabelsProviderProps): JSX.Element {
95
+ const labelNameFromMatchers: string[] = [];
96
+
97
+ const labelNamesFromAPI = labelNames;
98
+
99
+ const labelNameMappingsForMatchersInput = useLabelNameMappings(labelNamesFromAPI);
100
+
101
+ const allLabelNames = new Set(labelNamesFromAPI);
102
+
103
+ const nonMatchingLabels = labelNameFromMatchers.filter(label => !allLabelNames.has(label));
104
+
105
+ const labelNameMappingsForSimpleMatchers: LabelNameSection[] = [];
106
+
107
+ const labels = {
108
+ type: 'cpu',
109
+ labelNames: labelNamesFromAPI,
110
+ isLoading: isLabelNamesLoading,
111
+ };
112
+
113
+ labelNameMappingsForSimpleMatchers.push({
114
+ type: labels.type,
115
+ values: transformLabelsForSelect(labels.labelNames),
116
+ });
117
+
118
+ if (nonMatchingLabels.length > 0) {
119
+ const uniqueNonMatchingLabels = Array.from(new Set(nonMatchingLabels));
120
+ labelNameMappingsForSimpleMatchers.push({
121
+ type: '',
122
+ values: transformLabelsForSelect(uniqueNonMatchingLabels),
123
+ });
124
+ }
125
+
126
+ const value = {
127
+ labelNames: labelNamesFromAPI,
128
+ labelNameMappingsForMatchersInput,
129
+ isLabelNamesLoading,
130
+ isLabelValuesLoading,
131
+ currentLabelName,
132
+ labelValues,
133
+ setCurrentLabelName,
134
+ shouldHandlePrefixes: false,
135
+ refetchLabelValues: async () => {
136
+ await refetchLabelValues();
137
+ },
138
+ refetchLabelNames: async () => {
139
+ await refetchLabelNames();
140
+ },
141
+ labelNameFromMatchers,
142
+ labelNameMappingsForSimpleMatchers,
143
+ suffix,
144
+ };
145
+
146
+ return <UnifiedLabelsContext.Provider value={value}>{children}</UnifiedLabelsContext.Provider>;
147
+ }
148
+
149
+ export function useUnifiedLabels(): UnifiedLabelsContextType {
150
+ const context = useContext(UnifiedLabelsContext);
151
+ if (context === null) {
152
+ throw new Error('useUnifiedLabels must be used within a UnifiedLabelsProvider');
153
+ }
154
+ return context;
155
+ }
@@ -0,0 +1,43 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {useMemo} from 'react';
15
+
16
+ export interface LabelNameMapping {
17
+ displayName: string;
18
+ fullName: string;
19
+ }
20
+
21
+ export const transformLabelName = (labelName: string): string => {
22
+ return labelName.replace(/^(attributes\.|attributes_resource\.)/, '');
23
+ };
24
+
25
+ export const useLabelNameMappings = (labelNames: string[]): LabelNameMapping[] => {
26
+ return useMemo(() => {
27
+ return labelNames.map(name => ({
28
+ displayName: transformLabelName(name),
29
+ fullName: name,
30
+ }));
31
+ }, [labelNames]);
32
+ };
33
+
34
+ export const useExtractedLabelNames = (
35
+ response: {labelNames?: string[]} | undefined,
36
+ error: Error | undefined | null
37
+ ): string[] => {
38
+ return useMemo(() => {
39
+ return (error === undefined || error == null) && response !== undefined && response != null
40
+ ? response.labelNames?.filter(e => e !== '__name__') ?? []
41
+ : [];
42
+ }, [response, error]);
43
+ };