@oneuptime/common 7.0.4006 → 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.
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +68 -1
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +0 -79
- package/Server/API/OnCallDutyPolicyAPI.ts +87 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1743692467814-MigrationName.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1743714801105-MigrationName.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +243 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +46 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +237 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +210 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +61 -1
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +63 -0
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +597 -18
- package/Server/Services/OnCallDutyPolicyService.ts +129 -0
- package/Server/Services/TeamService.ts +36 -0
- package/Server/Services/UserNotificationSettingService.ts +114 -237
- package/Server/Services/UserService.ts +21 -0
- package/Server/Utils/Workspace/Slack/Actions/Alert.ts +2 -2
- package/Server/Utils/Workspace/Slack/Actions/Incident.ts +3 -3
- package/Types/Date.ts +23 -7
- package/Types/Email/EmailTemplateType.ts +6 -0
- package/Types/NotificationSetting/NotificationSettingEventType.ts +7 -0
- package/Types/OnCallDutyPolicy/Layer.ts +104 -20
- package/UI/Components/Alerts/Alert.tsx +6 -5
- package/UI/Components/HeaderAlert/HeaderAlert.tsx +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +70 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +0 -82
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
- package/build/dist/Server/API/OnCallDutyPolicyAPI.js +44 -0
- package/build/dist/Server/API/OnCallDutyPolicyAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1743692467814-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1743692467814-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1743714801105-MigrationName.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1743714801105-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +170 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +40 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +170 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +152 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +39 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +42 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +387 -8
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyService.js +101 -0
- package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +27 -0
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +35 -202
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +17 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +2 -2
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +3 -3
- package/build/dist/Types/Date.js +19 -7
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +5 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +6 -0
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +66 -11
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/UI/Components/Alerts/Alert.js +4 -4
- package/build/dist/UI/Components/Alerts/Alert.js.map +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +2 -2
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
- package/package.json +2 -2
|
@@ -7,23 +7,547 @@ 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";
|
|
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";
|
|
15
30
|
|
|
16
|
-
export class Service extends DatabaseService<
|
|
31
|
+
export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
17
32
|
public constructor() {
|
|
18
|
-
super(
|
|
33
|
+
super(OnCallDutyPolicySchedule);
|
|
19
34
|
}
|
|
20
35
|
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
public async getOnCallSchedulesWhereUserIsOnCallDuty(data: {
|
|
37
|
+
projectId: ObjectID;
|
|
38
|
+
userId: ObjectID;
|
|
39
|
+
}): Promise<Array<OnCallDutyPolicySchedule>> {
|
|
40
|
+
const schedules: Array<OnCallDutyPolicySchedule> = await this.findBy({
|
|
41
|
+
query: {
|
|
42
|
+
projectId: data.projectId,
|
|
43
|
+
currentUserIdOnRoster: data.userId,
|
|
44
|
+
},
|
|
45
|
+
select: {
|
|
46
|
+
_id: true,
|
|
47
|
+
name: true,
|
|
48
|
+
},
|
|
49
|
+
limit: LIMIT_PER_PROJECT,
|
|
50
|
+
skip: 0,
|
|
51
|
+
props: {
|
|
52
|
+
isRoot: true,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return schedules;
|
|
57
|
+
}
|
|
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
|
+
|
|
369
|
+
public async refreshCurrentUserIdAndHandoffTimeInSchedule(
|
|
23
370
|
scheduleId: ObjectID,
|
|
24
|
-
): Promise<
|
|
371
|
+
): Promise<{
|
|
372
|
+
currentUserId: ObjectID | null;
|
|
373
|
+
handOffTimeAt: Date | null;
|
|
374
|
+
nextUserId: ObjectID | null;
|
|
375
|
+
nextHandOffTimeAt: Date | null;
|
|
376
|
+
rosterStartAt: Date | null;
|
|
377
|
+
nextRosterStartAt: Date | null;
|
|
378
|
+
}> {
|
|
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: {
|
|
417
|
+
currentUserId: ObjectID | null;
|
|
418
|
+
handOffTimeAt: Date | null;
|
|
419
|
+
nextUserId: ObjectID | null;
|
|
420
|
+
nextHandOffTimeAt: Date | null;
|
|
421
|
+
rosterStartAt: Date | null;
|
|
422
|
+
nextRosterStartAt: Date | null;
|
|
423
|
+
} = await this.getCurrrentUserIdAndHandoffTimeInSchedule(scheduleId);
|
|
424
|
+
|
|
425
|
+
await this.updateOneById({
|
|
426
|
+
id: scheduleId!,
|
|
427
|
+
data: {
|
|
428
|
+
currentUserIdOnRoster: newInformation.currentUserId,
|
|
429
|
+
rosterHandoffAt: newInformation.handOffTimeAt,
|
|
430
|
+
nextUserIdOnRoster: newInformation.nextUserId,
|
|
431
|
+
rosterNextHandoffAt: newInformation.nextHandOffTimeAt,
|
|
432
|
+
rosterStartAt: newInformation.rosterStartAt,
|
|
433
|
+
rosterNextStartAt: newInformation.nextRosterStartAt,
|
|
434
|
+
},
|
|
435
|
+
props: {
|
|
436
|
+
isRoot: true,
|
|
437
|
+
ignoreHooks: true,
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
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;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
public async getCurrrentUserIdAndHandoffTimeInSchedule(
|
|
459
|
+
scheduleId: ObjectID,
|
|
460
|
+
): Promise<{
|
|
461
|
+
rosterStartAt: Date | null;
|
|
462
|
+
currentUserId: ObjectID | null;
|
|
463
|
+
handOffTimeAt: Date | null;
|
|
464
|
+
nextUserId: ObjectID | null;
|
|
465
|
+
nextHandOffTimeAt: Date | null;
|
|
466
|
+
nextRosterStartAt: Date | null;
|
|
467
|
+
}> {
|
|
468
|
+
const resultReturn: {
|
|
469
|
+
rosterStartAt: Date | null;
|
|
470
|
+
currentUserId: ObjectID | null;
|
|
471
|
+
handOffTimeAt: Date | null;
|
|
472
|
+
nextUserId: ObjectID | null;
|
|
473
|
+
nextHandOffTimeAt: Date | null;
|
|
474
|
+
nextRosterStartAt: Date | null;
|
|
475
|
+
} = {
|
|
476
|
+
currentUserId: null,
|
|
477
|
+
handOffTimeAt: null,
|
|
478
|
+
nextUserId: null,
|
|
479
|
+
nextHandOffTimeAt: null,
|
|
480
|
+
rosterStartAt: null,
|
|
481
|
+
nextRosterStartAt: null,
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const events: Array<CalendarEvent> | null =
|
|
485
|
+
await this.getEventByIndexInSchedule({
|
|
486
|
+
scheduleId: scheduleId,
|
|
487
|
+
getNumberOfEvents: 2,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
let currentEvent: CalendarEvent | null = events[0] || null;
|
|
491
|
+
let nextEvent: CalendarEvent | null = events[1] || null;
|
|
492
|
+
|
|
493
|
+
// if the current event start time in the future then the current event is the next event.
|
|
494
|
+
if (currentEvent && OneUptimeDate.isInTheFuture(currentEvent.start)) {
|
|
495
|
+
nextEvent = currentEvent;
|
|
496
|
+
currentEvent = null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (currentEvent) {
|
|
500
|
+
const userId: string | undefined = currentEvent?.title; // this is user id in string.
|
|
501
|
+
|
|
502
|
+
if (userId) {
|
|
503
|
+
resultReturn.currentUserId = new ObjectID(userId);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// get handOffTime
|
|
507
|
+
const handOffTime: Date | undefined = currentEvent?.end; // this is user id in string.
|
|
508
|
+
if (handOffTime) {
|
|
509
|
+
resultReturn.handOffTimeAt = handOffTime;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// get start time
|
|
513
|
+
const startTime: Date | undefined = currentEvent?.start; // this is user id in string.
|
|
514
|
+
if (startTime) {
|
|
515
|
+
resultReturn.rosterStartAt = startTime;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// do the same for next event.
|
|
520
|
+
|
|
521
|
+
if (nextEvent) {
|
|
522
|
+
const userId: string | undefined = nextEvent?.title; // this is user id in string.
|
|
523
|
+
|
|
524
|
+
if (userId) {
|
|
525
|
+
resultReturn.nextUserId = new ObjectID(userId);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// get handOffTime
|
|
529
|
+
const handOffTime: Date | undefined = nextEvent?.end; // this is user id in string.
|
|
530
|
+
if (handOffTime) {
|
|
531
|
+
resultReturn.nextHandOffTimeAt = handOffTime;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// get start time
|
|
535
|
+
const startTime: Date | undefined = nextEvent?.start; // this is user id in string.
|
|
536
|
+
if (startTime) {
|
|
537
|
+
resultReturn.nextRosterStartAt = startTime;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return resultReturn;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private async getScheduleLayerProps(data: {
|
|
545
|
+
scheduleId: ObjectID;
|
|
546
|
+
}): Promise<Array<LayerProps>> {
|
|
25
547
|
// get schedule layers.
|
|
26
548
|
|
|
549
|
+
const scheduleId: ObjectID = data.scheduleId;
|
|
550
|
+
|
|
27
551
|
const layers: Array<OnCallDutyPolicyScheduleLayer> =
|
|
28
552
|
await OnCallDutyPolicyScheduleLayerService.findBy({
|
|
29
553
|
query: {
|
|
@@ -70,12 +594,6 @@ export class Service extends DatabaseService<Model> {
|
|
|
70
594
|
},
|
|
71
595
|
});
|
|
72
596
|
|
|
73
|
-
const currentStartTime: Date = OneUptimeDate.getCurrentDate();
|
|
74
|
-
const currentEndTime: Date = OneUptimeDate.addRemoveSeconds(
|
|
75
|
-
currentStartTime,
|
|
76
|
-
1,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
597
|
const layerProps: Array<LayerProps> = [];
|
|
80
598
|
|
|
81
599
|
for (const layer of layers) {
|
|
@@ -101,17 +619,78 @@ export class Service extends DatabaseService<Model> {
|
|
|
101
619
|
});
|
|
102
620
|
}
|
|
103
621
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
622
|
+
return layerProps;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
public async getEventByIndexInSchedule(data: {
|
|
626
|
+
scheduleId: ObjectID;
|
|
627
|
+
getNumberOfEvents: number; // which event would you like to get. First event, second event, etc.
|
|
628
|
+
}): Promise<Array<CalendarEvent>> {
|
|
629
|
+
const layerProps: Array<LayerProps> = await this.getScheduleLayerProps({
|
|
630
|
+
scheduleId: data.scheduleId,
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
if (layerProps.length === 0) {
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const currentStartTime: Date = OneUptimeDate.getCurrentDate();
|
|
638
|
+
const currentEndTime: Date = OneUptimeDate.addRemoveYears(
|
|
639
|
+
currentStartTime,
|
|
640
|
+
1,
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
const numberOfEventsToGet: number = data.getNumberOfEvents;
|
|
644
|
+
const events: Array<CalendarEvent> = LayerUtil.getMultiLayerEvents(
|
|
645
|
+
{
|
|
646
|
+
layers: layerProps,
|
|
647
|
+
calendarStartDate: currentStartTime,
|
|
648
|
+
calendarEndDate: currentEndTime,
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
getNumberOfEvents: numberOfEventsToGet,
|
|
652
|
+
},
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
return events;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
@CaptureSpan()
|
|
659
|
+
public async getCurrentUserIdInSchedule(
|
|
660
|
+
scheduleId: ObjectID,
|
|
661
|
+
): Promise<ObjectID | null> {
|
|
662
|
+
const layerProps: Array<LayerProps> = await this.getScheduleLayerProps({
|
|
663
|
+
scheduleId: scheduleId,
|
|
108
664
|
});
|
|
109
665
|
|
|
110
|
-
if (
|
|
666
|
+
if (layerProps.length === 0) {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const currentStartTime: Date = OneUptimeDate.getCurrentDate();
|
|
671
|
+
const currentEndTime: Date = OneUptimeDate.addRemoveSeconds(
|
|
672
|
+
currentStartTime,
|
|
673
|
+
1,
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
const events: Array<CalendarEvent> = LayerUtil.getMultiLayerEvents(
|
|
677
|
+
{
|
|
678
|
+
layers: layerProps,
|
|
679
|
+
calendarStartDate: currentStartTime,
|
|
680
|
+
calendarEndDate: currentEndTime,
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
getNumberOfEvents: 1,
|
|
684
|
+
},
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
const currentEvent: CalendarEvent | null = events[0] || null;
|
|
688
|
+
|
|
689
|
+
if (!currentEvent) {
|
|
111
690
|
return null;
|
|
112
691
|
}
|
|
113
692
|
|
|
114
|
-
const userId: string | undefined =
|
|
693
|
+
const userId: string | undefined = currentEvent?.title; // this is user id in string.
|
|
115
694
|
|
|
116
695
|
if (!userId) {
|
|
117
696
|
return null;
|