@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.
- package/Models/DatabaseModels/StatusPage.ts +80 -0
- package/Models/DatabaseModels/TelemetryUsageBilling.ts +1 -1
- package/Server/API/StatusPageAPI.ts +138 -52
- package/Server/EnvironmentConfig.ts +37 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AnalyticsDatabaseService.ts +71 -11
- package/Server/Services/OpenTelemetryIngestService.ts +1 -39
- package/Server/Services/StatusPageService.ts +117 -0
- package/Server/Services/TelemetryUsageBillingService.ts +268 -15
- package/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +5 -0
- package/Server/Utils/Telemetry/Telemetry.ts +135 -81
- package/Server/Utils/VM/VMRunner.ts +3 -4
- package/Types/Date.ts +5 -0
- package/UI/Components/LogsViewer/LogItem.tsx +12 -4
- package/UI/Components/LogsViewer/LogsViewer.tsx +131 -29
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -2
- package/UI/Components/Table/TableRow.tsx +89 -77
- package/UI/esbuild-config.js +32 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +157 -74
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +15 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +55 -8
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +0 -30
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +95 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +211 -8
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js +4 -0
- package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +84 -60
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +2 -2
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Types/Date.js +4 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogItem.js +5 -3
- package/build/dist/UI/Components/LogsViewer/LogItem.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +73 -22
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -2
- package/build/dist/UI/Components/Table/TableRow.js +18 -6
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/package.json +4 -4
|
@@ -2331,4 +2331,84 @@ export default class StatusPage extends BaseModel {
|
|
|
2331
2331
|
create: PlanType.Free,
|
|
2332
2332
|
})
|
|
2333
2333
|
public ipWhitelist?: string = undefined;
|
|
2334
|
+
|
|
2335
|
+
@ColumnAccessControl({
|
|
2336
|
+
create: [
|
|
2337
|
+
Permission.ProjectOwner,
|
|
2338
|
+
Permission.ProjectAdmin,
|
|
2339
|
+
Permission.ProjectMember,
|
|
2340
|
+
Permission.CreateProjectStatusPage,
|
|
2341
|
+
],
|
|
2342
|
+
read: [
|
|
2343
|
+
Permission.ProjectOwner,
|
|
2344
|
+
Permission.ProjectAdmin,
|
|
2345
|
+
Permission.ProjectMember,
|
|
2346
|
+
Permission.ReadProjectStatusPage,
|
|
2347
|
+
],
|
|
2348
|
+
update: [
|
|
2349
|
+
Permission.ProjectOwner,
|
|
2350
|
+
Permission.ProjectAdmin,
|
|
2351
|
+
Permission.ProjectMember,
|
|
2352
|
+
Permission.EditProjectStatusPage,
|
|
2353
|
+
],
|
|
2354
|
+
})
|
|
2355
|
+
@TableColumn({
|
|
2356
|
+
isDefaultValueColumn: true,
|
|
2357
|
+
type: TableColumnType.Boolean,
|
|
2358
|
+
title: "Enable Embedded Overall Status Badge",
|
|
2359
|
+
description:
|
|
2360
|
+
"Enable embedded overall status badge that can be displayed on external websites?",
|
|
2361
|
+
defaultValue: false,
|
|
2362
|
+
})
|
|
2363
|
+
@Column({
|
|
2364
|
+
type: ColumnType.Boolean,
|
|
2365
|
+
default: false,
|
|
2366
|
+
nullable: false,
|
|
2367
|
+
})
|
|
2368
|
+
@ColumnBillingAccessControl({
|
|
2369
|
+
read: PlanType.Free,
|
|
2370
|
+
update: PlanType.Growth,
|
|
2371
|
+
create: PlanType.Free,
|
|
2372
|
+
})
|
|
2373
|
+
public enableEmbeddedOverallStatus?: boolean = undefined;
|
|
2374
|
+
|
|
2375
|
+
@ColumnAccessControl({
|
|
2376
|
+
create: [
|
|
2377
|
+
Permission.ProjectOwner,
|
|
2378
|
+
Permission.ProjectAdmin,
|
|
2379
|
+
Permission.ProjectMember,
|
|
2380
|
+
Permission.CreateProjectStatusPage,
|
|
2381
|
+
],
|
|
2382
|
+
read: [
|
|
2383
|
+
Permission.ProjectOwner,
|
|
2384
|
+
Permission.ProjectAdmin,
|
|
2385
|
+
Permission.ProjectMember,
|
|
2386
|
+
Permission.ReadProjectStatusPage,
|
|
2387
|
+
],
|
|
2388
|
+
update: [
|
|
2389
|
+
Permission.ProjectOwner,
|
|
2390
|
+
Permission.ProjectAdmin,
|
|
2391
|
+
Permission.ProjectMember,
|
|
2392
|
+
Permission.EditProjectStatusPage,
|
|
2393
|
+
],
|
|
2394
|
+
})
|
|
2395
|
+
@Index()
|
|
2396
|
+
@TableColumn({
|
|
2397
|
+
type: TableColumnType.ShortText,
|
|
2398
|
+
required: false,
|
|
2399
|
+
title: "Embedded Overall Status Token",
|
|
2400
|
+
description:
|
|
2401
|
+
"Security token required to access the embedded overall status badge. This token must be provided in the URL.",
|
|
2402
|
+
})
|
|
2403
|
+
@Column({
|
|
2404
|
+
type: ColumnType.ShortText,
|
|
2405
|
+
length: ColumnLength.ShortText,
|
|
2406
|
+
nullable: true,
|
|
2407
|
+
})
|
|
2408
|
+
@ColumnBillingAccessControl({
|
|
2409
|
+
read: PlanType.Free,
|
|
2410
|
+
update: PlanType.Growth,
|
|
2411
|
+
create: PlanType.Free,
|
|
2412
|
+
})
|
|
2413
|
+
public embeddedOverallStatusToken?: string = undefined;
|
|
2334
2414
|
}
|
|
@@ -39,7 +39,7 @@ export const DEFAULT_RETENTION_IN_DAYS: number = 15;
|
|
|
39
39
|
pluralName: "Telemetry Usage Billings",
|
|
40
40
|
icon: IconProp.Billing,
|
|
41
41
|
tableDescription:
|
|
42
|
-
"Stores historical usage billing data for your telemetry data like Logs, Metrics, and
|
|
42
|
+
"Stores historical usage billing data for your telemetry data like Logs, Metrics, Traces, and Exceptions.",
|
|
43
43
|
})
|
|
44
44
|
@Entity({
|
|
45
45
|
name: "TelemetryUsageBilling",
|
|
@@ -276,6 +276,142 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
276
276
|
},
|
|
277
277
|
);
|
|
278
278
|
|
|
279
|
+
// embedded overall status badge api
|
|
280
|
+
this.router.get(
|
|
281
|
+
`${new this.entityType()
|
|
282
|
+
.getCrudApiPath()
|
|
283
|
+
?.toString()}/badge/:statusPageId`,
|
|
284
|
+
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
285
|
+
try {
|
|
286
|
+
const statusPageId: ObjectID = new ObjectID(
|
|
287
|
+
req.params["statusPageId"] as string,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const token: string = req.query["token"] as string;
|
|
291
|
+
|
|
292
|
+
if (!token) {
|
|
293
|
+
return res.status(400).send("Token is required");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Fetch status page with security token
|
|
297
|
+
const statusPage: StatusPage | null =
|
|
298
|
+
await StatusPageService.findOneBy({
|
|
299
|
+
query: {
|
|
300
|
+
_id: statusPageId,
|
|
301
|
+
enableEmbeddedOverallStatus: true,
|
|
302
|
+
embeddedOverallStatusToken: token,
|
|
303
|
+
},
|
|
304
|
+
select: {
|
|
305
|
+
_id: true,
|
|
306
|
+
projectId: true,
|
|
307
|
+
downtimeMonitorStatuses: {
|
|
308
|
+
_id: true,
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
props: {
|
|
312
|
+
isRoot: true,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (!statusPage) {
|
|
317
|
+
return res.status(404).send("Status badge not found or disabled");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Get status page resources and current statuses
|
|
321
|
+
const statusPageResources: Array<StatusPageResource> =
|
|
322
|
+
await StatusPageResourceService.findBy({
|
|
323
|
+
query: {
|
|
324
|
+
statusPageId: statusPageId,
|
|
325
|
+
},
|
|
326
|
+
select: {
|
|
327
|
+
_id: true,
|
|
328
|
+
monitor: {
|
|
329
|
+
_id: true,
|
|
330
|
+
currentMonitorStatusId: true,
|
|
331
|
+
},
|
|
332
|
+
monitorGroupId: true,
|
|
333
|
+
},
|
|
334
|
+
limit: LIMIT_PER_PROJECT,
|
|
335
|
+
skip: 0,
|
|
336
|
+
props: {
|
|
337
|
+
isRoot: true,
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Get monitor statuses
|
|
342
|
+
const monitorStatuses: Array<MonitorStatus> =
|
|
343
|
+
await MonitorStatusService.findBy({
|
|
344
|
+
query: {
|
|
345
|
+
projectId: statusPage.projectId!,
|
|
346
|
+
},
|
|
347
|
+
select: {
|
|
348
|
+
_id: true,
|
|
349
|
+
name: true,
|
|
350
|
+
color: true,
|
|
351
|
+
priority: true,
|
|
352
|
+
isOperationalState: true,
|
|
353
|
+
},
|
|
354
|
+
sort: {
|
|
355
|
+
priority: SortOrder.Ascending,
|
|
356
|
+
},
|
|
357
|
+
skip: 0,
|
|
358
|
+
limit: LIMIT_PER_PROJECT,
|
|
359
|
+
props: {
|
|
360
|
+
isRoot: true,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Get monitor group current statuses
|
|
365
|
+
const monitorGroupCurrentStatuses: Dictionary<ObjectID> =
|
|
366
|
+
await StatusPageService.getMonitorGroupCurrentStatuses({
|
|
367
|
+
statusPageResources,
|
|
368
|
+
monitorStatuses,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Calculate overall status
|
|
372
|
+
const overallStatus: MonitorStatus | null =
|
|
373
|
+
StatusPageService.getOverallMonitorStatus({
|
|
374
|
+
statusPageResources,
|
|
375
|
+
monitorStatuses,
|
|
376
|
+
monitorGroupCurrentStatuses,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Generate SVG badge
|
|
380
|
+
const statusName: string = overallStatus?.name || "Unknown";
|
|
381
|
+
const statusColor: string =
|
|
382
|
+
overallStatus?.color?.toString() || "#808080";
|
|
383
|
+
|
|
384
|
+
const svg: string = `<svg xmlns="http://www.w3.org/2000/svg" width="150" height="20">
|
|
385
|
+
<linearGradient id="b" x2="0" y2="100%">
|
|
386
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
387
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
388
|
+
</linearGradient>
|
|
389
|
+
<mask id="a">
|
|
390
|
+
<rect width="150" height="20" rx="3" fill="#fff"/>
|
|
391
|
+
</mask>
|
|
392
|
+
<g mask="url(#a)">
|
|
393
|
+
<path fill="#555" d="M0 0h50v20H0z"/>
|
|
394
|
+
<path fill="${statusColor}" d="M50 0h100v20H50z"/>
|
|
395
|
+
<path fill="url(#b)" d="M0 0h150v20H0z"/>
|
|
396
|
+
</g>
|
|
397
|
+
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
|
398
|
+
<text x="25" y="15" fill="#010101" fill-opacity=".3">status</text>
|
|
399
|
+
<text x="25" y="14">status</text>
|
|
400
|
+
<text x="100" y="15" fill="#010101" fill-opacity=".3">${statusName}</text>
|
|
401
|
+
<text x="100" y="14">${statusName}</text>
|
|
402
|
+
</g>
|
|
403
|
+
</svg>`;
|
|
404
|
+
|
|
405
|
+
res.setHeader("Content-Type", "image/svg+xml");
|
|
406
|
+
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
407
|
+
return res.send(svg);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
logger.error(err);
|
|
410
|
+
return res.status(500).send("Internal Server Error");
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
);
|
|
414
|
+
|
|
279
415
|
// confirm subscription api
|
|
280
416
|
this.router.get(
|
|
281
417
|
`${new this.entityType()
|
|
@@ -1392,11 +1528,11 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
1392
1528
|
});
|
|
1393
1529
|
|
|
1394
1530
|
const overallStatus: MonitorStatus | null =
|
|
1395
|
-
|
|
1531
|
+
StatusPageService.getOverallMonitorStatus({
|
|
1396
1532
|
statusPageResources,
|
|
1397
1533
|
monitorStatuses,
|
|
1398
1534
|
monitorGroupCurrentStatuses,
|
|
1399
|
-
);
|
|
1535
|
+
});
|
|
1400
1536
|
|
|
1401
1537
|
const response: JSONObject = {
|
|
1402
1538
|
overallStatus: overallStatus
|
|
@@ -3099,56 +3235,6 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
3099
3235
|
return response;
|
|
3100
3236
|
}
|
|
3101
3237
|
|
|
3102
|
-
public getOverallMonitorStatus(
|
|
3103
|
-
statusPageResources: Array<StatusPageResource>,
|
|
3104
|
-
monitorStatuses: Array<MonitorStatus>,
|
|
3105
|
-
monitorGroupCurrentStatuses: Dictionary<ObjectID>,
|
|
3106
|
-
): MonitorStatus | null {
|
|
3107
|
-
let currentStatus: MonitorStatus | null =
|
|
3108
|
-
monitorStatuses.length > 0 && monitorStatuses[0]
|
|
3109
|
-
? monitorStatuses[0]
|
|
3110
|
-
: null;
|
|
3111
|
-
|
|
3112
|
-
const dict: Dictionary<number> = {};
|
|
3113
|
-
|
|
3114
|
-
for (const resource of statusPageResources) {
|
|
3115
|
-
if (resource.monitor?.currentMonitorStatusId) {
|
|
3116
|
-
if (
|
|
3117
|
-
!Object.keys(dict).includes(
|
|
3118
|
-
resource.monitor?.currentMonitorStatusId.toString() || "",
|
|
3119
|
-
)
|
|
3120
|
-
) {
|
|
3121
|
-
dict[resource.monitor?.currentMonitorStatusId?.toString()] = 1;
|
|
3122
|
-
} else {
|
|
3123
|
-
dict[resource.monitor!.currentMonitorStatusId!.toString()]!++;
|
|
3124
|
-
}
|
|
3125
|
-
}
|
|
3126
|
-
}
|
|
3127
|
-
|
|
3128
|
-
// check status of monitor groups.
|
|
3129
|
-
|
|
3130
|
-
for (const groupId in monitorGroupCurrentStatuses) {
|
|
3131
|
-
const statusId: ObjectID | undefined =
|
|
3132
|
-
monitorGroupCurrentStatuses[groupId];
|
|
3133
|
-
|
|
3134
|
-
if (statusId) {
|
|
3135
|
-
if (!Object.keys(dict).includes(statusId.toString() || "")) {
|
|
3136
|
-
dict[statusId.toString()] = 1;
|
|
3137
|
-
} else {
|
|
3138
|
-
dict[statusId.toString()]!++;
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3143
|
-
for (const monitorStatus of monitorStatuses) {
|
|
3144
|
-
if (monitorStatus._id && dict[monitorStatus._id]) {
|
|
3145
|
-
currentStatus = monitorStatus;
|
|
3146
|
-
}
|
|
3147
|
-
}
|
|
3148
|
-
|
|
3149
|
-
return currentStatus;
|
|
3150
|
-
}
|
|
3151
|
-
|
|
3152
3238
|
@CaptureSpan()
|
|
3153
3239
|
public async getStatusPageResourcesAndTimelines(data: {
|
|
3154
3240
|
statusPageId: ObjectID;
|
|
@@ -23,6 +23,25 @@ export const getAllEnvVars: () => JSONObject = (): JSONObject => {
|
|
|
23
23
|
return process.env;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
const parsePositiveNumberFromEnv: (
|
|
27
|
+
envKey: string,
|
|
28
|
+
fallback: number,
|
|
29
|
+
) => number = (envKey: string, fallback: number): number => {
|
|
30
|
+
const rawValue: string | undefined = process.env[envKey];
|
|
31
|
+
|
|
32
|
+
if (!rawValue) {
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parsedValue: number = parseFloat(rawValue);
|
|
37
|
+
|
|
38
|
+
if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return parsedValue;
|
|
43
|
+
};
|
|
44
|
+
|
|
26
45
|
export const IsBillingEnabled: boolean = BillingConfig.IsBillingEnabled;
|
|
27
46
|
export const BillingPublicKey: string = BillingConfig.BillingPublicKey;
|
|
28
47
|
export const BillingPrivateKey: string = BillingConfig.BillingPrivateKey;
|
|
@@ -346,6 +365,24 @@ export const DocsClientUrl: URL = new URL(
|
|
|
346
365
|
export const DisableTelemetry: boolean =
|
|
347
366
|
process.env["DISABLE_TELEMETRY"] === "true";
|
|
348
367
|
|
|
368
|
+
export const AverageSpanRowSizeInBytes: number = parsePositiveNumberFromEnv(
|
|
369
|
+
"AVERAGE_SPAN_ROW_SIZE_IN_BYTES",
|
|
370
|
+
1024,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
export const AverageLogRowSizeInBytes: number = parsePositiveNumberFromEnv(
|
|
374
|
+
"AVERAGE_LOG_ROW_SIZE_IN_BYTES",
|
|
375
|
+
1024,
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
export const AverageMetricRowSizeInBytes: number = parsePositiveNumberFromEnv(
|
|
379
|
+
"AVERAGE_METRIC_ROW_SIZE_IN_BYTES",
|
|
380
|
+
1024,
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
export const AverageExceptionRowSizeInBytes: number =
|
|
384
|
+
parsePositiveNumberFromEnv("AVERAGE_EXCEPTION_ROW_SIZE_IN_BYTES", 1024);
|
|
385
|
+
|
|
349
386
|
export const SlackAppClientId: string | null =
|
|
350
387
|
process.env["SLACK_APP_CLIENT_ID"] || null;
|
|
351
388
|
export const SlackAppClientSecret: string | null =
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1761232578396 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1761232578396";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "StatusPage" ADD "enableEmbeddedOverallStatus" boolean NOT NULL DEFAULT false`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "StatusPage" ADD "embeddedOverallStatusToken" character varying(100)`,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`CREATE INDEX "IDX_350d2250fb17e0dc10663de72a" ON "StatusPage" ("embeddedOverallStatusToken") `,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`DROP INDEX "public"."IDX_350d2250fb17e0dc10663de72a"`,
|
|
21
|
+
);
|
|
22
|
+
await queryRunner.query(
|
|
23
|
+
`ALTER TABLE "StatusPage" DROP COLUMN "embeddedOverallStatusToken"`,
|
|
24
|
+
);
|
|
25
|
+
await queryRunner.query(
|
|
26
|
+
`ALTER TABLE "StatusPage" DROP COLUMN "enableEmbeddedOverallStatus"`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -177,6 +177,7 @@ import { RenameUserTwoFactorAuthToUserTotpAuth1759234532998 } from "./1759234532
|
|
|
177
177
|
import { MigrationName1759943124812 } from "./1759943124812-MigrationName";
|
|
178
178
|
import { MigrationName1760345757975 } from "./1760345757975-MigrationName";
|
|
179
179
|
import { MigrationName1760357680881 } from "./1760357680881-MigrationName";
|
|
180
|
+
import { MigrationName1761232578396 } from "./1761232578396-MigrationName";
|
|
180
181
|
|
|
181
182
|
export default [
|
|
182
183
|
InitialMigration,
|
|
@@ -358,4 +359,5 @@ export default [
|
|
|
358
359
|
MigrationName1759943124812,
|
|
359
360
|
MigrationName1760345757975,
|
|
360
361
|
MigrationName1760357680881,
|
|
362
|
+
MigrationName1761232578396,
|
|
361
363
|
];
|
|
@@ -42,6 +42,7 @@ import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
|
42
42
|
import OneUptimeDate from "../../Types/Date";
|
|
43
43
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
44
44
|
import Exception from "../../Types/Exception/Exception";
|
|
45
|
+
import ExceptionCode from "../../Types/Exception/ExceptionCode";
|
|
45
46
|
import { JSONObject } from "../../Types/JSON";
|
|
46
47
|
import ObjectID from "../../Types/ObjectID";
|
|
47
48
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
@@ -66,7 +67,7 @@ export default class AnalyticsDatabaseService<
|
|
|
66
67
|
public modelType!: { new (): TBaseModel };
|
|
67
68
|
public database!: ClickhouseDatabase;
|
|
68
69
|
public model!: TBaseModel;
|
|
69
|
-
public databaseClient!: ClickhouseClient;
|
|
70
|
+
public databaseClient!: ClickhouseClient | null;
|
|
70
71
|
public statementGenerator!: StatementGenerator<TBaseModel>;
|
|
71
72
|
|
|
72
73
|
public constructor(data: {
|
|
@@ -82,7 +83,7 @@ export default class AnalyticsDatabaseService<
|
|
|
82
83
|
this.database = ClickhouseAppInstance; // default database
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
this.databaseClient = this.database.getDataSource()
|
|
86
|
+
this.databaseClient = this.database.getDataSource();
|
|
86
87
|
|
|
87
88
|
this.statementGenerator = new StatementGenerator<TBaseModel>({
|
|
88
89
|
modelType: this.modelType,
|
|
@@ -90,6 +91,46 @@ export default class AnalyticsDatabaseService<
|
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
@CaptureSpan()
|
|
95
|
+
public async insertJsonRows(rows: Array<JSONObject>): Promise<void> {
|
|
96
|
+
if (!rows || rows.length === 0) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const client: ClickhouseClient = this.getDatabaseClient();
|
|
101
|
+
|
|
102
|
+
const tableName: string = this.model.tableName;
|
|
103
|
+
|
|
104
|
+
if (!tableName) {
|
|
105
|
+
throw new Exception(
|
|
106
|
+
ExceptionCode.BadDataException,
|
|
107
|
+
"Analytics model table name not configured",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await client.insert({
|
|
113
|
+
table: tableName,
|
|
114
|
+
values: rows,
|
|
115
|
+
format: "JSONEachRow",
|
|
116
|
+
clickhouse_settings: {
|
|
117
|
+
async_insert: 1,
|
|
118
|
+
wait_for_async_insert: 0,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
logger.debug(
|
|
123
|
+
`ClickHouse insert succeeded for table ${tableName} at ${OneUptimeDate.toString(OneUptimeDate.getCurrentDate())}`,
|
|
124
|
+
);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
logger.error(
|
|
127
|
+
`ClickHouse insert failed for table ${tableName} at ${OneUptimeDate.toString(OneUptimeDate.getCurrentDate())}`,
|
|
128
|
+
);
|
|
129
|
+
logger.error(error);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
93
134
|
@CaptureSpan()
|
|
94
135
|
public async doesColumnExistInDatabase(columnName: string): Promise<boolean> {
|
|
95
136
|
const statement: string =
|
|
@@ -807,23 +848,21 @@ export default class AnalyticsDatabaseService<
|
|
|
807
848
|
|
|
808
849
|
public useDefaultDatabase(): void {
|
|
809
850
|
this.database = ClickhouseAppInstance;
|
|
810
|
-
this.databaseClient = this.database.getDataSource()
|
|
851
|
+
this.databaseClient = this.database.getDataSource();
|
|
811
852
|
}
|
|
812
853
|
|
|
813
854
|
@CaptureSpan()
|
|
814
855
|
public async execute(
|
|
815
856
|
statement: Statement | string
|
|
816
857
|
): Promise<ExecResult<Stream>> {
|
|
817
|
-
|
|
818
|
-
this.useDefaultDatabase();
|
|
819
|
-
}
|
|
858
|
+
const client: ClickhouseClient = this.getDatabaseClient();
|
|
820
859
|
|
|
821
860
|
const query: string =
|
|
822
861
|
statement instanceof Statement ? statement.query : statement;
|
|
823
862
|
const queryParams: Record<string, unknown> | undefined =
|
|
824
863
|
statement instanceof Statement ? statement.query_params : undefined;
|
|
825
864
|
|
|
826
|
-
return (await
|
|
865
|
+
return (await client.exec({
|
|
827
866
|
query: query,
|
|
828
867
|
query_params: queryParams || (undefined as any), // undefined is not specified in the type for query_params, but its ok to pass undefined.
|
|
829
868
|
})) as ExecResult<Stream>;
|
|
@@ -833,22 +872,43 @@ export default class AnalyticsDatabaseService<
|
|
|
833
872
|
public async executeQuery(
|
|
834
873
|
statement: Statement | string
|
|
835
874
|
): Promise<ResultSet<"JSON">> {
|
|
836
|
-
|
|
837
|
-
this.useDefaultDatabase();
|
|
838
|
-
}
|
|
875
|
+
const client: ClickhouseClient = this.getDatabaseClient();
|
|
839
876
|
|
|
840
877
|
const query: string =
|
|
841
878
|
statement instanceof Statement ? statement.query : statement;
|
|
842
879
|
const queryParams: Record<string, unknown> | undefined =
|
|
843
880
|
statement instanceof Statement ? statement.query_params : undefined;
|
|
844
881
|
|
|
845
|
-
return await
|
|
882
|
+
return await client.query({
|
|
846
883
|
query: query,
|
|
847
884
|
format: "JSON",
|
|
848
885
|
query_params: queryParams || (undefined as any), // undefined is not specified in the type for query_params, but its ok to pass undefined.
|
|
849
886
|
});
|
|
850
887
|
}
|
|
851
888
|
|
|
889
|
+
private getDatabaseClient(): ClickhouseClient {
|
|
890
|
+
/*
|
|
891
|
+
* Refresh the ClickHouse client lazily so services created before the
|
|
892
|
+
* ClickHouse connection was established pick up the live client.
|
|
893
|
+
*/
|
|
894
|
+
if (!this.database) {
|
|
895
|
+
this.useDefaultDatabase();
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (!this.databaseClient && this.database) {
|
|
899
|
+
this.databaseClient = this.database.getDataSource();
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (!this.databaseClient) {
|
|
903
|
+
throw new Exception(
|
|
904
|
+
ExceptionCode.DatabaseNotConnectedException,
|
|
905
|
+
"ClickHouse client is not connected",
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return this.databaseClient;
|
|
910
|
+
}
|
|
911
|
+
|
|
852
912
|
protected async onUpdateSuccess(
|
|
853
913
|
onUpdate: OnUpdate<TBaseModel>,
|
|
854
914
|
_updatedItemIds: Array<ObjectID>
|
|
@@ -4,11 +4,6 @@ import ObjectID from "../../Types/ObjectID";
|
|
|
4
4
|
import Metric, {
|
|
5
5
|
AggregationTemporality,
|
|
6
6
|
} from "../../Models/AnalyticsModels/Metric";
|
|
7
|
-
import Dictionary from "../../Types/Dictionary";
|
|
8
|
-
import ProductType from "../../Types/MeteredPlan/ProductType";
|
|
9
|
-
import { IsBillingEnabled } from "../../Server/EnvironmentConfig";
|
|
10
|
-
import TelemetryUsageBillingService from "../../Server/Services/TelemetryUsageBillingService";
|
|
11
|
-
import logger from "../../Server/Utils/Logger";
|
|
12
7
|
import TelemetryService from "../../Models/DatabaseModels/TelemetryService";
|
|
13
8
|
import TelemetryServiceService from "../../Server/Services/TelemetryServiceService";
|
|
14
9
|
import { DEFAULT_RETENTION_IN_DAYS } from "../../Models/DatabaseModels/TelemetryUsageBilling";
|
|
@@ -20,10 +15,9 @@ export enum OtelAggregationTemporality {
|
|
|
20
15
|
Delta = "AGGREGATION_TEMPORALITY_DELTA",
|
|
21
16
|
}
|
|
22
17
|
|
|
23
|
-
export interface
|
|
18
|
+
export interface TelemetryServiceMetadata {
|
|
24
19
|
serviceName: string;
|
|
25
20
|
serviceId: ObjectID;
|
|
26
|
-
dataIngestedInGB: number;
|
|
27
21
|
dataRententionInDays: number;
|
|
28
22
|
}
|
|
29
23
|
|
|
@@ -80,38 +74,6 @@ export default class OTelIngestService {
|
|
|
80
74
|
service.retainTelemetryDataForDays || DEFAULT_RETENTION_IN_DAYS,
|
|
81
75
|
};
|
|
82
76
|
}
|
|
83
|
-
|
|
84
|
-
@CaptureSpan()
|
|
85
|
-
public static async recordDataIngestedUsgaeBilling(data: {
|
|
86
|
-
services: Dictionary<TelemetryServiceDataIngested>;
|
|
87
|
-
projectId: ObjectID;
|
|
88
|
-
productType: ProductType;
|
|
89
|
-
}): Promise<void> {
|
|
90
|
-
if (!IsBillingEnabled) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
for (const serviceName in data.services) {
|
|
95
|
-
const serviceData: TelemetryServiceDataIngested | undefined =
|
|
96
|
-
data.services[serviceName];
|
|
97
|
-
|
|
98
|
-
if (!serviceData) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
TelemetryUsageBillingService.updateUsageBilling({
|
|
103
|
-
projectId: data.projectId,
|
|
104
|
-
productType: data.productType,
|
|
105
|
-
dataIngestedInGB: serviceData.dataIngestedInGB || 0,
|
|
106
|
-
telemetryServiceId: serviceData.serviceId,
|
|
107
|
-
retentionInDays: serviceData.dataRententionInDays,
|
|
108
|
-
}).catch((err: Error) => {
|
|
109
|
-
logger.error("Failed to update usage billing for OTel");
|
|
110
|
-
logger.error(err);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
77
|
@CaptureSpan()
|
|
116
78
|
public static getMetricFromDatapoint(data: {
|
|
117
79
|
dbMetric: Metric;
|