@parca/profile 0.16.61 → 0.16.63
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/Callgraph/index.js +17 -12
- package/dist/MatchersInput/index.d.ts +1 -5
- package/dist/MatchersInput/index.js +69 -280
- package/dist/ProfileMetricsGraph/index.d.ts +2 -2
- package/dist/ProfileMetricsGraph/index.js +22 -30
- package/dist/styles.css +1 -1
- package/package.json +4 -4
- package/src/Callgraph/index.tsx +18 -12
- package/src/MatchersInput/index.tsx +173 -436
- package/src/ProfileMetricsGraph/index.tsx +26 -23
|
@@ -14,10 +14,9 @@
|
|
|
14
14
|
import React, {Fragment, useState, useEffect} from 'react';
|
|
15
15
|
import {Transition} from '@headlessui/react';
|
|
16
16
|
import {Query} from '@parca/parser';
|
|
17
|
-
import {LabelsResponse, QueryServiceClient
|
|
17
|
+
import {LabelsResponse, QueryServiceClient} from '@parca/client';
|
|
18
18
|
import {usePopper} from 'react-popper';
|
|
19
19
|
import cx from 'classnames';
|
|
20
|
-
|
|
21
20
|
import {useParcaTheme, useGrpcMetadata} from '@parca/components';
|
|
22
21
|
|
|
23
22
|
interface MatchersInputProps {
|
|
@@ -31,39 +30,12 @@ export interface ILabelNamesResult {
|
|
|
31
30
|
response?: LabelsResponse;
|
|
32
31
|
error?: Error;
|
|
33
32
|
}
|
|
34
|
-
export interface ILabelValuesResult {
|
|
35
|
-
response?: ValuesResponse;
|
|
36
|
-
error?: Error;
|
|
37
|
-
}
|
|
38
33
|
|
|
39
34
|
interface UseLabelNames {
|
|
40
35
|
result: ILabelNamesResult;
|
|
41
36
|
loading: boolean;
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
interface Matchers {
|
|
45
|
-
key: string;
|
|
46
|
-
matcherType: string;
|
|
47
|
-
value: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
enum Labels {
|
|
51
|
-
labelName = 'labelName',
|
|
52
|
-
labelValue = 'labelValue',
|
|
53
|
-
literal = 'literal',
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// eslint-disable-next-line no-useless-escape
|
|
57
|
-
const labelNameValueRe = /(^([a-z])\w+)(=~|=|!=|!~)(\")[a-zA-Z0-9_.-:]+(\")$/g; // labelNameValueRe matches the following: labelName=~"labelValue"
|
|
58
|
-
const labelNameValueWithoutQuotesRe = /(^([a-z])\w+)(=~|=|!=|!~)[a-zA-Z0-9_.-:]+$/g; // labelNameValueWithoutQuotesRe matches the following: labelName=~labelValue
|
|
59
|
-
const labelNameLiteralRe = /(^([a-z])\w+)(=~|=|!=|!~)/; // labelNameLiteralRe matches the following: labelName=~, labelName!=~, labelName=, labelName!=
|
|
60
|
-
const literalRe = /(=~|=|!=|!~)/; // literalRe matches the following: =~, =, !=, !~
|
|
61
|
-
|
|
62
|
-
const addQuoteMarks = (labelValue: string): string => {
|
|
63
|
-
// eslint-disable-next-line no-useless-escape
|
|
64
|
-
return `\"${labelValue}\"`;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
39
|
export const useLabelNames = (client: QueryServiceClient): UseLabelNames => {
|
|
68
40
|
const [loading, setLoading] = useState(true);
|
|
69
41
|
const [result, setResult] = useState<ILabelNamesResult>({});
|
|
@@ -112,19 +84,16 @@ const MatchersInput = ({
|
|
|
112
84
|
runQuery,
|
|
113
85
|
currentQuery,
|
|
114
86
|
}: MatchersInputProps): JSX.Element => {
|
|
115
|
-
const [
|
|
116
|
-
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
117
|
-
const [localMatchers, setLocalMatchers] = useState<Matchers[] | null>(null);
|
|
87
|
+
const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null);
|
|
118
88
|
const [focusedInput, setFocusedInput] = useState(false);
|
|
119
89
|
const [showSuggest, setShowSuggest] = useState(true);
|
|
120
|
-
const [labelValuesLoading, setLabelValuesLoading] = useState(false);
|
|
121
90
|
const [highlightedSuggestionIndex, setHighlightedSuggestionIndex] = useState(-1);
|
|
91
|
+
const [labelValuesLoading, setLabelValuesLoading] = useState(false);
|
|
122
92
|
const [lastCompleted, setLastCompleted] = useState<Suggestion>(new Suggestion('', '', ''));
|
|
123
|
-
const [
|
|
124
|
-
const [
|
|
125
|
-
const [
|
|
126
|
-
const
|
|
127
|
-
const {styles, attributes} = usePopper(divInputRef, popperElement, {
|
|
93
|
+
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
94
|
+
const [labelValues, setLabelValues] = useState<string[] | null>(null);
|
|
95
|
+
const [currentLabelName, setCurrentLabelName] = useState<string | null>(null);
|
|
96
|
+
const {styles, attributes} = usePopper(inputRef, popperElement, {
|
|
128
97
|
placement: 'bottom-start',
|
|
129
98
|
});
|
|
130
99
|
const metadata = useGrpcMetadata();
|
|
@@ -137,17 +106,19 @@ const MatchersInput = ({
|
|
|
137
106
|
return <div className="pt-2 pb-4">{Spinner}</div>;
|
|
138
107
|
};
|
|
139
108
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (currentLabelName !== null) {
|
|
111
|
+
const call = queryClient.values({labelName: currentLabelName, match: []}, {meta: metadata});
|
|
112
|
+
setLabelValuesLoading(true);
|
|
113
|
+
|
|
114
|
+
call.response
|
|
115
|
+
.then(response => {
|
|
116
|
+
setLabelValues(response.labelValues);
|
|
117
|
+
})
|
|
118
|
+
.catch(() => setLabelValues(null))
|
|
119
|
+
.finally(() => setLabelValuesLoading(false));
|
|
120
|
+
}
|
|
121
|
+
}, [currentLabelName, queryClient, metadata]);
|
|
151
122
|
|
|
152
123
|
const labelNames =
|
|
153
124
|
(labelNamesError === undefined || labelNamesError == null) &&
|
|
@@ -156,12 +127,10 @@ const MatchersInput = ({
|
|
|
156
127
|
? labelNamesResponse.labelNames.filter(e => e !== '__name__')
|
|
157
128
|
: [];
|
|
158
129
|
|
|
159
|
-
const labelValues =
|
|
160
|
-
labelValuesResponse !== undefined && labelValuesResponse != null ? labelValuesResponse : [];
|
|
161
|
-
|
|
162
130
|
const value = currentQuery.matchersString();
|
|
163
131
|
|
|
164
|
-
|
|
132
|
+
const suggestionSections = new Suggestions();
|
|
133
|
+
Query.suggest(`${currentQuery.profileName()}{${value}`).forEach(function (s) {
|
|
165
134
|
// Skip suggestions that we just completed. This really only works,
|
|
166
135
|
// because we know the language is not repetitive. For a language that
|
|
167
136
|
// has a repeating word, this would not work.
|
|
@@ -172,63 +141,46 @@ const MatchersInput = ({
|
|
|
172
141
|
// Need to figure out if any literal suggestions make sense, but a
|
|
173
142
|
// closing bracket doesn't in the guided query experience because all
|
|
174
143
|
// we have the user do is type the matchers.
|
|
175
|
-
if (s.type ===
|
|
176
|
-
if (suggestionSections.literals.find(e => e.value === s.value) != null) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
144
|
+
if (s.type === 'literal' && s.value !== '}') {
|
|
179
145
|
suggestionSections.literals.push({
|
|
180
146
|
type: s.type,
|
|
181
|
-
typeahead:
|
|
147
|
+
typeahead: s.typeahead,
|
|
182
148
|
value: s.value,
|
|
183
149
|
});
|
|
184
|
-
suggestionSections.labelNames = [];
|
|
185
|
-
suggestionSections.labelValues = [];
|
|
186
150
|
}
|
|
187
|
-
|
|
188
|
-
if (s.type === Labels.labelName) {
|
|
151
|
+
if (s.type === 'labelName') {
|
|
189
152
|
const inputValue = s.typeahead.trim().toLowerCase();
|
|
190
153
|
const inputLength = inputValue.length;
|
|
191
|
-
|
|
192
154
|
const matches = labelNames.filter(function (label) {
|
|
193
155
|
return label.toLowerCase().slice(0, inputLength) === inputValue;
|
|
194
156
|
});
|
|
195
157
|
|
|
196
|
-
matches.forEach(m =>
|
|
197
|
-
if (suggestionSections.labelNames.find(e => e.value === m) != null) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
158
|
+
matches.forEach(m =>
|
|
201
159
|
suggestionSections.labelNames.push({
|
|
202
160
|
type: s.type,
|
|
203
161
|
typeahead: s.typeahead,
|
|
204
162
|
value: m,
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
suggestionSections.labelValues = [];
|
|
208
|
-
});
|
|
163
|
+
})
|
|
164
|
+
);
|
|
209
165
|
}
|
|
210
166
|
|
|
211
|
-
if (s.type ===
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return label.toLowerCase().slice(0, inputLength) === inputValue;
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
matches.forEach(m => {
|
|
220
|
-
if (suggestionSections.labelValues.find(e => e.value === m) != null) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
167
|
+
if (s.type === 'labelValue') {
|
|
168
|
+
if (currentLabelName === null || s.labelName !== currentLabelName) {
|
|
169
|
+
setCurrentLabelName(s.labelName);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
223
172
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
typeahead
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
173
|
+
if (labelValues !== null) {
|
|
174
|
+
labelValues
|
|
175
|
+
.filter(v => v.slice(0, s.typeahead.length) === s.typeahead)
|
|
176
|
+
.forEach(v =>
|
|
177
|
+
suggestionSections.labelValues.push({
|
|
178
|
+
type: s.type,
|
|
179
|
+
typeahead: s.typeahead,
|
|
180
|
+
value: v,
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
}
|
|
232
184
|
}
|
|
233
185
|
});
|
|
234
186
|
|
|
@@ -237,48 +189,12 @@ const MatchersInput = ({
|
|
|
237
189
|
suggestionSections.labelNames.length +
|
|
238
190
|
suggestionSections.labelValues.length;
|
|
239
191
|
|
|
240
|
-
const getLabelsFromMatchers = (matchers: Matchers[]): string[] => {
|
|
241
|
-
return matchers
|
|
242
|
-
.filter(matcher => matcher.key !== '__name__')
|
|
243
|
-
.map(matcher => `${matcher.key}${matcher.matcherType}${addQuoteMarks(matcher.value)}`);
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
useEffect(() => {
|
|
247
|
-
const matchers = currentQuery.matchers.filter(matcher => matcher.key !== '__name__');
|
|
248
|
-
|
|
249
|
-
if (matchers.length > 0) {
|
|
250
|
-
setCurrentLabelsCollection(getLabelsFromMatchers(matchers));
|
|
251
|
-
} else {
|
|
252
|
-
if (localMatchers !== null) setCurrentLabelsCollection(getLabelsFromMatchers(localMatchers));
|
|
253
|
-
}
|
|
254
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
255
|
-
}, [currentQuery.matchers]);
|
|
256
|
-
|
|
257
192
|
const resetHighlight = (): void => setHighlightedSuggestionIndex(-1);
|
|
258
193
|
const resetLastCompleted = (): void => setLastCompleted(new Suggestion('', '', ''));
|
|
259
194
|
|
|
260
195
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
|
261
196
|
const newValue = e.target.value;
|
|
262
|
-
|
|
263
|
-
// filter out the labelname list and move to the top the labelname that is most similar to what the user is typing.
|
|
264
|
-
if (suggestionSections.labelNames.length > 0) {
|
|
265
|
-
suggestionSections.labelNames = suggestionSections.labelNames.filter(suggestion =>
|
|
266
|
-
suggestion.value.toLowerCase().includes(newValue.toLowerCase())
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// this checks if the user has typed a label name and a literal (=/!=,=~,!~) i.e labelName=~, labelName!=~, labelName=, labelName!=
|
|
271
|
-
// and is about to type the label value, then it it will filter out the labelvalue list and move to the top
|
|
272
|
-
// the labelvalue that is most similar to what the user is typing.
|
|
273
|
-
if (suggestionSections.labelValues.length > 0 && labelNameLiteralRe.test(newValue)) {
|
|
274
|
-
const labelValueSearch = newValue.split(literalRe)[2];
|
|
275
|
-
|
|
276
|
-
suggestionSections.labelValues = suggestionSections.labelValues.filter(suggestion =>
|
|
277
|
-
suggestion.value.toLowerCase().includes(labelValueSearch.toLowerCase())
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
setInputRef(newValue);
|
|
197
|
+
setMatchersString(newValue);
|
|
282
198
|
resetLastCompleted();
|
|
283
199
|
resetHighlight();
|
|
284
200
|
};
|
|
@@ -288,22 +204,19 @@ const MatchersInput = ({
|
|
|
288
204
|
};
|
|
289
205
|
|
|
290
206
|
const getSuggestion = (index: number): Suggestion => {
|
|
291
|
-
if (suggestionSections.labelValues.length > 0) {
|
|
292
|
-
if (index < suggestionSections.labelValues.length) {
|
|
293
|
-
return suggestionSections.labelValues[index];
|
|
294
|
-
}
|
|
295
|
-
return suggestionSections.literals[index - suggestionSections.labelValues.length];
|
|
296
|
-
}
|
|
297
|
-
|
|
298
207
|
if (index < suggestionSections.labelNames.length) {
|
|
299
208
|
return suggestionSections.labelNames[index];
|
|
300
209
|
}
|
|
301
|
-
|
|
210
|
+
if (index < suggestionSections.labelNames.length + suggestionSections.literals.length) {
|
|
211
|
+
return suggestionSections.literals[index - suggestionSections.labelNames.length];
|
|
212
|
+
}
|
|
213
|
+
return suggestionSections.labelValues[
|
|
214
|
+
index - suggestionSections.labelNames.length - suggestionSections.literals.length
|
|
215
|
+
];
|
|
302
216
|
};
|
|
303
217
|
|
|
304
218
|
const highlightNext = (): void => {
|
|
305
219
|
const nextIndex = highlightedSuggestionIndex + 1;
|
|
306
|
-
|
|
307
220
|
if (nextIndex === suggestionsLength) {
|
|
308
221
|
resetHighlight();
|
|
309
222
|
return;
|
|
@@ -323,181 +236,30 @@ const MatchersInput = ({
|
|
|
323
236
|
|
|
324
237
|
const applySuggestion = (suggestionIndex: number): void => {
|
|
325
238
|
const suggestion = getSuggestion(suggestionIndex);
|
|
326
|
-
|
|
327
|
-
if (suggestion.type === Labels.labelValue) {
|
|
328
|
-
suggestion.value = addQuoteMarks(suggestion.value);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
239
|
const newValue = complete(suggestion);
|
|
332
240
|
resetHighlight();
|
|
333
|
-
|
|
334
|
-
if (suggestion.type === Labels.labelName) {
|
|
335
|
-
getLabelNameValues(suggestion.value);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
241
|
setLastCompleted(suggestion);
|
|
339
242
|
setMatchersString(newValue);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (currentLabelsCollection == null || currentLabelsCollection?.length === 0) {
|
|
345
|
-
setCurrentLabelsCollection(values);
|
|
346
|
-
} else {
|
|
347
|
-
setCurrentLabelsCollection((oldValues: string[] | null) => [
|
|
348
|
-
...(oldValues ?? []),
|
|
349
|
-
values[values.length - 1],
|
|
350
|
-
]);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
setInputRef('');
|
|
354
|
-
focus();
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (lastCompleted.type === Labels.labelValue && suggestion.type === Labels.literal) {
|
|
359
|
-
setInputRef('');
|
|
360
|
-
focus();
|
|
361
|
-
return;
|
|
243
|
+
if (inputRef !== null) {
|
|
244
|
+
inputRef.value = newValue;
|
|
245
|
+
inputRef.focus();
|
|
362
246
|
}
|
|
363
|
-
|
|
364
|
-
if (currentLabelsCollection !== null) {
|
|
365
|
-
setInputRef(newValue.substring(newValue.lastIndexOf(',') + 1));
|
|
366
|
-
focus();
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
setInputRef(newValue);
|
|
371
|
-
focus();
|
|
372
247
|
};
|
|
373
248
|
|
|
374
249
|
const applyHighlightedSuggestion = (): void => {
|
|
375
250
|
applySuggestion(highlightedSuggestionIndex);
|
|
376
251
|
};
|
|
377
252
|
|
|
378
|
-
// This function adds quotes to the query expression if the user has typed it in manually, i.e. did not use the arrow up / down keys + Enter
|
|
379
|
-
// to choose the label name and value. Therefore, labelName=value becomes labelName="value".
|
|
380
|
-
const addQuotesToInputRefLabelValue = (inputRef: string): string => {
|
|
381
|
-
const labelValue = inputRef.split(literalRe)[2].replaceAll(',', '');
|
|
382
|
-
const labelValueWithQuotes = addQuoteMarks(labelValue);
|
|
383
|
-
return inputRef.replace(labelValue, labelValueWithQuotes);
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
const handleKeyUp = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
387
|
-
const values = inputRef.replaceAll(',', '');
|
|
388
|
-
|
|
389
|
-
if (labelNameValueRe.test(inputRef)) {
|
|
390
|
-
if (currentLabelsCollection === null) {
|
|
391
|
-
setMatchersString(inputRef);
|
|
392
|
-
} else {
|
|
393
|
-
setMatchersString(currentLabelsCollection?.join(',') + ',' + values);
|
|
394
|
-
}
|
|
395
|
-
setInputRef('');
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (event.key === ',') {
|
|
399
|
-
if (inputRef.length === 0) event.preventDefault();
|
|
400
|
-
|
|
401
|
-
// If the current typed query expression matches the labelNameValueWithoutQuotesRe regex (i.e. the labelvalue is not quoted), then add quotes to the labelvalue.
|
|
402
|
-
// if not, just use the current inputRef value.
|
|
403
|
-
const inputValues = labelNameValueWithoutQuotesRe.test(inputRef)
|
|
404
|
-
? inputRef.replaceAll(',', '')
|
|
405
|
-
: addQuotesToInputRefLabelValue(inputRef).replaceAll(',', '');
|
|
406
|
-
|
|
407
|
-
// if the currentLabelsCollection array is null, we don't need to concat the current inputRef value with the currentLabelsCollection array, so we just push to it.
|
|
408
|
-
if (currentLabelsCollection === null) {
|
|
409
|
-
setCurrentLabelsCollection([inputValues]);
|
|
410
|
-
} else {
|
|
411
|
-
setCurrentLabelsCollection((oldValues: string[] | null) => {
|
|
412
|
-
// Don't add the current inputRef value to the currentLabelsCollection array if it doesn't match the regex because that will cause an API error.
|
|
413
|
-
if (!labelNameValueRe.test(inputRef)) return oldValues;
|
|
414
|
-
return [...(oldValues ?? []), inputValues];
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// update the currentQuery expression with the currentLabelsCollection array if it's not null, otherwise use the current inputRef value.
|
|
419
|
-
setMatchersString(
|
|
420
|
-
currentLabelsCollection !== null
|
|
421
|
-
? `${currentLabelsCollection?.join(',')},${inputValues}`
|
|
422
|
-
: `${inputValues},`
|
|
423
|
-
);
|
|
424
|
-
setInputRef('');
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// We suggest the appropriate label names and label values when a user is typing, depending on what the user has typed.
|
|
428
|
-
// For example, if the user types "labelName=", we suggest the label values next.
|
|
429
|
-
// This bit of code is used for the opposite of the above bit of code, when a user is deleting characters by pressing del/backspace
|
|
430
|
-
// We update the currentQuery expression with what's in the inputRef value so that the suggestions are updated accordingly.
|
|
431
|
-
if (event.key === 'Backspace' && inputRef.length > 0) {
|
|
432
|
-
// if the currentLabelsCollection array is not empty i.e has already previously completed expressions, then we first need to turn the array into a string
|
|
433
|
-
// so it can be concatenated with the current inputRef value. that becomes something like "labelName="value",newLabelName="val
|
|
434
|
-
if (currentLabelsCollection != null && currentLabelsCollection.length > 0) {
|
|
435
|
-
setMatchersString(`${currentLabelsCollection?.join(',')},${inputRef}}`);
|
|
436
|
-
} else {
|
|
437
|
-
// if not, we jsut update the currentQuery expression with the current inputRef value.
|
|
438
|
-
setMatchersString(inputRef);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (currentLabelsCollection === null && inputRef.length === 0) {
|
|
442
|
-
setMatchersString('');
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
|
|
447
253
|
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
448
254
|
// If there is a highlighted suggestion and enter is hit, we complete
|
|
449
255
|
// with the highlighted suggestion.
|
|
450
256
|
if (highlightedSuggestionIndex >= 0 && event.key === 'Enter') {
|
|
451
257
|
applyHighlightedSuggestion();
|
|
452
|
-
if (lastCompleted.type === Labels.labelValue) setLabelValuesResponse(null);
|
|
453
|
-
|
|
454
|
-
const matchers = currentQuery.matchers.filter(matcher => matcher.key !== '__name__');
|
|
455
|
-
setLocalMatchers(prevState => {
|
|
456
|
-
if (inputRef.length > 0) return prevState;
|
|
457
|
-
if (matchers.length === 0) return prevState;
|
|
458
|
-
return matchers;
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// If a user has manually typed in a label name that actually exists in the list of label name (and did not use the
|
|
463
|
-
// highlight + arrow keys up/down + Enter/Mouse click method to complete it), and has also typed a literal value, i.e. labelName=,
|
|
464
|
-
// then we can apply a suggestion using the typed label name. This will be as if the user had highlighted the label name and hit enter.
|
|
465
|
-
if (event.key === '!' || event.key === '~' || event.key === '=') {
|
|
466
|
-
const labelName = inputRef.split(literalRe)[0];
|
|
467
|
-
|
|
468
|
-
if (suggestionSections.labelNames.length > 0) {
|
|
469
|
-
// Find the label name in the suggestion list and get the index
|
|
470
|
-
const suggestion = suggestionSections.labelNames.find(
|
|
471
|
-
suggestion => suggestion.value === labelName
|
|
472
|
-
);
|
|
473
|
-
// If the typed label name exists, we can apply it using the applySuggestion function
|
|
474
|
-
if (suggestion != null) {
|
|
475
|
-
applySuggestion(suggestionSections.labelNames.indexOf(suggestion));
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Same as above, If a user has typed in a label name and literal (and did not use the suggestion box to complete it),
|
|
481
|
-
// we can manually show the next set of suggestions, which are the label values, by applying a literal suggestion.
|
|
482
|
-
if (labelNameLiteralRe.test(inputRef)) {
|
|
483
|
-
const literal = inputRef.split(literalRe)[1];
|
|
484
|
-
|
|
485
|
-
if (suggestionSections.literals.length > 0) {
|
|
486
|
-
// Find the literal in the suggestion list and get the index
|
|
487
|
-
const suggestion = suggestionSections.literals.find(
|
|
488
|
-
suggestion => suggestion.value === literal
|
|
489
|
-
);
|
|
490
|
-
// If the typed literal exists, we can apply it using the applySuggestion function
|
|
491
|
-
if (suggestion != null) {
|
|
492
|
-
applySuggestion(suggestionSections.literals.indexOf(suggestion));
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
258
|
}
|
|
496
259
|
|
|
497
260
|
// If no suggestions is highlighted and we hit enter, we run the query,
|
|
498
261
|
// and hide suggestions until another actions enables them again.
|
|
499
262
|
if (highlightedSuggestionIndex === -1 && event.key === 'Enter') {
|
|
500
|
-
if (lastCompleted.type === 'labelValue') setLabelValuesResponse(null);
|
|
501
263
|
setShowSuggest(false);
|
|
502
264
|
runQuery();
|
|
503
265
|
return;
|
|
@@ -507,13 +269,6 @@ const MatchersInput = ({
|
|
|
507
269
|
};
|
|
508
270
|
|
|
509
271
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
510
|
-
if (event.key === 'Backspace' && inputRef === '') {
|
|
511
|
-
if (currentLabelsCollection === null) return;
|
|
512
|
-
|
|
513
|
-
removeLabel(currentLabelsCollection.length - 1);
|
|
514
|
-
removeLocalMatcher();
|
|
515
|
-
}
|
|
516
|
-
|
|
517
272
|
// Don't need to handle any key interactions if no suggestions there.
|
|
518
273
|
if (suggestionsLength === 0) {
|
|
519
274
|
return;
|
|
@@ -551,147 +306,129 @@ const MatchersInput = ({
|
|
|
551
306
|
resetHighlight();
|
|
552
307
|
};
|
|
553
308
|
|
|
554
|
-
const
|
|
555
|
-
if (currentLabelsCollection === null) return;
|
|
556
|
-
|
|
557
|
-
const newLabels = [...currentLabelsCollection];
|
|
558
|
-
newLabels.splice(label, 1);
|
|
559
|
-
setCurrentLabelsCollection(newLabels);
|
|
560
|
-
|
|
561
|
-
const newLabelsAsAString = newLabels.join(',');
|
|
562
|
-
setMatchersString(newLabelsAsAString);
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
const removeLocalMatcher = (): void => {
|
|
566
|
-
if (localMatchers === null) return;
|
|
567
|
-
|
|
568
|
-
const newMatchers = [...localMatchers];
|
|
569
|
-
newMatchers.splice(localMatchers.length - 1, 1);
|
|
570
|
-
setLocalMatchers(newMatchers);
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
const profileSelected = currentQuery.profType.profileName === '';
|
|
309
|
+
const profileSelected = currentQuery.profileName() === '';
|
|
574
310
|
|
|
575
311
|
return (
|
|
576
|
-
|
|
577
|
-
<
|
|
578
|
-
ref={
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
disabled={profileSelected} // Disable input if no profile has been selected
|
|
609
|
-
title={
|
|
610
|
-
profileSelected ? 'Select a profile first to query profiles...' : 'query profiles...'
|
|
611
|
-
}
|
|
612
|
-
/>
|
|
613
|
-
</div>
|
|
614
|
-
|
|
615
|
-
<div
|
|
616
|
-
ref={setPopperElement}
|
|
617
|
-
style={{...styles.popper, marginLeft: 0}}
|
|
618
|
-
{...attributes.popper}
|
|
619
|
-
className="z-50"
|
|
620
|
-
>
|
|
621
|
-
<Transition
|
|
622
|
-
show={focusedInput && showSuggest}
|
|
623
|
-
as={Fragment}
|
|
624
|
-
leave="transition ease-in duration-100"
|
|
625
|
-
leaveFrom="opacity-100"
|
|
626
|
-
leaveTo="opacity-0"
|
|
312
|
+
<div className="font-mono flex-1 w-full block">
|
|
313
|
+
<input
|
|
314
|
+
ref={setInputRef}
|
|
315
|
+
type="text"
|
|
316
|
+
className={cx(
|
|
317
|
+
'bg-transparent focus:ring-indigo-800 flex-1 block w-full px-2 py-2 text-sm outline-none',
|
|
318
|
+
profileSelected && 'cursor-not-allowed'
|
|
319
|
+
)}
|
|
320
|
+
placeholder={
|
|
321
|
+
profileSelected
|
|
322
|
+
? 'Select a profile first to enter a filter...'
|
|
323
|
+
: 'filter profiles... eg. node="test"'
|
|
324
|
+
}
|
|
325
|
+
onChange={onChange}
|
|
326
|
+
value={value}
|
|
327
|
+
onBlur={unfocus}
|
|
328
|
+
onFocus={focus}
|
|
329
|
+
onKeyPress={handleKeyPress}
|
|
330
|
+
onKeyDown={handleKeyDown}
|
|
331
|
+
disabled={profileSelected} // Disable input if no profile has been selected
|
|
332
|
+
title={
|
|
333
|
+
profileSelected
|
|
334
|
+
? 'Select a profile first to enter a filter...'
|
|
335
|
+
: 'filter profiles... eg. node="test"'
|
|
336
|
+
}
|
|
337
|
+
/>
|
|
338
|
+
{suggestionsLength > 0 && (
|
|
339
|
+
<div
|
|
340
|
+
ref={setPopperElement}
|
|
341
|
+
style={{...styles.popper, marginLeft: 0}}
|
|
342
|
+
{...attributes.popper}
|
|
343
|
+
className="z-50"
|
|
627
344
|
>
|
|
628
|
-
<
|
|
629
|
-
|
|
630
|
-
|
|
345
|
+
<Transition
|
|
346
|
+
show={focusedInput && showSuggest}
|
|
347
|
+
as={Fragment}
|
|
348
|
+
leave="transition ease-in duration-100"
|
|
349
|
+
leaveFrom="opacity-100"
|
|
350
|
+
leaveTo="opacity-0"
|
|
631
351
|
>
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
352
|
+
<div
|
|
353
|
+
style={{width: inputRef?.offsetWidth}}
|
|
354
|
+
className="absolute z-10 max-h-[400px] mt-1 bg-gray-50 dark:bg-gray-900 shadow-lg rounded-md text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
|
|
355
|
+
>
|
|
356
|
+
{labelNamesLoading ? (
|
|
357
|
+
<LoadingSpinner />
|
|
358
|
+
) : (
|
|
359
|
+
<>
|
|
360
|
+
{suggestionSections.labelNames.map((l, i) => (
|
|
361
|
+
<div
|
|
362
|
+
key={i}
|
|
363
|
+
className={cx(
|
|
364
|
+
highlightedSuggestionIndex === i && 'text-white bg-indigo-600',
|
|
365
|
+
'cursor-default select-none relative py-2 pl-3 pr-9'
|
|
366
|
+
)}
|
|
367
|
+
onMouseOver={() => setHighlightedSuggestionIndex(i)}
|
|
368
|
+
onClick={() => applySuggestion(i)}
|
|
369
|
+
onMouseOut={() => resetHighlight()}
|
|
370
|
+
>
|
|
371
|
+
{l.value}
|
|
372
|
+
</div>
|
|
373
|
+
))}
|
|
374
|
+
</>
|
|
375
|
+
)}
|
|
376
|
+
|
|
377
|
+
{suggestionSections.literals.map((l, i) => (
|
|
378
|
+
<div
|
|
379
|
+
key={i}
|
|
380
|
+
className={cx(
|
|
381
|
+
highlightedSuggestionIndex === i + suggestionSections.labelNames.length &&
|
|
382
|
+
'text-white bg-indigo-600',
|
|
383
|
+
'cursor-default select-none relative py-2 pl-3 pr-9'
|
|
384
|
+
)}
|
|
385
|
+
onMouseOver={() =>
|
|
386
|
+
setHighlightedSuggestionIndex(i + suggestionSections.labelNames.length)
|
|
387
|
+
}
|
|
388
|
+
onClick={() => applySuggestion(i + suggestionSections.labelNames.length)}
|
|
389
|
+
onMouseOut={() => resetHighlight()}
|
|
390
|
+
>
|
|
391
|
+
{l.value}
|
|
392
|
+
</div>
|
|
393
|
+
))}
|
|
394
|
+
|
|
395
|
+
{labelValuesLoading && lastCompleted.type === 'literal' ? (
|
|
396
|
+
<LoadingSpinner />
|
|
397
|
+
) : (
|
|
398
|
+
<>
|
|
399
|
+
{suggestionSections.labelValues.map((l, i) => (
|
|
400
|
+
<div
|
|
401
|
+
key={i}
|
|
402
|
+
className={cx(
|
|
403
|
+
highlightedSuggestionIndex === i && 'text-white bg-indigo-600',
|
|
404
|
+
'cursor-default select-none relative py-2 pl-3 pr-9'
|
|
405
|
+
)}
|
|
406
|
+
onMouseOver={() =>
|
|
407
|
+
setHighlightedSuggestionIndex(
|
|
408
|
+
i +
|
|
409
|
+
suggestionSections.labelNames.length +
|
|
410
|
+
suggestionSections.literals.length
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
onClick={() =>
|
|
414
|
+
applySuggestion(
|
|
415
|
+
i +
|
|
416
|
+
suggestionSections.labelNames.length +
|
|
417
|
+
suggestionSections.literals.length
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
onMouseOut={() => resetHighlight()}
|
|
421
|
+
>
|
|
422
|
+
{l.value}
|
|
423
|
+
</div>
|
|
424
|
+
))}
|
|
425
|
+
</>
|
|
426
|
+
)}
|
|
427
|
+
</div>
|
|
428
|
+
</Transition>
|
|
429
|
+
</div>
|
|
430
|
+
)}
|
|
431
|
+
</div>
|
|
695
432
|
);
|
|
696
433
|
};
|
|
697
434
|
|