@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.
@@ -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, ValuesResponse} from '@parca/client';
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 [divInputRef, setDivInputRef] = useState<HTMLDivElement | null>(null);
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 [suggestionSections] = useState<Suggestions>(new Suggestions());
124
- const [inputRef, setInputRef] = useState<string>('');
125
- const [labelValuesResponse, setLabelValuesResponse] = useState<string[] | null>(null);
126
- const [currentLabelsCollection, setCurrentLabelsCollection] = useState<string[] | null>(null); // This is an array that contains query expressions that have been matched i.e. they have been completed and have the blue badge around them in the UI.
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
- const getLabelNameValues = (labelName: string): void => {
141
- const call = queryClient.values({labelName, match: []}, {meta: metadata});
142
- setLabelValuesLoading(true);
143
-
144
- call.response
145
- .then(response => {
146
- setLabelValuesResponse(response.labelValues);
147
- })
148
- .catch(() => setLabelValuesResponse(null))
149
- .finally(() => setLabelValuesLoading(false));
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
- Query.suggest(`{${value}`).forEach(function (s) {
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 === Labels.literal && s.value !== '}') {
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
- suggestionSections.literals = [];
207
- suggestionSections.labelValues = [];
208
- });
163
+ })
164
+ );
209
165
  }
210
166
 
211
- if (s.type === Labels.labelValue) {
212
- const inputValue = s.typeahead.trim().toLowerCase();
213
- const inputLength = inputValue.length;
214
-
215
- const matches = labelValues.filter(function (label) {
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
- suggestionSections.labelValues.push({
225
- type: s.type,
226
- typeahead: s.typeahead,
227
- value: m,
228
- });
229
- suggestionSections.labelNames = [];
230
- suggestionSections.literals = [];
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
- return suggestionSections.literals[index - suggestionSections.labelNames.length];
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
- if (suggestion.type === Labels.labelValue) {
342
- const values = newValue.split(',');
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 removeLabel = (label: number): void => {
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
- <div
578
- ref={setDivInputRef}
579
- className="w-full flex items-center text-sm border-gray-300 dark:border-gray-600 border-b"
580
- >
581
- <ul className="flex space-x-2">
582
- {currentLabelsCollection?.map((value, i) => (
583
- <li
584
- key={i}
585
- className="bg-indigo-600 w-fit py-1 px-2 text-gray-100 dark-gray-900 rounded-md"
586
- >
587
- {value}
588
- </li>
589
- ))}
590
- </ul>
591
-
592
- <input
593
- type="text"
594
- className={cx(
595
- 'bg-transparent focus:ring-indigo-800 flex-1 block w-full px-2 py-2 text-sm outline-none',
596
- profileSelected && 'cursor-not-allowed'
597
- )}
598
- placeholder={
599
- profileSelected ? 'Select a profile first to query profiles...' : 'query profiles...'
600
- }
601
- onChange={onChange}
602
- value={inputRef}
603
- onBlur={unfocus}
604
- onFocus={focus}
605
- onKeyPress={handleKeyPress}
606
- onKeyDown={handleKeyDown}
607
- onKeyUp={handleKeyUp}
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
- <div
629
- style={{width: divInputRef?.offsetWidth}}
630
- 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"
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
- {labelNamesLoading ? (
633
- <LoadingSpinner />
634
- ) : (
635
- <>
636
- {suggestionSections.labelNames.map((l, i) => (
637
- <div
638
- key={i}
639
- className={cx(
640
- highlightedSuggestionIndex === i && 'text-white bg-indigo-600',
641
- 'cursor-default select-none relative py-2 pl-3 pr-9'
642
- )}
643
- onMouseOver={() => setHighlightedSuggestionIndex(i)}
644
- onClick={() => applySuggestion(i)}
645
- onMouseOut={() => resetHighlight()}
646
- >
647
- {l.value}
648
- </div>
649
- ))}
650
- </>
651
- )}
652
-
653
- {suggestionSections.literals.map((l, i) => (
654
- <div
655
- key={i}
656
- className={cx(
657
- highlightedSuggestionIndex === i + suggestionSections.labelNames.length &&
658
- 'text-white bg-indigo-600',
659
- 'cursor-default select-none relative py-2 pl-3 pr-9'
660
- )}
661
- onMouseOver={() =>
662
- setHighlightedSuggestionIndex(i + suggestionSections.labelNames.length)
663
- }
664
- onClick={() => applySuggestion(i + suggestionSections.labelNames.length)}
665
- onMouseOut={() => resetHighlight()}
666
- >
667
- {l.value}
668
- </div>
669
- ))}
670
-
671
- {labelValuesLoading && lastCompleted.type === 'literal' ? (
672
- <LoadingSpinner />
673
- ) : (
674
- <>
675
- {suggestionSections.labelValues.map((l, i) => (
676
- <div
677
- key={i}
678
- className={cx(
679
- highlightedSuggestionIndex === i && 'text-white bg-indigo-600',
680
- 'cursor-default select-none relative py-2 pl-3 pr-9'
681
- )}
682
- onMouseOver={() => setHighlightedSuggestionIndex(i)}
683
- onClick={() => applySuggestion(i)}
684
- onMouseOut={() => resetHighlight()}
685
- >
686
- {l.value}
687
- </div>
688
- ))}
689
- </>
690
- )}
691
- </div>
692
- </Transition>
693
- </div>
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