@oneuptime/common 7.0.3156 → 7.0.3162

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 (25) hide show
  1. package/Models/DatabaseModels/StatusPageDomain.ts +86 -0
  2. package/Server/Infrastructure/Postgres/SchemaMigrations/1728472625805-MigrationName.ts +29 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  4. package/Server/Services/StatusPageDomainService.ts +14 -0
  5. package/Types/WebsiteRequest.ts +5 -0
  6. package/Utils/API.ts +5 -0
  7. package/Utils/StatusPage/ResourceUptime.ts +301 -0
  8. package/Utils/Uptime/UptimeUtil.ts +17 -134
  9. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +92 -0
  10. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  11. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1728472625805-MigrationName.js +16 -0
  12. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1728472625805-MigrationName.js.map +1 -0
  13. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  14. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  15. package/build/dist/Server/Services/StatusPageDomainService.js +9 -0
  16. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  17. package/build/dist/Types/WebsiteRequest.js +3 -0
  18. package/build/dist/Types/WebsiteRequest.js.map +1 -1
  19. package/build/dist/Utils/API.js +3 -0
  20. package/build/dist/Utils/API.js.map +1 -1
  21. package/build/dist/Utils/StatusPage/ResourceUptime.js +182 -0
  22. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -0
  23. package/build/dist/Utils/Uptime/UptimeUtil.js +15 -91
  24. package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
  25. package/package.json +2 -2
@@ -538,4 +538,90 @@ export default class StatusPageDomain extends BaseModel {
538
538
  transformer: ObjectID.getDatabaseTransformer(),
539
539
  })
540
540
  public deletedByUserId?: ObjectID = undefined;
541
+
542
+ @ColumnAccessControl({
543
+ create: [
544
+ Permission.ProjectOwner,
545
+ Permission.ProjectAdmin,
546
+ Permission.ProjectMember,
547
+ Permission.CreateStatusPageDomain,
548
+ ],
549
+ read: [
550
+ Permission.ProjectOwner,
551
+ Permission.ProjectAdmin,
552
+ Permission.ProjectMember,
553
+ Permission.ReadStatusPageDomain,
554
+ ],
555
+ update: [
556
+ Permission.ProjectOwner,
557
+ Permission.ProjectAdmin,
558
+ Permission.ProjectMember,
559
+ Permission.EditStatusPageDomain,
560
+ ],
561
+ })
562
+ @TableColumn({ type: TableColumnType.VeryLongText })
563
+ @Column({
564
+ type: ColumnType.VeryLongText,
565
+ nullable: true,
566
+ unique: false,
567
+ })
568
+ public customCertificate?: string = undefined;
569
+
570
+ @ColumnAccessControl({
571
+ create: [
572
+ Permission.ProjectOwner,
573
+ Permission.ProjectAdmin,
574
+ Permission.ProjectMember,
575
+ Permission.CreateStatusPageDomain,
576
+ ],
577
+ read: [
578
+ Permission.ProjectOwner,
579
+ Permission.ProjectAdmin,
580
+ Permission.ProjectMember,
581
+ Permission.ReadStatusPageDomain,
582
+ ],
583
+ update: [
584
+ Permission.ProjectOwner,
585
+ Permission.ProjectAdmin,
586
+ Permission.ProjectMember,
587
+ Permission.EditStatusPageDomain,
588
+ ],
589
+ })
590
+ @TableColumn({ type: TableColumnType.VeryLongText })
591
+ @Column({
592
+ type: ColumnType.VeryLongText,
593
+ nullable: true,
594
+ unique: false,
595
+ })
596
+ public customCertificateKey?: string = undefined;
597
+
598
+ // If this is true, then the certificate is custom and not managed by OneUptime (LetsEncrypt)
599
+ @ColumnAccessControl({
600
+ create: [
601
+ Permission.ProjectOwner,
602
+ Permission.ProjectAdmin,
603
+ Permission.ProjectMember,
604
+ Permission.CreateStatusPageDomain,
605
+ ],
606
+ read: [
607
+ Permission.ProjectOwner,
608
+ Permission.ProjectAdmin,
609
+ Permission.ProjectMember,
610
+ Permission.ReadStatusPageDomain,
611
+ ],
612
+ update: [
613
+ Permission.ProjectOwner,
614
+ Permission.ProjectAdmin,
615
+ Permission.ProjectMember,
616
+ Permission.EditStatusPageDomain,
617
+ ],
618
+ })
619
+ @TableColumn({ type: TableColumnType.Boolean })
620
+ @Column({
621
+ type: ColumnType.Boolean,
622
+ nullable: false,
623
+ unique: false,
624
+ default: false, // default is false
625
+ })
626
+ public isCustomCertificate?: boolean = undefined;
541
627
  }
@@ -0,0 +1,29 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1728472625805 implements MigrationInterface {
4
+ public name = "MigrationName1728472625805";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "StatusPageDomain" ADD "customCertificate" text`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "StatusPageDomain" ADD "customCertificateKey" text`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "StatusPageDomain" ADD "isCustomCertificate" boolean NOT NULL DEFAULT false`,
15
+ );
16
+ }
17
+
18
+ public async down(queryRunner: QueryRunner): Promise<void> {
19
+ await queryRunner.query(
20
+ `ALTER TABLE "StatusPageDomain" DROP COLUMN "isCustomCertificate"`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "StatusPageDomain" DROP COLUMN "customCertificateKey"`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "StatusPageDomain" DROP COLUMN "customCertificate"`,
27
+ );
28
+ }
29
+ }
@@ -73,6 +73,7 @@ import { MigrationName1727194211048 } from "./1727194211048-MigrationName";
73
73
  import { MigrationName1727194579925 } from "./1727194579925-MigrationName";
74
74
  import { MigrationName1727894983857 } from "./1727894983857-MigrationName";
75
75
  import { MigrationName1727906598804 } from "./1727906598804-MigrationName";
76
+ import { MigrationName1728472625805 } from "./1728472625805-MigrationName";
76
77
 
77
78
  export default [
78
79
  InitialMigration,
@@ -150,4 +151,5 @@ export default [
150
151
  MigrationName1727194579925,
151
152
  MigrationName1727894983857,
152
153
  MigrationName1727906598804,
154
+ MigrationName1728472625805,
153
155
  ];
@@ -50,6 +50,17 @@ export class Service extends DatabaseService<StatusPageDomain> {
50
50
 
51
51
  createBy.data.cnameVerificationToken = ObjectID.generate().toString();
52
52
 
53
+ if (createBy.data.isCustomCertificate) {
54
+ if (
55
+ !createBy.data.customCertificate ||
56
+ !createBy.data.customCertificateKey
57
+ ) {
58
+ throw new BadDataException(
59
+ "Custom certificate or private key is missing",
60
+ );
61
+ }
62
+ }
63
+
53
64
  return { createBy, carryForward: null };
54
65
  }
55
66
 
@@ -168,6 +179,7 @@ export class Service extends DatabaseService<StatusPageDomain> {
168
179
  const domains: Array<StatusPageDomain> = await this.findBy({
169
180
  query: {
170
181
  isSslOrdered: true,
182
+ isCustomCertificate: false,
171
183
  },
172
184
  select: {
173
185
  _id: true,
@@ -419,6 +431,7 @@ export class Service extends DatabaseService<StatusPageDomain> {
419
431
  const domains: Array<StatusPageDomain> = await this.findBy({
420
432
  query: {
421
433
  isSslOrdered: false,
434
+ isCustomCertificate: false, // only order for non custom certificates.
422
435
  },
423
436
  select: {
424
437
  _id: true,
@@ -507,6 +520,7 @@ export class Service extends DatabaseService<StatusPageDomain> {
507
520
  const domains: Array<StatusPageDomain> = await this.findBy({
508
521
  query: {
509
522
  isSslOrdered: true,
523
+ isCustomCertificate: false,
510
524
  },
511
525
  select: {
512
526
  _id: true,
@@ -21,6 +21,7 @@ export default class WebsiteRequest {
21
21
  headers?: Headers | undefined;
22
22
  timeout?: number | undefined;
23
23
  isHeadRequest?: boolean | undefined;
24
+ doNotFollowRedirects?: boolean | undefined;
24
25
  },
25
26
  ): Promise<WebsiteResponse> {
26
27
  const axiosOptions: AxiosRequestConfig = {
@@ -36,6 +37,10 @@ export default class WebsiteRequest {
36
37
  axiosOptions.method = HTTPMethod.HEAD;
37
38
  }
38
39
 
40
+ if (options.doNotFollowRedirects) {
41
+ axiosOptions.maxRedirects = 0;
42
+ }
43
+
39
44
  // use axios to fetch an HTML page
40
45
  let response: AxiosResponse | null = null;
41
46
 
package/Utils/API.ts CHANGED
@@ -18,6 +18,7 @@ export interface RequestOptions {
18
18
  retries?: number | undefined;
19
19
  exponentialBackoff?: boolean | undefined;
20
20
  timeout?: number | undefined;
21
+ doNotFollowRedirects?: boolean | undefined;
21
22
  }
22
23
 
23
24
  export default class API {
@@ -395,6 +396,10 @@ export default class API {
395
396
  axiosOptions.timeout = options.timeout;
396
397
  }
397
398
 
399
+ if (options?.doNotFollowRedirects) {
400
+ axiosOptions.maxRedirects = 0;
401
+ }
402
+
398
403
  result = await axios(axiosOptions);
399
404
 
400
405
  break;
@@ -0,0 +1,301 @@
1
+ import { Green } from "../../Types/BrandColors";
2
+ import ObjectID from "../../Types/ObjectID";
3
+ import MonitorStatus from "../../Models/DatabaseModels/MonitorStatus";
4
+ import MonitorStatusTimeline from "../../Models/DatabaseModels/MonitorStatusTimeline";
5
+ import StatusPageResource from "../../Models/DatabaseModels/StatusPageResource";
6
+ import Dictionary from "../../Types/Dictionary";
7
+ import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
8
+ import StatusPageGroup from "../../Models/DatabaseModels/StatusPageGroup";
9
+ import UptimeUtil from "../Uptime/UptimeUtil";
10
+
11
+ export default class StatusPageResourceUptimeUtil {
12
+ public static getMonitorStatusTimelineForResource(data: {
13
+ statusPageResource: StatusPageResource;
14
+ monitorStatusTimelines: Array<MonitorStatusTimeline>;
15
+ monitorsInGroup: Dictionary<Array<ObjectID>>;
16
+ }): Array<MonitorStatusTimeline> {
17
+ return [...data.monitorStatusTimelines].filter(
18
+ (timeline: MonitorStatusTimeline) => {
19
+ // check monitor if first.
20
+
21
+ if (data.statusPageResource.monitorId) {
22
+ return (
23
+ timeline.monitorId?.toString() ===
24
+ data.statusPageResource.monitorId?.toString()
25
+ );
26
+ }
27
+
28
+ if (data.statusPageResource.monitorGroupId) {
29
+ const monitorsInThisGroup: Array<ObjectID> | undefined =
30
+ data.monitorsInGroup[
31
+ data.statusPageResource.monitorGroupId?.toString() || ""
32
+ ];
33
+
34
+ if (!monitorsInThisGroup) {
35
+ return false;
36
+ }
37
+
38
+ return monitorsInThisGroup.find((monitorId: ObjectID) => {
39
+ return monitorId.toString() === timeline.monitorId?.toString();
40
+ });
41
+ }
42
+
43
+ return false;
44
+ },
45
+ );
46
+ }
47
+
48
+ public static getCurrentStatusPageGroupStatus(data: {
49
+ statusPageGroup: StatusPageGroup;
50
+ monitorStatusTimelines: Array<MonitorStatusTimeline>;
51
+ statusPageResources: Array<StatusPageResource>;
52
+ monitorStatuses: Array<MonitorStatus>;
53
+ monitorGroupCurrentStatuses: Dictionary<ObjectID>;
54
+ }): MonitorStatus {
55
+ let currentStatus: MonitorStatus = new MonitorStatus();
56
+ currentStatus.name = "Operational";
57
+ currentStatus.color = Green;
58
+
59
+ const resourcesInGroup: Array<StatusPageResource> =
60
+ this.getResourcesInStatusPageGroup({
61
+ statusPageGroup: data.statusPageGroup,
62
+ statusPageResources: data.statusPageResources,
63
+ });
64
+
65
+ for (const resource of resourcesInGroup) {
66
+ let currentMonitorStatus: MonitorStatus | undefined = undefined;
67
+
68
+ if (resource.monitor) {
69
+ currentMonitorStatus = data.monitorStatuses.find(
70
+ (status: MonitorStatus) => {
71
+ return (
72
+ status._id?.toString() ===
73
+ resource.monitor?.currentMonitorStatusId?.toString()
74
+ );
75
+ },
76
+ );
77
+ }
78
+
79
+ if (resource.monitorGroupId) {
80
+ currentMonitorStatus = data.monitorStatuses.find(
81
+ (status: MonitorStatus) => {
82
+ return (
83
+ status._id?.toString() ===
84
+ data.monitorGroupCurrentStatuses[
85
+ resource.monitorGroupId?.toString() || ""
86
+ ]?.toString()
87
+ );
88
+ },
89
+ );
90
+ }
91
+
92
+ if (!currentMonitorStatus) {
93
+ currentMonitorStatus = currentStatus;
94
+ }
95
+
96
+ if (
97
+ (currentStatus &&
98
+ currentStatus.priority &&
99
+ currentMonitorStatus?.priority &&
100
+ currentMonitorStatus?.priority > currentStatus.priority) ||
101
+ !currentStatus ||
102
+ !currentStatus.priority
103
+ ) {
104
+ currentStatus = currentMonitorStatus!;
105
+ }
106
+ }
107
+
108
+ return currentStatus;
109
+ }
110
+
111
+ public static calculateUptimePercentOfResource(data: {
112
+ statusPageResource: StatusPageResource;
113
+ monitorStatusTimelines: Array<MonitorStatusTimeline>;
114
+ precision: UptimePrecision;
115
+ downtimeMonitorStatuses: Array<MonitorStatus>;
116
+ monitorsInGroup: Dictionary<Array<ObjectID>>;
117
+ }): number | null {
118
+ if (!data.statusPageResource.showUptimePercent) {
119
+ return null;
120
+ }
121
+
122
+ const monitorStatusTimelines: Array<MonitorStatusTimeline> =
123
+ this.getMonitorStatusTimelineForResource({
124
+ statusPageResource: data.statusPageResource,
125
+ monitorStatusTimelines: data.monitorStatusTimelines,
126
+ monitorsInGroup: data.monitorsInGroup,
127
+ });
128
+
129
+ const downtimeMonitorStatuses: Array<MonitorStatus> =
130
+ data?.downtimeMonitorStatuses || [];
131
+
132
+ const uptimePercent: number = UptimeUtil.calculateUptimePercentage(
133
+ monitorStatusTimelines,
134
+ data.precision,
135
+ downtimeMonitorStatuses,
136
+ );
137
+
138
+ return uptimePercent;
139
+ }
140
+
141
+ public static calculateAvgUptimePercentOfStatusPageGroup(data: {
142
+ statusPageGroup: StatusPageGroup;
143
+ monitorStatusTimelines: Array<MonitorStatusTimeline>;
144
+ precision: UptimePrecision;
145
+ downtimeMonitorStatuses: Array<MonitorStatus>;
146
+ statusPageResources: Array<StatusPageResource>;
147
+ monitorsInGroup: Dictionary<Array<ObjectID>>;
148
+ }): number | null {
149
+ if (!data.statusPageGroup.showUptimePercent) {
150
+ return null;
151
+ }
152
+
153
+ const resourcesInGroup: Array<StatusPageResource> =
154
+ this.getResourcesInStatusPageGroup({
155
+ statusPageGroup: data.statusPageGroup,
156
+ statusPageResources: data.statusPageResources,
157
+ });
158
+
159
+ if (resourcesInGroup.length === 0) {
160
+ return null; // no resources in group.
161
+ }
162
+
163
+ const uptimePercentPerResource: Array<number> = [];
164
+
165
+ for (const resource of resourcesInGroup) {
166
+ const calculateUptimePercentOfResource: number | null =
167
+ this.calculateUptimePercentOfResource({
168
+ statusPageResource: resource,
169
+ monitorStatusTimelines: data.monitorStatusTimelines,
170
+ precision: data.precision,
171
+ downtimeMonitorStatuses: data.downtimeMonitorStatuses,
172
+ monitorsInGroup: data.monitorsInGroup,
173
+ });
174
+
175
+ if (calculateUptimePercentOfResource !== null) {
176
+ uptimePercentPerResource.push(calculateUptimePercentOfResource);
177
+ }
178
+ }
179
+
180
+ // calculate avg
181
+
182
+ if (uptimePercentPerResource.length === 0) {
183
+ return null;
184
+ }
185
+
186
+ const averageUptimePercentage: number =
187
+ uptimePercentPerResource.reduce((a: number, b: number) => {
188
+ return a + b;
189
+ }) / uptimePercentPerResource.length;
190
+
191
+ // if the current status is operational then show uptime Percent.
192
+
193
+ let precision: UptimePrecision = UptimePrecision.ONE_DECIMAL;
194
+
195
+ if (data.statusPageGroup.uptimePercentPrecision) {
196
+ precision = data.statusPageGroup.uptimePercentPrecision;
197
+ }
198
+
199
+ return UptimeUtil.roundToPrecision({
200
+ number: averageUptimePercentage,
201
+ precision: precision,
202
+ });
203
+ }
204
+
205
+ public static getResourcesWithoutStatusPageGroup(data: {
206
+ statusPageResources: Array<StatusPageResource>;
207
+ }): Array<StatusPageResource> {
208
+ return data.statusPageResources.filter((resource: StatusPageResource) => {
209
+ return !resource.statusPageGroupId;
210
+ });
211
+ }
212
+
213
+ public static getResourcesInStatusPageGroup(data: {
214
+ statusPageGroup: StatusPageGroup;
215
+ statusPageResources: Array<StatusPageResource>;
216
+ }): Array<StatusPageResource> {
217
+ return data.statusPageResources.filter((resource: StatusPageResource) => {
218
+ return (
219
+ resource.statusPageGroupId?.toString() ===
220
+ data.statusPageGroup._id?.toString()
221
+ );
222
+ });
223
+ }
224
+
225
+ public static calculateAvgUptimePercentageOfAllResources(data: {
226
+ monitorStatusTimelines: Array<MonitorStatusTimeline>;
227
+ precision: UptimePrecision;
228
+ downtimeMonitorStatuses: Array<MonitorStatus>;
229
+ statusPageResources: Array<StatusPageResource>;
230
+ resourceGroups: Array<StatusPageGroup>;
231
+ monitorsInGroup: Dictionary<Array<ObjectID>>;
232
+ }): number | null {
233
+ const showUptimePercentage: boolean = Boolean(
234
+ data.statusPageResources.find((item: StatusPageResource) => {
235
+ return item.showUptimePercent || item.showStatusHistoryChart;
236
+ }),
237
+ );
238
+
239
+ if (!showUptimePercentage) {
240
+ return null;
241
+ }
242
+
243
+ const allUptimePercent: Array<number> = [];
244
+
245
+ // calculate for groups first.
246
+
247
+ for (const group of data.resourceGroups) {
248
+ const calculateAvgUptimePercentOfStatusPageGroup: number | null =
249
+ this.calculateAvgUptimePercentOfStatusPageGroup({
250
+ statusPageGroup: group,
251
+ monitorStatusTimelines: data.monitorStatusTimelines,
252
+ precision: data.precision,
253
+ downtimeMonitorStatuses: data.downtimeMonitorStatuses,
254
+ statusPageResources: data.statusPageResources,
255
+ monitorsInGroup: data.monitorsInGroup,
256
+ });
257
+
258
+ if (calculateAvgUptimePercentOfStatusPageGroup !== null) {
259
+ allUptimePercent.push(calculateAvgUptimePercentOfStatusPageGroup);
260
+ }
261
+ }
262
+
263
+ // now fetch resources without group.
264
+
265
+ const resourcesWithoutGroup: Array<StatusPageResource> =
266
+ this.getResourcesWithoutStatusPageGroup({
267
+ statusPageResources: data.statusPageResources,
268
+ });
269
+
270
+ for (const resource of resourcesWithoutGroup) {
271
+ const calculateUptimePercentOfResource: number | null =
272
+ this.calculateUptimePercentOfResource({
273
+ statusPageResource: resource,
274
+ monitorStatusTimelines: data.monitorStatusTimelines,
275
+ precision: data.precision,
276
+ downtimeMonitorStatuses: data.downtimeMonitorStatuses,
277
+ monitorsInGroup: data.monitorsInGroup,
278
+ });
279
+
280
+ if (calculateUptimePercentOfResource !== null) {
281
+ allUptimePercent.push(calculateUptimePercentOfResource);
282
+ }
283
+ }
284
+
285
+ // calculate avg
286
+
287
+ if (allUptimePercent.length === 0) {
288
+ return null;
289
+ }
290
+
291
+ const averageUptimePercentage: number =
292
+ allUptimePercent.reduce((a: number, b: number) => {
293
+ return a + b;
294
+ }) / allUptimePercent.length;
295
+
296
+ return UptimeUtil.roundToPrecision({
297
+ number: averageUptimePercentage,
298
+ precision: data.precision,
299
+ });
300
+ }
301
+ }
@@ -5,8 +5,6 @@ import OneUptimeDate from "../../Types/Date";
5
5
  import ObjectID from "../../Types/ObjectID";
6
6
  import MonitorStatus from "../../Models/DatabaseModels/MonitorStatus";
7
7
  import MonitorStatusTimeline from "../../Models/DatabaseModels/MonitorStatusTimeline";
8
- import StatusPageResource from "../../Models/DatabaseModels/StatusPageResource";
9
- import Dictionary from "../../Types/Dictionary";
10
8
  import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
11
9
 
12
10
  export default class UptimeUtil {
@@ -301,111 +299,29 @@ export default class UptimeUtil {
301
299
  };
302
300
  }
303
301
 
304
- public static calculateAvgUptimePercentageOfAllResources(data: {
305
- monitorStatusTimelines: Array<MonitorStatusTimeline>;
302
+ public static roundToPrecision(data: {
303
+ number: number;
306
304
  precision: UptimePrecision;
307
- downtimeMonitorStatuses: Array<MonitorStatus>;
308
- statusPageResources: Array<StatusPageResource>;
309
- monitorsInGroup: Dictionary<Array<ObjectID>>;
310
- }): number | null {
311
- const showUptimePercentage: boolean = Boolean(
312
- data.statusPageResources.find((item: StatusPageResource) => {
313
- return item.showUptimePercent || item.showStatusHistoryChart;
314
- }),
315
- );
316
-
317
- if (!showUptimePercentage) {
318
- return null;
319
- }
320
-
321
- const uptimePercentPerResource: Array<number> = [];
322
-
323
- for (const resource of data.statusPageResources) {
324
- if (!resource.showUptimePercent && !resource.showStatusHistoryChart) {
325
- continue;
326
- }
327
-
328
- let timelinesForThisResource: Array<MonitorStatusTimeline> = [];
329
-
330
- if (resource.monitorGroupId) {
331
- timelinesForThisResource = [...data.monitorStatusTimelines].filter(
332
- (timeline: MonitorStatusTimeline) => {
333
- const monitorsInThisGroup: Array<ObjectID> | undefined =
334
- data.monitorsInGroup[resource.monitorGroupId?.toString() || ""];
335
-
336
- if (!monitorsInThisGroup) {
337
- return false;
338
- }
339
-
340
- return monitorsInThisGroup.find((monitorId: ObjectID) => {
341
- return monitorId.toString() === timeline.monitorId?.toString();
342
- });
343
- },
344
- );
345
- }
346
-
347
- if (resource.monitorId || resource.monitor?.id) {
348
- const monitorId: ObjectID | null | undefined =
349
- resource.monitorId || resource.monitor?.id;
350
-
351
- if (!monitorId) {
352
- // this should never happen.
353
- continue;
354
- }
355
-
356
- timelinesForThisResource = [...data.monitorStatusTimelines].filter(
357
- (timeline: MonitorStatusTimeline) => {
358
- return (
359
- timeline.monitorId?.toString() === resource.monitorId?.toString()
360
- );
361
- },
362
- );
363
- }
364
-
365
- const uptimePercent: number = this.calculateUptimePercentage(
366
- timelinesForThisResource,
367
- data.precision,
368
- data.downtimeMonitorStatuses,
369
- );
370
-
371
- uptimePercentPerResource.push(uptimePercent);
372
- }
373
-
374
- // calculate avg
375
-
376
- if (uptimePercentPerResource.length === 0) {
377
- return null;
378
- }
379
-
380
- const averageUptimePercentage: number =
381
- uptimePercentPerResource.reduce((a: number, b: number) => {
382
- return a + b;
383
- }) / uptimePercentPerResource.length;
305
+ }): number {
306
+ const { number, precision } = data;
384
307
 
385
- //round this to precision.
386
-
387
- if (data.precision === UptimePrecision.NO_DECIMAL) {
388
- const percent: number = Math.round(averageUptimePercentage);
389
-
390
- return percent;
308
+ if (precision === UptimePrecision.NO_DECIMAL) {
309
+ return Math.floor(number);
391
310
  }
392
311
 
393
- if (data.precision === UptimePrecision.ONE_DECIMAL) {
394
- const percent: number = Math.round(averageUptimePercentage * 10) / 10;
395
- return percent;
312
+ if (precision === UptimePrecision.ONE_DECIMAL) {
313
+ return Math.floor(number * 10) / 10;
396
314
  }
397
315
 
398
- if (data.precision === UptimePrecision.TWO_DECIMAL) {
399
- const percent: number = Math.round(averageUptimePercentage * 100) / 100;
400
- return percent;
316
+ if (precision === UptimePrecision.TWO_DECIMAL) {
317
+ return Math.floor(number * 100) / 100;
401
318
  }
402
319
 
403
- if (data.precision === UptimePrecision.THREE_DECIMAL) {
404
- const percent: number = Math.round(averageUptimePercentage * 1000) / 1000;
405
- return percent;
320
+ if (precision === UptimePrecision.THREE_DECIMAL) {
321
+ return Math.floor(number * 1000) / 1000;
406
322
  }
407
323
 
408
- return averageUptimePercentage;
324
+ return number;
409
325
  }
410
326
 
411
327
  public static calculateUptimePercentage(
@@ -434,42 +350,9 @@ export default class UptimeUtil {
434
350
  totalSecondsInTimePeriod) *
435
351
  100;
436
352
 
437
- if (precision === UptimePrecision.NO_DECIMAL) {
438
- const noDecimalPercent: number = Math.round(percentage);
439
- if (noDecimalPercent === 100 && totalDowntimeInSeconds > 0) {
440
- return 99;
441
- }
442
-
443
- return noDecimalPercent;
444
- }
445
-
446
- if (precision === UptimePrecision.ONE_DECIMAL) {
447
- const noDecimalPercent: number = Math.round(percentage * 10) / 10;
448
- if (noDecimalPercent === 100 && totalDowntimeInSeconds > 0) {
449
- return 99.9;
450
- }
451
-
452
- return noDecimalPercent;
453
- }
454
-
455
- if (precision === UptimePrecision.TWO_DECIMAL) {
456
- const noDecimalPercent: number = Math.round(percentage * 100) / 100;
457
- if (noDecimalPercent === 100 && totalDowntimeInSeconds > 0) {
458
- return 99.99;
459
- }
460
-
461
- return noDecimalPercent;
462
- }
463
-
464
- if (precision === UptimePrecision.THREE_DECIMAL) {
465
- const noDecimalPercent: number = Math.round(percentage * 1000) / 1000;
466
- if (noDecimalPercent === 100 && totalDowntimeInSeconds > 0) {
467
- return 99.999;
468
- }
469
-
470
- return noDecimalPercent;
471
- }
472
-
473
- return percentage;
353
+ return this.roundToPrecision({
354
+ number: percentage,
355
+ precision,
356
+ });
474
357
  }
475
358
  }