@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
@@ -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 Traces.",
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
- this.getOverallMonitorStatus(
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() as ClickhouseClient;
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() as ClickhouseClient;
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
- if (!this.databaseClient) {
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 this.databaseClient.exec({
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
- if (!this.databaseClient) {
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 this.databaseClient.query({
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 TelemetryServiceDataIngested {
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;