@oneuptime/common 10.0.54 → 10.0.56

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 (132) hide show
  1. package/Models/DatabaseModels/DockerHost.ts +662 -0
  2. package/Models/DatabaseModels/GlobalConfig.ts +112 -0
  3. package/Models/DatabaseModels/Index.ts +2 -0
  4. package/Server/API/TelemetryAPI.ts +352 -16
  5. package/Server/Infrastructure/ClickhouseConfig.ts +9 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.ts +76 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.ts +133 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.ts +51 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  10. package/Server/Services/DockerHostService.ts +173 -0
  11. package/Server/Services/ExceptionAggregationService.ts +335 -0
  12. package/Server/Services/Index.ts +2 -0
  13. package/Server/Services/LogAggregationService.ts +17 -0
  14. package/Server/Services/MonitorProbeService.ts +42 -21
  15. package/Server/Services/MonitorService.ts +21 -21
  16. package/Server/Services/TraceAggregationService.ts +514 -0
  17. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +73 -1
  18. package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
  19. package/Tests/__mocks__/mermaid.js +18 -0
  20. package/Tests/__mocks__/react-markdown.js +17 -0
  21. package/Tests/__mocks__/react-syntax-highlighter.js +19 -0
  22. package/Tests/__mocks__/remark-gfm.js +8 -0
  23. package/Types/Icon/IconProp.ts +1 -0
  24. package/Types/Monitor/DockerAlertTemplates.ts +507 -0
  25. package/Types/Monitor/DockerMetricCatalog.ts +226 -0
  26. package/Types/Monitor/MonitorStep.ts +33 -0
  27. package/Types/Monitor/MonitorStepDockerMonitor.ts +38 -0
  28. package/Types/Monitor/MonitorType.ts +15 -1
  29. package/Types/Permission.ts +38 -0
  30. package/UI/Components/Icon/Icon.tsx +87 -0
  31. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +7 -132
  32. package/UI/Components/ModelDetail/CardModelDetail.tsx +11 -1
  33. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +285 -0
  34. package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +85 -0
  35. package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +156 -0
  36. package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +160 -0
  37. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +85 -0
  38. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +102 -0
  39. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +280 -0
  40. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +125 -0
  41. package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +114 -0
  42. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +378 -0
  43. package/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.tsx +78 -0
  44. package/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.tsx +64 -0
  45. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +193 -0
  46. package/UI/Components/TelemetryViewer/types.ts +67 -0
  47. package/build/dist/Models/DatabaseModels/DockerHost.js +686 -0
  48. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -0
  49. package/build/dist/Models/DatabaseModels/GlobalConfig.js +117 -0
  50. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  51. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  52. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  53. package/build/dist/Server/API/TelemetryAPI.js +237 -16
  54. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  55. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +9 -0
  56. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  57. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js +35 -0
  58. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js.map +1 -0
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js +52 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js.map +1 -0
  61. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js +26 -0
  62. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js.map +1 -0
  63. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  64. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  65. package/build/dist/Server/Services/DockerHostService.js +162 -0
  66. package/build/dist/Server/Services/DockerHostService.js.map +1 -0
  67. package/build/dist/Server/Services/ExceptionAggregationService.js +224 -0
  68. package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -0
  69. package/build/dist/Server/Services/Index.js +2 -0
  70. package/build/dist/Server/Services/Index.js.map +1 -1
  71. package/build/dist/Server/Services/LogAggregationService.js +11 -0
  72. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  73. package/build/dist/Server/Services/MonitorProbeService.js +28 -14
  74. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  75. package/build/dist/Server/Services/MonitorService.js +19 -17
  76. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  77. package/build/dist/Server/Services/TraceAggregationService.js +364 -0
  78. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -0
  79. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +46 -1
  80. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  81. package/build/dist/Tests/Server/Services/LogAggregationService.test.js +2 -2
  82. package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
  83. package/build/dist/Types/Icon/IconProp.js +1 -0
  84. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  85. package/build/dist/Types/Monitor/DockerAlertTemplates.js +410 -0
  86. package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -0
  87. package/build/dist/Types/Monitor/DockerMetricCatalog.js +192 -0
  88. package/build/dist/Types/Monitor/DockerMetricCatalog.js.map +1 -0
  89. package/build/dist/Types/Monitor/MonitorStep.js +23 -0
  90. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  91. package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js +21 -0
  92. package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js.map +1 -0
  93. package/build/dist/Types/Monitor/MonitorType.js +14 -1
  94. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  95. package/build/dist/Types/Permission.js +36 -0
  96. package/build/dist/Types/Permission.js.map +1 -1
  97. package/build/dist/UI/Components/Icon/Icon.js +13 -0
  98. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  99. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +7 -75
  100. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  101. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +8 -1
  102. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  103. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +71 -0
  104. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -0
  105. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +39 -0
  106. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -0
  107. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +61 -0
  108. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -0
  109. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +66 -0
  110. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -0
  111. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +41 -0
  112. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -0
  113. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +35 -0
  114. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -0
  115. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +132 -0
  116. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -0
  117. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +65 -0
  118. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -0
  119. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +52 -0
  120. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -0
  121. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +224 -0
  122. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -0
  123. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js +35 -0
  124. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js.map +1 -0
  125. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js +27 -0
  126. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js.map +1 -0
  127. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +97 -0
  128. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -0
  129. package/build/dist/UI/Components/TelemetryViewer/types.js +6 -0
  130. package/build/dist/UI/Components/TelemetryViewer/types.js.map +1 -0
  131. package/jest.config.json +6 -1
  132. package/package.json +1 -1
@@ -0,0 +1,378 @@
1
+ import React, {
2
+ ReactElement,
3
+ useState,
4
+ useCallback,
5
+ useRef,
6
+ useEffect,
7
+ useImperativeHandle,
8
+ forwardRef,
9
+ KeyboardEvent,
10
+ } from "react";
11
+ import Icon from "../../Icon/Icon";
12
+ import IconProp from "../../../../Types/Icon/IconProp";
13
+ import TelemetrySearchSuggestions from "./TelemetrySearchSuggestions";
14
+ import TelemetrySearchHelp from "./TelemetrySearchHelp";
15
+ import { SearchHelpRow } from "../types";
16
+
17
+ export interface TelemetrySearchBarProps {
18
+ value: string;
19
+ onChange: (value: string) => void;
20
+ onSubmit: () => void;
21
+ // Field-name suggestions shown when user types "@".
22
+ suggestions?: Array<string> | undefined;
23
+ // field → allowed value completions (resolved field keys).
24
+ valueSuggestions?: Record<string, Array<string>> | undefined;
25
+ // Called when the user picks a concrete field:value chip from the dropdown.
26
+ onFieldValueSelect?: ((fieldKey: string, value: string) => void) | undefined;
27
+ // User-facing alias → backing field key (e.g. "service" -> "serviceId").
28
+ fieldAliasMap?: Record<string, string> | undefined;
29
+ placeholder?: string | undefined;
30
+ // Rows rendered in the help popover when the bar is empty + focused.
31
+ helpRows?: Array<SearchHelpRow> | undefined;
32
+ helpCombinedExample?: string | undefined;
33
+ }
34
+
35
+ export interface TelemetrySearchBarRef {
36
+ focus: () => void;
37
+ }
38
+
39
+ const TelemetrySearchBar: React.ForwardRefExoticComponent<
40
+ TelemetrySearchBarProps & React.RefAttributes<TelemetrySearchBarRef>
41
+ > = forwardRef<TelemetrySearchBarRef, TelemetrySearchBarProps>(
42
+ (
43
+ props: TelemetrySearchBarProps,
44
+ ref: React.Ref<TelemetrySearchBarRef>,
45
+ ): ReactElement => {
46
+ const [isFocused, setIsFocused] = useState<boolean>(false);
47
+ const [showSuggestions, setShowSuggestions] = useState<boolean>(false);
48
+ const [showHelp, setShowHelp] = useState<boolean>(false);
49
+ const [selectedSuggestionIndex, setSelectedSuggestionIndex] =
50
+ useState<number>(-1);
51
+ const inputRef: React.RefObject<HTMLInputElement> =
52
+ useRef<HTMLInputElement>(null!);
53
+ const containerRef: React.RefObject<HTMLDivElement> =
54
+ useRef<HTMLDivElement>(null!);
55
+
56
+ const fieldAliasMap: Record<string, string> = props.fieldAliasMap || {};
57
+
58
+ useImperativeHandle(ref, () => {
59
+ return {
60
+ focus: (): void => {
61
+ inputRef.current?.focus();
62
+ },
63
+ };
64
+ }, []);
65
+
66
+ const currentWord: string = extractCurrentWord(props.value);
67
+
68
+ const hasAtPrefix: boolean = currentWord.startsWith("@");
69
+ const normalizedWord: string = hasAtPrefix
70
+ ? currentWord.substring(1)
71
+ : currentWord;
72
+
73
+ const colonIndex: number = normalizedWord.indexOf(":");
74
+ const isValueMode: boolean = colonIndex > 0;
75
+ const fieldPrefix: string = isValueMode
76
+ ? normalizedWord.substring(0, colonIndex).toLowerCase()
77
+ : "";
78
+ const partialValue: string = isValueMode
79
+ ? normalizedWord.substring(colonIndex + 1)
80
+ : "";
81
+
82
+ const filteredSuggestions: Array<string> = isValueMode
83
+ ? getValueSuggestions(
84
+ fieldPrefix,
85
+ partialValue,
86
+ props.valueSuggestions || {},
87
+ fieldAliasMap,
88
+ )
89
+ : (props.suggestions || []).filter((s: string): boolean => {
90
+ if (!normalizedWord && !hasAtPrefix) {
91
+ return false;
92
+ }
93
+ if (hasAtPrefix && normalizedWord.length === 0) {
94
+ return true;
95
+ }
96
+ const normalizedSuggestion: string = s.startsWith("@")
97
+ ? s.substring(1).toLowerCase()
98
+ : s.toLowerCase();
99
+ return normalizedSuggestion.startsWith(normalizedWord.toLowerCase());
100
+ });
101
+
102
+ const shouldShowSuggestions: boolean =
103
+ showSuggestions &&
104
+ isFocused &&
105
+ filteredSuggestions.length > 0 &&
106
+ (isValueMode ? true : currentWord.length > 0);
107
+
108
+ const shouldShowHelp: boolean =
109
+ showHelp &&
110
+ isFocused &&
111
+ props.value.length === 0 &&
112
+ !shouldShowSuggestions &&
113
+ props.helpRows !== undefined &&
114
+ props.helpRows.length > 0;
115
+
116
+ useEffect(() => {
117
+ setSelectedSuggestionIndex(-1);
118
+ }, [currentWord]);
119
+
120
+ const handleKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void =
121
+ useCallback(
122
+ (e: KeyboardEvent<HTMLInputElement>): void => {
123
+ if (e.key === "Enter") {
124
+ if (
125
+ shouldShowSuggestions &&
126
+ selectedSuggestionIndex >= 0 &&
127
+ selectedSuggestionIndex < filteredSuggestions.length
128
+ ) {
129
+ applySuggestion(filteredSuggestions[selectedSuggestionIndex]!);
130
+ e.preventDefault();
131
+ return;
132
+ }
133
+
134
+ if (
135
+ isValueMode &&
136
+ partialValue.length > 0 &&
137
+ props.onFieldValueSelect
138
+ ) {
139
+ const resolvedField: string =
140
+ fieldAliasMap[fieldPrefix] || fieldPrefix;
141
+ const availableValues: Array<string> =
142
+ (props.valueSuggestions || {})[resolvedField] || [];
143
+ const lowerPartial: string = partialValue.toLowerCase();
144
+ const exactMatch: string | undefined = availableValues.find(
145
+ (v: string): boolean => {
146
+ return v.toLowerCase() === lowerPartial;
147
+ },
148
+ );
149
+
150
+ const resolvedMatch: string | undefined =
151
+ exactMatch ||
152
+ (filteredSuggestions.length === 1
153
+ ? filteredSuggestions[0]
154
+ : undefined);
155
+
156
+ if (resolvedMatch) {
157
+ props.onFieldValueSelect(fieldPrefix, resolvedMatch);
158
+ const parts: Array<string> = props.value.split(/\s+/);
159
+ parts.pop();
160
+ const remaining: string = parts.join(" ");
161
+ props.onChange(remaining ? remaining + " " : "");
162
+ setShowSuggestions(false);
163
+ setShowHelp(false);
164
+ e.preventDefault();
165
+ return;
166
+ }
167
+ }
168
+
169
+ props.onSubmit();
170
+ setShowSuggestions(false);
171
+ setShowHelp(false);
172
+ return;
173
+ }
174
+
175
+ if (e.key === "Escape") {
176
+ setShowSuggestions(false);
177
+ setShowHelp(false);
178
+ return;
179
+ }
180
+
181
+ if (!shouldShowSuggestions) {
182
+ return;
183
+ }
184
+
185
+ if (e.key === "ArrowDown") {
186
+ e.preventDefault();
187
+ setSelectedSuggestionIndex((prev: number): number => {
188
+ return Math.min(prev + 1, filteredSuggestions.length - 1);
189
+ });
190
+ return;
191
+ }
192
+
193
+ if (e.key === "ArrowUp") {
194
+ e.preventDefault();
195
+ setSelectedSuggestionIndex((prev: number): number => {
196
+ return Math.max(prev - 1, 0);
197
+ });
198
+ }
199
+ },
200
+ [
201
+ shouldShowSuggestions,
202
+ selectedSuggestionIndex,
203
+ filteredSuggestions,
204
+ isValueMode,
205
+ fieldPrefix,
206
+ partialValue,
207
+ props,
208
+ fieldAliasMap,
209
+ ],
210
+ );
211
+
212
+ const applySuggestion: (suggestion: string) => void = useCallback(
213
+ (suggestion: string): void => {
214
+ if (isValueMode) {
215
+ if (props.onFieldValueSelect) {
216
+ props.onFieldValueSelect(fieldPrefix, suggestion);
217
+ }
218
+
219
+ const parts: Array<string> = props.value.split(/\s+/);
220
+ parts.pop();
221
+ const remaining: string = parts.join(" ");
222
+ props.onChange(remaining ? remaining + " " : "");
223
+ setShowSuggestions(false);
224
+ setShowHelp(false);
225
+ inputRef.current?.focus();
226
+ return;
227
+ }
228
+
229
+ const parts: Array<string> = props.value.split(/\s+/);
230
+
231
+ if (parts.length > 0) {
232
+ parts[parts.length - 1] = suggestion + ":";
233
+ }
234
+
235
+ props.onChange(parts.join(" "));
236
+ setShowSuggestions(false);
237
+ setShowHelp(false);
238
+ inputRef.current?.focus();
239
+ },
240
+ [props, isValueMode, fieldPrefix],
241
+ );
242
+
243
+ const handleExampleClick: (example: string) => void = useCallback(
244
+ (example: string): void => {
245
+ props.onChange(example);
246
+ setShowHelp(false);
247
+ inputRef.current?.focus();
248
+ },
249
+ [props],
250
+ );
251
+
252
+ useEffect(() => {
253
+ const handleClickOutside: (e: MouseEvent) => void = (
254
+ e: MouseEvent,
255
+ ): void => {
256
+ if (
257
+ containerRef.current &&
258
+ !containerRef.current.contains(e.target as Node)
259
+ ) {
260
+ setShowSuggestions(false);
261
+ setShowHelp(false);
262
+ }
263
+ };
264
+
265
+ document.addEventListener("mousedown", handleClickOutside);
266
+ return () => {
267
+ document.removeEventListener("mousedown", handleClickOutside);
268
+ };
269
+ }, []);
270
+
271
+ return (
272
+ <div ref={containerRef} className="relative">
273
+ <div
274
+ className={`flex items-center gap-2 rounded-lg border bg-white px-3 py-2 transition-colors ${
275
+ isFocused
276
+ ? "border-indigo-400 ring-2 ring-indigo-100"
277
+ : "border-gray-200 hover:border-gray-300"
278
+ }`}
279
+ >
280
+ <Icon
281
+ icon={IconProp.Search}
282
+ className="h-4 w-4 flex-none text-gray-400"
283
+ />
284
+ <input
285
+ ref={inputRef}
286
+ type="text"
287
+ value={props.value}
288
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
289
+ props.onChange(e.target.value);
290
+ setShowSuggestions(true);
291
+ setShowHelp(false);
292
+ }}
293
+ onFocus={() => {
294
+ setIsFocused(true);
295
+ setShowSuggestions(true);
296
+ if (props.value.length === 0) {
297
+ setShowHelp(true);
298
+ }
299
+ }}
300
+ onBlur={() => {
301
+ setIsFocused(false);
302
+ }}
303
+ onKeyDown={handleKeyDown}
304
+ placeholder={props.placeholder || "Search..."}
305
+ className="flex-1 bg-transparent font-mono text-sm text-gray-900 placeholder-gray-400 outline-none"
306
+ spellCheck={false}
307
+ autoComplete="off"
308
+ />
309
+ {props.value.length > 0 && (
310
+ <button
311
+ type="button"
312
+ className="flex-none rounded-full p-1 text-gray-400 hover:bg-gray-100"
313
+ onClick={() => {
314
+ props.onChange("");
315
+ setShowHelp(true);
316
+ setShowSuggestions(false);
317
+ inputRef.current?.focus();
318
+ }}
319
+ title="Clear search"
320
+ >
321
+ <Icon icon={IconProp.Close} className="h-3.5 w-3.5" />
322
+ </button>
323
+ )}
324
+ </div>
325
+
326
+ {shouldShowSuggestions && (
327
+ <TelemetrySearchSuggestions
328
+ suggestions={filteredSuggestions}
329
+ selectedIndex={selectedSuggestionIndex}
330
+ onSelect={applySuggestion}
331
+ fieldContext={isValueMode ? fieldPrefix : undefined}
332
+ />
333
+ )}
334
+
335
+ {shouldShowHelp && props.helpRows && (
336
+ <TelemetrySearchHelp
337
+ rows={props.helpRows}
338
+ combinedExample={props.helpCombinedExample}
339
+ onExampleClick={handleExampleClick}
340
+ />
341
+ )}
342
+ </div>
343
+ );
344
+ },
345
+ );
346
+
347
+ function extractCurrentWord(value: string): string {
348
+ const parts: Array<string> = value.split(/\s+/);
349
+ return parts[parts.length - 1] || "";
350
+ }
351
+
352
+ function getValueSuggestions(
353
+ fieldName: string,
354
+ partialValue: string,
355
+ valueSuggestions: Record<string, Array<string>>,
356
+ aliasMap: Record<string, string>,
357
+ ): Array<string> {
358
+ const resolvedField: string = aliasMap[fieldName] || fieldName;
359
+
360
+ const values: Array<string> | undefined = valueSuggestions[resolvedField];
361
+
362
+ if (!values || values.length === 0) {
363
+ return [];
364
+ }
365
+
366
+ if (!partialValue || partialValue.length === 0) {
367
+ return values;
368
+ }
369
+
370
+ const lowerPartial: string = partialValue.toLowerCase();
371
+ return values.filter((v: string): boolean => {
372
+ return v.toLowerCase().startsWith(lowerPartial);
373
+ });
374
+ }
375
+
376
+ TelemetrySearchBar.displayName = "TelemetrySearchBar";
377
+
378
+ export default TelemetrySearchBar;
@@ -0,0 +1,78 @@
1
+ import React, { FunctionComponent, ReactElement } from "react";
2
+ import { SearchHelpRow } from "../types";
3
+
4
+ export interface TelemetrySearchHelpProps {
5
+ rows: Array<SearchHelpRow>;
6
+ combinedExample?: string | undefined;
7
+ onExampleClick?: ((example: string) => void) | undefined;
8
+ }
9
+
10
+ const TelemetrySearchHelp: FunctionComponent<TelemetrySearchHelpProps> = (
11
+ props: TelemetrySearchHelpProps,
12
+ ): ReactElement => {
13
+ return (
14
+ <div className="absolute left-0 top-full z-50 mt-1 w-[36rem] overflow-hidden rounded-lg border border-gray-200 bg-white shadow-lg">
15
+ <div className="border-b border-gray-100 px-3 py-2">
16
+ <span className="text-[11px] font-semibold uppercase tracking-wider text-gray-400">
17
+ Search syntax
18
+ </span>
19
+ </div>
20
+
21
+ <table className="w-full">
22
+ <tbody>
23
+ {props.rows.map((row: SearchHelpRow) => {
24
+ return (
25
+ <tr
26
+ key={row.syntax}
27
+ className="cursor-pointer transition-colors hover:bg-gray-50"
28
+ onMouseDown={(e: React.MouseEvent) => {
29
+ e.preventDefault();
30
+ if (props.onExampleClick) {
31
+ props.onExampleClick(row.example);
32
+ }
33
+ }}
34
+ >
35
+ <td className="whitespace-nowrap py-1.5 pl-3 pr-2">
36
+ <code className="font-mono text-xs text-indigo-600">
37
+ {row.syntax}
38
+ </code>
39
+ </td>
40
+ <td className="px-2 py-1.5">
41
+ <span className="text-xs text-gray-500">
42
+ {row.description}
43
+ </span>
44
+ </td>
45
+ <td className="whitespace-nowrap py-1.5 pl-2 pr-3 text-right">
46
+ <code className="font-mono text-[11px] text-gray-400">
47
+ {row.example}
48
+ </code>
49
+ </td>
50
+ </tr>
51
+ );
52
+ })}
53
+ </tbody>
54
+ </table>
55
+
56
+ <div className="border-t border-gray-100 px-3 py-1.5">
57
+ <span className="text-[10px] text-gray-400">
58
+ Press{" "}
59
+ <kbd className="rounded border border-gray-200 bg-gray-50 px-1 py-0.5 font-mono text-[10px]">
60
+ Enter
61
+ </kbd>{" "}
62
+ to search
63
+ {props.combinedExample ? (
64
+ <>
65
+ {" "}
66
+ · Combine filters:{" "}
67
+ <code className="font-mono text-[10px] text-gray-500">
68
+ {props.combinedExample}
69
+ </code>
70
+ </>
71
+ ) : null}
72
+ </span>
73
+ </div>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default TelemetrySearchHelp;
@@ -0,0 +1,64 @@
1
+ import React, { FunctionComponent, ReactElement } from "react";
2
+
3
+ export interface TelemetrySearchSuggestionsProps {
4
+ suggestions: Array<string>;
5
+ selectedIndex: number;
6
+ onSelect: (suggestion: string) => void;
7
+ fieldContext?: string | undefined;
8
+ }
9
+
10
+ const MAX_VISIBLE_SUGGESTIONS: number = 8;
11
+
12
+ const TelemetrySearchSuggestions: FunctionComponent<
13
+ TelemetrySearchSuggestionsProps
14
+ > = (props: TelemetrySearchSuggestionsProps): ReactElement => {
15
+ const visible: Array<string> = props.suggestions.slice(
16
+ 0,
17
+ MAX_VISIBLE_SUGGESTIONS,
18
+ );
19
+
20
+ return (
21
+ <div className="absolute left-0 right-0 top-full z-50 mt-1 max-h-56 overflow-y-auto rounded-lg border border-gray-200 bg-white py-1 shadow-lg">
22
+ {visible.map((suggestion: string, index: number) => {
23
+ const isSelected: boolean = index === props.selectedIndex;
24
+
25
+ return (
26
+ <button
27
+ key={suggestion}
28
+ type="button"
29
+ className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm transition-colors ${
30
+ isSelected
31
+ ? "bg-indigo-50 text-indigo-700"
32
+ : "text-gray-700 hover:bg-gray-50"
33
+ }`}
34
+ onMouseDown={(e: React.MouseEvent) => {
35
+ e.preventDefault();
36
+ props.onSelect(suggestion);
37
+ }}
38
+ >
39
+ {props.fieldContext ? (
40
+ <>
41
+ <span className="font-mono text-xs text-gray-400">
42
+ {props.fieldContext}:
43
+ </span>
44
+ <span className="font-mono">{suggestion}</span>
45
+ </>
46
+ ) : (
47
+ <>
48
+ <span className="font-mono text-xs text-indigo-400">@</span>
49
+ <span className="font-mono">{suggestion}</span>
50
+ </>
51
+ )}
52
+ </button>
53
+ );
54
+ })}
55
+ {props.suggestions.length > MAX_VISIBLE_SUGGESTIONS && (
56
+ <div className="px-3 py-1 text-[11px] text-gray-400">
57
+ +{props.suggestions.length - MAX_VISIBLE_SUGGESTIONS} more...
58
+ </div>
59
+ )}
60
+ </div>
61
+ );
62
+ };
63
+
64
+ export default TelemetrySearchSuggestions;