@oneuptime/common 7.0.4007 → 7.0.4019

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 (32) hide show
  1. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +243 -0
  2. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +46 -0
  3. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +237 -0
  4. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +210 -0
  5. package/Server/Services/OnCallDutyPolicyScheduleService.ts +392 -14
  6. package/Server/Services/UserNotificationSettingService.ts +114 -237
  7. package/Server/Services/UserService.ts +21 -0
  8. package/Server/Utils/Workspace/Slack/Actions/Alert.ts +2 -2
  9. package/Server/Utils/Workspace/Slack/Actions/Incident.ts +3 -3
  10. package/Types/Email/EmailTemplateType.ts +6 -0
  11. package/Types/NotificationSetting/NotificationSettingEventType.ts +7 -0
  12. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +170 -0
  13. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  14. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +40 -0
  15. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  16. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +170 -0
  17. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  18. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +152 -0
  19. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  20. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +266 -10
  21. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  22. package/build/dist/Server/Services/UserNotificationSettingService.js +35 -202
  23. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  24. package/build/dist/Server/Services/UserService.js +17 -0
  25. package/build/dist/Server/Services/UserService.js.map +1 -1
  26. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +2 -2
  27. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +3 -3
  28. package/build/dist/Types/Email/EmailTemplateType.js +5 -0
  29. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  30. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +6 -0
  31. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  32. package/package.json +2 -2
@@ -1,9 +1,219 @@
1
+ import BadDataException from "../../Types/Exception/BadDataException";
2
+ import ObjectID from "../../Types/ObjectID";
3
+ import { OnCreate, OnDelete } from "../Types/Database/Hooks";
1
4
  import DatabaseService from "./DatabaseService";
2
5
  import Model from "Common/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser";
6
+ import Dictionary from "../../Types/Dictionary";
7
+ import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
8
+ import EmailTemplateType from "../../Types/Email/EmailTemplateType";
9
+ import { EmailEnvelope } from "../../Types/Email/EmailMessage";
10
+ import { SMSMessage } from "../../Types/SMS/SMS";
11
+ import UserNotificationSettingService from "./UserNotificationSettingService";
12
+ import NotificationSettingEventType from "../../Types/NotificationSetting/NotificationSettingEventType";
13
+ import { CallRequestMessage } from "../../Types/Call/CallRequest";
14
+ import DeleteBy from "../Types/Database/DeleteBy";
15
+ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
3
16
 
4
17
  export class Service extends DatabaseService<Model> {
5
18
  public constructor() {
6
19
  super(Model);
7
20
  }
21
+
22
+ protected override async onCreateSuccess(
23
+ _onCreate: OnCreate<Model>,
24
+ createdItem: Model,
25
+ ): Promise<Model> {
26
+ const createdItemId: ObjectID = createdItem.id!;
27
+
28
+ if (!createdItemId) {
29
+ throw new BadDataException("Created item does not have an ID");
30
+ }
31
+
32
+ const createdModel: Model | null = await this.findOneById({
33
+ id: createdItemId,
34
+ select: {
35
+ projectId: true,
36
+ user: {
37
+ timezone: true,
38
+ _id: true,
39
+ },
40
+ onCallDutyPolicyEscalationRule: {
41
+ name: true,
42
+ _id: true,
43
+ order: true,
44
+ },
45
+ onCallDutyPolicy: {
46
+ name: true,
47
+ _id: true,
48
+ },
49
+ },
50
+ props: {
51
+ isRoot: true,
52
+ },
53
+ });
54
+
55
+ // send notification to the new current user.
56
+
57
+ const sendEmailToUserId: ObjectID | undefined | null =
58
+ createdModel?.user?.id;
59
+
60
+ if (!sendEmailToUserId) {
61
+ return createdItem;
62
+ }
63
+
64
+ if (!createdModel) {
65
+ return createdItem;
66
+ }
67
+
68
+ const vars: Dictionary<string> = {
69
+ onCallPolicyName:
70
+ createdModel.onCallDutyPolicy?.name || "No name provided",
71
+ escalationRuleName:
72
+ createdModel.onCallDutyPolicyEscalationRule?.name || "No name provided",
73
+ escalationRuleOrder:
74
+ createdModel.onCallDutyPolicyEscalationRule?.order?.toString() ||
75
+ "No order provided",
76
+ reason: "You have been added to the on-call duty policy escalation rule.",
77
+ onCallPolicyViewLink: (
78
+ await OnCallDutyPolicyService.getOnCallPolicyLinkInDashboard(
79
+ createdModel!.projectId!,
80
+ createdModel.onCallDutyPolicy!.id!,
81
+ )
82
+ ).toString(),
83
+ };
84
+
85
+ // current user changed, send alert the new current user.
86
+ const emailMessage: EmailEnvelope = {
87
+ templateType: EmailTemplateType.UserAddedToOnCallPolicy,
88
+ vars: vars,
89
+ subject: `You have been added to the on-call duty policy ${createdModel.onCallDutyPolicy?.name}`,
90
+ };
91
+
92
+ const sms: SMSMessage = {
93
+ message: `This is a message from OneUptime. You have been added to the on-call duty policy ${createdModel.onCallDutyPolicy?.name} for escalation rule ${createdModel.onCallDutyPolicyEscalationRule?.name} with order ${createdModel.onCallDutyPolicyEscalationRule?.order}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`,
94
+ };
95
+
96
+ const callMessage: CallRequestMessage = {
97
+ data: [
98
+ {
99
+ sayMessage: `This is a message from OneUptime. You have been added to the on-call duty policy ${createdModel.onCallDutyPolicy?.name} for escalation rule ${createdModel.onCallDutyPolicyEscalationRule?.name} with order ${createdModel.onCallDutyPolicyEscalationRule?.order}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good Bye`,
100
+ },
101
+ ],
102
+ };
103
+
104
+ await UserNotificationSettingService.sendUserNotification({
105
+ userId: sendEmailToUserId,
106
+ projectId: createdModel!.projectId!,
107
+ emailEnvelope: emailMessage,
108
+ smsMessage: sms,
109
+ callRequestMessage: callMessage,
110
+ eventType:
111
+ NotificationSettingEventType.SEND_WHEN_USER_IS_ADDED_TO_ON_CALL_POLICY,
112
+ });
113
+
114
+ return createdItem;
115
+ }
116
+
117
+ protected override async onBeforeDelete(
118
+ deleteBy: DeleteBy<Model>,
119
+ ): Promise<OnDelete<Model>> {
120
+ const itemsToFetchBeforeDelete: Array<Model> = await this.findBy({
121
+ query: deleteBy.query,
122
+ props: {
123
+ isRoot: true,
124
+ },
125
+ select: {
126
+ projectId: true,
127
+ user: {
128
+ timezone: true,
129
+ _id: true,
130
+ },
131
+ onCallDutyPolicyEscalationRule: {
132
+ name: true,
133
+ _id: true,
134
+ order: true,
135
+ },
136
+ onCallDutyPolicy: {
137
+ name: true,
138
+ _id: true,
139
+ },
140
+ },
141
+ limit: LIMIT_PER_PROJECT,
142
+ skip: 0,
143
+ });
144
+
145
+ return {
146
+ deleteBy,
147
+ carryForward: {
148
+ deletedItems: itemsToFetchBeforeDelete,
149
+ },
150
+ };
151
+ }
152
+
153
+ protected override async onDeleteSuccess(
154
+ onDelete: OnDelete<Model>,
155
+ _itemIdsBeforeDelete: Array<ObjectID>,
156
+ ): Promise<OnDelete<Model>> {
157
+ const deletedItems: Array<Model> = onDelete.carryForward.deletedItems;
158
+
159
+ for (const deletedItem of deletedItems) {
160
+ const sendEmailToUserId: ObjectID | undefined | null =
161
+ deletedItem.user?.id;
162
+
163
+ if (!sendEmailToUserId) {
164
+ return onDelete;
165
+ }
166
+
167
+ const vars: Dictionary<string> = {
168
+ onCallPolicyName:
169
+ deletedItem.onCallDutyPolicy?.name || "No name provided",
170
+ escalationRuleName:
171
+ deletedItem.onCallDutyPolicyEscalationRule?.name ||
172
+ "No name provided",
173
+ escalationRuleOrder:
174
+ deletedItem.onCallDutyPolicyEscalationRule?.order?.toString() ||
175
+ "No order provided",
176
+ reason:
177
+ "You have been removed from the on-call duty policy escalation rule.",
178
+ onCallPolicyViewLink: (
179
+ await OnCallDutyPolicyService.getOnCallPolicyLinkInDashboard(
180
+ deletedItem!.projectId!,
181
+ deletedItem.onCallDutyPolicy!.id!,
182
+ )
183
+ ).toString(),
184
+ };
185
+
186
+ // current user changed, send alert the new current user.
187
+ const emailMessage: EmailEnvelope = {
188
+ templateType: EmailTemplateType.UserRemovedFromOnCallPolicy,
189
+ vars: vars,
190
+ subject: `You have been removed from the on-call duty policy ${deletedItem.onCallDutyPolicy?.name}`,
191
+ };
192
+
193
+ const sms: SMSMessage = {
194
+ message: `This is a message from OneUptime. You have been removed from the on-call duty policy ${deletedItem.onCallDutyPolicy?.name} for escalation rule ${deletedItem.onCallDutyPolicyEscalationRule?.name} with order ${deletedItem.onCallDutyPolicyEscalationRule?.order}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`,
195
+ };
196
+
197
+ const callMessage: CallRequestMessage = {
198
+ data: [
199
+ {
200
+ sayMessage: `This is a message from OneUptime. You have been removed from the on-call duty policy ${deletedItem.onCallDutyPolicy?.name} for escalation rule ${deletedItem.onCallDutyPolicyEscalationRule?.name} with order ${deletedItem.onCallDutyPolicyEscalationRule?.order}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good Bye`,
201
+ },
202
+ ],
203
+ };
204
+
205
+ UserNotificationSettingService.sendUserNotification({
206
+ userId: sendEmailToUserId,
207
+ projectId: deletedItem!.projectId!,
208
+ emailEnvelope: emailMessage,
209
+ smsMessage: sms,
210
+ callRequestMessage: callMessage,
211
+ eventType:
212
+ NotificationSettingEventType.SEND_WHEN_USER_IS_REMOVED_FROM_ON_CALL_POLICY,
213
+ });
214
+ }
215
+
216
+ return onDelete;
217
+ }
8
218
  }
9
219
  export default new Service();
@@ -7,22 +7,37 @@ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
7
7
  import OneUptimeDate from "../../Types/Date";
8
8
  import ObjectID from "../../Types/ObjectID";
9
9
  import LayerUtil, { LayerProps } from "../../Types/OnCallDutyPolicy/Layer";
10
- import Model from "Common/Models/DatabaseModels/OnCallDutyPolicySchedule";
11
10
  import OnCallDutyPolicyScheduleLayer from "Common/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer";
12
11
  import OnCallDutyPolicyScheduleLayerUser from "Common/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser";
13
12
  import User from "Common/Models/DatabaseModels/User";
14
13
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
15
-
16
- export class Service extends DatabaseService<Model> {
14
+ import OnCallDutyPolicySchedule from "Common/Models/DatabaseModels/OnCallDutyPolicySchedule";
15
+ import OnCallDutyPolicyEscalationRuleSchedule from "../../Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule";
16
+ import OnCallDutyPolicyEscalationRuleScheduleService from "./OnCallDutyPolicyEscalationRuleScheduleService";
17
+ import Dictionary from "../../Types/Dictionary";
18
+ import { EmailEnvelope } from "../../Types/Email/EmailMessage";
19
+ import EmailTemplateType from "../../Types/Email/EmailTemplateType";
20
+ import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
21
+ import OnCallDutyPolicyEscalationRule from "../../Models/DatabaseModels/OnCallDutyPolicyEscalationRule";
22
+ import UserService from "./UserService";
23
+ import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
24
+ import { SMSMessage } from "../../Types/SMS/SMS";
25
+ import { CallRequestMessage } from "../../Types/Call/CallRequest";
26
+ import UserNotificationSettingService from "./UserNotificationSettingService";
27
+ import NotificationSettingEventType from "../../Types/NotificationSetting/NotificationSettingEventType";
28
+ import BadDataException from "../../Types/Exception/BadDataException";
29
+ import Timezone from "../../Types/Timezone";
30
+
31
+ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
17
32
  public constructor() {
18
- super(Model);
33
+ super(OnCallDutyPolicySchedule);
19
34
  }
20
35
 
21
36
  public async getOnCallSchedulesWhereUserIsOnCallDuty(data: {
22
37
  projectId: ObjectID;
23
38
  userId: ObjectID;
24
- }): Promise<Array<Model>> {
25
- const schedules: Array<Model> = await this.findBy({
39
+ }): Promise<Array<OnCallDutyPolicySchedule>> {
40
+ const schedules: Array<OnCallDutyPolicySchedule> = await this.findBy({
26
41
  query: {
27
42
  projectId: data.projectId,
28
43
  currentUserIdOnRoster: data.userId,
@@ -41,6 +56,316 @@ export class Service extends DatabaseService<Model> {
41
56
  return schedules;
42
57
  }
43
58
 
59
+ private async sendNotificationToUserOnScheduleHandoff(data: {
60
+ scheduleId: ObjectID;
61
+ previousInformation: {
62
+ currentUserIdOnRoster: ObjectID | null;
63
+ rosterHandoffAt: Date | null;
64
+ nextUserIdOnRoster: ObjectID | null;
65
+ nextHandOffTimeAt: Date | null;
66
+ rosterStartAt: Date | null;
67
+ nextRosterStartAt: Date | null;
68
+ };
69
+ newInformation: {
70
+ currentUserIdOnRoster: ObjectID | null;
71
+ rosterHandoffAt: Date | null;
72
+ nextUserIdOnRoster: ObjectID | null;
73
+ nextHandOffTimeAt: Date | null;
74
+ rosterStartAt: Date | null;
75
+ nextRosterStartAt: Date | null;
76
+ };
77
+ }): Promise<void> {
78
+ // Before we send any notification, we need to check if this schedule is attached to any on-call policy.
79
+
80
+ const escalationRulesAttachedToSchedule: Array<OnCallDutyPolicyEscalationRuleSchedule> =
81
+ await OnCallDutyPolicyEscalationRuleScheduleService.findBy({
82
+ query: {
83
+ onCallDutyPolicyScheduleId: data.scheduleId,
84
+ },
85
+ select: {
86
+ projectId: true,
87
+ _id: true,
88
+ onCallDutyPolicy: {
89
+ name: true,
90
+ _id: true,
91
+ },
92
+ onCallDutyPolicyEscalationRule: {
93
+ name: true,
94
+ _id: true,
95
+ order: true,
96
+ },
97
+ onCallDutyPolicySchedule: {
98
+ name: true,
99
+ _id: true,
100
+ },
101
+ },
102
+ props: {
103
+ isRoot: true,
104
+ },
105
+ limit: LIMIT_PER_PROJECT,
106
+ skip: 0,
107
+ });
108
+
109
+ if (escalationRulesAttachedToSchedule.length === 0) {
110
+ // do nothing.
111
+ return;
112
+ }
113
+
114
+ for (const escalationRule of escalationRulesAttachedToSchedule) {
115
+ const projectId: ObjectID = escalationRule.projectId!;
116
+
117
+ const onCallSchedule: OnCallDutyPolicySchedule | undefined =
118
+ escalationRule.onCallDutyPolicySchedule;
119
+
120
+ if (!onCallSchedule) {
121
+ continue;
122
+ }
123
+
124
+ const onCallPolicy: OnCallDutyPolicy | undefined =
125
+ escalationRule.onCallDutyPolicy;
126
+
127
+ if (!onCallPolicy) {
128
+ continue;
129
+ }
130
+
131
+ const onCallDutyPolicyEscalationRule:
132
+ | OnCallDutyPolicyEscalationRule
133
+ | undefined = escalationRule.onCallDutyPolicyEscalationRule;
134
+
135
+ if (!onCallDutyPolicyEscalationRule) {
136
+ continue;
137
+ }
138
+
139
+ const { previousInformation, newInformation } = data;
140
+
141
+ // if there's a change, witht he current user, send notification to the new current user.
142
+ // Send notificiation to the new current user.
143
+ if (
144
+ previousInformation.currentUserIdOnRoster?.toString() !==
145
+ newInformation.currentUserIdOnRoster?.toString() ||
146
+ previousInformation.rosterHandoffAt?.toString() !==
147
+ newInformation.rosterHandoffAt?.toString()
148
+ ) {
149
+ if (
150
+ previousInformation.currentUserIdOnRoster?.toString() !==
151
+ newInformation.currentUserIdOnRoster?.toString() &&
152
+ previousInformation.currentUserIdOnRoster?.toString()
153
+ ) {
154
+ // the user has changed. Send notifiction to old user that he has been removed.
155
+
156
+ // send notification to the new current user.
157
+
158
+ const sendEmailToUserId: ObjectID =
159
+ previousInformation.currentUserIdOnRoster;
160
+
161
+ const userTimezone: Timezone | null =
162
+ await UserService.getTimezoneForUser(sendEmailToUserId);
163
+
164
+ const vars: Dictionary<string> = {
165
+ onCallPolicyName: onCallPolicy.name || "No name provided",
166
+ escalationRuleName:
167
+ onCallDutyPolicyEscalationRule.name || "No name provided",
168
+ escalationRuleOrder:
169
+ onCallDutyPolicyEscalationRule.order?.toString() || "-",
170
+ reason:
171
+ "Your on-call roster on schedule " +
172
+ onCallSchedule.name +
173
+ " just ended.",
174
+ rosterStartsAt:
175
+ OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
176
+ date: previousInformation.rosterStartAt!,
177
+ timezones: userTimezone ? [userTimezone] : [],
178
+ }),
179
+ rosterEndsAt:
180
+ OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
181
+ date: OneUptimeDate.isInTheFuture(
182
+ previousInformation.rosterHandoffAt!,
183
+ )
184
+ ? OneUptimeDate.getCurrentDate()
185
+ : previousInformation.rosterHandoffAt!,
186
+ timezones: userTimezone ? [userTimezone] : [],
187
+ }),
188
+ onCallPolicyViewLink: (
189
+ await OnCallDutyPolicyService.getOnCallPolicyLinkInDashboard(
190
+ projectId,
191
+ onCallPolicy.id!,
192
+ )
193
+ ).toString(),
194
+ };
195
+
196
+ // current user changed, send alert the new current user.
197
+ const emailMessage: EmailEnvelope = {
198
+ templateType: EmailTemplateType.UserNoLongerActiveOnOnCallRoster,
199
+ vars: vars,
200
+ subject: "You are no longer on-call for " + onCallPolicy.name!,
201
+ };
202
+
203
+ const sms: SMSMessage = {
204
+ message: `This is a message from OneUptime. You are no longer on-call for ${onCallPolicy.name!} because your on-call roster on schedule ${onCallSchedule.name} just ended. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`,
205
+ };
206
+
207
+ const callMessage: CallRequestMessage = {
208
+ data: [
209
+ {
210
+ sayMessage: `This is a message from OneUptime. You are no longer on-call for ${onCallPolicy.name!} because your on-call roster on schedule ${onCallSchedule.name} just ended. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`,
211
+ },
212
+ ],
213
+ };
214
+
215
+ await UserNotificationSettingService.sendUserNotification({
216
+ userId: sendEmailToUserId,
217
+ projectId: projectId,
218
+ emailEnvelope: emailMessage,
219
+ smsMessage: sms,
220
+ callRequestMessage: callMessage,
221
+ eventType:
222
+ NotificationSettingEventType.SEND_WHEN_USER_IS_NO_LONGER_ACTIVE_ON_ON_CALL_ROSTER,
223
+ });
224
+ }
225
+
226
+ if (newInformation.currentUserIdOnRoster?.toString()) {
227
+ // send email to the new current user.
228
+ const sendEmailToUserId: ObjectID =
229
+ newInformation.currentUserIdOnRoster;
230
+ const userTimezone: Timezone | null =
231
+ await UserService.getTimezoneForUser(sendEmailToUserId);
232
+
233
+ const vars: Dictionary<string> = {
234
+ onCallPolicyName: onCallPolicy.name || "No name provided",
235
+ escalationRuleName:
236
+ onCallDutyPolicyEscalationRule.name || "No name provided",
237
+ escalationRuleOrder:
238
+ onCallDutyPolicyEscalationRule.order?.toString() || "-",
239
+ reason:
240
+ "You are now on-call for the policy " +
241
+ onCallPolicy.name +
242
+ " because your on-call roster on schedule " +
243
+ onCallSchedule.name,
244
+ rosterStartsAt:
245
+ OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
246
+ date: newInformation.rosterStartAt!,
247
+ timezones: userTimezone ? [userTimezone] : [],
248
+ }),
249
+ rosterEndsAt:
250
+ OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
251
+ date: newInformation.rosterHandoffAt!,
252
+ timezones: userTimezone ? [userTimezone] : [],
253
+ }),
254
+ onCallPolicyViewLink: (
255
+ await OnCallDutyPolicyService.getOnCallPolicyLinkInDashboard(
256
+ projectId,
257
+ onCallPolicy.id!,
258
+ )
259
+ ).toString(),
260
+ };
261
+
262
+ const emailMessage: EmailEnvelope = {
263
+ templateType: EmailTemplateType.UserCurrentlyOnOnCallRoster,
264
+ vars: vars,
265
+ subject: "You are now on-call for " + onCallPolicy.name!,
266
+ };
267
+
268
+ const sms: SMSMessage = {
269
+ message: `This is a message from OneUptime. You are now on-call for ${onCallPolicy.name!} because you are now on the roster for schedule ${onCallSchedule.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`,
270
+ };
271
+
272
+ const callMessage: CallRequestMessage = {
273
+ data: [
274
+ {
275
+ sayMessage: `This is a message from OneUptime. You are now on-call for ${onCallPolicy.name!} because you are now on the roster for schedule ${onCallSchedule.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`,
276
+ },
277
+ ],
278
+ };
279
+
280
+ await UserNotificationSettingService.sendUserNotification({
281
+ userId: sendEmailToUserId,
282
+ projectId: projectId,
283
+ emailEnvelope: emailMessage,
284
+ smsMessage: sms,
285
+ callRequestMessage: callMessage,
286
+ eventType:
287
+ NotificationSettingEventType.SEND_WHEN_USER_IS_ON_CALL_ROSTER,
288
+ });
289
+ }
290
+ }
291
+
292
+ // send an email to the next user.
293
+ if (
294
+ previousInformation.nextUserIdOnRoster?.toString() !==
295
+ newInformation.nextUserIdOnRoster?.toString() ||
296
+ previousInformation.nextHandOffTimeAt?.toString() !==
297
+ newInformation.nextHandOffTimeAt?.toString() ||
298
+ previousInformation.nextRosterStartAt?.toString() !==
299
+ newInformation.nextRosterStartAt?.toString()
300
+ ) {
301
+ if (newInformation.nextUserIdOnRoster?.toString()) {
302
+ // send email to the next user.
303
+ const sendEmailToUserId: ObjectID = newInformation.nextUserIdOnRoster;
304
+ const userTimezone: Timezone | null =
305
+ await UserService.getTimezoneForUser(sendEmailToUserId);
306
+
307
+ const vars: Dictionary<string> = {
308
+ onCallPolicyName: onCallPolicy.name || "No name provided",
309
+ escalationRuleName:
310
+ onCallDutyPolicyEscalationRule.name || "No name provided",
311
+ escalationRuleOrder:
312
+ onCallDutyPolicyEscalationRule.order?.toString() || "-",
313
+ reason:
314
+ "You are next on-call for the policy " +
315
+ onCallPolicy.name +
316
+ " because your on-call roster on schedule " +
317
+ onCallSchedule.name +
318
+ " will start when the next handoff happens.",
319
+ rosterStartsAt:
320
+ OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
321
+ date: newInformation.nextRosterStartAt!,
322
+ timezones: userTimezone ? [userTimezone] : [],
323
+ }),
324
+ rosterEndsAt:
325
+ OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
326
+ date: newInformation.nextHandOffTimeAt!,
327
+ timezones: userTimezone ? [userTimezone] : [],
328
+ }),
329
+ onCallPolicyViewLink: (
330
+ await OnCallDutyPolicyService.getOnCallPolicyLinkInDashboard(
331
+ projectId,
332
+ onCallPolicy.id!,
333
+ )
334
+ ).toString(),
335
+ };
336
+
337
+ const emailMessage: EmailEnvelope = {
338
+ templateType: EmailTemplateType.UserNextOnOnCallRoster,
339
+ vars: vars,
340
+ subject: "You are next on-call for " + onCallPolicy.name!,
341
+ };
342
+
343
+ const sms: SMSMessage = {
344
+ message: `This is a message from OneUptime. You are next on-call for ${onCallPolicy.name!} because your on-call roster on schedule ${onCallSchedule.name} will start when the next handoff happens. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`,
345
+ };
346
+
347
+ const callMessage: CallRequestMessage = {
348
+ data: [
349
+ {
350
+ sayMessage: `This is a message from OneUptime. You are next on-call for ${onCallPolicy.name!} because your on-call roster on schedule ${onCallSchedule.name} will start when the next handoff happens. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`,
351
+ },
352
+ ],
353
+ };
354
+
355
+ await UserNotificationSettingService.sendUserNotification({
356
+ userId: sendEmailToUserId,
357
+ projectId: projectId,
358
+ emailEnvelope: emailMessage,
359
+ smsMessage: sms,
360
+ callRequestMessage: callMessage,
361
+ eventType:
362
+ NotificationSettingEventType.SEND_WHEN_USER_IS_NEXT_ON_CALL_ROSTER,
363
+ });
364
+ }
365
+ }
366
+ }
367
+ }
368
+
44
369
  public async refreshCurrentUserIdAndHandoffTimeInSchedule(
45
370
  scheduleId: ObjectID,
46
371
  ): Promise<{
@@ -48,8 +373,47 @@ export class Service extends DatabaseService<Model> {
48
373
  handOffTimeAt: Date | null;
49
374
  nextUserId: ObjectID | null;
50
375
  nextHandOffTimeAt: Date | null;
376
+ rosterStartAt: Date | null;
377
+ nextRosterStartAt: Date | null;
51
378
  }> {
52
- const result: {
379
+ // get previoius result.
380
+ const onCallSchedule: OnCallDutyPolicySchedule | null =
381
+ await this.findOneById({
382
+ id: scheduleId,
383
+ select: {
384
+ currentUserIdOnRoster: true,
385
+ rosterHandoffAt: true,
386
+ nextUserIdOnRoster: true,
387
+ rosterNextHandoffAt: true,
388
+ rosterStartAt: true,
389
+ rosterNextStartAt: true,
390
+ },
391
+ props: {
392
+ isRoot: true,
393
+ },
394
+ });
395
+
396
+ if (!onCallSchedule) {
397
+ throw new BadDataException("Schedule not found");
398
+ }
399
+
400
+ const previousInformation: {
401
+ currentUserIdOnRoster: ObjectID | null;
402
+ rosterHandoffAt: Date | null;
403
+ nextUserIdOnRoster: ObjectID | null;
404
+ nextHandOffTimeAt: Date | null;
405
+ rosterStartAt: Date | null;
406
+ nextRosterStartAt: Date | null;
407
+ } = {
408
+ currentUserIdOnRoster: onCallSchedule.currentUserIdOnRoster || null,
409
+ rosterHandoffAt: onCallSchedule.rosterHandoffAt || null,
410
+ nextUserIdOnRoster: onCallSchedule.nextUserIdOnRoster || null,
411
+ nextHandOffTimeAt: onCallSchedule.rosterNextHandoffAt || null,
412
+ rosterStartAt: onCallSchedule.rosterStartAt || null,
413
+ nextRosterStartAt: onCallSchedule.rosterNextStartAt || null,
414
+ };
415
+
416
+ const newInformation: {
53
417
  currentUserId: ObjectID | null;
54
418
  handOffTimeAt: Date | null;
55
419
  nextUserId: ObjectID | null;
@@ -61,12 +425,12 @@ export class Service extends DatabaseService<Model> {
61
425
  await this.updateOneById({
62
426
  id: scheduleId!,
63
427
  data: {
64
- currentUserIdOnRoster: result.currentUserId,
65
- rosterHandoffAt: result.handOffTimeAt,
66
- nextUserIdOnRoster: result.nextUserId,
67
- rosterNextHandoffAt: result.nextHandOffTimeAt,
68
- rosterStartAt: result.rosterStartAt,
69
- rosterNextStartAt: result.nextRosterStartAt,
428
+ currentUserIdOnRoster: newInformation.currentUserId,
429
+ rosterHandoffAt: newInformation.handOffTimeAt,
430
+ nextUserIdOnRoster: newInformation.nextUserId,
431
+ rosterNextHandoffAt: newInformation.nextHandOffTimeAt,
432
+ rosterStartAt: newInformation.rosterStartAt,
433
+ rosterNextStartAt: newInformation.nextRosterStartAt,
70
434
  },
71
435
  props: {
72
436
  isRoot: true,
@@ -74,7 +438,21 @@ export class Service extends DatabaseService<Model> {
74
438
  },
75
439
  });
76
440
 
77
- return result;
441
+ // send notification to the users.
442
+ await this.sendNotificationToUserOnScheduleHandoff({
443
+ scheduleId: scheduleId,
444
+ previousInformation: previousInformation,
445
+ newInformation: {
446
+ currentUserIdOnRoster: newInformation.currentUserId,
447
+ rosterHandoffAt: newInformation.handOffTimeAt,
448
+ nextUserIdOnRoster: newInformation.nextUserId,
449
+ nextHandOffTimeAt: newInformation.nextHandOffTimeAt,
450
+ rosterStartAt: newInformation.rosterStartAt,
451
+ nextRosterStartAt: newInformation.nextRosterStartAt,
452
+ },
453
+ });
454
+
455
+ return newInformation;
78
456
  }
79
457
 
80
458
  public async getCurrrentUserIdAndHandoffTimeInSchedule(