@oneuptime/common 8.0.5438 → 8.0.5462

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 (56) hide show
  1. package/Models/DatabaseModels/StatusPage.ts +80 -0
  2. package/Server/API/StatusPageAPI.ts +138 -52
  3. package/Server/EnvironmentConfig.ts +34 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.ts +29 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  6. package/Server/Services/OpenTelemetryIngestService.ts +1 -39
  7. package/Server/Services/StatusPageService.ts +117 -0
  8. package/Server/Services/TelemetryUsageBillingService.ts +208 -15
  9. package/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +5 -0
  10. package/Server/Utils/Telemetry/Telemetry.ts +129 -81
  11. package/Server/Utils/VM/VMRunner.ts +3 -4
  12. package/UI/Components/Dictionary/Dictionary.tsx +3 -0
  13. package/UI/Components/Forms/Fields/FieldLabel.tsx +7 -3
  14. package/UI/Components/LogsViewer/LogItem.tsx +12 -4
  15. package/UI/Components/LogsViewer/LogsViewer.tsx +131 -29
  16. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -2
  17. package/UI/Components/ModelFilter/Filter.ts +1 -0
  18. package/UI/Components/ModelTable/BaseModelTable.tsx +2 -1
  19. package/UI/Components/Table/TableRow.tsx +89 -77
  20. package/UI/esbuild-config.js +32 -1
  21. package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
  22. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  23. package/build/dist/Server/API/StatusPageAPI.js +157 -74
  24. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  25. package/build/dist/Server/EnvironmentConfig.js +14 -0
  26. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  27. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js +16 -0
  28. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js.map +1 -0
  29. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  30. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  31. package/build/dist/Server/Services/OpenTelemetryIngestService.js +0 -30
  32. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  33. package/build/dist/Server/Services/StatusPageService.js +95 -0
  34. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  35. package/build/dist/Server/Services/TelemetryUsageBillingService.js +168 -8
  36. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  37. package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js +4 -0
  38. package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js.map +1 -1
  39. package/build/dist/Server/Utils/Telemetry/Telemetry.js +84 -60
  40. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  41. package/build/dist/Server/Utils/VM/VMRunner.js +2 -2
  42. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  43. package/build/dist/UI/Components/Dictionary/Dictionary.js +3 -3
  44. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  45. package/build/dist/UI/Components/Forms/Fields/FieldLabel.js +2 -1
  46. package/build/dist/UI/Components/Forms/Fields/FieldLabel.js.map +1 -1
  47. package/build/dist/UI/Components/LogsViewer/LogItem.js +5 -3
  48. package/build/dist/UI/Components/LogsViewer/LogItem.js.map +1 -1
  49. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +73 -22
  50. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  51. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -2
  52. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -1
  53. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  54. package/build/dist/UI/Components/Table/TableRow.js +18 -6
  55. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  56. 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
  }
@@ -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,21 @@ 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
+
349
383
  export const SlackAppClientId: string | null =
350
384
  process.env["SLACK_APP_CLIENT_ID"] || null;
351
385
  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
  ];
@@ -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;
@@ -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();