@oneuptime/common 12.0.29 → 12.0.30
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/Index.ts +11 -0
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
- package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
- package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
- package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
- package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
- package/Server/API/OnCallCalendarAPI.ts +2194 -0
- package/Server/EnvironmentConfig.ts +138 -0
- package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
- package/Server/Services/Index.ts +12 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
- package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
- package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
- package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
- package/Server/Services/TeamMemberService.ts +489 -1
- package/Server/Services/TeamService.ts +50 -0
- package/Server/Services/UserNotificationSettingService.ts +44 -1
- package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
- package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
- package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
- package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
- package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
- package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
- package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
- package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
- package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
- package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
- package/Server/Utils/Response.ts +134 -0
- package/Server/Utils/StartServer.ts +10 -0
- package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
- package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
- package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
- package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
- package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
- package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
- package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
- package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
- package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
- package/Tests/Server/API/Helpers.ts +8 -0
- package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
- package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
- package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
- package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
- package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
- package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
- package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
- package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
- package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
- package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
- package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
- package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
- package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
- package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
- package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
- package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
- package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
- package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
- package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
- package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
- package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
- package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
- package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
- package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
- package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
- package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
- package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
- package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Types/Calendar/ICalendar.ts +442 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
- package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
- package/Types/OnCallDutyPolicy/Layer.ts +190 -35
- package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
- package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
- package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
- package/build/dist/Models/DatabaseModels/Index.js +11 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
- package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
- package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
- package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
- package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
- package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +102 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
- package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
- package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
- package/build/dist/Server/Services/Index.js +12 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
- package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
- package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/TeamMemberService.js +396 -1
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +43 -0
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
- package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
- package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
- package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
- package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
- package/build/dist/Server/Utils/Response.js +97 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +9 -1
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
- package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
- package/build/dist/Types/Calendar/ICalendar.js +290 -0
- package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +2 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
- package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
- package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
- package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
- package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
- package/package.json +1 -1
|
@@ -18,12 +18,124 @@ import Timezone from "../../Types/Timezone";
|
|
|
18
18
|
import DeleteBy from "../Types/Database/DeleteBy";
|
|
19
19
|
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
20
20
|
import logger from "../Utils/Logger";
|
|
21
|
+
import { OnCallShiftChangeReason } from "../Utils/OnCall/OnCallShiftChangeListeners";
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* The (project, overridden user, substitute) triple an override names; the
|
|
25
|
+
* hooks capture it before an update / delete so the schedules of the OLD
|
|
26
|
+
* users can be refreshed and re-versioned once the row has changed.
|
|
27
|
+
*/
|
|
28
|
+
interface OverrideUserPair {
|
|
29
|
+
projectId: ObjectID;
|
|
30
|
+
overrideUserId: ObjectID;
|
|
31
|
+
routeAlertsToUserId?: ObjectID | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* The subset of OnCallDutyPolicyScheduleService the override hooks call.
|
|
36
|
+
* Lazily required (see refreshRostersForOverrideUser) because that service
|
|
37
|
+
* imports this one.
|
|
38
|
+
*/
|
|
39
|
+
interface ScheduleServiceForOverrides {
|
|
40
|
+
refreshRostersForUserInProject: (d: {
|
|
41
|
+
projectId: ObjectID;
|
|
42
|
+
userId: ObjectID;
|
|
43
|
+
}) => Promise<void>;
|
|
44
|
+
getScheduleIdsForUsersInProject: (d: {
|
|
45
|
+
projectId: ObjectID;
|
|
46
|
+
userIds: Array<ObjectID>;
|
|
47
|
+
}) => Promise<Array<ObjectID>>;
|
|
48
|
+
propagateShiftConfigChange: (d: {
|
|
49
|
+
scheduleIds: Array<ObjectID>;
|
|
50
|
+
projectId?: ObjectID | null | undefined;
|
|
51
|
+
userIds?: Array<ObjectID> | undefined;
|
|
52
|
+
reason: OnCallShiftChangeReason;
|
|
53
|
+
}) => Promise<void>;
|
|
54
|
+
}
|
|
21
55
|
|
|
22
56
|
export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
23
57
|
public constructor() {
|
|
24
58
|
super(OnCallDutyPolicyUserOverride);
|
|
25
59
|
}
|
|
26
60
|
|
|
61
|
+
private getScheduleService(): ScheduleServiceForOverrides {
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
63
|
+
return require("./OnCallDutyPolicyScheduleService").default;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* An override changes who is on call on every schedule that contains the
|
|
68
|
+
* overridden user, and it changes the SUBSTITUTE's personal calendar too.
|
|
69
|
+
* Find those schedules with the same layer-user query the roster refresh
|
|
70
|
+
* uses (for every overridden user, old and new), then bump their
|
|
71
|
+
* shiftConfigVersion, drop the feed caches — the substitutes' personal
|
|
72
|
+
* caches included — and let the shift-change listeners re-plan reminders.
|
|
73
|
+
* One call per project; best-effort, never throws into the CRUD path.
|
|
74
|
+
*/
|
|
75
|
+
private async propagateShiftChangeForOverrides(
|
|
76
|
+
pairs: Array<OverrideUserPair>,
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
const byProject: Map<
|
|
79
|
+
string,
|
|
80
|
+
{
|
|
81
|
+
projectId: ObjectID;
|
|
82
|
+
overrideUserIds: Array<ObjectID>;
|
|
83
|
+
allUserIds: Array<ObjectID>;
|
|
84
|
+
}
|
|
85
|
+
> = new Map();
|
|
86
|
+
|
|
87
|
+
for (const pair of pairs) {
|
|
88
|
+
if (!pair.projectId || !pair.overrideUserId) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const key: string = pair.projectId.toString();
|
|
93
|
+
const entry: {
|
|
94
|
+
projectId: ObjectID;
|
|
95
|
+
overrideUserIds: Array<ObjectID>;
|
|
96
|
+
allUserIds: Array<ObjectID>;
|
|
97
|
+
} = byProject.get(key) || {
|
|
98
|
+
projectId: pair.projectId,
|
|
99
|
+
overrideUserIds: [],
|
|
100
|
+
allUserIds: [],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
entry.overrideUserIds.push(pair.overrideUserId);
|
|
104
|
+
entry.allUserIds.push(pair.overrideUserId);
|
|
105
|
+
|
|
106
|
+
if (pair.routeAlertsToUserId) {
|
|
107
|
+
entry.allUserIds.push(pair.routeAlertsToUserId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
byProject.set(key, entry);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const entry of byProject.values()) {
|
|
114
|
+
try {
|
|
115
|
+
const scheduleService: ScheduleServiceForOverrides =
|
|
116
|
+
this.getScheduleService();
|
|
117
|
+
|
|
118
|
+
const scheduleIds: Array<ObjectID> =
|
|
119
|
+
await scheduleService.getScheduleIdsForUsersInProject({
|
|
120
|
+
projectId: entry.projectId,
|
|
121
|
+
userIds: entry.overrideUserIds,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await scheduleService.propagateShiftConfigChange({
|
|
125
|
+
scheduleIds,
|
|
126
|
+
projectId: entry.projectId,
|
|
127
|
+
userIds: entry.allUserIds,
|
|
128
|
+
reason: OnCallShiftChangeReason.OverrideChanged,
|
|
129
|
+
});
|
|
130
|
+
} catch (err) {
|
|
131
|
+
logger.error(
|
|
132
|
+
"Error propagating a user override change to the calendar feeds (best-effort).",
|
|
133
|
+
);
|
|
134
|
+
logger.error(err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
27
139
|
/**
|
|
28
140
|
* Re-resolve the persisted roster of every schedule in the project that
|
|
29
141
|
* contains the override user, so a created / updated / deleted override is
|
|
@@ -41,13 +153,8 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
41
153
|
}
|
|
42
154
|
|
|
43
155
|
try {
|
|
44
|
-
const scheduleService:
|
|
45
|
-
|
|
46
|
-
projectId: ObjectID;
|
|
47
|
-
userId: ObjectID;
|
|
48
|
-
}) => Promise<void>;
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
50
|
-
} = require("./OnCallDutyPolicyScheduleService").default;
|
|
156
|
+
const scheduleService: ScheduleServiceForOverrides =
|
|
157
|
+
this.getScheduleService();
|
|
51
158
|
|
|
52
159
|
await scheduleService.refreshRostersForUserInProject({
|
|
53
160
|
projectId: data.projectId,
|
|
@@ -190,6 +297,16 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
190
297
|
overrideUserId,
|
|
191
298
|
});
|
|
192
299
|
|
|
300
|
+
if (projectId && overrideUserId) {
|
|
301
|
+
await this.propagateShiftChangeForOverrides([
|
|
302
|
+
{
|
|
303
|
+
projectId,
|
|
304
|
+
overrideUserId,
|
|
305
|
+
routeAlertsToUserId: routeAlertsToUserId || undefined,
|
|
306
|
+
},
|
|
307
|
+
]);
|
|
308
|
+
}
|
|
309
|
+
|
|
193
310
|
return createdItem;
|
|
194
311
|
}
|
|
195
312
|
|
|
@@ -207,10 +324,7 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
207
324
|
* schedules by the NEW user only (audit M2). Mirrors the onBeforeDelete
|
|
208
325
|
* carry-forward pattern used by the delete path.
|
|
209
326
|
*/
|
|
210
|
-
const previous: Array<
|
|
211
|
-
projectId: ObjectID;
|
|
212
|
-
overrideUserId: ObjectID;
|
|
213
|
-
}> = [];
|
|
327
|
+
const previous: Array<OverrideUserPair> = [];
|
|
214
328
|
|
|
215
329
|
try {
|
|
216
330
|
const rows: Array<OnCallDutyPolicyUserOverride> = await this.findBy({
|
|
@@ -218,6 +332,7 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
218
332
|
select: {
|
|
219
333
|
projectId: true,
|
|
220
334
|
overrideUserId: true,
|
|
335
|
+
routeAlertsToUserId: true,
|
|
221
336
|
},
|
|
222
337
|
props: {
|
|
223
338
|
isRoot: true,
|
|
@@ -231,8 +346,14 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
231
346
|
row.projectId || row.project?.id;
|
|
232
347
|
const overrideUserId: ObjectID | undefined | null =
|
|
233
348
|
row.overrideUserId || row.overrideUser?.id;
|
|
349
|
+
const routeAlertsToUserId: ObjectID | undefined | null =
|
|
350
|
+
row.routeAlertsToUserId || row.routeAlertsToUser?.id;
|
|
234
351
|
if (projectId && overrideUserId) {
|
|
235
|
-
previous.push({
|
|
352
|
+
previous.push({
|
|
353
|
+
projectId,
|
|
354
|
+
overrideUserId,
|
|
355
|
+
routeAlertsToUserId: routeAlertsToUserId || undefined,
|
|
356
|
+
});
|
|
236
357
|
}
|
|
237
358
|
}
|
|
238
359
|
} catch (err) {
|
|
@@ -279,11 +400,11 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
279
400
|
};
|
|
280
401
|
|
|
281
402
|
// OLD override users captured before the update.
|
|
282
|
-
const previous: Array<
|
|
283
|
-
(onUpdate.carryForward as Array<
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
403
|
+
const previous: Array<OverrideUserPair> =
|
|
404
|
+
(onUpdate.carryForward as Array<OverrideUserPair>) || [];
|
|
405
|
+
|
|
406
|
+
// Everyone the edit touched, old and new, for the feed / reminder pass.
|
|
407
|
+
const affectedPairs: Array<OverrideUserPair> = [...previous];
|
|
287
408
|
|
|
288
409
|
for (const entry of previous) {
|
|
289
410
|
await refreshPair(entry.projectId, entry.overrideUserId);
|
|
@@ -296,6 +417,7 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
296
417
|
select: {
|
|
297
418
|
projectId: true,
|
|
298
419
|
overrideUserId: true,
|
|
420
|
+
routeAlertsToUserId: true,
|
|
299
421
|
},
|
|
300
422
|
props: {
|
|
301
423
|
isRoot: true,
|
|
@@ -306,12 +428,26 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
306
428
|
continue;
|
|
307
429
|
}
|
|
308
430
|
|
|
309
|
-
|
|
310
|
-
item.projectId || item.project?.id
|
|
311
|
-
|
|
312
|
-
|
|
431
|
+
const newProjectId: ObjectID | undefined | null =
|
|
432
|
+
item.projectId || item.project?.id;
|
|
433
|
+
const newOverrideUserId: ObjectID | undefined | null =
|
|
434
|
+
item.overrideUserId || item.overrideUser?.id;
|
|
435
|
+
const newRouteAlertsToUserId: ObjectID | undefined | null =
|
|
436
|
+
item.routeAlertsToUserId || item.routeAlertsToUser?.id;
|
|
437
|
+
|
|
438
|
+
await refreshPair(newProjectId, newOverrideUserId);
|
|
439
|
+
|
|
440
|
+
if (newProjectId && newOverrideUserId) {
|
|
441
|
+
affectedPairs.push({
|
|
442
|
+
projectId: newProjectId,
|
|
443
|
+
overrideUserId: newOverrideUserId,
|
|
444
|
+
routeAlertsToUserId: newRouteAlertsToUserId || undefined,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
313
447
|
}
|
|
314
448
|
|
|
449
|
+
await this.propagateShiftChangeForOverrides(affectedPairs);
|
|
450
|
+
|
|
315
451
|
return onUpdate;
|
|
316
452
|
}
|
|
317
453
|
|
|
@@ -326,11 +462,8 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
326
462
|
* than waiting for the next natural handoff (audit F4). The affected
|
|
327
463
|
* project/user pairs were captured in onBeforeDelete (the rows are gone now).
|
|
328
464
|
*/
|
|
329
|
-
const affected: Array<
|
|
330
|
-
(onDelete.carryForward as Array<
|
|
331
|
-
projectId: ObjectID;
|
|
332
|
-
overrideUserId: ObjectID;
|
|
333
|
-
}>) || [];
|
|
465
|
+
const affected: Array<OverrideUserPair> =
|
|
466
|
+
(onDelete.carryForward as Array<OverrideUserPair>) || [];
|
|
334
467
|
|
|
335
468
|
for (const entry of affected) {
|
|
336
469
|
await this.refreshRostersForOverrideUser({
|
|
@@ -339,6 +472,8 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
339
472
|
});
|
|
340
473
|
}
|
|
341
474
|
|
|
475
|
+
await this.propagateShiftChangeForOverrides(affected);
|
|
476
|
+
|
|
342
477
|
return onDelete;
|
|
343
478
|
}
|
|
344
479
|
|
|
@@ -368,10 +503,7 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
368
503
|
* onDeleteSuccess can refresh those schedules' rosters AFTER the rows are
|
|
369
504
|
* gone (audit F4). Collected for global overrides too (no policy id).
|
|
370
505
|
*/
|
|
371
|
-
const affectedRosters: Array<
|
|
372
|
-
projectId: ObjectID;
|
|
373
|
-
overrideUserId: ObjectID;
|
|
374
|
-
}> = [];
|
|
506
|
+
const affectedRosters: Array<OverrideUserPair> = [];
|
|
375
507
|
|
|
376
508
|
for (const item of itemsToDelete) {
|
|
377
509
|
const onCallDutyPolicyId: ObjectID | undefined | null =
|
|
@@ -387,7 +519,11 @@ export class Service extends DatabaseService<OnCallDutyPolicyUserOverride> {
|
|
|
387
519
|
item.routeAlertsToUserId || item.routeAlertsToUser?.id;
|
|
388
520
|
|
|
389
521
|
if (projectId && overrideUserId) {
|
|
390
|
-
affectedRosters.push({
|
|
522
|
+
affectedRosters.push({
|
|
523
|
+
projectId,
|
|
524
|
+
overrideUserId,
|
|
525
|
+
routeAlertsToUserId: routeAlertsToUserId || undefined,
|
|
526
|
+
});
|
|
391
527
|
}
|
|
392
528
|
|
|
393
529
|
if (
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
2
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
3
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
4
|
+
import DatabaseService from "./DatabaseService";
|
|
5
|
+
import {
|
|
6
|
+
MAX_MINIMUM_GAP_MINUTES,
|
|
7
|
+
MIN_MINIMUM_GAP_MINUTES,
|
|
8
|
+
} from "./OnCallDutyPolicyScheduleCalendarFeedService";
|
|
9
|
+
import CalendarFeedToken, {
|
|
10
|
+
CalendarFeedRotation,
|
|
11
|
+
} from "../Utils/OnCall/CalendarFeedToken";
|
|
12
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
13
|
+
import Model from "../../Models/DatabaseModels/ProjectOnCallCalendarFeed";
|
|
14
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
15
|
+
import NotFoundException from "../../Types/Exception/NotFoundException";
|
|
16
|
+
import CalendarFeedWindow from "../../Types/OnCallDutyPolicy/CalendarFeedWindow";
|
|
17
|
+
import ObjectID from "../../Types/ObjectID";
|
|
18
|
+
import PositiveNumber from "../../Types/PositiveNumber";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* The project-wide shared calendar feed: at most one row per project.
|
|
22
|
+
*
|
|
23
|
+
* The same guarantees as OnCallDutyPolicyScheduleCalendarFeedService minus
|
|
24
|
+
* the schedule check: one feed per project (unique index + a readable
|
|
25
|
+
* message), the token minted here on every create whatever the request
|
|
26
|
+
* carried, window and gap settings bounded on every write, root-only
|
|
27
|
+
* rotation with the 30-day grace hash.
|
|
28
|
+
*/
|
|
29
|
+
export class Service extends DatabaseService<Model> {
|
|
30
|
+
public constructor() {
|
|
31
|
+
super(Model);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@CaptureSpan()
|
|
35
|
+
protected override async onBeforeCreate(
|
|
36
|
+
createBy: CreateBy<Model>,
|
|
37
|
+
): Promise<OnCreate<Model>> {
|
|
38
|
+
const projectId: ObjectID | undefined =
|
|
39
|
+
createBy.data.projectId || createBy.props.tenantId;
|
|
40
|
+
|
|
41
|
+
if (!projectId) {
|
|
42
|
+
throw new BadDataException("projectId is required");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
createBy.data.projectId = projectId;
|
|
46
|
+
|
|
47
|
+
const existing: PositiveNumber = await this.countBy({
|
|
48
|
+
query: {
|
|
49
|
+
projectId: projectId,
|
|
50
|
+
},
|
|
51
|
+
props: {
|
|
52
|
+
isRoot: true,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (existing.toNumber() > 0) {
|
|
57
|
+
throw new BadDataException(
|
|
58
|
+
"This project already has a shared calendar feed. Regenerate or edit the existing one instead.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// A request body never chooses the secret; only root may pre-mint one.
|
|
63
|
+
CalendarFeedToken.applyTokenColumnsOnCreate(createBy.data, {
|
|
64
|
+
trustSuppliedToken: createBy.props.isRoot === true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
createBy.data.pastDays = CalendarFeedWindow.clampPastDays(
|
|
68
|
+
createBy.data.pastDays,
|
|
69
|
+
);
|
|
70
|
+
createBy.data.futureDays = CalendarFeedWindow.clampFutureDays(
|
|
71
|
+
createBy.data.futureDays,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (createBy.data.minimumGapMinutes !== undefined) {
|
|
75
|
+
createBy.data.minimumGapMinutes = Service.validateMinimumGapMinutes(
|
|
76
|
+
createBy.data.minimumGapMinutes,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { createBy, carryForward: null };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@CaptureSpan()
|
|
84
|
+
protected override async onBeforeUpdate(
|
|
85
|
+
updateBy: UpdateBy<Model>,
|
|
86
|
+
): Promise<OnUpdate<Model>> {
|
|
87
|
+
if (updateBy.data.pastDays !== undefined) {
|
|
88
|
+
updateBy.data.pastDays = CalendarFeedWindow.clampPastDays(
|
|
89
|
+
updateBy.data.pastDays,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (updateBy.data.futureDays !== undefined) {
|
|
94
|
+
updateBy.data.futureDays = CalendarFeedWindow.clampFutureDays(
|
|
95
|
+
updateBy.data.futureDays,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (updateBy.data.minimumGapMinutes !== undefined) {
|
|
100
|
+
updateBy.data.minimumGapMinutes = Service.validateMinimumGapMinutes(
|
|
101
|
+
updateBy.data.minimumGapMinutes,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { updateBy, carryForward: null };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public static validateMinimumGapMinutes(value: unknown): number {
|
|
109
|
+
const parsed: number =
|
|
110
|
+
typeof value === "string" && value.trim() !== ""
|
|
111
|
+
? Number(value)
|
|
112
|
+
: (value as number);
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
typeof parsed !== "number" ||
|
|
116
|
+
!Number.isInteger(parsed) ||
|
|
117
|
+
parsed < MIN_MINIMUM_GAP_MINUTES ||
|
|
118
|
+
parsed > MAX_MINIMUM_GAP_MINUTES
|
|
119
|
+
) {
|
|
120
|
+
throw new BadDataException(
|
|
121
|
+
`Minimum gap must be a whole number of minutes between ${MIN_MINIMUM_GAP_MINUTES} and ${MAX_MINIMUM_GAP_MINUTES}.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return parsed;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Rotate the token of an existing feed. Returns the new plaintext token
|
|
130
|
+
* ONCE. Root write; the caller purges caches.
|
|
131
|
+
*/
|
|
132
|
+
@CaptureSpan()
|
|
133
|
+
public async rotateTokenById(data: {
|
|
134
|
+
id: ObjectID;
|
|
135
|
+
}): Promise<CalendarFeedRotation> {
|
|
136
|
+
const existing: Model | null = await this.findOneBy({
|
|
137
|
+
query: { _id: data.id },
|
|
138
|
+
select: { _id: true, tokenHash: true },
|
|
139
|
+
props: { isRoot: true, ignoreHooks: true },
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!existing) {
|
|
143
|
+
throw new NotFoundException("Calendar feed not found.");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const rotation: CalendarFeedRotation = CalendarFeedToken.buildRotation({
|
|
147
|
+
currentTokenHash: existing.tokenHash,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await this.updateOneById({
|
|
151
|
+
id: data.id,
|
|
152
|
+
data: CalendarFeedToken.toRotationUpdateData(rotation),
|
|
153
|
+
props: { isRoot: true },
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return rotation;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Rotate the project's feed when a member leaves, if it is enabled and
|
|
161
|
+
* opted in. Returns the rotated id (at most one) for cache purging.
|
|
162
|
+
*/
|
|
163
|
+
@CaptureSpan()
|
|
164
|
+
public async rotateFeedsForMemberLeave(data: {
|
|
165
|
+
projectId: ObjectID;
|
|
166
|
+
}): Promise<Array<ObjectID>> {
|
|
167
|
+
const feed: Model | null = await this.findOneBy({
|
|
168
|
+
query: {
|
|
169
|
+
projectId: data.projectId,
|
|
170
|
+
isEnabled: true,
|
|
171
|
+
rotateWhenMemberLeaves: true,
|
|
172
|
+
},
|
|
173
|
+
select: {
|
|
174
|
+
_id: true,
|
|
175
|
+
},
|
|
176
|
+
props: {
|
|
177
|
+
isRoot: true,
|
|
178
|
+
ignoreHooks: true,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (!feed || !feed.id) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await this.rotateTokenById({ id: feed.id });
|
|
187
|
+
|
|
188
|
+
return [feed.id];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default new Service();
|