@oneuptime/common 9.1.0 → 9.1.2

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 (75) hide show
  1. package/Models/DatabaseModels/BillingPaymentMethod.ts +45 -9
  2. package/Models/DatabaseModels/Incident.ts +37 -0
  3. package/Models/DatabaseModels/StatusPageDomain.ts +3 -2
  4. package/Models/DatabaseModels/User.ts +1 -1
  5. package/Server/API/BillingPaymentMethodAPI.ts +6 -7
  6. package/Server/API/StatusPageAPI.ts +6 -1
  7. package/Server/EnvironmentConfig.ts +11 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-MigrationName.ts +30 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  10. package/Server/Services/IncidentService.ts +38 -0
  11. package/Server/Services/MonitorService.ts +49 -31
  12. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +18 -0
  13. package/Server/Services/StatusPageDomainService.ts +17 -10
  14. package/Server/Services/TeamMemberService.ts +8 -7
  15. package/Server/Services/TeamService.ts +2 -1
  16. package/Server/Types/Workflow/Components/Email.ts +10 -1
  17. package/Server/Utils/Browser.ts +28 -2
  18. package/Server/Utils/Captcha.ts +98 -0
  19. package/Server/Utils/Greenlock/Greenlock.ts +10 -3
  20. package/Server/Utils/Telemetry.ts +10 -2
  21. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +4 -1
  22. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +23 -10
  23. package/Tests/Server/Services/TeamMemberService.test.ts +119 -0
  24. package/UI/Components/Captcha/Captcha.tsx +75 -0
  25. package/UI/Config.ts +3 -0
  26. package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js +45 -9
  27. package/build/dist/Models/DatabaseModels/BillingPaymentMethod.js.map +1 -1
  28. package/build/dist/Models/DatabaseModels/Incident.js +39 -0
  29. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  30. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +2 -2
  31. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  32. package/build/dist/Models/DatabaseModels/User.js +1 -1
  33. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  34. package/build/dist/Server/API/BillingPaymentMethodAPI.js +6 -5
  35. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  36. package/build/dist/Server/API/StatusPageAPI.js +6 -1
  37. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  38. package/build/dist/Server/EnvironmentConfig.js +6 -0
  39. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  40. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-MigrationName.js +17 -0
  41. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-MigrationName.js.map +1 -0
  42. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  43. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  44. package/build/dist/Server/Services/IncidentService.js +32 -1
  45. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  46. package/build/dist/Server/Services/MonitorService.js +44 -24
  47. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  48. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +12 -0
  49. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  50. package/build/dist/Server/Services/StatusPageDomainService.js +12 -9
  51. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  52. package/build/dist/Server/Services/TeamMemberService.js +9 -8
  53. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  54. package/build/dist/Server/Services/TeamService.js +2 -1
  55. package/build/dist/Server/Services/TeamService.js.map +1 -1
  56. package/build/dist/Server/Types/Workflow/Components/Email.js +8 -1
  57. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  58. package/build/dist/Server/Utils/Browser.js +24 -2
  59. package/build/dist/Server/Utils/Browser.js.map +1 -1
  60. package/build/dist/Server/Utils/Captcha.js +58 -0
  61. package/build/dist/Server/Utils/Captcha.js.map +1 -0
  62. package/build/dist/Server/Utils/Greenlock/Greenlock.js +7 -2
  63. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  64. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  65. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +3 -1
  66. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  67. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +19 -11
  68. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  69. package/build/dist/Tests/Server/Services/TeamMemberService.test.js +79 -0
  70. package/build/dist/Tests/Server/Services/TeamMemberService.test.js.map +1 -1
  71. package/build/dist/UI/Components/Captcha/Captcha.js +37 -0
  72. package/build/dist/UI/Components/Captcha/Captcha.js.map +1 -0
  73. package/build/dist/UI/Config.js +2 -0
  74. package/build/dist/UI/Config.js.map +1 -1
  75. package/package.json +2 -1
@@ -20,7 +20,11 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
20
20
  @AllowAccessIfSubscriptionIsUnpaid()
21
21
  @TenantColumn("projectId")
22
22
  @TableAccessControl({
23
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
23
+ create: [
24
+ Permission.ProjectOwner,
25
+ Permission.ManageProjectBilling,
26
+ Permission.CreateBillingPaymentMethod,
27
+ ],
24
28
  read: [
25
29
  Permission.ProjectOwner,
26
30
  Permission.ProjectUser,
@@ -28,7 +32,11 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
28
32
  Permission.ProjectMember,
29
33
  Permission.ReadBillingPaymentMethod,
30
34
  ],
31
- delete: [Permission.ProjectOwner, Permission.DeleteBillingPaymentMethod],
35
+ delete: [
36
+ Permission.ProjectOwner,
37
+ Permission.ManageProjectBilling,
38
+ Permission.DeleteBillingPaymentMethod,
39
+ ],
32
40
  update: [],
33
41
  })
34
42
  @CrudApiEndpoint(new Route("/billing-payment-methods"))
@@ -45,7 +53,11 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
45
53
  })
46
54
  export default class BillingPaymentMethod extends BaseModel {
47
55
  @ColumnAccessControl({
48
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
56
+ create: [
57
+ Permission.ProjectOwner,
58
+ Permission.ManageProjectBilling,
59
+ Permission.CreateBillingPaymentMethod,
60
+ ],
49
61
  read: [
50
62
  Permission.ProjectOwner,
51
63
  Permission.ProjectUser,
@@ -77,7 +89,11 @@ export default class BillingPaymentMethod extends BaseModel {
77
89
  public project?: Project = undefined;
78
90
 
79
91
  @ColumnAccessControl({
80
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
92
+ create: [
93
+ Permission.ProjectOwner,
94
+ Permission.ManageProjectBilling,
95
+ Permission.CreateBillingPaymentMethod,
96
+ ],
81
97
  read: [
82
98
  Permission.ProjectOwner,
83
99
  Permission.ProjectAdmin,
@@ -103,7 +119,11 @@ export default class BillingPaymentMethod extends BaseModel {
103
119
  public projectId?: ObjectID = undefined;
104
120
 
105
121
  @ColumnAccessControl({
106
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
122
+ create: [
123
+ Permission.ProjectOwner,
124
+ Permission.ManageProjectBilling,
125
+ Permission.CreateBillingPaymentMethod,
126
+ ],
107
127
  read: [
108
128
  Permission.ProjectOwner,
109
129
  Permission.ProjectAdmin,
@@ -136,7 +156,11 @@ export default class BillingPaymentMethod extends BaseModel {
136
156
  public createdByUser?: User = undefined;
137
157
 
138
158
  @ColumnAccessControl({
139
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
159
+ create: [
160
+ Permission.ProjectOwner,
161
+ Permission.ManageProjectBilling,
162
+ Permission.CreateBillingPaymentMethod,
163
+ ],
140
164
  read: [
141
165
  Permission.ProjectOwner,
142
166
  Permission.ProjectAdmin,
@@ -218,7 +242,11 @@ export default class BillingPaymentMethod extends BaseModel {
218
242
  public deletedByUserId?: ObjectID = undefined;
219
243
 
220
244
  @ColumnAccessControl({
221
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
245
+ create: [
246
+ Permission.ProjectOwner,
247
+ Permission.ManageProjectBilling,
248
+ Permission.CreateBillingPaymentMethod,
249
+ ],
222
250
  read: [
223
251
  Permission.ProjectOwner,
224
252
  Permission.ProjectAdmin,
@@ -278,7 +306,11 @@ export default class BillingPaymentMethod extends BaseModel {
278
306
  public paymentProviderCustomerId?: string = undefined;
279
307
 
280
308
  @ColumnAccessControl({
281
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
309
+ create: [
310
+ Permission.ProjectOwner,
311
+ Permission.ManageProjectBilling,
312
+ Permission.CreateBillingPaymentMethod,
313
+ ],
282
314
  read: [
283
315
  Permission.ProjectOwner,
284
316
  Permission.ProjectAdmin,
@@ -298,7 +330,11 @@ export default class BillingPaymentMethod extends BaseModel {
298
330
  public last4Digits?: string = undefined;
299
331
 
300
332
  @ColumnAccessControl({
301
- create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod],
333
+ create: [
334
+ Permission.ProjectOwner,
335
+ Permission.ManageProjectBilling,
336
+ Permission.CreateBillingPaymentMethod,
337
+ ],
302
338
  read: [
303
339
  Permission.ProjectOwner,
304
340
  Permission.ProjectAdmin,
@@ -228,6 +228,43 @@ export default class Incident extends BaseModel {
228
228
  })
229
229
  public description?: string = undefined;
230
230
 
231
+ @ColumnAccessControl({
232
+ create: [
233
+ Permission.ProjectOwner,
234
+ Permission.ProjectAdmin,
235
+ Permission.ProjectMember,
236
+ Permission.CreateProjectIncident,
237
+ ],
238
+ read: [
239
+ Permission.ProjectOwner,
240
+ Permission.ProjectAdmin,
241
+ Permission.ProjectMember,
242
+ Permission.ReadProjectIncident,
243
+ ],
244
+ update: [
245
+ Permission.ProjectOwner,
246
+ Permission.ProjectAdmin,
247
+ Permission.ProjectMember,
248
+ Permission.EditProjectIncident,
249
+ ],
250
+ })
251
+ @Index()
252
+ @TableColumn({
253
+ required: true,
254
+ type: TableColumnType.Date,
255
+ title: "Declared At",
256
+ description: "Date and time when this incident was declared.",
257
+ isDefaultValueColumn: true,
258
+ })
259
+ @Column({
260
+ type: ColumnType.Date,
261
+ nullable: false,
262
+ default: () => {
263
+ return "now()";
264
+ },
265
+ })
266
+ public declaredAt?: Date = undefined;
267
+
231
268
  @Index()
232
269
  @ColumnAccessControl({
233
270
  create: [],
@@ -282,8 +282,9 @@ export default class StatusPageDomain extends BaseModel {
282
282
  @TableColumn({
283
283
  required: true,
284
284
  type: TableColumnType.ShortText,
285
- title: "Sumdomain",
286
- description: "Subdomain of your status page - like (status)",
285
+ title: "Subdomain",
286
+ description:
287
+ "Subdomain label for your status page such as 'status'. Leave blank or enter @ to use the root domain.",
287
288
  })
288
289
  @Column({
289
290
  nullable: false,
@@ -30,7 +30,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
30
30
  })
31
31
  @AllowAccessIfSubscriptionIsUnpaid()
32
32
  @TableAccessControl({
33
- create: [Permission.Public],
33
+ create: [],
34
34
  read: [Permission.CurrentUser],
35
35
  delete: [Permission.CurrentUser],
36
36
  update: [Permission.CurrentUser],
@@ -42,17 +42,16 @@ export default class UserAPI extends BaseAPI<
42
42
  const userPermissions: Array<UserPermission> = (
43
43
  await this.getPermissionsForTenant(req)
44
44
  ).filter((permission: UserPermission) => {
45
- return (
46
- permission.permission.toString() ===
47
- Permission.ProjectOwner.toString() ||
48
- permission.permission.toString() ===
49
- Permission.CreateBillingPaymentMethod.toString()
50
- );
45
+ return [
46
+ Permission.ProjectOwner,
47
+ Permission.ManageProjectBilling,
48
+ Permission.CreateBillingPaymentMethod,
49
+ ].includes(permission.permission);
51
50
  });
52
51
 
53
52
  if (userPermissions.length === 0) {
54
53
  throw new BadDataException(
55
- "Only Project owner can add payment methods.",
54
+ "Only project owners or members with Manage Billing access can add payment methods.",
56
55
  );
57
56
  }
58
57
 
@@ -1421,6 +1421,7 @@ export default class StatusPageAPI extends BaseAPI<
1421
1421
  if (monitorsOnStatusPage.length > 0) {
1422
1422
  let select: Select<Incident> = {
1423
1423
  createdAt: true,
1424
+ declaredAt: true,
1424
1425
  title: true,
1425
1426
  description: true,
1426
1427
  _id: true,
@@ -1474,6 +1475,7 @@ export default class StatusPageAPI extends BaseAPI<
1474
1475
  },
1475
1476
  select: select,
1476
1477
  sort: {
1478
+ declaredAt: SortOrder.Descending,
1477
1479
  createdAt: SortOrder.Descending,
1478
1480
  },
1479
1481
 
@@ -2809,7 +2811,7 @@ export default class StatusPageAPI extends BaseAPI<
2809
2811
  manageSubscriptionUrl: manageUrlink,
2810
2812
  },
2811
2813
  subject:
2812
- "Manage your Subscription for" +
2814
+ "Manage your Subscription for " +
2813
2815
  (statusPage.name || "Status Page"),
2814
2816
  },
2815
2817
  {
@@ -3303,6 +3305,7 @@ export default class StatusPageAPI extends BaseAPI<
3303
3305
 
3304
3306
  let selectIncidents: Select<Incident> = {
3305
3307
  createdAt: true,
3308
+ declaredAt: true,
3306
3309
  title: true,
3307
3310
  description: true,
3308
3311
  _id: true,
@@ -3336,6 +3339,7 @@ export default class StatusPageAPI extends BaseAPI<
3336
3339
  query: incidentQuery,
3337
3340
  select: selectIncidents,
3338
3341
  sort: {
3342
+ declaredAt: SortOrder.Descending,
3339
3343
  createdAt: SortOrder.Descending,
3340
3344
  },
3341
3345
  skip: 0,
@@ -3373,6 +3377,7 @@ export default class StatusPageAPI extends BaseAPI<
3373
3377
  },
3374
3378
  select: selectIncidents,
3375
3379
  sort: {
3380
+ declaredAt: SortOrder.Descending,
3376
3381
  createdAt: SortOrder.Descending,
3377
3382
  },
3378
3383
 
@@ -44,6 +44,8 @@ const FRONTEND_ENV_ALLOW_LIST: Array<string> = [
44
44
  "DISABLE_TELEMETRY",
45
45
  "SLACK_APP_CLIENT_ID",
46
46
  "MICROSOFT_TEAMS_APP_CLIENT_ID",
47
+ "CAPTCHA_ENABLED",
48
+ "CAPTCHA_SITE_KEY",
47
49
  ];
48
50
 
49
51
  const FRONTEND_ENV_ALLOW_PREFIXES: Array<string> = [
@@ -324,6 +326,13 @@ export const Host: string = process.env["HOST"] || "";
324
326
 
325
327
  export const ProvisionSsl: boolean = process.env["PROVISION_SSL"] === "true";
326
328
 
329
+ export const CaptchaEnabled: boolean =
330
+ process.env["CAPTCHA_ENABLED"] === "true";
331
+
332
+ export const CaptchaSecretKey: string = process.env["CAPTCHA_SECRET_KEY"] || "";
333
+
334
+ export const CaptchaSiteKey: string = process.env["CAPTCHA_SITE_KEY"] || "";
335
+
327
336
  export const WorkflowScriptTimeoutInMS: number = process.env[
328
337
  "WORKFLOW_SCRIPT_TIMEOUT_IN_MS"
329
338
  ]
@@ -446,6 +455,8 @@ export const MicrosoftTeamsAppClientId: string | null =
446
455
  process.env["MICROSOFT_TEAMS_APP_CLIENT_ID"] || null;
447
456
  export const MicrosoftTeamsAppClientSecret: string | null =
448
457
  process.env["MICROSOFT_TEAMS_APP_CLIENT_SECRET"] || null;
458
+ export const MicrosoftTeamsAppTenantId: string | null =
459
+ process.env["MICROSOFT_TEAMS_APP_TENANT_ID"] || null;
449
460
 
450
461
  // VAPID Configuration for Web Push Notifications
451
462
  export const VapidPublicKey: string | undefined =
@@ -0,0 +1,30 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1764324618043 implements MigrationInterface {
4
+ public name = "MigrationName1764324618043";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "Incident" ADD "declaredAt" TIMESTAMP WITH TIME ZONE`,
9
+ );
10
+ await queryRunner.query(
11
+ `UPDATE "Incident" SET "declaredAt" = "createdAt" WHERE "declaredAt" IS NULL`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "Incident" ALTER COLUMN "declaredAt" SET DEFAULT now()`,
15
+ );
16
+ await queryRunner.query(
17
+ `ALTER TABLE "Incident" ALTER COLUMN "declaredAt" SET NOT NULL`,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_b26979b9f119310661734465a4" ON "Incident" ("declaredAt") `,
21
+ );
22
+ }
23
+
24
+ public async down(queryRunner: QueryRunner): Promise<void> {
25
+ await queryRunner.query(
26
+ `DROP INDEX "public"."IDX_b26979b9f119310661734465a4"`,
27
+ );
28
+ await queryRunner.query(`ALTER TABLE "Incident" DROP COLUMN "declaredAt"`);
29
+ }
30
+ }
@@ -186,6 +186,7 @@ import { MigrationName1763471659817 } from "./1763471659817-MigrationName";
186
186
  import { MigrationName1763477560906 } from "./1763477560906-MigrationName";
187
187
  import { MigrationName1763480947474 } from "./1763480947474-MigrationName";
188
188
  import { MigrationName1763643080445 } from "./1763643080445-MigrationName";
189
+ import { MigrationName1764324618043 } from "./1764324618043-MigrationName";
189
190
 
190
191
  export default [
191
192
  InitialMigration,
@@ -376,4 +377,5 @@ export default [
376
377
  MigrationName1763477560906,
377
378
  MigrationName1763480947474,
378
379
  MigrationName1763643080445,
380
+ MigrationName1764324618043,
379
381
  ];
@@ -480,6 +480,14 @@ export class Service extends DatabaseService<Model> {
480
480
  const projectId: ObjectID =
481
481
  createBy.props.tenantId || createBy.data.projectId!;
482
482
 
483
+ if (!createBy.data.declaredAt) {
484
+ createBy.data.declaredAt = OneUptimeDate.getCurrentDate();
485
+ } else {
486
+ createBy.data.declaredAt = OneUptimeDate.fromString(
487
+ createBy.data.declaredAt as Date,
488
+ );
489
+ }
490
+
483
491
  // Determine the initial incident state
484
492
  let initialIncidentStateId: ObjectID | undefined = undefined;
485
493
 
@@ -975,6 +983,7 @@ ${incident.remediationNotes || "No remediation notes provided."}
975
983
  notifyOwners: false,
976
984
  rootCause: createdItem.rootCause,
977
985
  stateChangeLog: createdItem.createdStateLog,
986
+ timelineStartsAt: createdItem.declaredAt,
978
987
  props: {
979
988
  isRoot: true,
980
989
  },
@@ -1790,6 +1799,7 @@ ${incidentSeverity.name}
1790
1799
  limit: LIMIT_MAX,
1791
1800
  skip: 0,
1792
1801
  select: {
1802
+ _id: true,
1793
1803
  projectId: true,
1794
1804
  monitors: {
1795
1805
  _id: true,
@@ -1821,6 +1831,18 @@ ${incidentSeverity.name}
1821
1831
  incident.monitors,
1822
1832
  );
1823
1833
  }
1834
+
1835
+ if (incident.projectId && incident.id) {
1836
+ await MetricService.deleteBy({
1837
+ query: {
1838
+ projectId: incident.projectId,
1839
+ serviceId: incident.id,
1840
+ },
1841
+ props: {
1842
+ isRoot: true,
1843
+ },
1844
+ });
1845
+ }
1824
1846
  }
1825
1847
  }
1826
1848
 
@@ -1838,6 +1860,7 @@ ${incidentSeverity.name}
1838
1860
  rootCause: string | undefined;
1839
1861
  stateChangeLog: JSONObject | undefined;
1840
1862
  props: DatabaseCommonInteractionProps | undefined;
1863
+ timelineStartsAt?: Date | string | undefined;
1841
1864
  }): Promise<void> {
1842
1865
  const {
1843
1866
  projectId,
@@ -1849,8 +1872,13 @@ ${incidentSeverity.name}
1849
1872
  rootCause,
1850
1873
  stateChangeLog,
1851
1874
  props,
1875
+ timelineStartsAt,
1852
1876
  } = data;
1853
1877
 
1878
+ const declaredTimelineStart: Date | undefined = timelineStartsAt
1879
+ ? OneUptimeDate.fromString(timelineStartsAt as Date)
1880
+ : undefined;
1881
+
1854
1882
  // get last monitor status timeline.
1855
1883
  const lastIncidentStatusTimeline: IncidentStateTimeline | null =
1856
1884
  await IncidentStateTimelineService.findOneBy({
@@ -1888,6 +1916,10 @@ ${incidentSeverity.name}
1888
1916
  statusTimeline.shouldStatusPageSubscribersBeNotified =
1889
1917
  shouldNotifyStatusPageSubscribers;
1890
1918
 
1919
+ if (!lastIncidentStatusTimeline && declaredTimelineStart) {
1920
+ statusTimeline.startsAt = declaredTimelineStart;
1921
+ }
1922
+
1891
1923
  // Map boolean to enum value
1892
1924
  statusTimeline.subscriberNotificationStatus = isSubscribersNotified
1893
1925
  ? StatusPageSubscriberNotificationStatus.Success
@@ -1914,6 +1946,7 @@ ${incidentSeverity.name}
1914
1946
  id: data.incidentId,
1915
1947
  select: {
1916
1948
  projectId: true,
1949
+ declaredAt: true,
1917
1950
  monitors: {
1918
1951
  _id: true,
1919
1952
  name: true,
@@ -1970,6 +2003,7 @@ ${incidentSeverity.name}
1970
2003
 
1971
2004
  await MetricService.deleteBy({
1972
2005
  query: {
2006
+ projectId: incident.projectId,
1973
2007
  serviceId: data.incidentId,
1974
2008
  },
1975
2009
  props: {
@@ -1983,6 +2017,7 @@ ${incidentSeverity.name}
1983
2017
 
1984
2018
  const incidentStartsAt: Date =
1985
2019
  firstIncidentStateTimeline?.startsAt ||
2020
+ incident.declaredAt ||
1986
2021
  incident.createdAt ||
1987
2022
  OneUptimeDate.getCurrentDate();
1988
2023
 
@@ -2075,6 +2110,7 @@ ${incidentSeverity.name}
2075
2110
 
2076
2111
  timeToAcknowledgeMetric.time =
2077
2112
  ackIncidentStateTimeline?.startsAt ||
2113
+ incident.declaredAt ||
2078
2114
  incident.createdAt ||
2079
2115
  OneUptimeDate.getCurrentDate();
2080
2116
  timeToAcknowledgeMetric.timeUnixNano = OneUptimeDate.toUnixNano(
@@ -2140,6 +2176,7 @@ ${incidentSeverity.name}
2140
2176
 
2141
2177
  timeToResolveMetric.time =
2142
2178
  resolvedIncidentStateTimeline?.startsAt ||
2179
+ incident.declaredAt ||
2143
2180
  incident.createdAt ||
2144
2181
  OneUptimeDate.getCurrentDate();
2145
2182
  timeToResolveMetric.timeUnixNano = OneUptimeDate.toUnixNano(
@@ -2200,6 +2237,7 @@ ${incidentSeverity.name}
2200
2237
 
2201
2238
  incidentDurationMetric.time =
2202
2239
  lastIncidentStateTimeline?.startsAt ||
2240
+ incident.declaredAt ||
2203
2241
  incident.createdAt ||
2204
2242
  OneUptimeDate.getCurrentDate();
2205
2243
  incidentDurationMetric.timeUnixNano = OneUptimeDate.toUnixNano(
@@ -63,14 +63,13 @@ import MonitorFeedService from "./MonitorFeedService";
63
63
  import { MonitorFeedEventType } from "../../Models/DatabaseModels/MonitorFeed";
64
64
  import { Gray500, Green500 } from "../../Types/BrandColors";
65
65
  import LabelService from "./LabelService";
66
- import QueryOperator from "../../Types/BaseDatabase/QueryOperator";
67
- import { FindWhere } from "../../Types/BaseDatabase/Query";
68
66
  import logger from "../Utils/Logger";
69
67
  import PushNotificationUtil from "../Utils/PushNotificationUtil";
70
68
  import ExceptionMessages from "../../Types/Exception/ExceptionMessages";
71
69
  import Project from "../../Models/DatabaseModels/Project";
72
70
  import { createWhatsAppMessageFromTemplate } from "../Utils/WhatsAppTemplateUtil";
73
71
  import { WhatsAppMessagePayload } from "../../Types/WhatsApp/WhatsAppMessage";
72
+ import MetricService from "./MetricService";
74
73
 
75
74
  export class Service extends DatabaseService<Model> {
76
75
  public constructor() {
@@ -136,12 +135,26 @@ export class Service extends DatabaseService<Model> {
136
135
  protected override async onBeforeDelete(
137
136
  deleteBy: DeleteBy<Model>,
138
137
  ): Promise<OnDelete<Model>> {
139
- if (deleteBy.query._id) {
140
- // delete all the status page resource for this monitor.
138
+ const monitorsPendingDeletion: Array<Model> = await this.findBy({
139
+ query: deleteBy.query,
140
+ limit: LIMIT_MAX,
141
+ skip: 0,
142
+ select: {
143
+ _id: true,
144
+ projectId: true,
145
+ },
146
+ props: deleteBy.props,
147
+ });
141
148
 
149
+ for (const monitor of monitorsPendingDeletion) {
150
+ if (!monitor.id) {
151
+ continue;
152
+ }
153
+
154
+ // delete all the status page resources for this monitor.
142
155
  await StatusPageResourceService.deleteBy({
143
156
  query: {
144
- monitorId: new ObjectID(deleteBy.query._id as string),
157
+ monitorId: monitor.id,
145
158
  },
146
159
  limit: LIMIT_MAX,
147
160
  skip: 0,
@@ -150,37 +163,19 @@ export class Service extends DatabaseService<Model> {
150
163
  },
151
164
  });
152
165
 
153
- let projectId: FindWhere<ObjectID> | QueryOperator<ObjectID> | undefined =
154
- deleteBy.query.projectId || deleteBy.props.tenantId;
166
+ const projectId: ObjectID | undefined = monitor.projectId as
167
+ | ObjectID
168
+ | undefined;
155
169
 
156
170
  if (!projectId) {
157
- // fetch this monitor from the database to get the projectId.
158
- const monitor: Model | null = await this.findOneById({
159
- id: new ObjectID(deleteBy.query._id as string) as ObjectID,
160
- select: {
161
- projectId: true,
162
- },
163
- props: {
164
- isRoot: true,
165
- },
166
- });
167
-
168
- if (!monitor) {
169
- throw new BadDataException(ExceptionMessages.MonitorNotFound);
170
- }
171
-
172
- if (!monitor.id) {
173
- throw new BadDataException(ExceptionMessages.MonitorNotFound);
174
- }
175
-
176
- projectId = monitor.projectId!;
171
+ continue;
177
172
  }
178
173
 
179
174
  try {
180
175
  await WorkspaceNotificationRuleService.archiveWorkspaceChannels({
181
- projectId: projectId as ObjectID,
176
+ projectId: projectId,
182
177
  notificationFor: {
183
- monitorId: new ObjectID(deleteBy.query._id as string) as ObjectID,
178
+ monitorId: monitor.id,
184
179
  },
185
180
  sendMessageBeforeArchiving: {
186
181
  _type: "WorkspacePayloadMarkdown",
@@ -189,12 +184,17 @@ export class Service extends DatabaseService<Model> {
189
184
  });
190
185
  } catch (error) {
191
186
  logger.error(
192
- `Error while archiving workspace channels for monitor ${deleteBy.query._id}: ${error}`,
187
+ `Error while archiving workspace channels for monitor ${monitor.id?.toString()}: ${error}`,
193
188
  );
194
189
  }
195
190
  }
196
191
 
197
- return { deleteBy, carryForward: null };
192
+ return {
193
+ deleteBy,
194
+ carryForward: {
195
+ monitors: monitorsPendingDeletion,
196
+ },
197
+ };
198
198
  }
199
199
 
200
200
  @CaptureSpan()
@@ -208,6 +208,24 @@ export class Service extends DatabaseService<Model> {
208
208
  );
209
209
  }
210
210
 
211
+ if (onDelete.carryForward && onDelete.carryForward.monitors) {
212
+ for (const monitor of onDelete.carryForward.monitors as Array<Model>) {
213
+ if (!monitor.projectId || !monitor.id) {
214
+ continue;
215
+ }
216
+
217
+ await MetricService.deleteBy({
218
+ query: {
219
+ projectId: monitor.projectId,
220
+ serviceId: monitor.id,
221
+ },
222
+ props: {
223
+ isRoot: true,
224
+ },
225
+ });
226
+ }
227
+ }
228
+
211
229
  return onDelete;
212
230
  }
213
231
 
@@ -510,12 +510,30 @@ export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline>
510
510
  monitors: {
511
511
  _id: true,
512
512
  },
513
+ nextSubscriberNotificationBeforeTheEventAt: true,
513
514
  },
514
515
  props: {
515
516
  isRoot: true,
516
517
  },
517
518
  });
518
519
 
520
+ const hasProgressedBeyondScheduledState: boolean = Boolean(
521
+ scheduledMaintenanceState && !scheduledMaintenanceState.isScheduledState,
522
+ );
523
+
524
+ if (
525
+ hasProgressedBeyondScheduledState &&
526
+ scheduledMaintenanceEvent?.nextSubscriberNotificationBeforeTheEventAt
527
+ ) {
528
+ await ScheduledMaintenanceService.updateOneById({
529
+ id: createdItem.scheduledMaintenanceId!,
530
+ data: {
531
+ nextSubscriberNotificationBeforeTheEventAt: null,
532
+ },
533
+ props: onCreate.createBy.props,
534
+ });
535
+ }
536
+
519
537
  if (isOngoingState) {
520
538
  if (
521
539
  scheduledMaintenanceEvent &&
@@ -48,19 +48,26 @@ export class Service extends DatabaseService<StatusPageDomain> {
48
48
  );
49
49
  }
50
50
 
51
- if (createBy.data.subdomain) {
52
- // trim and lowercase the subdomain.
53
- createBy.data.subdomain = createBy.data.subdomain.trim().toLowerCase();
51
+ let normalizedSubdomain: string =
52
+ createBy.data.subdomain?.trim().toLowerCase() || "";
53
+
54
+ if (normalizedSubdomain === "@") {
55
+ normalizedSubdomain = "";
54
56
  }
55
57
 
58
+ createBy.data.subdomain = normalizedSubdomain;
59
+
56
60
  if (domain) {
57
- createBy.data.fullDomain = (
58
- createBy.data.subdomain +
59
- "." +
60
- domain.domain?.toString()
61
- )
62
- .toLowerCase()
63
- .trim();
61
+ const baseDomain: string =
62
+ domain.domain?.toString().toLowerCase().trim() || "";
63
+
64
+ if (!baseDomain) {
65
+ throw new BadDataException("Please select a valid domain.");
66
+ }
67
+
68
+ createBy.data.fullDomain = normalizedSubdomain
69
+ ? `${normalizedSubdomain}.${baseDomain}`
70
+ : baseDomain;
64
71
  }
65
72
 
66
73
  createBy.data.cnameVerificationToken = ObjectID.generate().toString();