@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.
- package/Models/DatabaseModels/DockerHost.ts +662 -0
- package/Models/DatabaseModels/GlobalConfig.ts +112 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Server/API/TelemetryAPI.ts +352 -16
- package/Server/Infrastructure/ClickhouseConfig.ts +9 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.ts +76 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.ts +133 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/DockerHostService.ts +173 -0
- package/Server/Services/ExceptionAggregationService.ts +335 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LogAggregationService.ts +17 -0
- package/Server/Services/MonitorProbeService.ts +42 -21
- package/Server/Services/MonitorService.ts +21 -21
- package/Server/Services/TraceAggregationService.ts +514 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +73 -1
- package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
- package/Tests/__mocks__/mermaid.js +18 -0
- package/Tests/__mocks__/react-markdown.js +17 -0
- package/Tests/__mocks__/react-syntax-highlighter.js +19 -0
- package/Tests/__mocks__/remark-gfm.js +8 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/Monitor/DockerAlertTemplates.ts +507 -0
- package/Types/Monitor/DockerMetricCatalog.ts +226 -0
- package/Types/Monitor/MonitorStep.ts +33 -0
- package/Types/Monitor/MonitorStepDockerMonitor.ts +38 -0
- package/Types/Monitor/MonitorType.ts +15 -1
- package/Types/Permission.ts +38 -0
- package/UI/Components/Icon/Icon.tsx +87 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +7 -132
- package/UI/Components/ModelDetail/CardModelDetail.tsx +11 -1
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +285 -0
- package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +85 -0
- package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +156 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +160 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +85 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +102 -0
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +280 -0
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +125 -0
- package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +114 -0
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +378 -0
- package/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.tsx +78 -0
- package/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.tsx +64 -0
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +193 -0
- package/UI/Components/TelemetryViewer/types.ts +67 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js +686 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +117 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +237 -16
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +9 -0
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js +35 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js +52 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +162 -0
- package/build/dist/Server/Services/DockerHostService.js.map +1 -0
- package/build/dist/Server/Services/ExceptionAggregationService.js +224 -0
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -0
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +11 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +28 -14
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +19 -17
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +364 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +46 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Tests/Server/Services/LogAggregationService.test.js +2 -2
- package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Monitor/DockerAlertTemplates.js +410 -0
- package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/DockerMetricCatalog.js +192 -0
- package/build/dist/Types/Monitor/DockerMetricCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStep.js +23 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js +21 -0
- package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +14 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Permission.js +36 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +13 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +7 -75
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +8 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +71 -0
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +39 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +61 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +66 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +41 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +35 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +132 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +65 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +52 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +224 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js +35 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js +27 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +97 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/types.js +6 -0
- package/build/dist/UI/Components/TelemetryViewer/types.js.map +1 -0
- package/jest.config.json +6 -1
- 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;
|