@oneuptime/common 10.0.55 → 10.0.57

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 (129) 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 +360 -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/MonitorService.ts +21 -21
  15. package/Server/Services/TraceAggregationService.ts +514 -0
  16. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +73 -1
  17. package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
  18. package/Tests/__mocks__/mermaid.js +18 -0
  19. package/Tests/__mocks__/react-markdown.js +17 -0
  20. package/Tests/__mocks__/react-syntax-highlighter.js +19 -0
  21. package/Tests/__mocks__/remark-gfm.js +8 -0
  22. package/Types/Icon/IconProp.ts +1 -0
  23. package/Types/Monitor/DockerAlertTemplates.ts +507 -0
  24. package/Types/Monitor/DockerMetricCatalog.ts +226 -0
  25. package/Types/Monitor/MonitorStep.ts +33 -0
  26. package/Types/Monitor/MonitorStepDockerMonitor.ts +38 -0
  27. package/Types/Monitor/MonitorType.ts +15 -1
  28. package/Types/Permission.ts +38 -0
  29. package/UI/Components/Icon/Icon.tsx +87 -0
  30. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +7 -132
  31. package/UI/Components/ModelDetail/CardModelDetail.tsx +11 -1
  32. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +285 -0
  33. package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +85 -0
  34. package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +156 -0
  35. package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +160 -0
  36. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +85 -0
  37. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +102 -0
  38. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +280 -0
  39. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +125 -0
  40. package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +114 -0
  41. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +378 -0
  42. package/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.tsx +78 -0
  43. package/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.tsx +64 -0
  44. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +193 -0
  45. package/UI/Components/TelemetryViewer/types.ts +67 -0
  46. package/build/dist/Models/DatabaseModels/DockerHost.js +686 -0
  47. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -0
  48. package/build/dist/Models/DatabaseModels/GlobalConfig.js +117 -0
  49. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  50. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  51. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  52. package/build/dist/Server/API/TelemetryAPI.js +240 -16
  53. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  54. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +9 -0
  55. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  56. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js +35 -0
  57. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js.map +1 -0
  58. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js +52 -0
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js.map +1 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js +26 -0
  61. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js.map +1 -0
  62. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  63. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  64. package/build/dist/Server/Services/DockerHostService.js +162 -0
  65. package/build/dist/Server/Services/DockerHostService.js.map +1 -0
  66. package/build/dist/Server/Services/ExceptionAggregationService.js +224 -0
  67. package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -0
  68. package/build/dist/Server/Services/Index.js +2 -0
  69. package/build/dist/Server/Services/Index.js.map +1 -1
  70. package/build/dist/Server/Services/LogAggregationService.js +11 -0
  71. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  72. package/build/dist/Server/Services/MonitorService.js +19 -17
  73. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  74. package/build/dist/Server/Services/TraceAggregationService.js +364 -0
  75. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -0
  76. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +46 -1
  77. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  78. package/build/dist/Tests/Server/Services/LogAggregationService.test.js +2 -2
  79. package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
  80. package/build/dist/Types/Icon/IconProp.js +1 -0
  81. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  82. package/build/dist/Types/Monitor/DockerAlertTemplates.js +410 -0
  83. package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -0
  84. package/build/dist/Types/Monitor/DockerMetricCatalog.js +192 -0
  85. package/build/dist/Types/Monitor/DockerMetricCatalog.js.map +1 -0
  86. package/build/dist/Types/Monitor/MonitorStep.js +23 -0
  87. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  88. package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js +21 -0
  89. package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js.map +1 -0
  90. package/build/dist/Types/Monitor/MonitorType.js +14 -1
  91. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  92. package/build/dist/Types/Permission.js +36 -0
  93. package/build/dist/Types/Permission.js.map +1 -1
  94. package/build/dist/UI/Components/Icon/Icon.js +13 -0
  95. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  96. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +7 -75
  97. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  98. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +8 -1
  99. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  100. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +71 -0
  101. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -0
  102. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +39 -0
  103. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -0
  104. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +61 -0
  105. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -0
  106. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +66 -0
  107. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -0
  108. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +41 -0
  109. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -0
  110. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +35 -0
  111. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -0
  112. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +132 -0
  113. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -0
  114. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +65 -0
  115. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -0
  116. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +52 -0
  117. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -0
  118. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +224 -0
  119. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -0
  120. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js +35 -0
  121. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js.map +1 -0
  122. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js +27 -0
  123. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js.map +1 -0
  124. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +97 -0
  125. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -0
  126. package/build/dist/UI/Components/TelemetryViewer/types.js +6 -0
  127. package/build/dist/UI/Components/TelemetryViewer/types.js.map +1 -0
  128. package/jest.config.json +6 -1
  129. package/package.json +1 -1
@@ -0,0 +1,335 @@
1
+ import { SQL, Statement } from "../Utils/AnalyticsDatabase/Statement";
2
+ import ExceptionInstanceService from "./ExceptionInstanceService";
3
+ import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
4
+ import { JSONObject } from "../../Types/JSON";
5
+ import ObjectID from "../../Types/ObjectID";
6
+ import BadDataException from "../../Types/Exception/BadDataException";
7
+ import Includes from "../../Types/BaseDatabase/Includes";
8
+ import AnalyticsTableName from "../../Types/AnalyticsDatabase/AnalyticsTableName";
9
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
10
+ import { DbJSONResponse, Results } from "./AnalyticsDatabaseService";
11
+
12
+ export interface HistogramBucket {
13
+ time: string;
14
+ series: string;
15
+ count: number;
16
+ }
17
+
18
+ export interface ExceptionFilters {
19
+ serviceIds?: Array<ObjectID> | undefined;
20
+ exceptionTypes?: Array<string> | undefined;
21
+ environments?: Array<string> | undefined;
22
+ fingerprints?: Array<string> | undefined;
23
+ traceIds?: Array<string> | undefined;
24
+ escaped?: boolean | undefined;
25
+ messageSearchText?: string | undefined;
26
+ }
27
+
28
+ export interface HistogramRequest extends ExceptionFilters {
29
+ projectId: ObjectID;
30
+ startTime: Date;
31
+ endTime: Date;
32
+ bucketSizeInMinutes: number;
33
+ }
34
+
35
+ export interface FacetValue {
36
+ value: string;
37
+ count: number;
38
+ }
39
+
40
+ export interface FacetRequest extends ExceptionFilters {
41
+ projectId: ObjectID;
42
+ startTime: Date;
43
+ endTime: Date;
44
+ facetKey: string;
45
+ limit?: number | undefined;
46
+ }
47
+
48
+ export class ExceptionAggregationService {
49
+ private static readonly DEFAULT_FACET_LIMIT: number = 500;
50
+ private static readonly TABLE_NAME: string =
51
+ AnalyticsTableName.ExceptionInstance;
52
+ private static readonly TOP_LEVEL_COLUMNS: Set<string> = new Set([
53
+ "serviceId",
54
+ "exceptionType",
55
+ "environment",
56
+ "fingerprint",
57
+ "traceId",
58
+ "spanId",
59
+ "escaped",
60
+ "release",
61
+ ]);
62
+ private static readonly ATTRIBUTE_KEY_PATTERN: RegExp = /^[a-zA-Z0-9._:/-]+$/;
63
+ private static readonly MAX_FACET_KEY_LENGTH: number = 256;
64
+
65
+ @CaptureSpan()
66
+ public static async getHistogram(
67
+ request: HistogramRequest,
68
+ ): Promise<Array<HistogramBucket>> {
69
+ const statement: Statement =
70
+ ExceptionAggregationService.buildHistogramStatement(request);
71
+
72
+ const dbResult: Results =
73
+ await ExceptionInstanceService.executeQuery(statement);
74
+ const response: DbJSONResponse = await dbResult.json<{
75
+ data?: Array<JSONObject>;
76
+ }>();
77
+
78
+ const rows: Array<JSONObject> = response.data || [];
79
+
80
+ return rows.map((row: JSONObject): HistogramBucket => {
81
+ return {
82
+ time: String(row["bucket"] || ""),
83
+ series: ExceptionAggregationService.mapEscapedToSeries(row["escaped"]),
84
+ count: Number(row["cnt"] || 0),
85
+ };
86
+ });
87
+ }
88
+
89
+ @CaptureSpan()
90
+ public static async getFacetValues(
91
+ request: FacetRequest,
92
+ ): Promise<Array<FacetValue>> {
93
+ const statement: Statement =
94
+ ExceptionAggregationService.buildFacetStatement(request);
95
+
96
+ const dbResult: Results =
97
+ await ExceptionInstanceService.executeQuery(statement);
98
+ const response: DbJSONResponse = await dbResult.json<{
99
+ data?: Array<JSONObject>;
100
+ }>();
101
+
102
+ const rows: Array<JSONObject> = response.data || [];
103
+
104
+ return rows
105
+ .map((row: JSONObject): FacetValue => {
106
+ return {
107
+ value: String(row["val"] || ""),
108
+ count: Number(row["cnt"] || 0),
109
+ };
110
+ })
111
+ .filter((facet: FacetValue): boolean => {
112
+ return facet.value.length > 0;
113
+ });
114
+ }
115
+
116
+ private static mapEscapedToSeries(raw: unknown): string {
117
+ // ClickHouse returns booleans as 0/1
118
+ if (raw === true || raw === 1 || raw === "1" || raw === "true") {
119
+ return "unhandled";
120
+ }
121
+ return "handled";
122
+ }
123
+
124
+ private static buildHistogramStatement(request: HistogramRequest): Statement {
125
+ const intervalSeconds: number = request.bucketSizeInMinutes * 60;
126
+
127
+ const statement: Statement = SQL`
128
+ SELECT
129
+ toStartOfInterval(time, INTERVAL ${{
130
+ type: TableColumnType.Number,
131
+ value: intervalSeconds,
132
+ }} SECOND) AS bucket,
133
+ escaped,
134
+ count() AS cnt
135
+ FROM ${ExceptionAggregationService.TABLE_NAME}
136
+ WHERE projectId = ${{
137
+ type: TableColumnType.ObjectID,
138
+ value: request.projectId,
139
+ }}
140
+ AND time >= ${{
141
+ type: TableColumnType.Date,
142
+ value: request.startTime,
143
+ }}
144
+ AND time <= ${{
145
+ type: TableColumnType.Date,
146
+ value: request.endTime,
147
+ }}
148
+ `;
149
+
150
+ ExceptionAggregationService.appendCommonFilters(statement, request);
151
+
152
+ statement.append(" GROUP BY bucket, escaped ORDER BY bucket ASC");
153
+
154
+ /*
155
+ * Defense in depth: cap histogram runtime below nginx's 60s
156
+ * proxy_read_timeout. 'break' returns partial aggregated results
157
+ * rather than throwing, which is acceptable for a density viz.
158
+ */
159
+ statement.append(
160
+ " SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
161
+ );
162
+
163
+ return statement;
164
+ }
165
+
166
+ private static buildFacetStatement(request: FacetRequest): Statement {
167
+ const limit: number =
168
+ request.limit ?? ExceptionAggregationService.DEFAULT_FACET_LIMIT;
169
+
170
+ ExceptionAggregationService.validateFacetKey(request.facetKey);
171
+
172
+ const isTopLevelColumn: boolean =
173
+ ExceptionAggregationService.isTopLevelColumn(request.facetKey);
174
+
175
+ const statement: Statement = new Statement();
176
+
177
+ if (isTopLevelColumn) {
178
+ statement.append(
179
+ SQL`SELECT toString(${request.facetKey}) AS val, count() AS cnt FROM ${ExceptionAggregationService.TABLE_NAME}`,
180
+ );
181
+ } else {
182
+ statement.append(
183
+ SQL`SELECT JSONExtractRaw(attributes, ${{
184
+ type: TableColumnType.Text,
185
+ value: request.facetKey,
186
+ }}) AS val, count() AS cnt FROM ${ExceptionAggregationService.TABLE_NAME}`,
187
+ );
188
+ }
189
+
190
+ statement.append(
191
+ SQL` WHERE projectId = ${{
192
+ type: TableColumnType.ObjectID,
193
+ value: request.projectId,
194
+ }} AND time >= ${{
195
+ type: TableColumnType.Date,
196
+ value: request.startTime,
197
+ }} AND time <= ${{
198
+ type: TableColumnType.Date,
199
+ value: request.endTime,
200
+ }}`,
201
+ );
202
+
203
+ if (!isTopLevelColumn) {
204
+ statement.append(
205
+ SQL` AND JSONHas(attributes, ${{
206
+ type: TableColumnType.Text,
207
+ value: request.facetKey,
208
+ }}) = 1`,
209
+ );
210
+ }
211
+
212
+ ExceptionAggregationService.appendCommonFilters(statement, request);
213
+
214
+ statement.append(
215
+ SQL` GROUP BY val ORDER BY cnt DESC LIMIT ${{
216
+ type: TableColumnType.Number,
217
+ value: limit,
218
+ }}`,
219
+ );
220
+
221
+ /*
222
+ * Defense in depth: cap individual facet query runtime below nginx's
223
+ * 60s proxy_read_timeout so a slow facet never starves the endpoint.
224
+ */
225
+ statement.append(
226
+ " SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
227
+ );
228
+
229
+ return statement;
230
+ }
231
+
232
+ private static appendCommonFilters(
233
+ statement: Statement,
234
+ request: ExceptionFilters,
235
+ ): void {
236
+ if (request.serviceIds && request.serviceIds.length > 0) {
237
+ statement.append(
238
+ SQL` AND serviceId IN (${{
239
+ type: TableColumnType.ObjectID,
240
+ value: new Includes(
241
+ request.serviceIds.map((id: ObjectID) => {
242
+ return id.toString();
243
+ }),
244
+ ),
245
+ }})`,
246
+ );
247
+ }
248
+
249
+ if (request.exceptionTypes && request.exceptionTypes.length > 0) {
250
+ statement.append(
251
+ SQL` AND exceptionType IN (${{
252
+ type: TableColumnType.Text,
253
+ value: new Includes(request.exceptionTypes),
254
+ }})`,
255
+ );
256
+ }
257
+
258
+ if (request.environments && request.environments.length > 0) {
259
+ statement.append(
260
+ SQL` AND environment IN (${{
261
+ type: TableColumnType.Text,
262
+ value: new Includes(request.environments),
263
+ }})`,
264
+ );
265
+ }
266
+
267
+ if (request.fingerprints && request.fingerprints.length > 0) {
268
+ statement.append(
269
+ SQL` AND fingerprint IN (${{
270
+ type: TableColumnType.Text,
271
+ value: new Includes(request.fingerprints),
272
+ }})`,
273
+ );
274
+ }
275
+
276
+ if (request.traceIds && request.traceIds.length > 0) {
277
+ statement.append(
278
+ SQL` AND traceId IN (${{
279
+ type: TableColumnType.Text,
280
+ value: new Includes(request.traceIds),
281
+ }})`,
282
+ );
283
+ }
284
+
285
+ if (request.escaped !== undefined) {
286
+ statement.append(
287
+ SQL` AND escaped = ${{
288
+ type: TableColumnType.Boolean,
289
+ value: request.escaped,
290
+ }}`,
291
+ );
292
+ }
293
+
294
+ if (
295
+ request.messageSearchText &&
296
+ request.messageSearchText.trim().length > 0
297
+ ) {
298
+ statement.append(
299
+ SQL` AND message ILIKE ${{
300
+ type: TableColumnType.Text,
301
+ value: `%${request.messageSearchText.trim()}%`,
302
+ }}`,
303
+ );
304
+ }
305
+ }
306
+
307
+ private static isTopLevelColumn(key: string): boolean {
308
+ return ExceptionAggregationService.TOP_LEVEL_COLUMNS.has(key);
309
+ }
310
+
311
+ private static validateFacetKey(
312
+ facetKey: unknown,
313
+ ): asserts facetKey is string {
314
+ if (typeof facetKey !== "string") {
315
+ throw new BadDataException("Invalid facetKey");
316
+ }
317
+
318
+ if (
319
+ facetKey.length === 0 ||
320
+ facetKey.length > ExceptionAggregationService.MAX_FACET_KEY_LENGTH
321
+ ) {
322
+ throw new BadDataException("Invalid facetKey");
323
+ }
324
+
325
+ if (ExceptionAggregationService.isTopLevelColumn(facetKey)) {
326
+ return;
327
+ }
328
+
329
+ if (!ExceptionAggregationService.ATTRIBUTE_KEY_PATTERN.test(facetKey)) {
330
+ throw new BadDataException("Invalid facetKey");
331
+ }
332
+ }
333
+ }
334
+
335
+ export default ExceptionAggregationService;
@@ -34,6 +34,7 @@ import IncidentStateTimelineService from "./IncidentStateTimelineService";
34
34
  //Labels.
35
35
  import LabelService from "./LabelService";
36
36
  import KubernetesClusterService from "./KubernetesClusterService";
37
+ import DockerHostService from "./DockerHostService";
37
38
  import LlmProviderService from "./LlmProviderService";
38
39
  import LogService from "./LogService";
39
40
  import MailService from "./MailService";
@@ -258,6 +259,7 @@ const services: Array<BaseService> = [
258
259
 
259
260
  LabelService,
260
261
  KubernetesClusterService,
262
+ DockerHostService,
261
263
  LlmProviderService,
262
264
 
263
265
  MailService,
@@ -174,6 +174,15 @@ export class LogAggregationService {
174
174
 
175
175
  statement.append(" GROUP BY bucket, severityText ORDER BY bucket ASC");
176
176
 
177
+ /*
178
+ * Defense in depth: cap histogram runtime below nginx's 60s
179
+ * proxy_read_timeout. 'break' returns partial aggregated results
180
+ * rather than throwing, which is acceptable for a density viz.
181
+ */
182
+ statement.append(
183
+ " SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
184
+ );
185
+
177
186
  return statement;
178
187
  }
179
188
 
@@ -233,6 +242,14 @@ export class LogAggregationService {
233
242
  }}`,
234
243
  );
235
244
 
245
+ /*
246
+ * Defense in depth: cap individual facet query runtime below nginx's
247
+ * 60s proxy_read_timeout so a slow facet never starves the endpoint.
248
+ */
249
+ statement.append(
250
+ " SETTINGS max_execution_time = 45, timeout_overflow_mode = 'break'",
251
+ );
252
+
236
253
  return statement;
237
254
  }
238
255
 
@@ -71,7 +71,6 @@ import ExceptionMessages from "../../Types/Exception/ExceptionMessages";
71
71
  import Project from "../../Models/DatabaseModels/Project";
72
72
  import { createWhatsAppMessageFromTemplate } from "../Utils/WhatsAppTemplateUtil";
73
73
  import { WhatsAppMessagePayload } from "../../Types/WhatsApp/WhatsAppMessage";
74
- import MetricService from "./MetricService";
75
74
 
76
75
  export interface MonitorDestinationInfo {
77
76
  monitorDestination: string;
@@ -284,27 +283,28 @@ export class Service extends DatabaseService<Model> {
284
283
  onDelete: OnDelete<Model>,
285
284
  _itemIdsBeforeDelete: ObjectID[],
286
285
  ): Promise<OnDelete<Model>> {
286
+ /*
287
+ * The monitor has already been deleted from the database at this point.
288
+ * Any failure in the post-delete side effects below (e.g. billing
289
+ * reporting) must NOT propagate up to the caller as a 500 — otherwise the
290
+ * client sees "500 Internal Server Error" even though the delete actually
291
+ * succeeded. Log and swallow instead.
292
+ *
293
+ * Note: we intentionally do NOT delete Metric rows for this monitor here.
294
+ * The Metric table has a ClickHouse TTL on retentionDate (set at ingest
295
+ * from GlobalConfig.monitorMetricRetentionInDays) that auto-drops rows.
296
+ * A synchronous ALTER TABLE … DELETE on every monitor deletion is both
297
+ * redundant and expensive.
298
+ */
287
299
  if (onDelete.deleteBy.props.tenantId && IsBillingEnabled) {
288
- await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider(
289
- onDelete.deleteBy.props.tenantId,
290
- );
291
- }
292
-
293
- if (onDelete.carryForward && onDelete.carryForward.monitors) {
294
- for (const monitor of onDelete.carryForward.monitors as Array<Model>) {
295
- if (!monitor.projectId || !monitor.id) {
296
- continue;
297
- }
298
-
299
- await MetricService.deleteBy({
300
- query: {
301
- projectId: monitor.projectId,
302
- serviceId: monitor.id,
303
- },
304
- props: {
305
- isRoot: true,
306
- },
307
- });
300
+ try {
301
+ await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider(
302
+ onDelete.deleteBy.props.tenantId,
303
+ );
304
+ } catch (error) {
305
+ logger.error(
306
+ `Error while reporting active monitor quantity to billing provider for project ${onDelete.deleteBy.props.tenantId?.toString()}: ${error}`,
307
+ );
308
308
  }
309
309
  }
310
310