@oneuptime/common 10.0.86 → 10.0.89

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 (126) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +54 -0
  2. package/Models/DatabaseModels/GlobalConfig.ts +51 -0
  3. package/Server/API/EnterpriseLicenseAPI.ts +83 -0
  4. package/Server/API/GlobalConfigAPI.ts +59 -0
  5. package/Server/API/MetricAPI.ts +149 -0
  6. package/Server/API/TelemetryAPI.ts +24 -0
  7. package/Server/EnvironmentConfig.ts +10 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.ts +59 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  10. package/Server/Infrastructure/Queue.ts +4 -4
  11. package/Server/Services/AnalyticsDatabaseService.ts +21 -0
  12. package/Server/Services/MetricService.ts +193 -1
  13. package/Server/Services/TelemetryAttributeService.ts +37 -3
  14. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +174 -7
  15. package/Tests/Types/Date.test.ts +46 -0
  16. package/Types/Dashboard/DashboardComponentType.ts +3 -0
  17. package/Types/Dashboard/DashboardComponents/DashboardAlertListComponent.ts +13 -0
  18. package/Types/Dashboard/DashboardComponents/DashboardIncidentListComponent.ts +13 -0
  19. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +13 -0
  20. package/Types/Date.ts +9 -4
  21. package/Types/JSONFunctions.ts +61 -1
  22. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +60 -21
  23. package/UI/Components/Dictionary/Dictionary.tsx +188 -26
  24. package/UI/Components/Dictionary/DictionaryFilterOperator.ts +357 -0
  25. package/UI/Components/Dictionary/DictionaryOfStrings.tsx +12 -7
  26. package/UI/Components/EditionLabel/EditionLabel.tsx +224 -10
  27. package/UI/Components/Filters/FilterViewer.tsx +81 -16
  28. package/UI/Components/Filters/FiltersForm.tsx +18 -3
  29. package/UI/Components/Filters/JSONFilter.tsx +11 -2
  30. package/UI/Components/Filters/Types/Filter.ts +3 -0
  31. package/UI/Components/Forms/Fields/FormField.tsx +6 -1
  32. package/UI/Components/Forms/Types/Field.ts +5 -0
  33. package/UI/Components/LogsViewer/LogsViewer.tsx +73 -4
  34. package/UI/Components/LogsViewer/components/LogSearchBar.tsx +77 -31
  35. package/UI/Components/LogsViewer/components/LogSearchSuggestions.tsx +44 -1
  36. package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +7 -5
  37. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +6 -0
  38. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +84 -25
  39. package/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.tsx +44 -1
  40. package/Utils/Dashboard/Components/DashboardAlertListComponent.ts +86 -0
  41. package/Utils/Dashboard/Components/DashboardIncidentListComponent.ts +86 -0
  42. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +85 -0
  43. package/Utils/Dashboard/Components/Index.ts +21 -0
  44. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +57 -0
  45. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  46. package/build/dist/Models/DatabaseModels/GlobalConfig.js +54 -0
  47. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  48. package/build/dist/Server/API/EnterpriseLicenseAPI.js +64 -1
  49. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  50. package/build/dist/Server/API/GlobalConfigAPI.js +47 -0
  51. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  52. package/build/dist/Server/API/MetricAPI.js +123 -0
  53. package/build/dist/Server/API/MetricAPI.js.map +1 -0
  54. package/build/dist/Server/API/TelemetryAPI.js +9 -0
  55. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  56. package/build/dist/Server/EnvironmentConfig.js +3 -0
  57. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  58. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.js +26 -0
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777629313843-MigrationName.js.map +1 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  61. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  62. package/build/dist/Server/Infrastructure/Queue.js +3 -3
  63. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  64. package/build/dist/Server/Services/AnalyticsDatabaseService.js +18 -0
  65. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  66. package/build/dist/Server/Services/MetricService.js +151 -1
  67. package/build/dist/Server/Services/MetricService.js.map +1 -1
  68. package/build/dist/Server/Services/TelemetryAttributeService.js +36 -7
  69. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  70. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +135 -5
  71. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  72. package/build/dist/Tests/Types/Date.test.js +40 -0
  73. package/build/dist/Tests/Types/Date.test.js.map +1 -1
  74. package/build/dist/Types/Dashboard/DashboardComponentType.js +3 -0
  75. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  76. package/build/dist/Types/Dashboard/DashboardComponents/DashboardAlertListComponent.js +2 -0
  77. package/build/dist/Types/Dashboard/DashboardComponents/DashboardAlertListComponent.js.map +1 -0
  78. package/build/dist/Types/Dashboard/DashboardComponents/DashboardIncidentListComponent.js +2 -0
  79. package/build/dist/Types/Dashboard/DashboardComponents/DashboardIncidentListComponent.js.map +1 -0
  80. package/build/dist/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.js +2 -0
  81. package/build/dist/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.js.map +1 -0
  82. package/build/dist/Types/Date.js +7 -2
  83. package/build/dist/Types/Date.js.map +1 -1
  84. package/build/dist/Types/JSONFunctions.js +47 -1
  85. package/build/dist/Types/JSONFunctions.js.map +1 -1
  86. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +21 -10
  87. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  88. package/build/dist/UI/Components/Dictionary/Dictionary.js +109 -16
  89. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  90. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +263 -0
  91. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -0
  92. package/build/dist/UI/Components/Dictionary/DictionaryOfStrings.js +10 -6
  93. package/build/dist/UI/Components/Dictionary/DictionaryOfStrings.js.map +1 -1
  94. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +124 -6
  95. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  96. package/build/dist/UI/Components/Filters/FilterViewer.js +50 -12
  97. package/build/dist/UI/Components/Filters/FilterViewer.js.map +1 -1
  98. package/build/dist/UI/Components/Filters/FiltersForm.js +5 -4
  99. package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
  100. package/build/dist/UI/Components/Filters/JSONFilter.js +1 -1
  101. package/build/dist/UI/Components/Filters/JSONFilter.js.map +1 -1
  102. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  103. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  104. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +54 -5
  105. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  106. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +59 -29
  107. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
  108. package/build/dist/UI/Components/LogsViewer/components/LogSearchSuggestions.js +10 -2
  109. package/build/dist/UI/Components/LogsViewer/components/LogSearchSuggestions.js.map +1 -1
  110. package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +2 -5
  111. package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -1
  112. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
  113. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  114. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +59 -22
  115. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
  116. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js +10 -2
  117. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js.map +1 -1
  118. package/build/dist/Utils/Dashboard/Components/DashboardAlertListComponent.js +70 -0
  119. package/build/dist/Utils/Dashboard/Components/DashboardAlertListComponent.js.map +1 -0
  120. package/build/dist/Utils/Dashboard/Components/DashboardIncidentListComponent.js +70 -0
  121. package/build/dist/Utils/Dashboard/Components/DashboardIncidentListComponent.js.map +1 -0
  122. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +69 -0
  123. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -0
  124. package/build/dist/Utils/Dashboard/Components/Index.js +12 -0
  125. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  126. package/package.json +1 -1
@@ -9,6 +9,8 @@ export interface LogsFilterCardProps {
9
9
  onSearchSubmit: () => void;
10
10
  valueSuggestions?: Record<string, Array<string>> | undefined;
11
11
  onFieldValueSelect?: ((fieldKey: string, value: string) => void) | undefined;
12
+ isAttributesLoading?: boolean | undefined;
13
+ isValuesLoading?: boolean | undefined;
12
14
  }
13
15
 
14
16
  const LogsFilterCard: React.ForwardRefExoticComponent<
@@ -18,14 +20,11 @@ const LogsFilterCard: React.ForwardRefExoticComponent<
18
20
  props: LogsFilterCardProps,
19
21
  ref: React.Ref<LogSearchBarRef>,
20
22
  ): ReactElement => {
21
- const searchBarSuggestions: Array<string> = [
23
+ const fieldSuggestions: Array<string> = [
22
24
  "severity",
23
25
  "service",
24
26
  "trace",
25
27
  "span",
26
- ...props.logAttributes.map((attr: string) => {
27
- return `@${attr}`;
28
- }),
29
28
  ];
30
29
 
31
30
  return (
@@ -36,9 +35,12 @@ const LogsFilterCard: React.ForwardRefExoticComponent<
36
35
  value={props.searchQuery}
37
36
  onChange={props.onSearchQueryChange}
38
37
  onSubmit={props.onSearchSubmit}
39
- suggestions={searchBarSuggestions}
38
+ suggestions={fieldSuggestions}
39
+ attributeSuggestions={props.logAttributes}
40
40
  valueSuggestions={props.valueSuggestions}
41
41
  onFieldValueSelect={props.onFieldValueSelect}
42
+ isAttributesLoading={props.isAttributesLoading}
43
+ isValuesLoading={props.isValuesLoading}
42
44
  />
43
45
  </div>
44
46
  <div>{props.toolbar}</div>
@@ -42,6 +42,7 @@ export interface TelemetryViewerProps<T> {
42
42
  onSearchSubmit: () => void;
43
43
  searchPlaceholder?: string | undefined;
44
44
  searchSuggestions?: Array<string> | undefined;
45
+ searchAttributeSuggestions?: Array<string> | undefined;
45
46
  searchValueSuggestions?: Record<string, Array<string>> | undefined;
46
47
  searchFieldAliasMap?: Record<string, string> | undefined;
47
48
  onSearchFieldValueSelect?:
@@ -50,6 +51,8 @@ export interface TelemetryViewerProps<T> {
50
51
  searchHelpRows?: Array<SearchHelpRow> | undefined;
51
52
  searchHelpCombinedExample?: string | undefined;
52
53
  searchBarRef?: React.Ref<TelemetrySearchBarRef> | undefined;
54
+ searchAttributesLoading?: boolean | undefined;
55
+ searchValuesLoading?: boolean | undefined;
53
56
 
54
57
  // -- Toolbar: time --
55
58
  timeRange: RangeStartAndEndDateTime;
@@ -118,11 +121,14 @@ function TelemetryViewerInner<T>(props: TelemetryViewerProps<T>): ReactElement {
118
121
  onSubmit={props.onSearchSubmit}
119
122
  placeholder={props.searchPlaceholder}
120
123
  suggestions={props.searchSuggestions}
124
+ attributeSuggestions={props.searchAttributeSuggestions}
121
125
  valueSuggestions={props.searchValueSuggestions}
122
126
  fieldAliasMap={props.searchFieldAliasMap}
123
127
  onFieldValueSelect={props.onSearchFieldValueSelect}
124
128
  helpRows={props.searchHelpRows}
125
129
  helpCombinedExample={props.searchHelpCombinedExample}
130
+ isAttributesLoading={props.searchAttributesLoading}
131
+ isValuesLoading={props.searchValuesLoading}
126
132
  />
127
133
  </div>
128
134
 
@@ -18,8 +18,17 @@ export interface TelemetrySearchBarProps {
18
18
  value: string;
19
19
  onChange: (value: string) => void;
20
20
  onSubmit: () => void;
21
- // Field-name suggestions shown when user types "@".
21
+ /*
22
+ * Top-level field names like "service", "name" — used as `field:value`
23
+ * (no @). Shown when the user types regular text.
24
+ */
22
25
  suggestions?: Array<string> | undefined;
26
+ /*
27
+ * Telemetry attribute keys like "host.name", "container.id" — used as
28
+ * `@attr:value`. Shown when the user types `@`. Pass plain keys, not
29
+ * pre-prefixed with `@` — the bar adds it on submit.
30
+ */
31
+ attributeSuggestions?: Array<string> | undefined;
23
32
  // field → allowed value completions (resolved field keys).
24
33
  valueSuggestions?: Record<string, Array<string>> | undefined;
25
34
  // Called when the user picks a concrete field:value chip from the dropdown.
@@ -30,6 +39,10 @@ export interface TelemetrySearchBarProps {
30
39
  // Rows rendered in the help popover when the bar is empty + focused.
31
40
  helpRows?: Array<SearchHelpRow> | undefined;
32
41
  helpCombinedExample?: string | undefined;
42
+ // Loading state for `@attribute` autocomplete (initial fetch of keys).
43
+ isAttributesLoading?: boolean | undefined;
44
+ // Loading state for `@attribute:value` autocomplete (per-key value fetch).
45
+ isValuesLoading?: boolean | undefined;
33
46
  }
34
47
 
35
48
  export interface TelemetrySearchBarRef {
@@ -79,6 +92,17 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
79
92
  ? normalizedWord.substring(colonIndex + 1)
80
93
  : "";
81
94
 
95
+ /*
96
+ * Pick the suggestion list based on whether the user typed `@`.
97
+ * `@` is the explicit trigger for attribute mode — only show attribute
98
+ * keys there. Without `@`, only show top-level field names. Mixing the
99
+ * two led to confusing dropdowns that rendered field names like "name"
100
+ * as if they were attributes.
101
+ */
102
+ const activeSuggestions: Array<string> = hasAtPrefix
103
+ ? props.attributeSuggestions || []
104
+ : props.suggestions || [];
105
+
82
106
  const filteredSuggestions: Array<string> = isValueMode
83
107
  ? getValueSuggestions(
84
108
  fieldPrefix,
@@ -86,23 +110,32 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
86
110
  props.valueSuggestions || {},
87
111
  fieldAliasMap,
88
112
  )
89
- : (props.suggestions || []).filter((s: string): boolean => {
113
+ : activeSuggestions.filter((s: string): boolean => {
90
114
  if (!normalizedWord && !hasAtPrefix) {
91
115
  return false;
92
116
  }
93
117
  if (hasAtPrefix && normalizedWord.length === 0) {
94
118
  return true;
95
119
  }
96
- const normalizedSuggestion: string = s.startsWith("@")
97
- ? s.substring(1).toLowerCase()
98
- : s.toLowerCase();
99
- return normalizedSuggestion.startsWith(normalizedWord.toLowerCase());
120
+ return s.toLowerCase().startsWith(normalizedWord.toLowerCase());
100
121
  });
101
122
 
123
+ /*
124
+ * Show a loader inside the dropdown while the parent is fetching:
125
+ * - attribute keys: `@` was just typed but the keys haven't arrived
126
+ * - attribute values: `@key:` was typed but values for that key
127
+ * haven't arrived yet
128
+ */
129
+ const isLoadingForCurrentMode: boolean = isValueMode
130
+ ? Boolean(props.isValuesLoading)
131
+ : hasAtPrefix
132
+ ? Boolean(props.isAttributesLoading)
133
+ : false;
134
+
102
135
  const shouldShowSuggestions: boolean =
103
136
  showSuggestions &&
104
137
  isFocused &&
105
- filteredSuggestions.length > 0 &&
138
+ (filteredSuggestions.length > 0 || isLoadingForCurrentMode) &&
106
139
  (isValueMode ? true : currentWord.length > 0);
107
140
 
108
141
  const shouldShowHelp: boolean =
@@ -123,6 +156,7 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
123
156
  if (e.key === "Enter") {
124
157
  if (
125
158
  shouldShowSuggestions &&
159
+ !isLoadingForCurrentMode &&
126
160
  selectedSuggestionIndex >= 0 &&
127
161
  selectedSuggestionIndex < filteredSuggestions.length
128
162
  ) {
@@ -136,6 +170,11 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
136
170
  partialValue.length > 0 &&
137
171
  props.onFieldValueSelect
138
172
  ) {
173
+ /*
174
+ * Prefer a match from the suggestion list (so casing matches
175
+ * what's actually in the data); otherwise accept the typed
176
+ * value as-is so users aren't blocked when no suggestion exists.
177
+ */
139
178
  const resolvedField: string =
140
179
  fieldAliasMap[fieldPrefix] || fieldPrefix;
141
180
  const availableValues: Array<string> =
@@ -147,23 +186,21 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
147
186
  },
148
187
  );
149
188
 
150
- const resolvedMatch: string | undefined =
189
+ const resolvedMatch: string =
151
190
  exactMatch ||
152
191
  (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
- }
192
+ ? filteredSuggestions[0]!
193
+ : partialValue);
194
+
195
+ props.onFieldValueSelect(fieldPrefix, resolvedMatch);
196
+ const parts: Array<string> = props.value.split(/\s+/);
197
+ parts.pop();
198
+ const remaining: string = parts.join(" ");
199
+ props.onChange(remaining ? remaining + " " : "");
200
+ setShowSuggestions(false);
201
+ setShowHelp(false);
202
+ e.preventDefault();
203
+ return;
167
204
  }
168
205
 
169
206
  props.onSubmit();
@@ -178,7 +215,7 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
178
215
  return;
179
216
  }
180
217
 
181
- if (!shouldShowSuggestions) {
218
+ if (!shouldShowSuggestions || isLoadingForCurrentMode) {
182
219
  return;
183
220
  }
184
221
 
@@ -206,6 +243,7 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
206
243
  partialValue,
207
244
  props,
208
245
  fieldAliasMap,
246
+ isLoadingForCurrentMode,
209
247
  ],
210
248
  );
211
249
 
@@ -229,7 +267,13 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
229
267
  const parts: Array<string> = props.value.split(/\s+/);
230
268
 
231
269
  if (parts.length > 0) {
232
- parts[parts.length - 1] = suggestion + ":";
270
+ /*
271
+ * Attribute suggestions are stored without `@`; add it back
272
+ * when filling the bar so the parser recognizes it as an attribute.
273
+ */
274
+ parts[parts.length - 1] = hasAtPrefix
275
+ ? "@" + suggestion + ":"
276
+ : suggestion + ":";
233
277
  }
234
278
 
235
279
  props.onChange(parts.join(" "));
@@ -237,7 +281,7 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
237
281
  setShowHelp(false);
238
282
  inputRef.current?.focus();
239
283
  },
240
- [props, isValueMode, fieldPrefix],
284
+ [props, isValueMode, fieldPrefix, hasAtPrefix],
241
285
  );
242
286
 
243
287
  const handleExampleClick: (example: string) => void = useCallback(
@@ -268,6 +312,17 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
268
312
  };
269
313
  }, []);
270
314
 
315
+ const loadingMessage: string = isValueMode
316
+ ? `Loading values for ${fieldPrefix}...`
317
+ : "Loading attributes...";
318
+
319
+ const emptyMessage: string | undefined =
320
+ isValueMode &&
321
+ !isLoadingForCurrentMode &&
322
+ filteredSuggestions.length === 0
323
+ ? `No matching values — press Enter to filter by "${partialValue}"`
324
+ : undefined;
325
+
271
326
  return (
272
327
  <div ref={containerRef} className="relative">
273
328
  <div
@@ -329,6 +384,10 @@ const TelemetrySearchBar: React.ForwardRefExoticComponent<
329
384
  selectedIndex={selectedSuggestionIndex}
330
385
  onSelect={applySuggestion}
331
386
  fieldContext={isValueMode ? fieldPrefix : undefined}
387
+ isAttributeMode={hasAtPrefix}
388
+ isLoading={isLoadingForCurrentMode}
389
+ loadingMessage={loadingMessage}
390
+ emptyMessage={emptyMessage}
332
391
  />
333
392
  )}
334
393
 
@@ -5,6 +5,14 @@ export interface TelemetrySearchSuggestionsProps {
5
5
  selectedIndex: number;
6
6
  onSelect: (suggestion: string) => void;
7
7
  fieldContext?: string | undefined;
8
+ /*
9
+ * When true, items are attribute keys (rendered with the @ prefix).
10
+ * When false, items are top-level field names (no prefix).
11
+ */
12
+ isAttributeMode?: boolean | undefined;
13
+ isLoading?: boolean | undefined;
14
+ loadingMessage?: string | undefined;
15
+ emptyMessage?: string | undefined;
8
16
  }
9
17
 
10
18
  const MAX_VISIBLE_SUGGESTIONS: number = 8;
@@ -19,6 +27,39 @@ const TelemetrySearchSuggestions: FunctionComponent<
19
27
 
20
28
  return (
21
29
  <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">
30
+ {props.isLoading && (
31
+ <div className="flex w-full items-center px-3 py-2 text-left text-sm text-gray-500">
32
+ <svg
33
+ className="animate-spin -ml-0.5 mr-2 h-4 w-4 text-indigo-500"
34
+ xmlns="http://www.w3.org/2000/svg"
35
+ fill="none"
36
+ viewBox="0 0 24 24"
37
+ aria-hidden="true"
38
+ >
39
+ <circle
40
+ className="opacity-25"
41
+ cx="12"
42
+ cy="12"
43
+ r="10"
44
+ stroke="currentColor"
45
+ strokeWidth="4"
46
+ ></circle>
47
+ <path
48
+ className="opacity-75"
49
+ fill="currentColor"
50
+ d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
51
+ ></path>
52
+ </svg>
53
+ <span>{props.loadingMessage || "Loading..."}</span>
54
+ </div>
55
+ )}
56
+ {!props.isLoading &&
57
+ visible.length === 0 &&
58
+ props.emptyMessage !== undefined && (
59
+ <div className="px-3 py-2 text-sm text-gray-400">
60
+ {props.emptyMessage}
61
+ </div>
62
+ )}
22
63
  {visible.map((suggestion: string, index: number) => {
23
64
  const isSelected: boolean = index === props.selectedIndex;
24
65
 
@@ -43,11 +84,13 @@ const TelemetrySearchSuggestions: FunctionComponent<
43
84
  </span>
44
85
  <span className="font-mono">{suggestion}</span>
45
86
  </>
46
- ) : (
87
+ ) : props.isAttributeMode ? (
47
88
  <>
48
89
  <span className="font-mono text-xs text-indigo-400">@</span>
49
90
  <span className="font-mono">{suggestion}</span>
50
91
  </>
92
+ ) : (
93
+ <span className="font-mono">{suggestion}</span>
51
94
  )}
52
95
  </button>
53
96
  );
@@ -0,0 +1,86 @@
1
+ import DashboardAlertListComponent from "../../../Types/Dashboard/DashboardComponents/DashboardAlertListComponent";
2
+ import { ObjectType } from "../../../Types/JSON";
3
+ import ObjectID from "../../../Types/ObjectID";
4
+ import DashboardBaseComponentUtil from "./DashboardBaseComponent";
5
+ import {
6
+ ComponentArgument,
7
+ ComponentArgumentSection,
8
+ ComponentInputType,
9
+ } from "../../../Types/Dashboard/DashboardComponents/ComponentArgument";
10
+ import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
11
+
12
+ const DisplaySection: ComponentArgumentSection = {
13
+ name: "Display Options",
14
+ description: "Configure the widget title and row limit",
15
+ order: 1,
16
+ };
17
+
18
+ const FiltersSection: ComponentArgumentSection = {
19
+ name: "Filters",
20
+ description: "Narrow down which alerts are shown",
21
+ order: 2,
22
+ defaultCollapsed: true,
23
+ };
24
+
25
+ export default class DashboardAlertListComponentUtil extends DashboardBaseComponentUtil {
26
+ public static override getDefaultComponent(): DashboardAlertListComponent {
27
+ return {
28
+ _type: ObjectType.DashboardComponent,
29
+ componentType: DashboardComponentType.AlertList,
30
+ widthInDashboardUnits: 6,
31
+ heightInDashboardUnits: 4,
32
+ topInDashboardUnits: 0,
33
+ leftInDashboardUnits: 0,
34
+ componentId: ObjectID.generate(),
35
+ minHeightInDashboardUnits: 3,
36
+ minWidthInDashboardUnits: 6,
37
+ arguments: {
38
+ maxRows: 25,
39
+ },
40
+ };
41
+ }
42
+
43
+ public static override getComponentConfigArguments(): Array<
44
+ ComponentArgument<DashboardAlertListComponent>
45
+ > {
46
+ const componentArguments: Array<
47
+ ComponentArgument<DashboardAlertListComponent>
48
+ > = [];
49
+
50
+ componentArguments.push({
51
+ name: "Title",
52
+ description: "Header shown above the alert list",
53
+ required: false,
54
+ type: ComponentInputType.Text,
55
+ id: "title",
56
+ section: DisplaySection,
57
+ });
58
+
59
+ componentArguments.push({
60
+ name: "Max Rows",
61
+ description: "Maximum number of alerts to show",
62
+ required: false,
63
+ type: ComponentInputType.Number,
64
+ id: "maxRows",
65
+ placeholder: "25",
66
+ section: DisplaySection,
67
+ });
68
+
69
+ componentArguments.push({
70
+ name: "State",
71
+ description: "Filter alerts by lifecycle state",
72
+ required: false,
73
+ type: ComponentInputType.Dropdown,
74
+ id: "stateFilter",
75
+ section: FiltersSection,
76
+ dropdownOptions: [
77
+ { label: "All", value: "" },
78
+ { label: "Unresolved (open)", value: "unresolved" },
79
+ { label: "Acknowledged", value: "acknowledged" },
80
+ { label: "Resolved", value: "resolved" },
81
+ ],
82
+ });
83
+
84
+ return componentArguments;
85
+ }
86
+ }
@@ -0,0 +1,86 @@
1
+ import DashboardIncidentListComponent from "../../../Types/Dashboard/DashboardComponents/DashboardIncidentListComponent";
2
+ import { ObjectType } from "../../../Types/JSON";
3
+ import ObjectID from "../../../Types/ObjectID";
4
+ import DashboardBaseComponentUtil from "./DashboardBaseComponent";
5
+ import {
6
+ ComponentArgument,
7
+ ComponentArgumentSection,
8
+ ComponentInputType,
9
+ } from "../../../Types/Dashboard/DashboardComponents/ComponentArgument";
10
+ import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
11
+
12
+ const DisplaySection: ComponentArgumentSection = {
13
+ name: "Display Options",
14
+ description: "Configure the widget title and row limit",
15
+ order: 1,
16
+ };
17
+
18
+ const FiltersSection: ComponentArgumentSection = {
19
+ name: "Filters",
20
+ description: "Narrow down which incidents are shown",
21
+ order: 2,
22
+ defaultCollapsed: true,
23
+ };
24
+
25
+ export default class DashboardIncidentListComponentUtil extends DashboardBaseComponentUtil {
26
+ public static override getDefaultComponent(): DashboardIncidentListComponent {
27
+ return {
28
+ _type: ObjectType.DashboardComponent,
29
+ componentType: DashboardComponentType.IncidentList,
30
+ widthInDashboardUnits: 6,
31
+ heightInDashboardUnits: 4,
32
+ topInDashboardUnits: 0,
33
+ leftInDashboardUnits: 0,
34
+ componentId: ObjectID.generate(),
35
+ minHeightInDashboardUnits: 3,
36
+ minWidthInDashboardUnits: 6,
37
+ arguments: {
38
+ maxRows: 25,
39
+ },
40
+ };
41
+ }
42
+
43
+ public static override getComponentConfigArguments(): Array<
44
+ ComponentArgument<DashboardIncidentListComponent>
45
+ > {
46
+ const componentArguments: Array<
47
+ ComponentArgument<DashboardIncidentListComponent>
48
+ > = [];
49
+
50
+ componentArguments.push({
51
+ name: "Title",
52
+ description: "Header shown above the incident list",
53
+ required: false,
54
+ type: ComponentInputType.Text,
55
+ id: "title",
56
+ section: DisplaySection,
57
+ });
58
+
59
+ componentArguments.push({
60
+ name: "Max Rows",
61
+ description: "Maximum number of incidents to show",
62
+ required: false,
63
+ type: ComponentInputType.Number,
64
+ id: "maxRows",
65
+ placeholder: "25",
66
+ section: DisplaySection,
67
+ });
68
+
69
+ componentArguments.push({
70
+ name: "State",
71
+ description: "Filter incidents by lifecycle state",
72
+ required: false,
73
+ type: ComponentInputType.Dropdown,
74
+ id: "stateFilter",
75
+ section: FiltersSection,
76
+ dropdownOptions: [
77
+ { label: "All", value: "" },
78
+ { label: "Unresolved (open)", value: "unresolved" },
79
+ { label: "Acknowledged", value: "acknowledged" },
80
+ { label: "Resolved", value: "resolved" },
81
+ ],
82
+ });
83
+
84
+ return componentArguments;
85
+ }
86
+ }
@@ -0,0 +1,85 @@
1
+ import DashboardMonitorListComponent from "../../../Types/Dashboard/DashboardComponents/DashboardMonitorListComponent";
2
+ import { ObjectType } from "../../../Types/JSON";
3
+ import ObjectID from "../../../Types/ObjectID";
4
+ import DashboardBaseComponentUtil from "./DashboardBaseComponent";
5
+ import {
6
+ ComponentArgument,
7
+ ComponentArgumentSection,
8
+ ComponentInputType,
9
+ } from "../../../Types/Dashboard/DashboardComponents/ComponentArgument";
10
+ import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
11
+
12
+ const DisplaySection: ComponentArgumentSection = {
13
+ name: "Display Options",
14
+ description: "Configure the widget title and row limit",
15
+ order: 1,
16
+ };
17
+
18
+ const FiltersSection: ComponentArgumentSection = {
19
+ name: "Filters",
20
+ description: "Narrow down which monitors are shown",
21
+ order: 2,
22
+ defaultCollapsed: true,
23
+ };
24
+
25
+ export default class DashboardMonitorListComponentUtil extends DashboardBaseComponentUtil {
26
+ public static override getDefaultComponent(): DashboardMonitorListComponent {
27
+ return {
28
+ _type: ObjectType.DashboardComponent,
29
+ componentType: DashboardComponentType.MonitorList,
30
+ widthInDashboardUnits: 6,
31
+ heightInDashboardUnits: 4,
32
+ topInDashboardUnits: 0,
33
+ leftInDashboardUnits: 0,
34
+ componentId: ObjectID.generate(),
35
+ minHeightInDashboardUnits: 3,
36
+ minWidthInDashboardUnits: 6,
37
+ arguments: {
38
+ maxRows: 25,
39
+ },
40
+ };
41
+ }
42
+
43
+ public static override getComponentConfigArguments(): Array<
44
+ ComponentArgument<DashboardMonitorListComponent>
45
+ > {
46
+ const componentArguments: Array<
47
+ ComponentArgument<DashboardMonitorListComponent>
48
+ > = [];
49
+
50
+ componentArguments.push({
51
+ name: "Title",
52
+ description: "Header shown above the monitor list",
53
+ required: false,
54
+ type: ComponentInputType.Text,
55
+ id: "title",
56
+ section: DisplaySection,
57
+ });
58
+
59
+ componentArguments.push({
60
+ name: "Max Rows",
61
+ description: "Maximum number of monitors to show",
62
+ required: false,
63
+ type: ComponentInputType.Number,
64
+ id: "maxRows",
65
+ placeholder: "25",
66
+ section: DisplaySection,
67
+ });
68
+
69
+ componentArguments.push({
70
+ name: "Status",
71
+ description: "Filter monitors by current status",
72
+ required: false,
73
+ type: ComponentInputType.Dropdown,
74
+ id: "statusFilter",
75
+ section: FiltersSection,
76
+ dropdownOptions: [
77
+ { label: "All", value: "" },
78
+ { label: "Operational only", value: "operational" },
79
+ { label: "Not operational only", value: "non-operational" },
80
+ ],
81
+ });
82
+
83
+ return componentArguments;
84
+ }
85
+ }
@@ -2,9 +2,12 @@ import { ComponentArgument } from "../../../Types/Dashboard/DashboardComponents/
2
2
  import DashboardBaseComponent from "../../../Types/Dashboard/DashboardComponents/DashboardBaseComponent";
3
3
  import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
4
4
  import BadDataException from "../../../Types/Exception/BadDataException";
5
+ import DashboardAlertListComponentUtil from "./DashboardAlertListComponent";
5
6
  import DashboardChartComponentUtil from "./DashboardChartComponent";
6
7
  import DashboardGaugeComponentUtil from "./DashboardGaugeComponent";
8
+ import DashboardIncidentListComponentUtil from "./DashboardIncidentListComponent";
7
9
  import DashboardLogStreamComponentUtil from "./DashboardLogStreamComponent";
10
+ import DashboardMonitorListComponentUtil from "./DashboardMonitorListComponent";
8
11
  import DashboardTableComponentUtil from "./DashboardTableComponent";
9
12
  import DashboardTextComponentUtil from "./DashboardTextComponent";
10
13
  import DashboardTraceListComponentUtil from "./DashboardTraceListComponent";
@@ -56,6 +59,24 @@ export default class DashboardComponentsUtil {
56
59
  >;
57
60
  }
58
61
 
62
+ if (dashboardComponentType === DashboardComponentType.IncidentList) {
63
+ return DashboardIncidentListComponentUtil.getComponentConfigArguments() as Array<
64
+ ComponentArgument<DashboardBaseComponent>
65
+ >;
66
+ }
67
+
68
+ if (dashboardComponentType === DashboardComponentType.AlertList) {
69
+ return DashboardAlertListComponentUtil.getComponentConfigArguments() as Array<
70
+ ComponentArgument<DashboardBaseComponent>
71
+ >;
72
+ }
73
+
74
+ if (dashboardComponentType === DashboardComponentType.MonitorList) {
75
+ return DashboardMonitorListComponentUtil.getComponentConfigArguments() as Array<
76
+ ComponentArgument<DashboardBaseComponent>
77
+ >;
78
+ }
79
+
59
80
  throw new BadDataException(
60
81
  `Unknown dashboard component type: ${dashboardComponentType}`,
61
82
  );