@oneuptime/common 8.0.5440 → 8.0.5466

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 (55) hide show
  1. package/Models/DatabaseModels/StatusPage.ts +80 -0
  2. package/Models/DatabaseModels/TelemetryUsageBilling.ts +1 -1
  3. package/Server/API/StatusPageAPI.ts +138 -52
  4. package/Server/EnvironmentConfig.ts +37 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.ts +29 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Services/AnalyticsDatabaseService.ts +71 -11
  8. package/Server/Services/OpenTelemetryIngestService.ts +1 -39
  9. package/Server/Services/StatusPageService.ts +117 -0
  10. package/Server/Services/TelemetryUsageBillingService.ts +268 -15
  11. package/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +5 -0
  12. package/Server/Utils/Telemetry/Telemetry.ts +135 -81
  13. package/Server/Utils/VM/VMRunner.ts +3 -4
  14. package/Types/Date.ts +5 -0
  15. package/UI/Components/LogsViewer/LogItem.tsx +12 -4
  16. package/UI/Components/LogsViewer/LogsViewer.tsx +131 -29
  17. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -2
  18. package/UI/Components/Table/TableRow.tsx +89 -77
  19. package/UI/esbuild-config.js +32 -1
  20. package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
  21. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  22. package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js +1 -1
  23. package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js.map +1 -1
  24. package/build/dist/Server/API/StatusPageAPI.js +157 -74
  25. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  26. package/build/dist/Server/EnvironmentConfig.js +15 -0
  27. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  28. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js +16 -0
  29. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js.map +1 -0
  30. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  31. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  32. package/build/dist/Server/Services/AnalyticsDatabaseService.js +55 -8
  33. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  34. package/build/dist/Server/Services/OpenTelemetryIngestService.js +0 -30
  35. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  36. package/build/dist/Server/Services/StatusPageService.js +95 -0
  37. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  38. package/build/dist/Server/Services/TelemetryUsageBillingService.js +211 -8
  39. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  40. package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js +4 -0
  41. package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js.map +1 -1
  42. package/build/dist/Server/Utils/Telemetry/Telemetry.js +84 -60
  43. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  44. package/build/dist/Server/Utils/VM/VMRunner.js +2 -2
  45. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  46. package/build/dist/Types/Date.js +4 -0
  47. package/build/dist/Types/Date.js.map +1 -1
  48. package/build/dist/UI/Components/LogsViewer/LogItem.js +5 -3
  49. package/build/dist/UI/Components/LogsViewer/LogItem.js.map +1 -1
  50. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +73 -22
  51. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  52. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -2
  53. package/build/dist/UI/Components/Table/TableRow.js +18 -6
  54. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  55. package/package.json +4 -4
@@ -1150,5 +1150,122 @@ export class Service extends DatabaseService<StatusPage> {
1150
1150
  },
1151
1151
  );
1152
1152
  }
1153
+
1154
+ @CaptureSpan()
1155
+ public async getMonitorGroupCurrentStatuses(data: {
1156
+ statusPageResources: Array<StatusPageResource>;
1157
+ monitorStatuses: Array<MonitorStatus>;
1158
+ }): Promise<Dictionary<ObjectID>> {
1159
+ const monitorGroupCurrentStatuses: Dictionary<ObjectID> = {};
1160
+
1161
+ for (const resource of data.statusPageResources) {
1162
+ if (resource.monitorGroupId) {
1163
+ const monitorGroupResources: Array<MonitorGroupResource> =
1164
+ await MonitorGroupResourceService.findBy({
1165
+ query: {
1166
+ monitorGroupId: resource.monitorGroupId,
1167
+ },
1168
+ select: {
1169
+ monitorId: true,
1170
+ monitor: {
1171
+ currentMonitorStatusId: true,
1172
+ },
1173
+ },
1174
+ skip: 0,
1175
+ limit: LIMIT_PER_PROJECT,
1176
+ props: {
1177
+ isRoot: true,
1178
+ },
1179
+ });
1180
+
1181
+ const statuses: Array<ObjectID> = monitorGroupResources
1182
+ .filter((item: MonitorGroupResource) => {
1183
+ return (
1184
+ item.monitor &&
1185
+ item.monitor.currentMonitorStatusId &&
1186
+ item.monitorId
1187
+ );
1188
+ })
1189
+ .map((item: MonitorGroupResource) => {
1190
+ return item.monitor!.currentMonitorStatusId!;
1191
+ });
1192
+
1193
+ let worstStatus: MonitorStatus | null = null;
1194
+
1195
+ for (const statusId of statuses) {
1196
+ const status: MonitorStatus | undefined = data.monitorStatuses.find(
1197
+ (status: MonitorStatus) => {
1198
+ return status._id?.toString() === statusId.toString();
1199
+ },
1200
+ );
1201
+
1202
+ if (
1203
+ status &&
1204
+ (!worstStatus || status.priority! < worstStatus.priority!)
1205
+ ) {
1206
+ worstStatus = status;
1207
+ }
1208
+ }
1209
+
1210
+ if (worstStatus && worstStatus._id) {
1211
+ monitorGroupCurrentStatuses[resource.monitorGroupId.toString()] =
1212
+ new ObjectID(worstStatus._id);
1213
+ }
1214
+ }
1215
+ }
1216
+
1217
+ return monitorGroupCurrentStatuses;
1218
+ }
1219
+
1220
+ @CaptureSpan()
1221
+ public getOverallMonitorStatus(data: {
1222
+ statusPageResources: Array<StatusPageResource>;
1223
+ monitorStatuses: Array<MonitorStatus>;
1224
+ monitorGroupCurrentStatuses: Dictionary<ObjectID>;
1225
+ }): MonitorStatus | null {
1226
+ let currentStatus: MonitorStatus | null =
1227
+ data.monitorStatuses.length > 0 && data.monitorStatuses[0]
1228
+ ? data.monitorStatuses[0]
1229
+ : null;
1230
+
1231
+ const dict: Dictionary<number> = {};
1232
+
1233
+ for (const resource of data.statusPageResources) {
1234
+ if (resource.monitor?.currentMonitorStatusId) {
1235
+ if (
1236
+ !Object.keys(dict).includes(
1237
+ resource.monitor?.currentMonitorStatusId.toString() || "",
1238
+ )
1239
+ ) {
1240
+ dict[resource.monitor?.currentMonitorStatusId?.toString()] = 1;
1241
+ } else {
1242
+ dict[resource.monitor!.currentMonitorStatusId!.toString()]!++;
1243
+ }
1244
+ }
1245
+ }
1246
+
1247
+ // check status of monitor groups.
1248
+
1249
+ for (const groupId in data.monitorGroupCurrentStatuses) {
1250
+ const statusId: ObjectID | undefined =
1251
+ data.monitorGroupCurrentStatuses[groupId];
1252
+
1253
+ if (statusId) {
1254
+ if (!Object.keys(dict).includes(statusId.toString() || "")) {
1255
+ dict[statusId.toString()] = 1;
1256
+ } else {
1257
+ dict[statusId.toString()]!++;
1258
+ }
1259
+ }
1260
+ }
1261
+
1262
+ for (const monitorStatus of data.monitorStatuses) {
1263
+ if (monitorStatus._id && dict[monitorStatus._id]) {
1264
+ currentStatus = monitorStatus;
1265
+ }
1266
+ }
1267
+
1268
+ return currentStatus;
1269
+ }
1153
1270
  }
1154
1271
  export default new Service();
@@ -1,16 +1,33 @@
1
1
  import { MeteredPlanUtil } from "../Types/Billing/MeteredPlan/AllMeteredPlans";
2
2
  import TelemetryMeteredPlan from "../Types/Billing/MeteredPlan/TelemetryMeteredPlan";
3
- import QueryHelper from "../Types/Database/QueryHelper";
4
3
  import DatabaseService from "./DatabaseService";
5
4
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
6
- import LIMIT_MAX from "../../Types/Database/LimitMax";
5
+ import LIMIT_MAX, { LIMIT_INFINITY } from "../../Types/Database/LimitMax";
7
6
  import OneUptimeDate from "../../Types/Date";
8
7
  import Decimal from "../../Types/Decimal";
9
8
  import BadDataException from "../../Types/Exception/BadDataException";
10
9
  import ProductType from "../../Types/MeteredPlan/ProductType";
11
10
  import ObjectID from "../../Types/ObjectID";
12
- import Model from "../../Models/DatabaseModels/TelemetryUsageBilling";
13
- import { IsBillingEnabled } from "../EnvironmentConfig";
11
+ import Model, {
12
+ DEFAULT_RETENTION_IN_DAYS,
13
+ } from "../../Models/DatabaseModels/TelemetryUsageBilling";
14
+ import TelemetryServiceService from "./TelemetryServiceService";
15
+ import SpanService from "./SpanService";
16
+ import LogService from "./LogService";
17
+ import MetricService from "./MetricService";
18
+ import ExceptionInstanceService from "./ExceptionInstanceService";
19
+ import AnalyticsQueryHelper from "../Types/AnalyticsDatabase/QueryHelper";
20
+ import DiskSize from "../../Types/DiskSize";
21
+ import logger from "../Utils/Logger";
22
+ import PositiveNumber from "../../Types/PositiveNumber";
23
+ import TelemetryServiceModel from "../../Models/DatabaseModels/TelemetryService";
24
+ import {
25
+ AverageSpanRowSizeInBytes,
26
+ AverageLogRowSizeInBytes,
27
+ AverageMetricRowSizeInBytes,
28
+ AverageExceptionRowSizeInBytes,
29
+ IsBillingEnabled,
30
+ } from "../EnvironmentConfig";
14
31
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
15
32
 
16
33
  export class Service extends DatabaseService<Model> {
@@ -31,9 +48,6 @@ export class Service extends DatabaseService<Model> {
31
48
  projectId: data.projectId,
32
49
  productType: data.productType,
33
50
  isReportedToBillingProvider: false,
34
- createdAt: QueryHelper.lessThan(
35
- OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -1),
36
- ), // we need to get everything that's not today.
37
51
  },
38
52
  skip: 0,
39
53
  limit: LIMIT_MAX, /// because a project can have MANY telemetry services.
@@ -47,6 +61,203 @@ export class Service extends DatabaseService<Model> {
47
61
  });
48
62
  }
49
63
 
64
+ @CaptureSpan()
65
+ public async stageTelemetryUsageForProject(data: {
66
+ projectId: ObjectID;
67
+ productType: ProductType;
68
+ usageDate?: Date;
69
+ }): Promise<void> {
70
+ if (!IsBillingEnabled) {
71
+ return;
72
+ }
73
+
74
+ const usageDate: Date = data.usageDate
75
+ ? OneUptimeDate.fromString(data.usageDate)
76
+ : OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -1);
77
+
78
+ const averageRowSizeInBytes: number = this.getAverageRowSizeForProduct(
79
+ data.productType,
80
+ );
81
+ const averageExceptionRowSizeInBytes: number =
82
+ this.getAverageExceptionRowSize();
83
+
84
+ if (data.productType !== ProductType.Traces && averageRowSizeInBytes <= 0) {
85
+ return;
86
+ }
87
+
88
+ if (
89
+ data.productType === ProductType.Traces &&
90
+ averageRowSizeInBytes <= 0 &&
91
+ averageExceptionRowSizeInBytes <= 0
92
+ ) {
93
+ return;
94
+ }
95
+
96
+ const usageDayString: string = OneUptimeDate.getDateString(usageDate);
97
+ const startOfDay: Date = OneUptimeDate.getStartOfDay(usageDate);
98
+ const endOfDay: Date = OneUptimeDate.getEndOfDay(usageDate);
99
+
100
+ const telemetryServices: Array<TelemetryServiceModel> =
101
+ await TelemetryServiceService.findBy({
102
+ query: {
103
+ projectId: data.projectId,
104
+ },
105
+ select: {
106
+ _id: true,
107
+ retainTelemetryDataForDays: true,
108
+ },
109
+ skip: 0,
110
+ limit: LIMIT_MAX,
111
+ props: {
112
+ isRoot: true,
113
+ },
114
+ });
115
+
116
+ if (!telemetryServices || telemetryServices.length === 0) {
117
+ return;
118
+ }
119
+
120
+ for (const telemetryService of telemetryServices) {
121
+ if (!telemetryService?.id) {
122
+ continue;
123
+ }
124
+
125
+ const existingEntry: Model | null = await this.findOneBy({
126
+ query: {
127
+ projectId: data.projectId,
128
+ productType: data.productType,
129
+ telemetryServiceId: telemetryService.id,
130
+ day: usageDayString,
131
+ },
132
+ select: {
133
+ _id: true,
134
+ },
135
+ props: {
136
+ isRoot: true,
137
+ },
138
+ });
139
+
140
+ if (existingEntry) {
141
+ continue;
142
+ }
143
+
144
+ let estimatedBytes: number = 0;
145
+
146
+ try {
147
+ if (data.productType === ProductType.Traces) {
148
+ const spanCount: PositiveNumber = await SpanService.countBy({
149
+ query: {
150
+ projectId: data.projectId,
151
+ serviceId: telemetryService.id,
152
+ startTime: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
153
+ },
154
+ skip: 0,
155
+ limit: LIMIT_INFINITY,
156
+ props: {
157
+ isRoot: true,
158
+ },
159
+ });
160
+
161
+ const exceptionCount: PositiveNumber =
162
+ await ExceptionInstanceService.countBy({
163
+ query: {
164
+ projectId: data.projectId,
165
+ serviceId: telemetryService.id,
166
+ time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
167
+ },
168
+ skip: 0,
169
+ limit: LIMIT_INFINITY,
170
+ props: {
171
+ isRoot: true,
172
+ },
173
+ });
174
+
175
+ const totalSpanCount: number = spanCount.toNumber();
176
+ const totalExceptionCount: number = exceptionCount.toNumber();
177
+
178
+ if (totalSpanCount <= 0 && totalExceptionCount <= 0) {
179
+ continue;
180
+ }
181
+
182
+ estimatedBytes =
183
+ totalSpanCount * averageRowSizeInBytes +
184
+ totalExceptionCount * averageExceptionRowSizeInBytes;
185
+ } else if (data.productType === ProductType.Logs) {
186
+ const count: PositiveNumber = await LogService.countBy({
187
+ query: {
188
+ projectId: data.projectId,
189
+ serviceId: telemetryService.id,
190
+ time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
191
+ },
192
+ skip: 0,
193
+ limit: LIMIT_INFINITY,
194
+ props: {
195
+ isRoot: true,
196
+ },
197
+ });
198
+
199
+ const totalRowCount: number = count.toNumber();
200
+
201
+ if (totalRowCount <= 0) {
202
+ continue;
203
+ }
204
+
205
+ estimatedBytes = totalRowCount * averageRowSizeInBytes;
206
+ } else if (data.productType === ProductType.Metrics) {
207
+ const count: PositiveNumber = await MetricService.countBy({
208
+ query: {
209
+ projectId: data.projectId,
210
+ serviceId: telemetryService.id,
211
+ time: AnalyticsQueryHelper.inBetween(startOfDay, endOfDay),
212
+ },
213
+ skip: 0,
214
+ limit: LIMIT_INFINITY,
215
+ props: {
216
+ isRoot: true,
217
+ },
218
+ });
219
+
220
+ const totalRowCount: number = count.toNumber();
221
+
222
+ if (totalRowCount <= 0) {
223
+ continue;
224
+ }
225
+
226
+ estimatedBytes = totalRowCount * averageRowSizeInBytes;
227
+ }
228
+ } catch (error) {
229
+ logger.error(
230
+ `Failed to compute telemetry usage for service ${telemetryService.id?.toString()}:`,
231
+ );
232
+ logger.error(error as Error);
233
+ continue;
234
+ }
235
+
236
+ if (estimatedBytes <= 0) {
237
+ continue;
238
+ }
239
+
240
+ const estimatedGigabytes: number = DiskSize.byteSizeToGB(estimatedBytes);
241
+
242
+ if (!Number.isFinite(estimatedGigabytes) || estimatedGigabytes <= 0) {
243
+ continue;
244
+ }
245
+
246
+ const dataRetentionInDays: number =
247
+ telemetryService.retainTelemetryDataForDays ||
248
+ DEFAULT_RETENTION_IN_DAYS;
249
+
250
+ await this.updateUsageBilling({
251
+ projectId: data.projectId,
252
+ productType: data.productType,
253
+ telemetryServiceId: telemetryService.id,
254
+ dataIngestedInGB: estimatedGigabytes,
255
+ retentionInDays: dataRetentionInDays,
256
+ usageDate: usageDate,
257
+ });
258
+ }
259
+ }
260
+
50
261
  @CaptureSpan()
51
262
  public async updateUsageBilling(data: {
52
263
  projectId: ObjectID;
@@ -54,6 +265,7 @@ export class Service extends DatabaseService<Model> {
54
265
  telemetryServiceId: ObjectID;
55
266
  dataIngestedInGB: number;
56
267
  retentionInDays: number;
268
+ usageDate?: Date;
57
269
  }): Promise<void> {
58
270
  if (
59
271
  data.productType !== ProductType.Traces &&
@@ -70,6 +282,12 @@ export class Service extends DatabaseService<Model> {
70
282
  data.productType,
71
283
  ) as TelemetryMeteredPlan;
72
284
 
285
+ const usageDate: Date = data.usageDate
286
+ ? OneUptimeDate.fromString(data.usageDate)
287
+ : OneUptimeDate.getCurrentDate();
288
+
289
+ const usageDayString: string = OneUptimeDate.getDateString(usageDate);
290
+
73
291
  const totalCostOfThisOperationInUSD: number =
74
292
  serverMeteredPlan.getTotalCostInUSD({
75
293
  dataIngestedInGB: data.dataIngestedInGB,
@@ -82,10 +300,7 @@ export class Service extends DatabaseService<Model> {
82
300
  productType: data.productType,
83
301
  telemetryServiceId: data.telemetryServiceId,
84
302
  isReportedToBillingProvider: false,
85
- createdAt: QueryHelper.inBetween(
86
- OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -1),
87
- OneUptimeDate.getCurrentDate(),
88
- ),
303
+ day: usageDayString,
89
304
  },
90
305
  select: {
91
306
  _id: true,
@@ -135,11 +350,9 @@ export class Service extends DatabaseService<Model> {
135
350
  usageBilling.telemetryServiceId = data.telemetryServiceId;
136
351
  usageBilling.retainTelemetryDataForDays = data.retentionInDays;
137
352
  usageBilling.isReportedToBillingProvider = false;
138
- usageBilling.createdAt = OneUptimeDate.getCurrentDate();
353
+ usageBilling.createdAt = usageDate;
139
354
 
140
- usageBilling.day = OneUptimeDate.getDateString(
141
- OneUptimeDate.getCurrentDate(),
142
- );
355
+ usageBilling.day = usageDayString;
143
356
 
144
357
  usageBilling.totalCostInUSD = new Decimal(totalCostOfThisOperationInUSD);
145
358
 
@@ -151,6 +364,46 @@ export class Service extends DatabaseService<Model> {
151
364
  });
152
365
  }
153
366
  }
367
+
368
+ private getAverageRowSizeForProduct(productType: ProductType): number {
369
+ const fallbackSize: number = 1024;
370
+
371
+ // Narrow to telemetry product types before indexing to satisfy TypeScript
372
+ if (
373
+ productType !== ProductType.Traces &&
374
+ productType !== ProductType.Logs &&
375
+ productType !== ProductType.Metrics
376
+ ) {
377
+ return fallbackSize;
378
+ }
379
+
380
+ const value: number =
381
+ {
382
+ [ProductType.Traces]: AverageSpanRowSizeInBytes,
383
+ [ProductType.Logs]: AverageLogRowSizeInBytes,
384
+ [ProductType.Metrics]: AverageMetricRowSizeInBytes,
385
+ }[productType] ?? fallbackSize;
386
+
387
+ if (!Number.isFinite(value) || value <= 0) {
388
+ return fallbackSize;
389
+ }
390
+
391
+ return value;
392
+ }
393
+
394
+ private getAverageExceptionRowSize(): number {
395
+ const fallbackSize: number = 1024;
396
+
397
+ if (!Number.isFinite(AverageExceptionRowSizeInBytes)) {
398
+ return fallbackSize;
399
+ }
400
+
401
+ if (AverageExceptionRowSizeInBytes <= 0) {
402
+ return fallbackSize;
403
+ }
404
+
405
+ return AverageExceptionRowSizeInBytes;
406
+ }
154
407
  }
155
408
 
156
409
  export default new Service();
@@ -56,6 +56,11 @@ export default class TelemetryMeteredPlan extends ServerMeteredPlan {
56
56
  ): Promise<void> {
57
57
  // get all unreported logs
58
58
 
59
+ await TelemetryUsageBillingService.stageTelemetryUsageForProject({
60
+ projectId: projectId,
61
+ productType: this.productType,
62
+ });
63
+
59
64
  const usageBillings: Array<TelemetryUsageBilling> =
60
65
  await TelemetryUsageBillingService.getUnreportedUsageBilling({
61
66
  projectId: projectId,