@oneuptime/common 7.0.3515 → 7.0.3526

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/Models/DatabaseModels/Incident.ts +6 -1
  2. package/Models/DatabaseModels/ScheduledMaintenanceFeed.ts +15 -7
  3. package/Server/API/BillingInvoiceAPI.ts +2 -32
  4. package/Server/Services/AlertFeedService.ts +44 -37
  5. package/Server/Services/BillingInvoiceService.ts +170 -0
  6. package/Server/Services/IncidentFeedService.ts +59 -54
  7. package/Server/Services/IncidentInternalNoteService.ts +46 -1
  8. package/Server/Services/IncidentPublicNoteService.ts +47 -2
  9. package/Server/Services/IncidentService.ts +18 -0
  10. package/Server/Services/ScheduledMaintenanceFeedService.ts +51 -40
  11. package/Server/Services/ScheduledMaintenanceInternalNoteService.ts +75 -0
  12. package/Server/Services/ScheduledMaintenanceOwnerTeamService.ts +116 -0
  13. package/Server/Services/ScheduledMaintenanceOwnerUserService.ts +118 -0
  14. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +74 -1
  15. package/Server/Services/ScheduledMaintenanceService.ts +68 -0
  16. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +30 -0
  17. package/Server/Utils/Logger.ts +14 -0
  18. package/UI/Utils/Project.ts +25 -0
  19. package/build/dist/Models/DatabaseModels/Incident.js +6 -1
  20. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  21. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceFeed.js +15 -7
  22. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceFeed.js.map +1 -1
  23. package/build/dist/Server/API/BillingInvoiceAPI.js +2 -19
  24. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  25. package/build/dist/Server/Services/AlertFeedService.js +43 -35
  26. package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
  27. package/build/dist/Server/Services/BillingInvoiceService.js +123 -0
  28. package/build/dist/Server/Services/BillingInvoiceService.js.map +1 -1
  29. package/build/dist/Server/Services/IncidentFeedService.js +45 -39
  30. package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
  31. package/build/dist/Server/Services/IncidentInternalNoteService.js +37 -0
  32. package/build/dist/Server/Services/IncidentInternalNoteService.js.map +1 -1
  33. package/build/dist/Server/Services/IncidentPublicNoteService.js +38 -1
  34. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  35. package/build/dist/Server/Services/IncidentService.js +15 -0
  36. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  37. package/build/dist/Server/Services/ScheduledMaintenanceFeedService.js +45 -37
  38. package/build/dist/Server/Services/ScheduledMaintenanceFeedService.js.map +1 -1
  39. package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js +56 -0
  40. package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js.map +1 -1
  41. package/build/dist/Server/Services/ScheduledMaintenanceOwnerTeamService.js +86 -0
  42. package/build/dist/Server/Services/ScheduledMaintenanceOwnerTeamService.js.map +1 -1
  43. package/build/dist/Server/Services/ScheduledMaintenanceOwnerUserService.js +89 -0
  44. package/build/dist/Server/Services/ScheduledMaintenanceOwnerUserService.js.map +1 -1
  45. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +56 -0
  46. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  47. package/build/dist/Server/Services/ScheduledMaintenanceService.js +57 -0
  48. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  49. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +24 -0
  50. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  51. package/build/dist/Server/Utils/Logger.js +11 -0
  52. package/build/dist/Server/Utils/Logger.js.map +1 -1
  53. package/build/dist/UI/Utils/Project.js +11 -0
  54. package/build/dist/UI/Utils/Project.js.map +1 -1
  55. package/package.json +2 -2
@@ -853,7 +853,12 @@ export default class Incident extends BaseModel {
853
853
  Permission.ProjectMember,
854
854
  Permission.ReadProjectIncident,
855
855
  ],
856
- update: [],
856
+ update: [
857
+ Permission.ProjectOwner,
858
+ Permission.ProjectAdmin,
859
+ Permission.ProjectMember,
860
+ Permission.EditProjectIncident,
861
+ ],
857
862
  })
858
863
  @TableColumn({
859
864
  type: TableColumnType.Markdown,
@@ -23,12 +23,20 @@ import Color from "../../Types/Color";
23
23
 
24
24
  export enum ScheduledMaintenanceFeedEventType {
25
25
  PublicNote = "PublicNote",
26
- SubscriberEmailSent = "SubscriberEmailSent",
27
- OwnerEmailSent = "OwnerEmailSent",
26
+ SubscriberNotificationSent = "SubscriberNotificationSent",
27
+ OwnerNotificationSent = "OwnerNotificationSent",
28
+ OwnerUserAdded = "OwnerUserAdded",
29
+ OwnerTeamAdded = "OwnerTeamAdded",
28
30
  ScheduledMaintenanceCreated = "ScheduledMaintenanceCreated",
29
- ScheduledMaintenanceAcknowledged = "ScheduledMaintenanceAcknowledged",
30
- ScheduledMaintenanceResolved = "ScheduledMaintenanceResolved",
31
+ ScheduledMaintenanceStateChanged = "ScheduledMaintenanceStateChanged",
31
32
  PrivateNote = "PrivateNote",
33
+ ScheduledMaintenanceUpdated = "ScheduledMaintenanceUpdated",
34
+ RootCause = "RootCause",
35
+ RemediationNotes = "RemediationNotes",
36
+ OwnerUserRemoved = "OwnerUserRemoved",
37
+ OwnerTeamRemoved = "OwnerTeamRemoved",
38
+ OnCallPolicy = "OnCallPolicy",
39
+ OnCallNotification = "OnCallNotification",
32
40
  }
33
41
 
34
42
  @EnableDocumentation()
@@ -332,7 +340,7 @@ export default class ScheduledMaintenanceFeed extends BaseModel {
332
340
  required: true,
333
341
  title: "Log (in Markdown)",
334
342
  description:
335
- "Log of the entire scheduledMaintenance state change in Markdown",
343
+ "Log of the entire scheduled maintenance state change in Markdown",
336
344
  })
337
345
  @Column({
338
346
  type: ColumnType.Markdown,
@@ -403,13 +411,13 @@ export default class ScheduledMaintenanceFeed extends BaseModel {
403
411
  Permission.ProjectOwner,
404
412
  Permission.ProjectAdmin,
405
413
  Permission.ProjectMember,
406
- Permission.CreateIncidentFeed,
414
+ Permission.CreateScheduledMaintenanceFeed,
407
415
  ],
408
416
  read: [
409
417
  Permission.ProjectOwner,
410
418
  Permission.ProjectAdmin,
411
419
  Permission.ProjectMember,
412
- Permission.ReadIncidentFeed,
420
+ Permission.ReadScheduledMaintenanceFeed,
413
421
  ],
414
422
  update: [],
415
423
  })
@@ -14,7 +14,6 @@ import {
14
14
  import Response from "../Utils/Response";
15
15
  import BaseAPI from "./BaseAPI";
16
16
  import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
17
- import SubscriptionStatus from "Common/Types/Billing/SubscriptionStatus";
18
17
  import BadDataException from "Common/Types/Exception/BadDataException";
19
18
  import { JSONObject } from "Common/Types/JSON";
20
19
  import Permission, { UserPermission } from "Common/Types/Permission";
@@ -127,37 +126,8 @@ export default class UserAPI extends BaseAPI<
127
126
  },
128
127
  });
129
128
 
130
- // refresh subscription status.
131
- const subscriptionState: SubscriptionStatus =
132
- await BillingService.getSubscriptionStatus(
133
- project.paymentProviderSubscriptionId as string,
134
- );
135
-
136
- const meteredSubscriptionState: SubscriptionStatus =
137
- await BillingService.getSubscriptionStatus(
138
- project.paymentProviderMeteredSubscriptionId as string,
139
- );
140
-
141
- // if subscription is cancelled, create a new subscription and update project.
142
-
143
- if (
144
- meteredSubscriptionState === SubscriptionStatus.Canceled ||
145
- subscriptionState === SubscriptionStatus.Canceled
146
- ) {
147
- await ProjectService.reactiveSubscription(project.id!);
148
- }
149
-
150
- await ProjectService.updateOneById({
151
- id: project.id!,
152
- data: {
153
- paymentProviderSubscriptionStatus: subscriptionState,
154
- paymentProviderMeteredSubscriptionStatus:
155
- meteredSubscriptionState,
156
- },
157
- props: {
158
- isRoot: true,
159
- ignoreHooks: true,
160
- },
129
+ await BillingInvoiceService.refreshSubscriptionStatus({
130
+ projectId: project.id!,
161
131
  });
162
132
 
163
133
  return Response.sendEmptySuccessResponse(req, res);
@@ -4,6 +4,7 @@ import OneUptimeDate from "../../Types/Date";
4
4
  import BadDataException from "../../Types/Exception/BadDataException";
5
5
  import ObjectID from "../../Types/ObjectID";
6
6
  import { IsBillingEnabled } from "../EnvironmentConfig";
7
+ import logger from "../Utils/Logger";
7
8
  import DatabaseService from "./DatabaseService";
8
9
  import Model, {
9
10
  AlertFeedEventType,
@@ -27,54 +28,60 @@ export class Service extends DatabaseService<Model> {
27
28
  displayColor?: Color | undefined;
28
29
  userId?: ObjectID | undefined;
29
30
  postedAt?: Date | undefined;
30
- }): Promise<Model> {
31
- if (!data.alertId) {
32
- throw new BadDataException("Alert ID is required");
33
- }
31
+ }): Promise<void> {
32
+ try {
33
+ if (!data.alertId) {
34
+ throw new BadDataException("Alert ID is required");
35
+ }
34
36
 
35
- if (!data.feedInfoInMarkdown) {
36
- throw new BadDataException("Log in markdown is required");
37
- }
37
+ if (!data.feedInfoInMarkdown) {
38
+ throw new BadDataException("Log in markdown is required");
39
+ }
38
40
 
39
- if (!data.alertFeedEventType) {
40
- throw new BadDataException("Alert log event is required");
41
- }
41
+ if (!data.alertFeedEventType) {
42
+ throw new BadDataException("Alert log event is required");
43
+ }
42
44
 
43
- if (!data.projectId) {
44
- throw new BadDataException("Project ID is required");
45
- }
45
+ if (!data.projectId) {
46
+ throw new BadDataException("Project ID is required");
47
+ }
46
48
 
47
- const alertFeed: Model = new Model();
49
+ const alertFeed: Model = new Model();
48
50
 
49
- if (!data.displayColor) {
50
- data.displayColor = Blue500;
51
- }
51
+ if (!data.displayColor) {
52
+ data.displayColor = Blue500;
53
+ }
52
54
 
53
- if (data.userId) {
54
- alertFeed.userId = data.userId;
55
- }
55
+ if (data.userId) {
56
+ alertFeed.userId = data.userId;
57
+ }
56
58
 
57
- alertFeed.displayColor = data.displayColor;
59
+ alertFeed.displayColor = data.displayColor;
58
60
 
59
- alertFeed.alertId = data.alertId;
60
- alertFeed.feedInfoInMarkdown = data.feedInfoInMarkdown;
61
- alertFeed.alertFeedEventType = data.alertFeedEventType;
62
- alertFeed.projectId = data.projectId;
61
+ alertFeed.alertId = data.alertId;
62
+ alertFeed.feedInfoInMarkdown = data.feedInfoInMarkdown;
63
+ alertFeed.alertFeedEventType = data.alertFeedEventType;
64
+ alertFeed.projectId = data.projectId;
63
65
 
64
- if (!data.postedAt) {
65
- alertFeed.postedAt = OneUptimeDate.getCurrentDate();
66
- }
66
+ if (!data.postedAt) {
67
+ alertFeed.postedAt = OneUptimeDate.getCurrentDate();
68
+ }
67
69
 
68
- if (data.moreInformationInMarkdown) {
69
- alertFeed.moreInformationInMarkdown = data.moreInformationInMarkdown;
70
- }
70
+ if (data.moreInformationInMarkdown) {
71
+ alertFeed.moreInformationInMarkdown = data.moreInformationInMarkdown;
72
+ }
71
73
 
72
- return await this.create({
73
- data: alertFeed,
74
- props: {
75
- isRoot: true,
76
- },
77
- });
74
+ await this.create({
75
+ data: alertFeed,
76
+ props: {
77
+ isRoot: true,
78
+ },
79
+ });
80
+ } catch (error) {
81
+ logger.error("AlertFeedService.createAlertFeed");
82
+ logger.error(error);
83
+ // we dont want to throw the error here, as this is a non-critical operation
84
+ }
78
85
  }
79
86
  }
80
87
 
@@ -10,6 +10,11 @@ import Model, {
10
10
  InvoiceStatus,
11
11
  } from "Common/Models/DatabaseModels/BillingInvoice";
12
12
  import Project from "Common/Models/DatabaseModels/Project";
13
+ import SubscriptionStatus from "../../Types/Billing/SubscriptionStatus";
14
+ import ObjectID from "../../Types/ObjectID";
15
+ import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
16
+ import logger from "../Utils/Logger";
17
+ import OneUptimeDate from "../../Types/Date";
13
18
 
14
19
  export class Service extends DatabaseService<Model> {
15
20
  public constructor() {
@@ -17,6 +22,166 @@ export class Service extends DatabaseService<Model> {
17
22
  this.setDoNotAllowDelete(true);
18
23
  }
19
24
 
25
+ public async refreshSubscriptionStatus(data: {
26
+ projectId: ObjectID;
27
+ }): Promise<void> {
28
+ let mutex: SemaphoreMutex | null = null;
29
+
30
+ try {
31
+ mutex = await Semaphore.lock({
32
+ key: data.projectId.toString(),
33
+ namespace: "BillingInoviceService.refreshSubscriptionStatus",
34
+ lockTimeout: 15000,
35
+ acquireTimeout: 20000,
36
+ });
37
+ logger.debug(
38
+ "Mutex acquired - " +
39
+ data.projectId.toString() +
40
+ " at " +
41
+ OneUptimeDate.getCurrentDateAsFormattedString(),
42
+ );
43
+ } catch (err) {
44
+ logger.debug(
45
+ "Mutex acquire failed - " +
46
+ data.projectId.toString() +
47
+ " at " +
48
+ OneUptimeDate.getCurrentDateAsFormattedString(),
49
+ );
50
+ logger.error(err);
51
+ }
52
+
53
+ let project: Project | null = await ProjectService.findOneById({
54
+ id: data.projectId,
55
+ props: {
56
+ isRoot: true,
57
+ },
58
+ select: {
59
+ _id: true,
60
+ paymentProviderCustomerId: true,
61
+ paymentProviderSubscriptionId: true,
62
+ paymentProviderMeteredSubscriptionId: true,
63
+ },
64
+ });
65
+
66
+ // refresh the subscription status. This is a hack to ensure that the subscription status is always up to date.
67
+ // This is because the subscription status can change at any time and we need to ensure that the subscription status is always up to date.
68
+
69
+ if (!project) {
70
+ throw new BadDataException("Project not found");
71
+ }
72
+
73
+ if (!project.paymentProviderCustomerId) {
74
+ throw new BadDataException("Payment provider customer id not found.");
75
+ }
76
+
77
+ let subscriptionState: SubscriptionStatus =
78
+ await BillingService.getSubscriptionStatus(
79
+ project.paymentProviderSubscriptionId as string,
80
+ );
81
+
82
+ let meteredSubscriptionState: SubscriptionStatus =
83
+ await BillingService.getSubscriptionStatus(
84
+ project.paymentProviderMeteredSubscriptionId as string,
85
+ );
86
+
87
+ // update the project.
88
+
89
+ await ProjectService.updateOneById({
90
+ id: project.id!,
91
+ data: {
92
+ paymentProviderSubscriptionStatus: subscriptionState,
93
+ paymentProviderMeteredSubscriptionStatus: meteredSubscriptionState,
94
+ },
95
+ props: {
96
+ isRoot: true,
97
+ ignoreHooks: true,
98
+ },
99
+ });
100
+
101
+ if (
102
+ meteredSubscriptionState === SubscriptionStatus.Canceled ||
103
+ subscriptionState === SubscriptionStatus.Canceled
104
+ ) {
105
+ // check if all invoices are paid. If yes, then reactivate the subscription.
106
+
107
+ const invoices: Array<Invoice> = await BillingService.getInvoices(
108
+ project.paymentProviderCustomerId,
109
+ );
110
+
111
+ let allInvoicesPaid: boolean = true;
112
+
113
+ for (const invoice of invoices) {
114
+ if (
115
+ invoice.status === InvoiceStatus.Open ||
116
+ invoice.status === InvoiceStatus.Uncollectible
117
+ ) {
118
+ allInvoicesPaid = false;
119
+ break;
120
+ }
121
+ }
122
+
123
+ if (allInvoicesPaid) {
124
+ await ProjectService.reactiveSubscription(project.id!);
125
+ project = await ProjectService.findOneById({
126
+ id: data.projectId,
127
+ props: {
128
+ isRoot: true,
129
+ },
130
+ select: {
131
+ _id: true,
132
+ paymentProviderCustomerId: true,
133
+ paymentProviderSubscriptionId: true,
134
+ paymentProviderMeteredSubscriptionId: true,
135
+ },
136
+ });
137
+
138
+ if (!project) {
139
+ throw new BadDataException("Project not found");
140
+ }
141
+
142
+ subscriptionState = await BillingService.getSubscriptionStatus(
143
+ project.paymentProviderSubscriptionId as string,
144
+ );
145
+
146
+ meteredSubscriptionState = await BillingService.getSubscriptionStatus(
147
+ project.paymentProviderMeteredSubscriptionId as string,
148
+ );
149
+
150
+ await ProjectService.updateOneById({
151
+ id: project.id!,
152
+ data: {
153
+ paymentProviderSubscriptionStatus: subscriptionState,
154
+ paymentProviderMeteredSubscriptionStatus: meteredSubscriptionState,
155
+ },
156
+ props: {
157
+ isRoot: true,
158
+ ignoreHooks: true,
159
+ },
160
+ });
161
+ }
162
+ }
163
+
164
+ if (mutex) {
165
+ try {
166
+ await Semaphore.release(mutex);
167
+ logger.debug(
168
+ "Mutex released - " +
169
+ data.projectId.toString() +
170
+ " at " +
171
+ OneUptimeDate.getCurrentDateAsFormattedString(),
172
+ );
173
+ } catch (err) {
174
+ logger.debug(
175
+ "Mutex release failed - " +
176
+ data.projectId.toString() +
177
+ " at " +
178
+ OneUptimeDate.getCurrentDateAsFormattedString(),
179
+ );
180
+ logger.error(err);
181
+ }
182
+ }
183
+ }
184
+
20
185
  protected override async onBeforeFind(
21
186
  findBy: FindBy<Model>,
22
187
  ): Promise<OnFind<Model>> {
@@ -37,6 +202,11 @@ export class Service extends DatabaseService<Model> {
37
202
  },
38
203
  });
39
204
 
205
+ // refresh the subscription status. This is a hack to ensure that the subscription status is always up to date.
206
+ // This is because the subscription status can change at any time and we need to ensure that the subscription status is always up to date.
207
+
208
+ await this.refreshSubscriptionStatus({ projectId: findBy.props.tenantId! });
209
+
40
210
  if (!project) {
41
211
  throw new BadDataException("Project not found");
42
212
  }
@@ -28,61 +28,66 @@ export class Service extends DatabaseService<IncidentFeed> {
28
28
  displayColor?: Color | undefined;
29
29
  userId?: ObjectID | undefined;
30
30
  postedAt?: Date | undefined;
31
- }): Promise<IncidentFeed> {
32
- logger.debug("IncidentFeedService.createIncidentFeed");
33
- logger.debug(data);
34
-
35
- const incidentFeed: IncidentFeed = new IncidentFeed();
36
-
37
- if (!data.incidentId) {
38
- throw new BadDataException("Incident ID is required");
39
- }
40
-
41
- if (!data.feedInfoInMarkdown) {
42
- throw new BadDataException("Log in markdown is required");
43
- }
44
-
45
- if (!data.incidentFeedEventType) {
46
- throw new BadDataException("Incident log event is required");
47
- }
48
-
49
- if (!data.projectId) {
50
- throw new BadDataException("Project ID is required");
51
- }
52
-
53
- if (!data.displayColor) {
54
- data.displayColor = Blue500;
31
+ }): Promise<void> {
32
+ try {
33
+ logger.debug("IncidentFeedService.createIncidentFeed");
34
+ logger.debug(data);
35
+
36
+ const incidentFeed: IncidentFeed = new IncidentFeed();
37
+
38
+ if (!data.incidentId) {
39
+ throw new BadDataException("Incident ID is required");
40
+ }
41
+
42
+ if (!data.feedInfoInMarkdown) {
43
+ throw new BadDataException("Log in markdown is required");
44
+ }
45
+
46
+ if (!data.incidentFeedEventType) {
47
+ throw new BadDataException("Incident log event is required");
48
+ }
49
+
50
+ if (!data.projectId) {
51
+ throw new BadDataException("Project ID is required");
52
+ }
53
+
54
+ if (!data.displayColor) {
55
+ data.displayColor = Blue500;
56
+ }
57
+
58
+ incidentFeed.displayColor = data.displayColor;
59
+ incidentFeed.incidentId = data.incidentId;
60
+ incidentFeed.feedInfoInMarkdown = data.feedInfoInMarkdown;
61
+ incidentFeed.incidentFeedEventType = data.incidentFeedEventType;
62
+ incidentFeed.projectId = data.projectId;
63
+
64
+ if (!data.postedAt) {
65
+ incidentFeed.postedAt = OneUptimeDate.getCurrentDate();
66
+ }
67
+
68
+ if (data.userId) {
69
+ incidentFeed.userId = data.userId;
70
+ }
71
+
72
+ if (data.moreInformationInMarkdown) {
73
+ incidentFeed.moreInformationInMarkdown = data.moreInformationInMarkdown;
74
+ }
75
+
76
+ const createdIncidentFeed: IncidentFeed = await this.create({
77
+ data: incidentFeed,
78
+ props: {
79
+ isRoot: true,
80
+ },
81
+ });
82
+
83
+ logger.debug("Incident Feed created");
84
+ logger.debug(createdIncidentFeed);
85
+ } catch (e) {
86
+ logger.error("Error in creating incident feed");
87
+ logger.error(e);
88
+
89
+ // we dont throw this error as it is not a critical error
55
90
  }
56
-
57
- incidentFeed.displayColor = data.displayColor;
58
- incidentFeed.incidentId = data.incidentId;
59
- incidentFeed.feedInfoInMarkdown = data.feedInfoInMarkdown;
60
- incidentFeed.incidentFeedEventType = data.incidentFeedEventType;
61
- incidentFeed.projectId = data.projectId;
62
-
63
- if (!data.postedAt) {
64
- incidentFeed.postedAt = OneUptimeDate.getCurrentDate();
65
- }
66
-
67
- if (data.userId) {
68
- incidentFeed.userId = data.userId;
69
- }
70
-
71
- if (data.moreInformationInMarkdown) {
72
- incidentFeed.moreInformationInMarkdown = data.moreInformationInMarkdown;
73
- }
74
-
75
- const createdIncidentFeed: IncidentFeed = await this.create({
76
- data: incidentFeed,
77
- props: {
78
- isRoot: true,
79
- },
80
- });
81
-
82
- logger.debug("Incident Feed created");
83
- logger.debug(createdIncidentFeed);
84
-
85
- return createdIncidentFeed;
86
91
  }
87
92
  }
88
93
 
@@ -1,10 +1,11 @@
1
1
  import ObjectID from "../../Types/ObjectID";
2
2
  import DatabaseService from "./DatabaseService";
3
3
  import Model from "Common/Models/DatabaseModels/IncidentInternalNote";
4
- import { OnCreate } from "../Types/Database/Hooks";
4
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
5
5
  import IncidentFeedService from "./IncidentFeedService";
6
6
  import { IncidentFeedEventType } from "../../Models/DatabaseModels/IncidentFeed";
7
7
  import { Blue500 } from "../../Types/BrandColors";
8
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
8
9
 
9
10
  export class Service extends DatabaseService<Model> {
10
11
  public constructor() {
@@ -33,6 +34,50 @@ ${createdItem.note}
33
34
 
34
35
  return createdItem;
35
36
  }
37
+
38
+ public override async onUpdateSuccess(
39
+ onUpdate: OnUpdate<Model>,
40
+ _updatedItemIds: Array<ObjectID>,
41
+ ): Promise<OnUpdate<Model>> {
42
+ if (onUpdate.updateBy.data.note) {
43
+ const updatedItems: Array<Model> = await this.findBy({
44
+ query: onUpdate.updateBy.query,
45
+ limit: LIMIT_PER_PROJECT,
46
+ skip: 0,
47
+ props: {
48
+ isRoot: true,
49
+ },
50
+ select: {
51
+ incidentId: true,
52
+ projectId: true,
53
+ note: true,
54
+ createdByUserId: true,
55
+ createdByUser: {
56
+ _id: true,
57
+ },
58
+ },
59
+ });
60
+
61
+ const userId: ObjectID | null | undefined =
62
+ onUpdate.updateBy.props.userId;
63
+
64
+ for (const updatedItem of updatedItems) {
65
+ await IncidentFeedService.createIncidentFeed({
66
+ incidentId: updatedItem.incidentId!,
67
+ projectId: updatedItem.projectId!,
68
+ incidentFeedEventType: IncidentFeedEventType.PrivateNote,
69
+ displayColor: Blue500,
70
+ userId: userId || undefined,
71
+
72
+ feedInfoInMarkdown: `**Updated Internal / Private Note**
73
+
74
+ ${updatedItem.note}
75
+ `,
76
+ });
77
+ }
78
+ }
79
+ return onUpdate;
80
+ }
36
81
  }
37
82
 
38
83
  export default new Service();
@@ -1,12 +1,13 @@
1
1
  import CreateBy from "../Types/Database/CreateBy";
2
- import { OnCreate } from "../Types/Database/Hooks";
2
+ import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
3
3
  import DatabaseService from "./DatabaseService";
4
4
  import OneUptimeDate from "../../Types/Date";
5
5
  import Model from "Common/Models/DatabaseModels/IncidentPublicNote";
6
6
  import IncidentFeedService from "./IncidentFeedService";
7
7
  import { IncidentFeedEventType } from "../../Models/DatabaseModels/IncidentFeed";
8
- import { Indigo500 } from "../../Types/BrandColors";
8
+ import { Blue500, Indigo500 } from "../../Types/BrandColors";
9
9
  import ObjectID from "../../Types/ObjectID";
10
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
10
11
 
11
12
  export class Service extends DatabaseService<Model> {
12
13
  public constructor() {
@@ -47,6 +48,50 @@ ${createdItem.note}
47
48
 
48
49
  return createdItem;
49
50
  }
51
+
52
+ public override async onUpdateSuccess(
53
+ onUpdate: OnUpdate<Model>,
54
+ _updatedItemIds: Array<ObjectID>,
55
+ ): Promise<OnUpdate<Model>> {
56
+ if (onUpdate.updateBy.data.note) {
57
+ const updatedItems: Array<Model> = await this.findBy({
58
+ query: onUpdate.updateBy.query,
59
+ limit: LIMIT_PER_PROJECT,
60
+ skip: 0,
61
+ props: {
62
+ isRoot: true,
63
+ },
64
+ select: {
65
+ incidentId: true,
66
+ projectId: true,
67
+ note: true,
68
+ createdByUserId: true,
69
+ createdByUser: {
70
+ _id: true,
71
+ },
72
+ },
73
+ });
74
+
75
+ const userId: ObjectID | null | undefined =
76
+ onUpdate.updateBy.props.userId;
77
+
78
+ for (const updatedItem of updatedItems) {
79
+ await IncidentFeedService.createIncidentFeed({
80
+ incidentId: updatedItem.incidentId!,
81
+ projectId: updatedItem.projectId!,
82
+ incidentFeedEventType: IncidentFeedEventType.PublicNote,
83
+ displayColor: Blue500,
84
+ userId: userId || undefined,
85
+
86
+ feedInfoInMarkdown: `**Updated Public Note**
87
+
88
+ ${updatedItem.note}
89
+ `,
90
+ });
91
+ }
92
+ }
93
+ return onUpdate;
94
+ }
50
95
  }
51
96
 
52
97
  export default new Service();
@@ -662,6 +662,24 @@ ${onUpdate.updateBy.data.title || "No title provided."}
662
662
  });
663
663
  }
664
664
 
665
+ if (onUpdate.updateBy.data.rootCause) {
666
+ // add incident feed.
667
+ const createdByUserId: ObjectID | undefined | null =
668
+ onUpdate.updateBy.props.userId;
669
+
670
+ await IncidentFeedService.createIncidentFeed({
671
+ incidentId: incidentId,
672
+ projectId: onUpdate.updateBy.props.tenantId as ObjectID,
673
+ incidentFeedEventType: IncidentFeedEventType.IncidentUpdated,
674
+ displayColor: Gray500,
675
+ feedInfoInMarkdown: `**Incident root cause was updated.** Here's the new root cause.
676
+
677
+ ${onUpdate.updateBy.data.rootCause || "No root cause provided."}
678
+ `,
679
+ userId: createdByUserId || undefined,
680
+ });
681
+ }
682
+
665
683
  if (onUpdate.updateBy.data.description) {
666
684
  // add incident feed.
667
685
  const createdByUserId: ObjectID | undefined | null =