@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.
Files changed (189) hide show
  1. package/Models/DatabaseModels/Index.ts +11 -0
  2. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +43 -0
  3. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +662 -0
  4. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +584 -0
  5. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +546 -0
  6. package/Models/DatabaseModels/UserOnCallShiftReminder.ts +231 -0
  7. package/Models/DatabaseModels/UserOnCallShiftReminderLog.ts +331 -0
  8. package/Server/API/OnCallCalendarAPI.ts +2194 -0
  9. package/Server/EnvironmentConfig.ts +138 -0
  10. package/Server/Infrastructure/OnCallCalendarFeedCache.ts +1050 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.ts +253 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  13. package/Server/Middleware/OnCallCalendarFeedRateLimit.ts +487 -0
  14. package/Server/Services/Index.ts +12 -0
  15. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +32 -0
  16. package/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.ts +260 -0
  17. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +70 -3
  18. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +138 -3
  19. package/Server/Services/OnCallDutyPolicyScheduleService.ts +1465 -134
  20. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +167 -31
  21. package/Server/Services/ProjectOnCallCalendarFeedService.ts +192 -0
  22. package/Server/Services/TeamMemberService.ts +489 -1
  23. package/Server/Services/TeamService.ts +50 -0
  24. package/Server/Services/UserNotificationSettingService.ts +44 -1
  25. package/Server/Services/UserOnCallCalendarFeedService.ts +159 -0
  26. package/Server/Services/UserOnCallShiftReminderLogService.ts +151 -0
  27. package/Server/Services/UserOnCallShiftReminderService.ts +112 -0
  28. package/Server/Utils/OnCall/CalendarFeedToken.ts +241 -0
  29. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1488 -0
  30. package/Server/Utils/OnCall/OnCallCalendarFeedUrls.ts +230 -0
  31. package/Server/Utils/OnCall/OnCallShiftChangeListeners.ts +227 -0
  32. package/Server/Utils/OnCall/OnCallShiftMaterializer.ts +1127 -0
  33. package/Server/Utils/OnCall/OnCallShiftReminderListener.ts +174 -0
  34. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +2584 -0
  35. package/Server/Utils/Response.ts +134 -0
  36. package/Server/Utils/StartServer.ts +10 -0
  37. package/Server/Utils/Telemetry/AppMetrics.ts +51 -0
  38. package/Tests/App/Dashboard/OnCallCalendarFeedLinks.test.tsx +229 -0
  39. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +489 -0
  40. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +149 -0
  41. package/Tests/App/Dashboard/OnCallCalendarFeedUtil.test.ts +855 -0
  42. package/Tests/App/Dashboard/OnCallPersonalCalendarFeed.test.tsx +1209 -0
  43. package/Tests/App/Dashboard/OnCallSharedCalendarFeedCard.test.tsx +1019 -0
  44. package/Tests/App/Dashboard/OnCallShiftRemindersAndUpcomingShifts.test.tsx +655 -0
  45. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +73 -0
  46. package/Tests/Models/OnCallCalendarFeedModels.test.ts +642 -0
  47. package/Tests/Models/OnCallDutyPolicyScheduleCalendarFeed.test.ts +288 -0
  48. package/Tests/Models/OnCallDutyPolicyScheduleShiftConfigVersion.test.ts +88 -0
  49. package/Tests/Models/UserOnCallShiftReminderModels.test.ts +368 -0
  50. package/Tests/Server/API/Helpers.ts +8 -0
  51. package/Tests/Server/API/OnCallCalendarAPI.test.ts +4461 -0
  52. package/Tests/Server/EnvironmentConfigOnCallCalendarFeed.test.ts +357 -0
  53. package/Tests/Server/Infrastructure/OnCallCalendarFeedCache.test.ts +1589 -0
  54. package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +369 -0
  55. package/Tests/Server/Middleware/OnCallCalendarFeedRateLimit.test.ts +1365 -0
  56. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +70 -0
  57. package/Tests/Server/Services/OnCallCalendarFeedServices.test.ts +1103 -0
  58. package/Tests/Server/Services/OnCallDutyPolicyScheduleResolver.test.ts +1037 -0
  59. package/Tests/Server/Services/OnCallShiftConfigPropagation.test.ts +1270 -0
  60. package/Tests/Server/Services/TeamDeleteMemberCleanup.test.ts +156 -0
  61. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +755 -0
  62. package/Tests/Server/Services/UserNotificationSettingShiftReminderDefaults.test.ts +267 -0
  63. package/Tests/Server/Services/UserOnCallShiftReminderServices.test.ts +583 -0
  64. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +254 -0
  65. package/Tests/Server/Utils/OnCall/CalendarFeedToken.test.ts +467 -0
  66. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedRenderer.test.ts +2142 -0
  67. package/Tests/Server/Utils/OnCall/OnCallCalendarFeedUrls.test.ts +278 -0
  68. package/Tests/Server/Utils/OnCall/OnCallResolverTestHarness.ts +440 -0
  69. package/Tests/Server/Utils/OnCall/OnCallShiftChangeListeners.test.ts +314 -0
  70. package/Tests/Server/Utils/OnCall/OnCallShiftMaterializer.test.ts +1502 -0
  71. package/Tests/Server/Utils/OnCall/OnCallShiftReminderListener.test.ts +491 -0
  72. package/Tests/Server/Utils/OnCall/OnCallShiftReminderRunner.test.ts +3088 -0
  73. package/Tests/Server/Utils/OnCall/OnCallShiftReminderTestHarness.ts +910 -0
  74. package/Tests/Server/Utils/ResponseSendCalendarResponse.test.ts +561 -0
  75. package/Tests/Server/Utils/StartServerEncryptionSecretWarning.test.ts +64 -0
  76. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +29 -0
  77. package/Tests/Types/Calendar/ICalendar.test.ts +488 -0
  78. package/Tests/Types/NotificationSetting/NotificationSettingEventType.test.ts +53 -0
  79. package/Tests/Types/OnCallDutyPolicy/CalendarFeedTestFixtures.ts +310 -0
  80. package/Tests/Types/OnCallDutyPolicy/CalendarFeedWindow.test.ts +205 -0
  81. package/Tests/Types/OnCallDutyPolicy/LayerUtilLayerMetaAndIterationCap.test.ts +458 -0
  82. package/Tests/Types/OnCallDutyPolicy/MaterializedShift.test.ts +197 -0
  83. package/Tests/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.test.ts +1986 -0
  84. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtilGroupKey.test.ts +678 -0
  85. package/Tests/Types/OnCallDutyPolicy/ShiftSeamUtil.test.ts +417 -0
  86. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  87. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  88. package/Types/Calendar/ICalendar.ts +442 -0
  89. package/Types/Email/EmailTemplateType.ts +2 -0
  90. package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
  91. package/Types/OnCallDutyPolicy/CalendarFeedWindow.ts +140 -0
  92. package/Types/OnCallDutyPolicy/Layer.ts +190 -35
  93. package/Types/OnCallDutyPolicy/MaterializedShift.ts +274 -0
  94. package/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.ts +1415 -0
  95. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +159 -4
  96. package/Types/OnCallDutyPolicy/ShiftSeamUtil.ts +106 -0
  97. package/build/dist/Models/DatabaseModels/Index.js +11 -0
  98. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +44 -0
  100. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  101. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +699 -0
  102. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -0
  103. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +620 -0
  104. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -0
  105. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +585 -0
  106. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -0
  107. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js +247 -0
  108. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminder.js.map +1 -0
  109. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js +355 -0
  110. package/build/dist/Models/DatabaseModels/UserOnCallShiftReminderLog.js.map +1 -0
  111. package/build/dist/Server/API/OnCallCalendarAPI.js +1371 -0
  112. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -0
  113. package/build/dist/Server/EnvironmentConfig.js +102 -0
  114. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  115. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js +663 -0
  116. package/build/dist/Server/Infrastructure/OnCallCalendarFeedCache.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js +103 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790400000000-AddOnCallCalendarFeeds.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  121. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js +342 -0
  122. package/build/dist/Server/Middleware/OnCallCalendarFeedRateLimit.js.map +1 -0
  123. package/build/dist/Server/Services/Index.js +12 -0
  124. package/build/dist/Server/Services/Index.js.map +1 -1
  125. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +20 -0
  126. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  127. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js +219 -0
  128. package/build/dist/Server/Services/OnCallDutyPolicyScheduleCalendarFeedService.js.map +1 -0
  129. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +47 -3
  130. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  131. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +112 -3
  132. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  133. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +955 -9
  134. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  135. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +93 -6
  136. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  137. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js +157 -0
  138. package/build/dist/Server/Services/ProjectOnCallCalendarFeedService.js.map +1 -0
  139. package/build/dist/Server/Services/TeamMemberService.js +396 -1
  140. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  141. package/build/dist/Server/Services/TeamService.js +43 -0
  142. package/build/dist/Server/Services/TeamService.js.map +1 -1
  143. package/build/dist/Server/Services/UserNotificationSettingService.js +26 -1
  144. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  145. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js +140 -0
  146. package/build/dist/Server/Services/UserOnCallCalendarFeedService.js.map +1 -0
  147. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js +116 -0
  148. package/build/dist/Server/Services/UserOnCallShiftReminderLogService.js.map +1 -0
  149. package/build/dist/Server/Services/UserOnCallShiftReminderService.js +92 -0
  150. package/build/dist/Server/Services/UserOnCallShiftReminderService.js.map +1 -0
  151. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js +161 -0
  152. package/build/dist/Server/Utils/OnCall/CalendarFeedToken.js.map +1 -0
  153. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js +983 -0
  154. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedRenderer.js.map +1 -0
  155. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js +155 -0
  156. package/build/dist/Server/Utils/OnCall/OnCallCalendarFeedUrls.js.map +1 -0
  157. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js +145 -0
  158. package/build/dist/Server/Utils/OnCall/OnCallShiftChangeListeners.js.map +1 -0
  159. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js +752 -0
  160. package/build/dist/Server/Utils/OnCall/OnCallShiftMaterializer.js.map +1 -0
  161. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js +92 -0
  162. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderListener.js.map +1 -0
  163. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1786 -0
  164. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -0
  165. package/build/dist/Server/Utils/Response.js +97 -0
  166. package/build/dist/Server/Utils/Response.js.map +1 -1
  167. package/build/dist/Server/Utils/StartServer.js +9 -1
  168. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  169. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +41 -0
  170. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  171. package/build/dist/Types/Calendar/ICalendar.js +290 -0
  172. package/build/dist/Types/Calendar/ICalendar.js.map +1 -0
  173. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  174. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  175. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  176. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  177. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js +97 -0
  178. package/build/dist/Types/OnCallDutyPolicy/CalendarFeedWindow.js.map +1 -0
  179. package/build/dist/Types/OnCallDutyPolicy/Layer.js +113 -19
  180. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  181. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js +134 -0
  182. package/build/dist/Types/OnCallDutyPolicy/MaterializedShift.js.map +1 -0
  183. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js +800 -0
  184. package/build/dist/Types/OnCallDutyPolicy/OnCallCalendarFeedUtil.js.map +1 -0
  185. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +97 -3
  186. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  187. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js +77 -0
  188. package/build/dist/Types/OnCallDutyPolicy/ShiftSeamUtil.js.map +1 -0
  189. package/package.json +1 -1
@@ -0,0 +1,752 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import crypto from "crypto";
11
+ import OnCallDutyPolicyScheduleService from "../../Services/OnCallDutyPolicyScheduleService";
12
+ import OnCallDutyPolicyScheduleLayerUserService from "../../Services/OnCallDutyPolicyScheduleLayerUserService";
13
+ import OnCallDutyPolicyUserOverrideService from "../../Services/OnCallDutyPolicyUserOverrideService";
14
+ import ProjectService from "../../Services/ProjectService";
15
+ import UserService from "../../Services/UserService";
16
+ import QueryHelper from "../../Types/Database/QueryHelper";
17
+ import CaptureSpan from "../Telemetry/CaptureSpan";
18
+ import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
19
+ import OneUptimeDate from "../../../Types/Date";
20
+ import BadDataException from "../../../Types/Exception/BadDataException";
21
+ import { FEED_SIMULATION_ITERATION_CAP } from "../../../Types/OnCallDutyPolicy/CalendarFeedWindow";
22
+ import MaterializedShiftUtil from "../../../Types/OnCallDutyPolicy/MaterializedShift";
23
+ import OnCallCalendarFeedUtil from "../../../Types/OnCallDutyPolicy/OnCallCalendarFeedUtil";
24
+ import ScheduleShiftUtil from "../../../Types/OnCallDutyPolicy/ScheduleShiftUtil";
25
+ import ShiftSeamUtil from "../../../Types/OnCallDutyPolicy/ShiftSeamUtil";
26
+ export default class OnCallShiftMaterializer {
27
+ // -- Entry points -----------------------------------------------------
28
+ static async materializeForSchedule(options) {
29
+ return await OnCallShiftMaterializer.materializeForSchedules({
30
+ scheduleIds: [options.scheduleId],
31
+ windowStart: options.windowStart,
32
+ windowEnd: options.windowEnd,
33
+ now: options.now,
34
+ maxSimulationIterations: options.maxSimulationIterations,
35
+ });
36
+ }
37
+ static async materializeForSchedules(options) {
38
+ var _a;
39
+ OnCallShiftMaterializer.validateWindow(options.windowStart, options.windowEnd);
40
+ const now = options.now || OneUptimeDate.getCurrentDate();
41
+ const scheduleIds = OnCallShiftMaterializer.dedupeIds(options.scheduleIds);
42
+ if (scheduleIds.length === 0) {
43
+ return OnCallShiftMaterializer.emptyResult(now);
44
+ }
45
+ const resolved = await OnCallDutyPolicyScheduleService.getResolvedShiftSegmentsForSchedules({
46
+ scheduleIds,
47
+ windowStart: options.windowStart,
48
+ windowEnd: options.windowEnd,
49
+ maxSimulationIterations: (_a = options.maxSimulationIterations) !== null && _a !== void 0 ? _a : FEED_SIMULATION_ITERATION_CAP,
50
+ });
51
+ if (resolved.length === 0) {
52
+ return OnCallShiftMaterializer.emptyResult(now);
53
+ }
54
+ const users = await OnCallShiftMaterializer.loadUsers(OnCallShiftMaterializer.collectUserIds(resolved));
55
+ const projectNames = await OnCallShiftMaterializer.loadProjectNames(resolved.map((item) => {
56
+ return item.schedule.projectId;
57
+ }));
58
+ const shifts = [];
59
+ const schedules = [];
60
+ let truncated = false;
61
+ for (const item of resolved) {
62
+ const projectName = projectNames.get(item.schedule.projectId);
63
+ shifts.push(...OnCallShiftMaterializer.materializeResolved(item, {
64
+ now,
65
+ users,
66
+ projectName,
67
+ }));
68
+ const info = {
69
+ scheduleId: item.schedule.id,
70
+ scheduleName: item.schedule.name,
71
+ projectId: item.schedule.projectId,
72
+ shiftConfigVersion: item.schedule.shiftConfigVersion,
73
+ lastModifiedAt: item.lastModifiedAt,
74
+ truncated: item.truncated,
75
+ attachedPolicies: item.attachedPolicies,
76
+ layerProps: item.layerProps,
77
+ scheduleUserIds: item.scheduleUserIds,
78
+ };
79
+ if (projectName !== undefined) {
80
+ info.projectName = projectName;
81
+ }
82
+ if (item.schedule.timezone !== undefined) {
83
+ info.scheduleTimezone = item.schedule.timezone;
84
+ }
85
+ schedules.push(info);
86
+ truncated = truncated || item.truncated;
87
+ }
88
+ return {
89
+ shifts: MaterializedShiftUtil.sortByStart(shifts),
90
+ truncated,
91
+ schedules,
92
+ users: Array.from(users.values()),
93
+ generatedAt: now,
94
+ };
95
+ }
96
+ static async materializeForUser(options) {
97
+ OnCallShiftMaterializer.validateWindow(options.windowStart, options.windowEnd);
98
+ const now = options.now || OneUptimeDate.getCurrentDate();
99
+ const candidateScheduleIds = await OnCallShiftMaterializer.getCandidateScheduleIdsForUser({
100
+ userId: options.userId,
101
+ projectIds: options.projectIds,
102
+ scheduleId: options.scheduleId,
103
+ windowStart: options.windowStart,
104
+ windowEnd: options.windowEnd,
105
+ includeCoveringShifts: options.includeCoveringShifts,
106
+ });
107
+ if (candidateScheduleIds.length === 0) {
108
+ return OnCallShiftMaterializer.emptyResult(now);
109
+ }
110
+ const result = await OnCallShiftMaterializer.materializeForSchedules({
111
+ scheduleIds: candidateScheduleIds,
112
+ windowStart: options.windowStart,
113
+ windowEnd: options.windowEnd,
114
+ now,
115
+ maxSimulationIterations: options.maxSimulationIterations,
116
+ });
117
+ return Object.assign(Object.assign({}, result), { shifts: OnCallShiftMaterializer.filterShiftsForUser(result.shifts, options.userId) });
118
+ }
119
+ static async materializeForProject(options) {
120
+ OnCallShiftMaterializer.validateWindow(options.windowStart, options.windowEnd);
121
+ const now = options.now || OneUptimeDate.getCurrentDate();
122
+ const schedules = await OnCallDutyPolicyScheduleService.findBy({
123
+ query: {
124
+ projectId: options.projectId,
125
+ },
126
+ select: {
127
+ _id: true,
128
+ },
129
+ limit: LIMIT_PER_PROJECT,
130
+ skip: 0,
131
+ props: {
132
+ isRoot: true,
133
+ },
134
+ });
135
+ const scheduleIds = schedules
136
+ .map((schedule) => {
137
+ return schedule.id;
138
+ })
139
+ .filter((id) => {
140
+ return Boolean(id);
141
+ });
142
+ if (scheduleIds.length === 0) {
143
+ return OnCallShiftMaterializer.emptyResult(now);
144
+ }
145
+ return await OnCallShiftMaterializer.materializeForSchedules({
146
+ scheduleIds,
147
+ windowStart: options.windowStart,
148
+ windowEnd: options.windowEnd,
149
+ now,
150
+ maxSimulationIterations: options.maxSimulationIterations,
151
+ });
152
+ }
153
+ // -- Candidate schedules ----------------------------------------------
154
+ /**
155
+ * The schedules whose materialization can contain the user's shifts:
156
+ * distinct schedule ids from the user's layer-user rows (optionally within
157
+ * `projectIds`), plus — when includeCoveringShifts — the schedules of every
158
+ * user the caller substitutes for through an override overlapping the
159
+ * window. Optionally narrowed to one schedule. Root reads: the caller has
160
+ * already decided the user may see these.
161
+ *
162
+ * One user, 2-3 queries. Anything that needs this for a GROUP of users
163
+ * (the reminder sweep and its change pass walk every reminded user of a
164
+ * project on every tick) must call getCandidateScheduleIdsForUsers, which
165
+ * answers the whole group in the same 2-3 queries.
166
+ */
167
+ static async getCandidateScheduleIdsForUser(data) {
168
+ const byUser = await OnCallShiftMaterializer.getCandidateScheduleIdsForUsers({
169
+ userIds: [data.userId],
170
+ projectIds: data.projectIds,
171
+ scheduleId: data.scheduleId,
172
+ windowStart: data.windowStart,
173
+ windowEnd: data.windowEnd,
174
+ includeCoveringShifts: data.includeCoveringShifts,
175
+ });
176
+ return byUser.get(data.userId.toString()) || [];
177
+ }
178
+ /**
179
+ * The batched form of getCandidateScheduleIdsForUser: the candidate
180
+ * schedules of MANY users in the same two or three queries it takes for
181
+ * one, keyed by user id (every requested user gets an entry, empty when
182
+ * they are on nothing). Semantics per user are identical — own layer-user
183
+ * rows, plus the schedules of the people that user substitutes for through
184
+ * an override overlapping the window, narrowed to `scheduleId` when given.
185
+ *
186
+ * The covering overrides are matched back to the SUBSTITUTE that each one
187
+ * routes to, so one user's coverage never leaks into another's list.
188
+ */
189
+ static async getCandidateScheduleIdsForUsers(data) {
190
+ var _a, _b, _c, _d, _e, _f;
191
+ const userIds = OnCallShiftMaterializer.dedupeIds(data.userIds);
192
+ const candidatesByUser = new Map();
193
+ for (const userId of userIds) {
194
+ candidatesByUser.set(userId.toString(), []);
195
+ }
196
+ if (userIds.length === 0) {
197
+ return candidatesByUser;
198
+ }
199
+ const projectFilter = data.projectIds && data.projectIds.length > 0
200
+ ? OnCallShiftMaterializer.dedupeIds(data.projectIds)
201
+ : undefined;
202
+ const ownRows = await OnCallDutyPolicyScheduleLayerUserService.findBy({
203
+ query: projectFilter
204
+ ? {
205
+ userId: QueryHelper.any(userIds),
206
+ projectId: QueryHelper.any(projectFilter),
207
+ }
208
+ : {
209
+ userId: QueryHelper.any(userIds),
210
+ },
211
+ select: {
212
+ onCallDutyPolicyScheduleId: true,
213
+ projectId: true,
214
+ userId: true,
215
+ },
216
+ limit: LIMIT_PER_PROJECT,
217
+ skip: 0,
218
+ props: {
219
+ isRoot: true,
220
+ },
221
+ });
222
+ for (const row of ownRows) {
223
+ const userKey = ((_a = row.userId) === null || _a === void 0 ? void 0 : _a.toString()) || "";
224
+ const candidates = candidatesByUser.get(userKey);
225
+ if (!candidates || !row.onCallDutyPolicyScheduleId) {
226
+ continue;
227
+ }
228
+ candidates.push(row.onCallDutyPolicyScheduleId);
229
+ }
230
+ if (data.includeCoveringShifts) {
231
+ const coveringOverrides = await OnCallDutyPolicyUserOverrideService.findBy({
232
+ query: projectFilter
233
+ ? {
234
+ routeAlertsToUserId: QueryHelper.any(userIds),
235
+ projectId: QueryHelper.any(projectFilter),
236
+ startsAt: QueryHelper.lessThanEqualTo(data.windowEnd),
237
+ endsAt: QueryHelper.greaterThanEqualTo(data.windowStart),
238
+ }
239
+ : {
240
+ routeAlertsToUserId: QueryHelper.any(userIds),
241
+ startsAt: QueryHelper.lessThanEqualTo(data.windowEnd),
242
+ endsAt: QueryHelper.greaterThanEqualTo(data.windowStart),
243
+ },
244
+ select: {
245
+ projectId: true,
246
+ overrideUserId: true,
247
+ routeAlertsToUserId: true,
248
+ startsAt: true,
249
+ endsAt: true,
250
+ },
251
+ limit: LIMIT_PER_PROJECT,
252
+ skip: 0,
253
+ props: {
254
+ isRoot: true,
255
+ },
256
+ });
257
+ // substitute -> the (project, overridden user) pairs THEY cover.
258
+ const pairsByUser = new Map();
259
+ const overriddenUserIds = [];
260
+ const overrideProjectIds = [];
261
+ for (const override of coveringOverrides) {
262
+ const overriddenUserId = override.overrideUserId;
263
+ const projectId = override.projectId;
264
+ const substituteKey = ((_b = override.routeAlertsToUserId) === null || _b === void 0 ? void 0 : _b.toString()) || "";
265
+ if (!overriddenUserId ||
266
+ !projectId ||
267
+ !candidatesByUser.has(substituteKey)) {
268
+ continue;
269
+ }
270
+ if (override.startsAt &&
271
+ override.endsAt &&
272
+ (OneUptimeDate.isAfter(override.startsAt, data.windowEnd) ||
273
+ OneUptimeDate.isBefore(override.endsAt, data.windowStart))) {
274
+ continue;
275
+ }
276
+ let pairs = pairsByUser.get(substituteKey);
277
+ if (!pairs) {
278
+ pairs = new Set();
279
+ pairsByUser.set(substituteKey, pairs);
280
+ }
281
+ pairs.add(`${projectId.toString()}:${overriddenUserId.toString()}`);
282
+ overriddenUserIds.push(overriddenUserId);
283
+ overrideProjectIds.push(projectId);
284
+ }
285
+ if (pairsByUser.size > 0) {
286
+ const coveredRows = await OnCallDutyPolicyScheduleLayerUserService.findBy({
287
+ query: {
288
+ userId: QueryHelper.any(OnCallShiftMaterializer.dedupeIds(overriddenUserIds)),
289
+ projectId: QueryHelper.any(OnCallShiftMaterializer.dedupeIds(overrideProjectIds)),
290
+ },
291
+ select: {
292
+ onCallDutyPolicyScheduleId: true,
293
+ projectId: true,
294
+ userId: true,
295
+ },
296
+ limit: LIMIT_PER_PROJECT,
297
+ skip: 0,
298
+ props: {
299
+ isRoot: true,
300
+ },
301
+ });
302
+ for (const row of coveredRows) {
303
+ const key = `${((_c = row.projectId) === null || _c === void 0 ? void 0 : _c.toString()) || ""}:${((_d = row.userId) === null || _d === void 0 ? void 0 : _d.toString()) || ""}`;
304
+ if (!row.onCallDutyPolicyScheduleId) {
305
+ continue;
306
+ }
307
+ for (const [substituteKey, pairs] of pairsByUser) {
308
+ if (pairs.has(key)) {
309
+ (_e = candidatesByUser
310
+ .get(substituteKey)) === null || _e === void 0 ? void 0 : _e.push(row.onCallDutyPolicyScheduleId);
311
+ }
312
+ }
313
+ }
314
+ }
315
+ }
316
+ const wanted = (_f = data.scheduleId) === null || _f === void 0 ? void 0 : _f.toString();
317
+ for (const [userKey, candidates] of candidatesByUser) {
318
+ let deduped = OnCallShiftMaterializer.dedupeIds(candidates);
319
+ if (wanted) {
320
+ deduped = deduped.filter((id) => {
321
+ return id.toString() === wanted;
322
+ });
323
+ }
324
+ candidatesByUser.set(userKey, deduped);
325
+ }
326
+ return candidatesByUser;
327
+ }
328
+ // -- Pure materialization ---------------------------------------------
329
+ /**
330
+ * Materialize ONE resolved schedule: base shifts plus policy-variant
331
+ * shifts. Pure — every lookup has already happened.
332
+ */
333
+ static materializeResolved(resolved, options) {
334
+ const baseShifts = OnCallShiftMaterializer.toShifts(resolved.segments);
335
+ const shifts = baseShifts.map((shift) => {
336
+ return OnCallShiftMaterializer.toMaterializedShift({
337
+ shift,
338
+ resolved,
339
+ options,
340
+ policyVariant: null,
341
+ });
342
+ });
343
+ for (const variant of resolved.policyVariants) {
344
+ const variantShifts = OnCallShiftMaterializer.toShifts(variant.segments);
345
+ const differing = OnCallShiftMaterializer.diffPolicyVariant(baseShifts, variantShifts);
346
+ for (const entry of differing) {
347
+ shifts.push(OnCallShiftMaterializer.toMaterializedShift({
348
+ shift: entry.shift,
349
+ resolved,
350
+ options,
351
+ policyVariant: {
352
+ policyId: variant.policyId,
353
+ policyName: variant.policyName,
354
+ globalUserId: entry.globalUserId,
355
+ },
356
+ }));
357
+ }
358
+ }
359
+ return MaterializedShiftUtil.sortByStart(shifts);
360
+ }
361
+ /*
362
+ * Group + seam-normalise raw segments into shifts. Exposed so tests and
363
+ * the coverage-gap logic can reproduce exactly what the feed sees.
364
+ */
365
+ static toShifts(segments) {
366
+ const grouped = ScheduleShiftUtil.groupEventsIntoShifts(segments, {
367
+ mergeAcrossGaps: false,
368
+ groupKey: ScheduleShiftUtil.groupKeyByUserOverrideAndLayer,
369
+ });
370
+ /*
371
+ * Seam normalisation widens a shift by the engine's one-second seams;
372
+ * with mergeAcrossGaps = false every shift is one contiguous block, so
373
+ * its honest active time is exactly its span. Recompute rather than
374
+ * carry the pre-normalisation sum, which would read 28799 for an 8-hour
375
+ * block.
376
+ */
377
+ return ShiftSeamUtil.normalizeSeams(grouped)
378
+ .filter((shift) => {
379
+ return shift.end.getTime() > shift.start.getTime();
380
+ })
381
+ .map((shift) => {
382
+ return Object.assign(Object.assign({}, shift), { coverageSeconds: Math.round((shift.end.getTime() - shift.start.getTime()) / 1000) });
383
+ });
384
+ }
385
+ /*
386
+ * The variant shifts during which a DIFFERENT person is paged than in the
387
+ * base resolution. A variant shift whose whole span is covered by base
388
+ * shifts of the same user is identical for the subscriber and dropped; one
389
+ * that overlaps a base shift of another user (or no base shift at all) is
390
+ * kept, together with who the base resolution pages at its start.
391
+ */
392
+ static diffPolicyVariant(baseShifts, variantShifts) {
393
+ var _a;
394
+ const result = [];
395
+ for (const variant of variantShifts) {
396
+ const overlapping = baseShifts.filter((base) => {
397
+ return (base.start.getTime() < variant.end.getTime() &&
398
+ base.end.getTime() > variant.start.getTime());
399
+ });
400
+ const differingBase = overlapping.find((base) => {
401
+ return base.userId !== variant.userId;
402
+ });
403
+ if (overlapping.length > 0 && !differingBase) {
404
+ continue; // same person throughout: nothing policy-specific here.
405
+ }
406
+ const globalUserId = (differingBase === null || differingBase === void 0 ? void 0 : differingBase.userId) ||
407
+ ((_a = variant.override) === null || _a === void 0 ? void 0 : _a.originalUserId) ||
408
+ variant.userId;
409
+ result.push({ shift: variant, globalUserId });
410
+ }
411
+ return result;
412
+ }
413
+ /** Keep only the shifts where `userId` is the person on call. */
414
+ static filterShiftsForUser(shifts, userId) {
415
+ const wanted = userId.toString();
416
+ return shifts.filter((shift) => {
417
+ return shift.userId === wanted;
418
+ });
419
+ }
420
+ /*
421
+ * sha256 over every field that changes what a subscriber sees for this
422
+ * shift, excluding the identity (shiftKey), the version and the
423
+ * modification stamp, so two renders of an unchanged shift hash the same
424
+ * and any content change — the person, the end, the layer, the policies,
425
+ * the override provenance — changes the hash.
426
+ */
427
+ static computeContentHash(shift) {
428
+ var _a, _b, _c, _d, _e;
429
+ const payload = {
430
+ scheduleId: shift.scheduleId,
431
+ scheduleName: shift.scheduleName,
432
+ scheduleTimezone: (_a = shift.scheduleTimezone) !== null && _a !== void 0 ? _a : null,
433
+ projectName: (_b = shift.projectName) !== null && _b !== void 0 ? _b : null,
434
+ userId: shift.userId,
435
+ userName: shift.userName,
436
+ start: shift.start.toISOString(),
437
+ end: shift.end.toISOString(),
438
+ coverageSeconds: shift.coverageSeconds,
439
+ layerId: (_c = shift.layerId) !== null && _c !== void 0 ? _c : null,
440
+ layerName: (_d = shift.layerName) !== null && _d !== void 0 ? _d : null,
441
+ override: shift.override
442
+ ? {
443
+ originalUserId: shift.override.originalUserId,
444
+ originalUserName: shift.override.originalUserName,
445
+ overrideStartsAt: shift.override.overrideStartsAt.toISOString(),
446
+ overrideEndsAt: shift.override.overrideEndsAt.toISOString(),
447
+ onCallDutyPolicyId: (_e = shift.override.onCallDutyPolicyId) !== null && _e !== void 0 ? _e : null,
448
+ }
449
+ : null,
450
+ policyVariantOf: shift.policyVariantOf
451
+ ? {
452
+ policyId: shift.policyVariantOf.policyId,
453
+ policyName: shift.policyVariantOf.policyName,
454
+ globalUserId: shift.policyVariantOf.globalUserId,
455
+ }
456
+ : null,
457
+ policies: shift.policies.map((policy) => {
458
+ return {
459
+ policyId: policy.policyId,
460
+ policyName: policy.policyName,
461
+ ruleId: policy.ruleId,
462
+ ruleName: policy.ruleName,
463
+ ruleOrder: policy.ruleOrder,
464
+ };
465
+ }),
466
+ };
467
+ return crypto
468
+ .createHash("sha256")
469
+ .update(JSON.stringify(payload), "utf8")
470
+ .digest("hex");
471
+ }
472
+ // -- Internals ---------------------------------------------------------
473
+ static toMaterializedShift(data) {
474
+ var _a, _b;
475
+ const { shift, resolved, options } = data;
476
+ const base = {
477
+ projectId: resolved.schedule.projectId,
478
+ scheduleId: resolved.schedule.id,
479
+ scheduleName: resolved.schedule.name,
480
+ userId: shift.userId,
481
+ userName: OnCallShiftMaterializer.displayName(options.users, shift.userId),
482
+ start: shift.start,
483
+ end: shift.end,
484
+ coverageSeconds: shift.coverageSeconds,
485
+ policies: resolved.attachedPolicies.map((policy) => {
486
+ return Object.assign({}, policy);
487
+ }),
488
+ isPast: shift.start.getTime() < options.now.getTime(),
489
+ };
490
+ if (options.projectName !== undefined) {
491
+ base.projectName = options.projectName;
492
+ }
493
+ if (resolved.schedule.timezone !== undefined) {
494
+ base.scheduleTimezone = resolved.schedule.timezone;
495
+ }
496
+ if (shift.layerId !== undefined) {
497
+ base.layerId = shift.layerId;
498
+ }
499
+ if (shift.layerName !== undefined) {
500
+ base.layerName = shift.layerName;
501
+ }
502
+ if (shift.override) {
503
+ const override = {
504
+ originalUserId: shift.override.originalUserId,
505
+ originalUserName: OnCallShiftMaterializer.displayName(options.users, shift.override.originalUserId),
506
+ overrideStartsAt: new Date(shift.override.overrideStartsAt.getTime()),
507
+ overrideEndsAt: new Date(shift.override.overrideEndsAt.getTime()),
508
+ };
509
+ const policyId = OnCallShiftMaterializer.findOverridePolicyId(resolved.overrides, {
510
+ originalUserId: shift.override.originalUserId,
511
+ substituteUserId: shift.userId,
512
+ startsAt: shift.override.overrideStartsAt,
513
+ endsAt: shift.override.overrideEndsAt,
514
+ preferredPolicyId: (_a = data.policyVariant) === null || _a === void 0 ? void 0 : _a.policyId,
515
+ });
516
+ if (policyId) {
517
+ override.onCallDutyPolicyId = policyId;
518
+ }
519
+ base.override = override;
520
+ }
521
+ if (data.policyVariant) {
522
+ base.policyVariantOf = {
523
+ policyId: data.policyVariant.policyId,
524
+ policyName: data.policyVariant.policyName,
525
+ globalUserId: data.policyVariant.globalUserId,
526
+ };
527
+ }
528
+ return Object.assign(Object.assign({}, base), { shiftKey: MaterializedShiftUtil.buildShiftKey({
529
+ scheduleId: resolved.schedule.id,
530
+ start: shift.start,
531
+ policyId: (_b = data.policyVariant) === null || _b === void 0 ? void 0 : _b.policyId,
532
+ }), contentHash: OnCallShiftMaterializer.computeContentHash(base), lastModifiedAt: resolved.lastModifiedAt, shiftConfigVersion: resolved.schedule.shiftConfigVersion });
533
+ }
534
+ /*
535
+ * Map a segment's override meta back to the override record that produced
536
+ * it, to learn its policy scope. Prefers a record scoped to
537
+ * `preferredPolicyId` (the variant being materialized); falls back to any
538
+ * record with the same users and window.
539
+ */
540
+ static findOverridePolicyId(overrides, data) {
541
+ const matches = overrides.filter((record) => {
542
+ return (record.overrideUserId === data.originalUserId &&
543
+ record.routeAlertsToUserId === data.substituteUserId &&
544
+ record.startsAt.getTime() === data.startsAt.getTime() &&
545
+ record.endsAt.getTime() === data.endsAt.getTime());
546
+ });
547
+ if (matches.length === 0) {
548
+ return null;
549
+ }
550
+ if (data.preferredPolicyId) {
551
+ const preferred = matches.find((record) => {
552
+ return record.onCallDutyPolicyId === data.preferredPolicyId;
553
+ });
554
+ if (preferred) {
555
+ return preferred.onCallDutyPolicyId || null;
556
+ }
557
+ }
558
+ // A global match wins over a scoped one for the base resolution.
559
+ const global = matches.find((record) => {
560
+ return !record.onCallDutyPolicyId;
561
+ });
562
+ if (global) {
563
+ return null;
564
+ }
565
+ return matches[0].onCallDutyPolicyId || null;
566
+ }
567
+ static displayName(users, userId) {
568
+ var _a;
569
+ return (((_a = users.get(userId)) === null || _a === void 0 ? void 0 : _a.userName) || OnCallCalendarFeedUtil.FALLBACK_USER_NAME);
570
+ }
571
+ static collectUserIds(resolved) {
572
+ const ids = new Set();
573
+ for (const item of resolved) {
574
+ for (const segment of item.segments) {
575
+ if (segment.title) {
576
+ ids.add(segment.title);
577
+ }
578
+ }
579
+ for (const variant of item.policyVariants) {
580
+ for (const segment of variant.segments) {
581
+ if (segment.title) {
582
+ ids.add(segment.title);
583
+ }
584
+ }
585
+ }
586
+ for (const override of item.overrides) {
587
+ if (override.overrideUserId) {
588
+ ids.add(override.overrideUserId);
589
+ }
590
+ if (override.routeAlertsToUserId) {
591
+ ids.add(override.routeAlertsToUserId);
592
+ }
593
+ }
594
+ for (const userId of item.scheduleUserIds) {
595
+ ids.add(userId);
596
+ }
597
+ }
598
+ return Array.from(ids);
599
+ }
600
+ /*
601
+ * One root user lookup for names and timezones. Name falls back to email;
602
+ * the feed mapper masks emails where they must not appear (shared feeds).
603
+ */
604
+ static async loadUsers(userIds) {
605
+ var _a, _b, _c, _d;
606
+ const users = new Map();
607
+ if (userIds.length === 0) {
608
+ return users;
609
+ }
610
+ const rows = await UserService.findBy({
611
+ query: {
612
+ _id: QueryHelper.any(userIds),
613
+ },
614
+ select: {
615
+ _id: true,
616
+ name: true,
617
+ email: true,
618
+ timezone: true,
619
+ },
620
+ limit: LIMIT_PER_PROJECT,
621
+ skip: 0,
622
+ props: {
623
+ isRoot: true,
624
+ },
625
+ });
626
+ for (const row of rows) {
627
+ const userId = (_a = row.id) === null || _a === void 0 ? void 0 : _a.toString();
628
+ if (!userId) {
629
+ continue;
630
+ }
631
+ const name = ((_b = row.name) === null || _b === void 0 ? void 0 : _b.toString().trim()) || "";
632
+ const email = ((_c = row.email) === null || _c === void 0 ? void 0 : _c.toString().trim()) || "";
633
+ const timezone = ((_d = row.timezone) === null || _d === void 0 ? void 0 : _d.toString()) || "";
634
+ const info = {
635
+ userId,
636
+ userName: name || email || OnCallCalendarFeedUtil.FALLBACK_USER_NAME,
637
+ };
638
+ if (email) {
639
+ info.email = email;
640
+ }
641
+ if (timezone) {
642
+ info.timezone = timezone;
643
+ }
644
+ users.set(userId, info);
645
+ }
646
+ return users;
647
+ }
648
+ static async loadProjectNames(projectIds) {
649
+ var _a;
650
+ const names = new Map();
651
+ const distinct = Array.from(new Set(projectIds.filter((id) => {
652
+ return Boolean(id);
653
+ })));
654
+ if (distinct.length === 0) {
655
+ return names;
656
+ }
657
+ const projects = await ProjectService.findBy({
658
+ query: {
659
+ _id: QueryHelper.any(distinct),
660
+ },
661
+ select: {
662
+ _id: true,
663
+ name: true,
664
+ },
665
+ limit: LIMIT_PER_PROJECT,
666
+ skip: 0,
667
+ props: {
668
+ isRoot: true,
669
+ },
670
+ });
671
+ for (const project of projects) {
672
+ const id = (_a = project.id) === null || _a === void 0 ? void 0 : _a.toString();
673
+ if (id && project.name) {
674
+ names.set(id, project.name);
675
+ }
676
+ }
677
+ return names;
678
+ }
679
+ static validateWindow(windowStart, windowEnd) {
680
+ if (!(windowStart instanceof Date) ||
681
+ !(windowEnd instanceof Date) ||
682
+ Number.isNaN(windowStart.getTime()) ||
683
+ Number.isNaN(windowEnd.getTime())) {
684
+ throw new BadDataException("windowStart and windowEnd must be dates");
685
+ }
686
+ if (windowStart.getTime() >= windowEnd.getTime()) {
687
+ throw new BadDataException("windowStart must be before windowEnd");
688
+ }
689
+ }
690
+ static emptyResult(now) {
691
+ return {
692
+ shifts: [],
693
+ truncated: false,
694
+ schedules: [],
695
+ users: [],
696
+ generatedAt: now,
697
+ };
698
+ }
699
+ static dedupeIds(ids) {
700
+ const seen = new Set();
701
+ const result = [];
702
+ for (const id of ids) {
703
+ if (!id) {
704
+ continue;
705
+ }
706
+ const key = id.toString();
707
+ if (!key || seen.has(key)) {
708
+ continue;
709
+ }
710
+ seen.add(key);
711
+ result.push(id);
712
+ }
713
+ return result;
714
+ }
715
+ }
716
+ __decorate([
717
+ CaptureSpan(),
718
+ __metadata("design:type", Function),
719
+ __metadata("design:paramtypes", [Object]),
720
+ __metadata("design:returntype", Promise)
721
+ ], OnCallShiftMaterializer, "materializeForSchedule", null);
722
+ __decorate([
723
+ CaptureSpan(),
724
+ __metadata("design:type", Function),
725
+ __metadata("design:paramtypes", [Object]),
726
+ __metadata("design:returntype", Promise)
727
+ ], OnCallShiftMaterializer, "materializeForSchedules", null);
728
+ __decorate([
729
+ CaptureSpan(),
730
+ __metadata("design:type", Function),
731
+ __metadata("design:paramtypes", [Object]),
732
+ __metadata("design:returntype", Promise)
733
+ ], OnCallShiftMaterializer, "materializeForUser", null);
734
+ __decorate([
735
+ CaptureSpan(),
736
+ __metadata("design:type", Function),
737
+ __metadata("design:paramtypes", [Object]),
738
+ __metadata("design:returntype", Promise)
739
+ ], OnCallShiftMaterializer, "materializeForProject", null);
740
+ __decorate([
741
+ CaptureSpan(),
742
+ __metadata("design:type", Function),
743
+ __metadata("design:paramtypes", [Object]),
744
+ __metadata("design:returntype", Promise)
745
+ ], OnCallShiftMaterializer, "getCandidateScheduleIdsForUser", null);
746
+ __decorate([
747
+ CaptureSpan(),
748
+ __metadata("design:type", Function),
749
+ __metadata("design:paramtypes", [Object]),
750
+ __metadata("design:returntype", Promise)
751
+ ], OnCallShiftMaterializer, "getCandidateScheduleIdsForUsers", null);
752
+ //# sourceMappingURL=OnCallShiftMaterializer.js.map