@oneuptime/common 7.0.3377 → 7.0.3392

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 (73) hide show
  1. package/Models/DatabaseModels/StatusPageSubscriber.ts +57 -0
  2. package/Server/API/StatusPageAPI.ts +60 -0
  3. package/Server/EnvironmentConfig.ts +3 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/1734435866602-MigrationName.ts +23 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  6. package/Server/Services/AnalyticsDatabaseService.ts +4 -0
  7. package/Server/Services/StatusPageService.ts +8 -14
  8. package/Server/Services/StatusPageSubscriberService.ts +242 -13
  9. package/Server/Types/AnalyticsDatabase/AggregateBy.ts +4 -0
  10. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +26 -16
  11. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +53 -7
  12. package/Server/Utils/Monitor/MonitorAlert.ts +5 -0
  13. package/Server/Utils/Monitor/MonitorIncident.ts +5 -0
  14. package/Server/Utils/Monitor/MonitorResource.ts +26 -0
  15. package/Server/Utils/Stream.ts +4 -0
  16. package/Types/Date.ts +9 -0
  17. package/Types/Email/EmailTemplateType.ts +1 -0
  18. package/Types/Metrics/MetricViewData.ts +6 -0
  19. package/Types/Monitor/CriteriaFilter.ts +5 -4
  20. package/Types/Monitor/MonitorCriteriaInstance.ts +4 -0
  21. package/Types/Monitor/MonitorType.ts +6 -6
  22. package/Types/Telemetry/TelemetryQuery.ts +3 -2
  23. package/UI/Components/Charts/Utils/XAxis.ts +3 -2
  24. package/UI/Components/Pill/Pill.tsx +34 -22
  25. package/UI/Components/RollingTimePicker/RollingTimePicker.tsx +12 -25
  26. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +59 -0
  27. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  28. package/build/dist/Server/API/StatusPageAPI.js +70 -32
  29. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  30. package/build/dist/Server/EnvironmentConfig.js +1 -0
  31. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  32. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1734435866602-MigrationName.js +14 -0
  33. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1734435866602-MigrationName.js.map +1 -0
  34. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  35. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  36. package/build/dist/Server/Services/AnalyticsDatabaseService.js +3 -0
  37. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  38. package/build/dist/Server/Services/StatusPageService.js +6 -11
  39. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  40. package/build/dist/Server/Services/StatusPageSubscriberService.js +182 -11
  41. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  42. package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js +3 -0
  43. package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js.map +1 -1
  44. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +25 -14
  45. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  46. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +34 -8
  47. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  48. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +4 -0
  49. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  50. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +4 -0
  51. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  52. package/build/dist/Server/Utils/Monitor/MonitorResource.js +19 -0
  53. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  54. package/build/dist/Server/Utils/Stream.js +3 -0
  55. package/build/dist/Server/Utils/Stream.js.map +1 -1
  56. package/build/dist/Types/Date.js +5 -0
  57. package/build/dist/Types/Date.js.map +1 -1
  58. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  59. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  60. package/build/dist/Types/Metrics/MetricViewData.js +2 -0
  61. package/build/dist/Types/Metrics/MetricViewData.js.map +1 -0
  62. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  63. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +3 -1
  64. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  65. package/build/dist/Types/Monitor/MonitorType.js +5 -6
  66. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  67. package/build/dist/UI/Components/Charts/Utils/XAxis.js +3 -2
  68. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  69. package/build/dist/UI/Components/Pill/Pill.js +19 -11
  70. package/build/dist/UI/Components/Pill/Pill.js.map +1 -1
  71. package/build/dist/UI/Components/RollingTimePicker/RollingTimePicker.js +9 -18
  72. package/build/dist/UI/Components/RollingTimePicker/RollingTimePicker.js.map +1 -1
  73. package/package.json +2 -2
@@ -434,6 +434,63 @@ export default class StatusPageSubscriber extends BaseModel {
434
434
  })
435
435
  public deletedByUserId?: ObjectID = undefined;
436
436
 
437
+ @ColumnAccessControl({
438
+ create: [
439
+ Permission.ProjectOwner,
440
+ Permission.ProjectAdmin,
441
+ Permission.ProjectMember,
442
+ Permission.CreateStatusPageSubscriber,
443
+ Permission.Public,
444
+ ],
445
+ read: [
446
+ Permission.ProjectOwner,
447
+ Permission.ProjectAdmin,
448
+ Permission.ProjectMember,
449
+ Permission.ReadStatusPageSubscriber,
450
+ ],
451
+ update: [
452
+ Permission.ProjectOwner,
453
+ Permission.ProjectAdmin,
454
+ Permission.ProjectMember,
455
+ Permission.EditStatusPageSubscriber,
456
+ ],
457
+ })
458
+ @TableColumn({
459
+ isDefaultValueColumn: true,
460
+ type: TableColumnType.Boolean,
461
+ title: "Is Subscription Confirmed",
462
+ description:
463
+ "Has subscriber confirmed their subscription? (for example, by clicking on a confirmation link in an email)",
464
+ })
465
+ @Column({
466
+ type: ColumnType.Boolean,
467
+ default: false,
468
+ })
469
+ public isSubscriptionConfirmed?: boolean = undefined;
470
+
471
+ @ColumnAccessControl({
472
+ create: [
473
+ Permission.ProjectOwner,
474
+ Permission.ProjectAdmin,
475
+ Permission.ProjectMember,
476
+ Permission.CreateStatusPageSubscriber,
477
+ ],
478
+ read: [],
479
+ update: [],
480
+ })
481
+ @TableColumn({
482
+ isDefaultValueColumn: false,
483
+ type: TableColumnType.ShortText,
484
+ title: "Subscription Confirmation Token",
485
+ description:
486
+ "Token used to confirm subscription. This is a random token that is sent to the subscriber's email address to confirm their subscription.",
487
+ })
488
+ @Column({
489
+ type: ColumnType.ShortText,
490
+ nullable: true,
491
+ })
492
+ public subscriptionConfirmationToken?: string = undefined;
493
+
437
494
  @ColumnAccessControl({
438
495
  create: [
439
496
  Permission.ProjectOwner,
@@ -82,6 +82,66 @@ export default class StatusPageAPI extends BaseAPI<
82
82
  public constructor() {
83
83
  super(StatusPage, StatusPageService);
84
84
 
85
+ // confirm subscription api
86
+ this.router.get(
87
+ `${new this.entityType()
88
+ .getCrudApiPath()
89
+ ?.toString()}/confirm-subscription/:statusPageSubscriberId`,
90
+ async (req: ExpressRequest, res: ExpressResponse) => {
91
+ const token: string = req.query["verification-token"] as string;
92
+
93
+ const statusPageSubscriberId: ObjectID = new ObjectID(
94
+ req.params["statusPageSubscriberId"] as string,
95
+ );
96
+
97
+ const subscriber: StatusPageSubscriber | null =
98
+ await StatusPageSubscriberService.findOneBy({
99
+ query: {
100
+ _id: statusPageSubscriberId,
101
+ subscriptionConfirmationToken: token,
102
+ },
103
+ select: {
104
+ isSubscriptionConfirmed: true,
105
+ },
106
+ props: {
107
+ isRoot: true,
108
+ },
109
+ });
110
+
111
+ if (!subscriber) {
112
+ return Response.sendErrorResponse(
113
+ req,
114
+ res,
115
+ new NotFoundException(
116
+ "Subscriber not found or confirmation token is invalid",
117
+ ),
118
+ );
119
+ }
120
+
121
+ // check if subscription confirmed already.
122
+
123
+ if (subscriber.isSubscriptionConfirmed) {
124
+ return Response.sendEmptySuccessResponse(req, res);
125
+ }
126
+
127
+ await StatusPageSubscriberService.updateOneById({
128
+ id: statusPageSubscriberId,
129
+ data: {
130
+ isSubscriptionConfirmed: true,
131
+ },
132
+ props: {
133
+ isRoot: true,
134
+ },
135
+ });
136
+
137
+ await StatusPageSubscriberService.sendYouHaveSubscribedEmail({
138
+ subscriberId: statusPageSubscriberId,
139
+ });
140
+
141
+ return Response.sendEmptySuccessResponse(req, res);
142
+ },
143
+ );
144
+
85
145
  // CNAME verification api
86
146
  this.router.get(
87
147
  `${new this.entityType()
@@ -198,6 +198,9 @@ export const AnalyticsHost: string = process.env["ANALYTICS_HOST"] || "";
198
198
  export const DisableAutomaticIncidentCreation: boolean =
199
199
  process.env["DISABLE_AUTOMATIC_INCIDENT_CREATION"] === "true";
200
200
 
201
+ export const DisableAutomaticAlertCreation: boolean =
202
+ process.env["DISABLE_AUTOMATIC_ALERT_CREATION"] === "true";
203
+
201
204
  export const ClickhouseHost: Hostname = Hostname.fromString(
202
205
  process.env["CLICKHOUSE_HOST"] || "clickhouse",
203
206
  );
@@ -0,0 +1,23 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1734435866602 implements MigrationInterface {
4
+ public name = "MigrationName1734435866602";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "StatusPageSubscriber" ADD "isSubscriptionConfirmed" boolean NOT NULL DEFAULT false`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "StatusPageSubscriber" ADD "subscriptionConfirmationToken" character varying`,
12
+ );
13
+ }
14
+
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
16
+ await queryRunner.query(
17
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "subscriptionConfirmationToken"`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "StatusPageSubscriber" DROP COLUMN "isSubscriptionConfirmed"`,
21
+ );
22
+ }
23
+ }
@@ -83,6 +83,7 @@ import { MigrationName1731433309124 } from "./1731433309124-MigrationName";
83
83
  import { MigrationName1731435267537 } from "./1731435267537-MigrationName";
84
84
  import { MigrationName1731435514287 } from "./1731435514287-MigrationName";
85
85
  import { MigrationName1732553444010 } from "./1732553444010-MigrationName";
86
+ import { MigrationName1734435866602 } from "./1734435866602-MigrationName";
86
87
 
87
88
  export default [
88
89
  InitialMigration,
@@ -170,4 +171,5 @@ export default [
170
171
  MigrationName1731435267537,
171
172
  MigrationName1731435514287,
172
173
  MigrationName1732553444010,
174
+ MigrationName1734435866602,
173
175
  ];
@@ -415,6 +415,10 @@ export default class AnalyticsDatabaseService<
415
415
  strResult: string,
416
416
  columns: string[],
417
417
  ): JSONObject[] {
418
+ if (!strResult || !strResult.trim()) {
419
+ return [];
420
+ }
421
+
418
422
  const jsonItems: Array<JSONObject> = [];
419
423
 
420
424
  const rows: Array<string> = strResult.split("\n");
@@ -454,8 +454,8 @@ export class Service extends DatabaseService<StatusPage> {
454
454
  }
455
455
 
456
456
  public async getStatusPageURL(statusPageId: ObjectID): Promise<string> {
457
- const domains: Array<StatusPageDomain> =
458
- await StatusPageDomainService.findBy({
457
+ const domain: StatusPageDomain | null =
458
+ await StatusPageDomainService.findOneBy({
459
459
  query: {
460
460
  statusPageId: statusPageId,
461
461
  isSslProvisioned: true,
@@ -463,21 +463,15 @@ export class Service extends DatabaseService<StatusPage> {
463
463
  select: {
464
464
  fullDomain: true,
465
465
  },
466
- skip: 0,
467
- limit: LIMIT_PER_PROJECT,
468
466
  props: {
469
467
  isRoot: true,
470
468
  ignoreHooks: true,
471
469
  },
472
470
  });
473
471
 
474
- let statusPageURL: string = domains
475
- .map((d: StatusPageDomain) => {
476
- return d.fullDomain;
477
- })
478
- .join(", ");
472
+ let statusPageURL: string = domain?.fullDomain || "";
479
473
 
480
- if (domains.length === 0) {
474
+ if (!statusPageURL) {
481
475
  const host: Hostname = await DatabaseConfig.getHost();
482
476
 
483
477
  const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
@@ -750,11 +744,11 @@ export class Service extends DatabaseService<StatusPage> {
750
744
  statusPageId: data.statusPageId,
751
745
  });
752
746
 
747
+ const numberOfDays: number = data.historyDays || 14;
748
+
753
749
  const currentDate: Date = OneUptimeDate.getCurrentDate();
754
- const startDate: Date = OneUptimeDate.getSomeDaysAgo(
755
- data.historyDays || 14,
756
- );
757
- const startAndEndDate: string = `${OneUptimeDate.getDateAsLocalFormattedString(startDate, true)} - ${OneUptimeDate.getDateAsLocalFormattedString(currentDate, true)}`;
750
+ const startDate: Date = OneUptimeDate.getSomeDaysAgo(numberOfDays);
751
+ const startAndEndDate: string = `${numberOfDays} days (${OneUptimeDate.getDateAsLocalFormattedString(startDate, true)} - ${OneUptimeDate.getDateAsLocalFormattedString(currentDate, true)})`;
758
752
 
759
753
  if (statusPageResources.length === 0) {
760
754
  return {
@@ -29,6 +29,7 @@ import StatusPageResource from "Common/Models/DatabaseModels/StatusPageResource"
29
29
  import Model from "Common/Models/DatabaseModels/StatusPageSubscriber";
30
30
  import PositiveNumber from "../../Types/PositiveNumber";
31
31
  import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
32
+ import NumberUtil from "../../Utils/Number";
32
33
 
33
34
  export class Service extends DatabaseService<Model> {
34
35
  public constructor() {
@@ -160,6 +161,22 @@ export class Service extends DatabaseService<Model> {
160
161
 
161
162
  data.data.projectId = statuspage.projectId;
162
163
 
164
+ const isEmailSubscriber: boolean = Boolean(data.data.subscriberEmail);
165
+ const isSubscriptionConfirmed: boolean = Boolean(
166
+ data.data.isSubscriptionConfirmed,
167
+ );
168
+
169
+ if (isEmailSubscriber && !isSubscriptionConfirmed) {
170
+ data.data.isSubscriptionConfirmed = false;
171
+ } else {
172
+ data.data.isSubscriptionConfirmed = true; // if the subscriber is not email, then set it to true for SMS subscribers.
173
+ }
174
+
175
+ data.data.subscriptionConfirmationToken = NumberUtil.getRandomNumber(
176
+ 100000,
177
+ 999999,
178
+ ).toString();
179
+
163
180
  return { createBy: data, carryForward: statuspage };
164
181
  }
165
182
 
@@ -180,10 +197,6 @@ export class Service extends DatabaseService<Model> {
180
197
  onCreate.carryForward.name ||
181
198
  "Status Page";
182
199
 
183
- const host: Hostname = await DatabaseConfig.getHost();
184
-
185
- const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
186
-
187
200
  const unsubscribeLink: string = this.getUnsubscribeLink(
188
201
  URL.fromString(statusPageURL),
189
202
  createdItem.id!,
@@ -237,28 +250,235 @@ export class Service extends DatabaseService<Model> {
237
250
  if (
238
251
  createdItem.statusPageId &&
239
252
  createdItem.subscriberEmail &&
240
- createdItem._id &&
241
- createdItem.sendYouHaveSubscribedMessage
253
+ createdItem._id
242
254
  ) {
243
255
  // Call mail service and send an email.
244
256
 
245
257
  // get status page domain for this status page.
246
258
  // if the domain is not found, use the internal status page preview link.
247
259
 
260
+ const isSubcriptionConfirmed: boolean = Boolean(
261
+ createdItem.isSubscriptionConfirmed,
262
+ );
263
+
264
+ if (!isSubcriptionConfirmed) {
265
+ await this.sendConfirmSubscriptionEmail({
266
+ subscriberId: createdItem.id!,
267
+ });
268
+ }
269
+
270
+ if (isSubcriptionConfirmed && createdItem.sendYouHaveSubscribedMessage) {
271
+ await this.sendYouHaveSubscribedEmail({
272
+ subscriberId: createdItem.id!,
273
+ });
274
+ }
275
+ }
276
+
277
+ return createdItem;
278
+ }
279
+
280
+ public async sendConfirmSubscriptionEmail(data: {
281
+ subscriberId: ObjectID;
282
+ }): Promise<void> {
283
+ // get subscriber
284
+ const subscriber: Model | null = await this.findOneBy({
285
+ query: {
286
+ _id: data.subscriberId,
287
+ },
288
+ select: {
289
+ statusPageId: true,
290
+ subscriberEmail: true,
291
+ subscriberPhone: true,
292
+ projectId: true,
293
+ subscriptionConfirmationToken: true,
294
+ sendYouHaveSubscribedMessage: true,
295
+ },
296
+ props: {
297
+ isRoot: true,
298
+ ignoreHooks: true,
299
+ },
300
+ });
301
+
302
+ // get status page
303
+ if (!subscriber || !subscriber.statusPageId) {
304
+ return;
305
+ }
306
+
307
+ const statusPage: StatusPage | null = await StatusPageService.findOneBy({
308
+ query: {
309
+ _id: subscriber.statusPageId.toString(),
310
+ },
311
+ select: {
312
+ logoFileId: true,
313
+ isPublicStatusPage: true,
314
+ pageTitle: true,
315
+ name: true,
316
+ smtpConfig: {
317
+ _id: true,
318
+ hostname: true,
319
+ port: true,
320
+ username: true,
321
+ password: true,
322
+ fromEmail: true,
323
+ fromName: true,
324
+ secure: true,
325
+ },
326
+ },
327
+ props: {
328
+ isRoot: true,
329
+ ignoreHooks: true,
330
+ },
331
+ });
332
+
333
+ if (!statusPage || !statusPage.id) {
334
+ return;
335
+ }
336
+
337
+ const statusPageURL: string = await StatusPageService.getStatusPageURL(
338
+ statusPage.id,
339
+ );
340
+
341
+ const statusPageName: string =
342
+ statusPage.pageTitle || statusPage.name || "Status Page";
343
+
344
+ const host: Hostname = await DatabaseConfig.getHost();
345
+
346
+ const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
347
+
348
+ const confirmSubscriptionLink: string = this.getConfirmSubscriptionLink({
349
+ statusPageUrl: statusPageURL,
350
+ confirmationToken: subscriber.subscriptionConfirmationToken || "",
351
+ statusPageSubscriberId: subscriber.id!,
352
+ }).toString();
353
+
354
+ if (
355
+ subscriber.statusPageId &&
356
+ subscriber.subscriberEmail &&
357
+ subscriber._id
358
+ ) {
359
+ MailService.sendMail(
360
+ {
361
+ toEmail: subscriber.subscriberEmail,
362
+ templateType: EmailTemplateType.ConfirmStatusPageSubscription,
363
+ vars: {
364
+ statusPageName: statusPageName,
365
+ logoUrl: statusPage.logoFileId
366
+ ? new URL(httpProtocol, host)
367
+ .addRoute(FileRoute)
368
+ .addRoute("/image/" + statusPage.logoFileId)
369
+ .toString()
370
+ : "",
371
+ statusPageUrl: statusPageURL,
372
+ isPublicStatusPage: statusPage.isPublicStatusPage
373
+ ? "true"
374
+ : "false",
375
+ confirmationUrl: confirmSubscriptionLink,
376
+ },
377
+ subject: "Confirm your subscription to " + statusPageName,
378
+ },
379
+ {
380
+ projectId: subscriber.projectId,
381
+ mailServer: ProjectSMTPConfigService.toEmailServer(
382
+ statusPage.smtpConfig,
383
+ ),
384
+ },
385
+ ).catch((err: Error) => {
386
+ logger.error(err);
387
+ });
388
+ }
389
+ }
390
+
391
+ public async sendYouHaveSubscribedEmail(data: {
392
+ subscriberId: ObjectID;
393
+ }): Promise<void> {
394
+ // get subscriber
395
+ const subscriber: Model | null = await this.findOneBy({
396
+ query: {
397
+ _id: data.subscriberId,
398
+ },
399
+ select: {
400
+ statusPageId: true,
401
+ subscriberEmail: true,
402
+ subscriberPhone: true,
403
+ projectId: true,
404
+ sendYouHaveSubscribedMessage: true,
405
+ },
406
+ props: {
407
+ isRoot: true,
408
+ ignoreHooks: true,
409
+ },
410
+ });
411
+
412
+ // get status page
413
+ if (!subscriber || !subscriber.statusPageId) {
414
+ return;
415
+ }
416
+
417
+ const statusPage: StatusPage | null = await StatusPageService.findOneBy({
418
+ query: {
419
+ _id: subscriber.statusPageId.toString(),
420
+ },
421
+ select: {
422
+ logoFileId: true,
423
+ isPublicStatusPage: true,
424
+ pageTitle: true,
425
+ name: true,
426
+ smtpConfig: {
427
+ _id: true,
428
+ hostname: true,
429
+ port: true,
430
+ username: true,
431
+ password: true,
432
+ fromEmail: true,
433
+ fromName: true,
434
+ secure: true,
435
+ },
436
+ },
437
+ props: {
438
+ isRoot: true,
439
+ ignoreHooks: true,
440
+ },
441
+ });
442
+
443
+ if (!statusPage || !statusPage.id) {
444
+ return;
445
+ }
446
+
447
+ const statusPageURL: string = await StatusPageService.getStatusPageURL(
448
+ statusPage.id,
449
+ );
450
+
451
+ const statusPageName: string =
452
+ statusPage.pageTitle || statusPage.name || "Status Page";
453
+
454
+ const host: Hostname = await DatabaseConfig.getHost();
455
+
456
+ const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
457
+
458
+ const unsubscribeLink: string = this.getUnsubscribeLink(
459
+ URL.fromString(statusPageURL),
460
+ subscriber.id!,
461
+ ).toString();
462
+
463
+ if (
464
+ subscriber.statusPageId &&
465
+ subscriber.subscriberEmail &&
466
+ subscriber._id
467
+ ) {
248
468
  MailService.sendMail(
249
469
  {
250
- toEmail: createdItem.subscriberEmail,
470
+ toEmail: subscriber.subscriberEmail,
251
471
  templateType: EmailTemplateType.SubscribedToStatusPage,
252
472
  vars: {
253
473
  statusPageName: statusPageName,
254
- logoUrl: onCreate.carryForward.logoFileId
474
+ logoUrl: statusPage.logoFileId
255
475
  ? new URL(httpProtocol, host)
256
476
  .addRoute(FileRoute)
257
- .addRoute("/image/" + onCreate.carryForward.logoFileId)
477
+ .addRoute("/image/" + statusPage.logoFileId)
258
478
  .toString()
259
479
  : "",
260
480
  statusPageUrl: statusPageURL,
261
- isPublicStatusPage: onCreate.carryForward.isPublicStatusPage
481
+ isPublicStatusPage: statusPage.isPublicStatusPage
262
482
  ? "true"
263
483
  : "false",
264
484
  unsubscribeUrl: unsubscribeLink,
@@ -266,17 +486,25 @@ export class Service extends DatabaseService<Model> {
266
486
  subject: "You have been subscribed to " + statusPageName,
267
487
  },
268
488
  {
269
- projectId: createdItem.projectId,
489
+ projectId: subscriber.projectId,
270
490
  mailServer: ProjectSMTPConfigService.toEmailServer(
271
- onCreate.carryForward.smtpConfig,
491
+ statusPage.smtpConfig,
272
492
  ),
273
493
  },
274
494
  ).catch((err: Error) => {
275
495
  logger.error(err);
276
496
  });
277
497
  }
498
+ }
278
499
 
279
- return createdItem;
500
+ public getConfirmSubscriptionLink(data: {
501
+ statusPageUrl: string;
502
+ confirmationToken: string;
503
+ statusPageSubscriberId: ObjectID;
504
+ }): URL {
505
+ return URL.fromString(data.statusPageUrl).addRoute(
506
+ `/confirm-subscription/${data.statusPageSubscriberId.toString()}?verification-token=${data.confirmationToken}`,
507
+ );
280
508
  }
281
509
 
282
510
  public async getSubscribersByStatusPage(
@@ -287,6 +515,7 @@ export class Service extends DatabaseService<Model> {
287
515
  query: {
288
516
  statusPageId: statusPageId,
289
517
  isUnsubscribed: false,
518
+ isSubscriptionConfirmed: true,
290
519
  },
291
520
  select: {
292
521
  _id: true,
@@ -2,6 +2,7 @@ import AggregationInterval from "Common/Types/BaseDatabase/AggregationInterval";
2
2
  import CommonAggregateBy from "Common/Types/BaseDatabase/AggregateBy";
3
3
  import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
4
4
  import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps";
5
+ import OneUptimeDate from "../../../Types/Date";
5
6
 
6
7
  export default interface AggregateBy<TBaseModel extends AnalyticsBaseModel>
7
8
  extends CommonAggregateBy<TBaseModel> {
@@ -13,6 +14,9 @@ export class AggregateUtil {
13
14
  startDate: Date;
14
15
  endDate: Date;
15
16
  }): AggregationInterval {
17
+ data.startDate = OneUptimeDate.fromString(data.startDate);
18
+ data.endDate = OneUptimeDate.fromString(data.endDate);
19
+
16
20
  const diff: number = data.endDate.getTime() - data.startDate.getTime();
17
21
 
18
22
  if (diff <= 1000 * 60 * 60 * 3) {
@@ -313,7 +313,8 @@ export default class CompareCriteria {
313
313
  CompareCriteria.isTrue({
314
314
  value: data.value,
315
315
  evaluationType:
316
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType,
316
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
317
+ data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
317
318
  })
318
319
  ) {
319
320
  return CompareCriteria.getCompareMessage({
@@ -331,7 +332,8 @@ export default class CompareCriteria {
331
332
  CompareCriteria.isFalse({
332
333
  value: data.value,
333
334
  evaluationType:
334
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType,
335
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
336
+ data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
335
337
  })
336
338
  ) {
337
339
  return CompareCriteria.getCompareMessage({
@@ -366,7 +368,8 @@ export default class CompareCriteria {
366
368
  threshold: data.threshold as number,
367
369
  value: data.value,
368
370
  evaluationType:
369
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType,
371
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
372
+ data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
370
373
  })
371
374
  ) {
372
375
  return CompareCriteria.getCompareMessage({
@@ -385,7 +388,8 @@ export default class CompareCriteria {
385
388
  threshold: data.threshold as number,
386
389
  value: data.value,
387
390
  evaluationType:
388
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType,
391
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
392
+ data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
389
393
  })
390
394
  ) {
391
395
  return CompareCriteria.getCompareMessage({
@@ -404,7 +408,8 @@ export default class CompareCriteria {
404
408
  threshold: data.threshold as number,
405
409
  value: data.value,
406
410
  evaluationType:
407
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType,
411
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
412
+ data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
408
413
  })
409
414
  ) {
410
415
  return CompareCriteria.getCompareMessage({
@@ -423,7 +428,8 @@ export default class CompareCriteria {
423
428
  threshold: data.threshold as number,
424
429
  value: data.value,
425
430
  evaluationType:
426
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType,
431
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
432
+ data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
427
433
  })
428
434
  ) {
429
435
  return CompareCriteria.getCompareMessage({
@@ -442,7 +448,8 @@ export default class CompareCriteria {
442
448
  threshold: data.threshold as number,
443
449
  value: data.value,
444
450
  evaluationType:
445
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType,
451
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
452
+ data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
446
453
  })
447
454
  ) {
448
455
  return CompareCriteria.getCompareMessage({
@@ -461,7 +468,8 @@ export default class CompareCriteria {
461
468
  threshold: data.threshold as number,
462
469
  value: data.value,
463
470
  evaluationType:
464
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType,
471
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
472
+ data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
465
473
  })
466
474
  ) {
467
475
  return CompareCriteria.getCompareMessage({
@@ -485,17 +493,19 @@ export default class CompareCriteria {
485
493
  // CPU Percent over the last 5 minutes is 10 which is less than the threshold of 20
486
494
  let message: string = "";
487
495
 
488
- if (
489
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ===
490
- EvaluateOverTimeType.AnyValue
491
- ) {
496
+ let evaluationType: EvaluateOverTimeType | undefined =
497
+ data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType;
498
+
499
+ if (data.criteriaFilter.metricMonitorOptions?.metricAggregationType) {
500
+ evaluationType =
501
+ data.criteriaFilter.metricMonitorOptions.metricAggregationType;
502
+ }
503
+
504
+ if (evaluationType === EvaluateOverTimeType.AnyValue) {
492
505
  message += "Any value of";
493
506
  }
494
507
 
495
- if (
496
- data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ===
497
- EvaluateOverTimeType.AllValues
498
- ) {
508
+ if (evaluationType === EvaluateOverTimeType.AllValues) {
499
509
  message += "All values of";
500
510
  }
501
511