Skip to content

Commit 7e22b1c

Browse files
authored
Refactor ProfileSelector and MetricsGraphSection components (#6025)
* Refactor to not work with utilization labels * wip: continue refactoring * Add support for external label sources in query controls * Add a unified labels context * fix code scan alert * Remove externalLabelSource prop * Fetch labels from UnifiedContext * Export useQueryState hook and de-dedupe series length check * Refactor label matcher state management Replaces matcher string and query management in UnifiedLabelsContext with useQueryState across MatchersInput, SimpleMatchers, and ViewMatchers. Removes unused props and context values, and updates label value fetching to use ParcaContext. Exports LabelsQueryProviderContextType for type safety. * Add parsed query to the usestatehook
1 parent 7728d75 commit 7e22b1c

File tree

21 files changed

+822
-1896
lines changed

21 files changed

+822
-1896
lines changed

ui/packages/shared/profile/src/MatchersInput/index.tsx

Lines changed: 17 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -11,179 +11,44 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14-
import React, {useEffect, useMemo, useRef, useState} from 'react';
14+
import React, {useMemo, useRef, useState} from 'react';
1515

16-
import {useQuery} from '@tanstack/react-query';
1716
import cx from 'classnames';
1817
import TextareaAutosize from 'react-textarea-autosize';
1918

20-
import {LabelsRequest, LabelsResponse, QueryServiceClient, ValuesRequest} from '@parca/client';
21-
import {useGrpcMetadata} from '@parca/components';
2219
import {Query} from '@parca/parser';
2320
import {TEST_IDS, testId} from '@parca/test-utils';
24-
import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
2521

26-
import {UtilizationLabels} from '../ProfileSelector';
27-
import {LabelsProvider, useLabels} from '../contexts/MatchersInputLabelsContext';
28-
import useGrpcQuery from '../useGrpcQuery';
22+
import {useUnifiedLabels} from '../contexts/UnifiedLabelsContext';
23+
import {useQueryState} from '../hooks/useQueryState';
2924
import SuggestionsList, {Suggestion, Suggestions} from './SuggestionsList';
3025

31-
interface MatchersInputProps {
32-
queryClient: QueryServiceClient;
33-
setMatchersString: (arg: string) => void;
34-
runQuery: () => void;
35-
currentQuery: Query;
36-
profileType: string;
37-
start?: number;
38-
end?: number;
39-
}
40-
41-
export interface ILabelNamesResult {
42-
response?: LabelsResponse;
43-
error?: Error;
44-
}
45-
46-
interface UseLabelNames {
47-
result: ILabelNamesResult;
48-
loading: boolean;
49-
refetch: () => Promise<void>;
50-
}
51-
52-
export const useLabelNames = (
53-
client: QueryServiceClient,
54-
profileType: string,
55-
start?: number,
56-
end?: number,
57-
match?: string[]
58-
): UseLabelNames => {
59-
const metadata = useGrpcMetadata();
60-
61-
const {data, isLoading, error, refetch} = useGrpcQuery<LabelsResponse>({
62-
key: ['labelNames', profileType, match?.join(','), start, end],
63-
queryFn: async signal => {
64-
const request: LabelsRequest = {match: match !== undefined ? match : []};
65-
if (start !== undefined && end !== undefined) {
66-
request.start = millisToProtoTimestamp(start);
67-
request.end = millisToProtoTimestamp(end);
68-
}
69-
if (profileType !== undefined) {
70-
request.profileType = profileType;
71-
}
72-
const {response} = await client.labels(request, {meta: metadata, abort: signal});
73-
return response;
74-
},
75-
options: {
76-
enabled: profileType !== undefined && profileType !== '',
77-
keepPreviousData: false,
78-
},
79-
});
80-
81-
useEffect(() => {
82-
console.log('Label names query result:', {data, error, isLoading});
83-
}, [data, error, isLoading]);
84-
85-
return {
86-
result: {response: data, error: error as Error},
87-
loading: isLoading,
88-
refetch: async () => {
89-
await refetch();
90-
},
91-
};
92-
};
93-
94-
interface UseLabelValues {
95-
result: {
96-
response: string[];
97-
error?: Error;
98-
};
99-
loading: boolean;
100-
refetch: () => Promise<void>;
101-
}
102-
103-
export const useLabelValues = (
104-
client: QueryServiceClient,
105-
labelName: string,
106-
profileType: string,
107-
start?: number,
108-
end?: number
109-
): UseLabelValues => {
110-
const metadata = useGrpcMetadata();
111-
112-
const {data, isLoading, error, refetch} = useGrpcQuery<string[]>({
113-
key: ['labelValues', labelName, profileType, start, end],
114-
queryFn: async signal => {
115-
const request: ValuesRequest = {labelName, match: [], profileType};
116-
if (start !== undefined && end !== undefined) {
117-
request.start = millisToProtoTimestamp(start);
118-
request.end = millisToProtoTimestamp(end);
119-
}
120-
const {response} = await client.values(request, {meta: metadata, abort: signal});
121-
return sanitizeLabelValue(response.labelValues);
122-
},
123-
options: {
124-
enabled:
125-
profileType !== undefined &&
126-
profileType !== '' &&
127-
labelName !== undefined &&
128-
labelName !== '',
129-
keepPreviousData: false,
130-
},
131-
});
132-
133-
console.log('Label values query result:', {data, error, isLoading, labelName});
134-
135-
return {
136-
result: {response: data ?? [], error: error as Error},
137-
loading: isLoading,
138-
refetch: async () => {
139-
await refetch();
140-
},
141-
};
142-
};
143-
144-
export const useFetchUtilizationLabelValues = (
145-
labelName: string,
146-
utilizationLabels?: UtilizationLabels
147-
): string[] => {
148-
const {data} = useQuery({
149-
queryKey: ['utilizationLabelValues', labelName],
150-
queryFn: async () => {
151-
const result = await utilizationLabels?.utilizationFetchLabelValues?.(labelName);
152-
return result ?? [];
153-
},
154-
enabled: utilizationLabels?.utilizationFetchLabelValues != null && labelName !== '',
155-
});
156-
157-
return data ?? [];
158-
};
159-
160-
const MatchersInput = ({
161-
setMatchersString,
162-
runQuery,
163-
currentQuery,
164-
}: MatchersInputProps): JSX.Element => {
26+
const MatchersInput = (): JSX.Element => {
16527
const inputRef = useRef<HTMLTextAreaElement | null>(null);
16628
const [focusedInput, setFocusedInput] = useState(false);
16729
const [lastCompleted, setLastCompleted] = useState<Suggestion>(new Suggestion('', '', ''));
16830

16931
const {
17032
labelNames,
17133
labelValues,
172-
labelNameMappings,
34+
labelNameMappingsForMatchersInput: labelNameMappings,
17335
isLabelNamesLoading,
17436
isLabelValuesLoading,
17537
currentLabelName,
17638
setCurrentLabelName,
17739
shouldHandlePrefixes,
17840
refetchLabelValues,
17941
refetchLabelNames,
180-
} = useLabels();
42+
suffix,
43+
} = useUnifiedLabels();
44+
45+
const {setDraftMatchers, commitDraft, draftParsedQuery} = useQueryState({suffix});
18146

182-
const value = currentQuery.matchersString();
47+
const value = draftParsedQuery != null ? draftParsedQuery.matchersString() : '';
18348

18449
const suggestionSections = useMemo(() => {
18550
const suggestionSections = new Suggestions();
186-
Query.suggest(`${currentQuery.profileName()}{${value}`).forEach(function (s) {
51+
Query.suggest(`${draftParsedQuery?.profileName() as string}{${value}`).forEach(function (s) {
18752
// Skip suggestions that we just completed. This really only works,
18853
// because we know the language is not repetitive. For a language that
18954
// has a repeating word, this would not work.
@@ -256,7 +121,7 @@ const MatchersInput = ({
256121
});
257122
return suggestionSections;
258123
}, [
259-
currentQuery,
124+
draftParsedQuery,
260125
lastCompleted,
261126
labelNames,
262127
labelValues,
@@ -271,7 +136,7 @@ const MatchersInput = ({
271136

272137
const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>): void => {
273138
const newValue = e.target.value;
274-
setMatchersString(newValue);
139+
setDraftMatchers(newValue);
275140
resetLastCompleted();
276141
};
277142

@@ -294,7 +159,7 @@ const MatchersInput = ({
294159
const applySuggestion = (suggestion: Suggestion): void => {
295160
const newValue = complete(suggestion);
296161
setLastCompleted(suggestion);
297-
setMatchersString(newValue);
162+
setDraftMatchers(newValue);
298163
if (inputRef.current !== null) {
299164
inputRef.current.value = newValue;
300165
inputRef.current.focus();
@@ -309,7 +174,7 @@ const MatchersInput = ({
309174
setFocusedInput(false);
310175
};
311176

312-
const profileSelected = currentQuery.profileName() === '';
177+
const profileSelected = draftParsedQuery?.profileName() === '';
313178

314179
return (
315180
<div
@@ -345,7 +210,7 @@ const MatchersInput = ({
345210
suggestions={suggestionSections}
346211
applySuggestion={applySuggestion}
347212
inputRef={inputRef.current}
348-
runQuery={runQuery}
213+
runQuery={commitDraft}
349214
focusedInput={focusedInput}
350215
isLabelValuesLoading={
351216
isLabelValuesLoading && lastCompleted.type === 'literal' && lastCompleted.value !== ','
@@ -358,15 +223,4 @@ const MatchersInput = ({
358223
);
359224
};
360225

361-
export default function MatchersInputWithProvider(props: MatchersInputProps): JSX.Element {
362-
return (
363-
<LabelsProvider
364-
queryClient={props.queryClient}
365-
profileType={props.profileType}
366-
start={props.start}
367-
end={props.end}
368-
>
369-
<MatchersInput {...props} />
370-
</LabelsProvider>
371-
);
372-
}
226+
export default MatchersInput;

0 commit comments

Comments
 (0)