@oneuptime/common 12.0.8 → 12.0.9

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 (769) hide show
  1. package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +1 -1
  2. package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +1 -1
  3. package/Models/DatabaseModels/AIConversation.ts +32 -0
  4. package/Models/DatabaseModels/AIConversationMessage.ts +41 -0
  5. package/Models/DatabaseModels/AlertEpisodeMember.ts +27 -0
  6. package/Models/DatabaseModels/IncidentEpisodeMember.ts +28 -0
  7. package/Models/DatabaseModels/Index.ts +12 -4
  8. package/Models/DatabaseModels/{TelemetryEntity.ts → InventoryItem.ts} +139 -9
  9. package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
  10. package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +8 -8
  11. package/Models/DatabaseModels/NetworkDevice.ts +141 -0
  12. package/Models/DatabaseModels/NetworkDeviceLink.ts +699 -0
  13. package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +467 -0
  14. package/Models/DatabaseModels/NetworkTopologySuppression.ts +429 -0
  15. package/Models/DatabaseModels/OnCallDutyPolicyFeed.ts +9 -0
  16. package/Models/DatabaseModels/Project.ts +78 -0
  17. package/Models/DatabaseModels/UserCall.ts +42 -0
  18. package/Models/DatabaseModels/UserEmail.ts +44 -0
  19. package/Models/DatabaseModels/UserNotificationRule.ts +425 -55
  20. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +24 -2
  21. package/Models/DatabaseModels/UserPush.ts +49 -0
  22. package/Models/DatabaseModels/UserSMS.ts +43 -0
  23. package/Models/DatabaseModels/UserTelegram.ts +59 -0
  24. package/Models/DatabaseModels/UserWebhook.ts +52 -0
  25. package/Models/DatabaseModels/UserWhatsApp.ts +41 -0
  26. package/Models/DatabaseModels/WorkflowVariable.ts +12 -0
  27. package/Server/API/AIChatAPI.ts +315 -1
  28. package/Server/API/DashboardAPI.ts +217 -1
  29. package/Server/API/OnCallReadinessAPI.ts +841 -0
  30. package/Server/API/TeamComplianceAPI.ts +69 -17
  31. package/Server/API/TelemetryAPI.ts +220 -12
  32. package/Server/EnvironmentConfig.ts +52 -0
  33. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +22 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +4 -4
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +5 -5
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.ts +35 -0
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.ts +82 -0
  38. package/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.ts +91 -0
  39. package/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.ts +47 -0
  40. package/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.ts +255 -0
  41. package/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.ts +107 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.ts +90 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.ts +39 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.ts +33 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.ts +59 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  47. package/Server/Infrastructure/Queue.ts +78 -13
  48. package/Server/Middleware/PublicDashboardRateLimit.ts +593 -0
  49. package/Server/Services/AIService.ts +7 -0
  50. package/Server/Services/AlertEpisodeStateTimelineService.ts +29 -0
  51. package/Server/Services/AlertSeverityService.ts +63 -0
  52. package/Server/Services/DashboardService.ts +9 -10
  53. package/Server/Services/DatabaseService.ts +32 -2
  54. package/Server/Services/IncidentEpisodeStateTimelineService.ts +29 -0
  55. package/Server/Services/IncidentSeverityService.ts +76 -0
  56. package/Server/Services/Index.ts +12 -4
  57. package/Server/Services/InventoryItemCustomFieldService.ts +9 -0
  58. package/Server/Services/{TelemetryEntityRelationshipService.ts → InventoryItemRelationshipService.ts} +4 -4
  59. package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +89 -20
  60. package/Server/Services/LogAggregationService.ts +45 -8
  61. package/Server/Services/MetricAggregationService.ts +121 -0
  62. package/Server/Services/MetricService.ts +7 -7
  63. package/Server/Services/NetworkDeviceLinkRuleService.ts +10 -0
  64. package/Server/Services/NetworkDeviceLinkService.ts +84 -0
  65. package/Server/Services/NetworkDeviceService.ts +140 -0
  66. package/Server/Services/NetworkSiteService.ts +77 -25
  67. package/Server/Services/NetworkTopologySuppressionService.ts +84 -0
  68. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +41 -29
  69. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +8 -0
  70. package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +62 -13
  71. package/Server/Services/OnCallDutyPolicyScheduleService.ts +61 -1
  72. package/Server/Services/OnCallNotificationAlertingService.ts +742 -0
  73. package/Server/Services/OnCallReadinessService.ts +2803 -0
  74. package/Server/Services/OnCallSetupReminderService.ts +955 -0
  75. package/Server/Services/ProfileAggregationService.ts +123 -0
  76. package/Server/Services/StatusPageService.ts +9 -10
  77. package/Server/Services/TeamComplianceService.ts +429 -252
  78. package/Server/Services/UserCallService.ts +26 -1
  79. package/Server/Services/UserEmailService.ts +26 -1
  80. package/Server/Services/UserNotificationRuleAdminService.ts +1183 -0
  81. package/Server/Services/UserNotificationRuleService.ts +3812 -333
  82. package/Server/Services/UserOnCallLogService.ts +561 -48
  83. package/Server/Services/UserPushService.ts +29 -0
  84. package/Server/Services/UserService.ts +11 -0
  85. package/Server/Services/UserSmsService.ts +26 -1
  86. package/Server/Services/UserTelegramService.ts +24 -1
  87. package/Server/Services/UserWebhookService.ts +28 -1
  88. package/Server/Services/UserWhatsAppService.ts +24 -1
  89. package/Server/Types/Database/Permissions/BasePermission.ts +19 -0
  90. package/Server/Types/Database/Permissions/CreatePermission.ts +164 -0
  91. package/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.ts +340 -0
  92. package/Server/Types/Database/Permissions/QueryPermission.ts +48 -0
  93. package/Server/Types/Database/Permissions/TenantPermission.ts +8 -1
  94. package/Server/Types/Workflow/Components/API/Delete.ts +1 -1
  95. package/Server/Types/Workflow/Components/API/Get.ts +1 -1
  96. package/Server/Types/Workflow/Components/API/Patch.ts +1 -1
  97. package/Server/Types/Workflow/Components/API/Post.ts +1 -1
  98. package/Server/Types/Workflow/Components/API/Put.ts +1 -1
  99. package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +29 -5
  100. package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +18 -10
  101. package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +55 -0
  102. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +3 -17
  103. package/Server/Types/Workflow/Components/Email.ts +25 -7
  104. package/Server/Types/Workflow/Components/JavaScript.ts +10 -3
  105. package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +1 -1
  106. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +643 -48
  107. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +32 -3
  108. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +20 -6
  109. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +7 -0
  110. package/Server/Utils/AI/Toolbox/AIActionTools.ts +2 -2
  111. package/Server/Utils/AI/Toolbox/AIMetaTools.ts +863 -0
  112. package/Server/Utils/AI/Toolbox/AlertTools.ts +177 -15
  113. package/Server/Utils/AI/Toolbox/IncidentTools.ts +191 -10
  114. package/Server/Utils/AI/Toolbox/Index.ts +48 -0
  115. package/Server/Utils/AI/Toolbox/MonitorTools.ts +298 -11
  116. package/Server/Utils/AI/Toolbox/NoteWriteTools.ts +295 -0
  117. package/Server/Utils/AI/Toolbox/OnCallTools.ts +1246 -0
  118. package/Server/Utils/AI/Toolbox/RunbookTools.ts +424 -0
  119. package/Server/Utils/AI/Toolbox/SloTools.ts +456 -0
  120. package/Server/Utils/AI/Toolbox/StatusPageTools.ts +559 -0
  121. package/Server/Utils/AI/Toolbox/TeamTools.ts +327 -0
  122. package/Server/Utils/AI/Toolbox/TimelineTools.ts +615 -0
  123. package/Server/Utils/AI/Toolbox/WorkflowProbeTools.ts +664 -0
  124. package/Server/Utils/ClientIp.ts +221 -0
  125. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +47 -0
  126. package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +163 -0
  127. package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +147 -0
  128. package/Server/Utils/Express.ts +12 -17
  129. package/Server/Utils/LLM/LLMService.ts +85 -8
  130. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +204 -10
  131. package/Server/Utils/SSRFProtection.ts +98 -23
  132. package/Server/Utils/StartServer.ts +12 -3
  133. package/Server/Utils/Telemetry/EntityRegistry.ts +205 -18
  134. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +25 -25
  135. package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
  136. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
  137. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +2146 -0
  138. package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +561 -0
  139. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +2470 -0
  140. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +1897 -0
  141. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3606 -0
  142. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +784 -0
  143. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +1119 -0
  144. package/Tests/App/Dashboard/SloWidgetFetching.test.tsx +531 -0
  145. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +1312 -0
  146. package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +1390 -0
  147. package/Tests/Models/InventoryItemModel.test.ts +174 -0
  148. package/Tests/Models/InventoryItemNaming.test.ts +302 -0
  149. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +437 -0
  150. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +659 -0
  151. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +18 -0
  152. package/Tests/Server/API/DashboardPublicSloAPI.test.ts +880 -0
  153. package/Tests/Server/API/Helpers.ts +24 -15
  154. package/Tests/Server/API/OnCallReadinessAPI.test.ts +2680 -0
  155. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +915 -0
  156. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +533 -0
  157. package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +213 -0
  158. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +432 -0
  159. package/Tests/Server/Infrastructure/Queue.test.ts +293 -0
  160. package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +1645 -0
  161. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +2848 -0
  162. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1393 -0
  163. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +1802 -0
  164. package/Tests/Server/Services/EpisodeStateTimelineNote.test.ts +304 -0
  165. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +339 -0
  166. package/Tests/Server/Services/{TelemetryEntityManualCreate.test.ts → InventoryItemManualCreate.test.ts} +27 -27
  167. package/Tests/Server/Services/IpAllowlistSpoofing.test.ts +450 -0
  168. package/Tests/Server/Services/LogAggregationService.test.ts +235 -1
  169. package/Tests/Server/Services/MetricAggregationService.test.ts +231 -0
  170. package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +80 -29
  171. package/Tests/Server/Services/MetricServiceAggregate.test.ts +30 -30
  172. package/Tests/Server/Services/NetworkSiteService.test.ts +18 -3
  173. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +1728 -0
  174. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +2402 -0
  175. package/Tests/Server/Services/OnCallDutyPolicyExecutionLogTimelineGapFeed.test.ts +394 -0
  176. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +1744 -0
  177. package/Tests/Server/Services/OnCallReadinessService.test.ts +4295 -0
  178. package/Tests/Server/Services/OnCallSetupReminder.test.ts +1272 -0
  179. package/Tests/Server/Services/OnCallWeeklyReadinessDigest.test.ts +1021 -0
  180. package/Tests/Server/Services/ProfileAggregationService.test.ts +296 -0
  181. package/Tests/Server/Services/SeverityCreationRuleBackfill.test.ts +1536 -0
  182. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +1818 -0
  183. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1845 -0
  184. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +1394 -0
  185. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +1166 -0
  186. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1468 -0
  187. package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1457 -0
  188. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +1546 -0
  189. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +529 -0
  190. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +1219 -0
  191. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1089 -0
  192. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +2 -1
  193. package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +190 -0
  194. package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +44 -14
  195. package/Tests/Server/Types/Workflow/Components/Email.test.ts +151 -0
  196. package/Tests/Server/Types/Workflow/Components/IfElse.test.ts +98 -0
  197. package/Tests/Server/Types/Workflow/Components/JavaScript.test.ts +51 -0
  198. package/Tests/Server/Utils/AI/AIMetaTools.test.ts +586 -0
  199. package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +582 -0
  200. package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +726 -0
  201. package/Tests/Server/Utils/AI/IncidentToolsFilters.test.ts +315 -0
  202. package/Tests/Server/Utils/AI/LLMServiceStopReason.test.ts +314 -0
  203. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +26 -3
  204. package/Tests/Server/Utils/AI/NoteWriteTools.test.ts +268 -0
  205. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +169 -0
  206. package/Tests/Server/Utils/AI/OnCallTools.test.ts +664 -0
  207. package/Tests/Server/Utils/AI/RunbookTools.test.ts +325 -0
  208. package/Tests/Server/Utils/AI/SloTools.test.ts +306 -0
  209. package/Tests/Server/Utils/AI/StatusPageTools.test.ts +391 -0
  210. package/Tests/Server/Utils/AI/TeamTools.test.ts +257 -0
  211. package/Tests/Server/Utils/AI/TimelineTools.test.ts +472 -0
  212. package/Tests/Server/Utils/AI/WorkflowProbeTools.test.ts +428 -0
  213. package/Tests/Server/Utils/ClientIp.test.ts +438 -0
  214. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +171 -0
  215. package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +383 -0
  216. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +23 -25
  217. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +6 -0
  218. package/Tests/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.test.ts +307 -0
  219. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +468 -0
  220. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorTelemetryDeepLinks.test.ts +460 -0
  221. package/Tests/Server/Utils/ResponseRateLimitStatusCodes.test.ts +137 -0
  222. package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +40 -8
  223. package/Tests/Server/Utils/SSRFProtectionUserInfo.test.ts +351 -0
  224. package/Tests/Server/Utils/Telemetry/EntityRegistryRetirement.test.ts +531 -0
  225. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +15 -15
  226. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
  227. package/Tests/Types/IP/IP.test.ts +263 -0
  228. package/Tests/Types/IP/IPWhitelist.test.ts +197 -0
  229. package/Tests/Types/IP/IPv6.test.ts +12 -1
  230. package/Tests/Types/Monitor/SnmpOid.test.ts +64 -0
  231. package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +110 -0
  232. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +106 -0
  233. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeDifferential.test.ts +511 -0
  234. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageEndToEnd.test.ts +489 -0
  235. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageGapTolerance.test.ts +479 -0
  236. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageState.test.ts +822 -0
  237. package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +6 -6
  238. package/Tests/Types/Workflow/BaseModelComponents.test.ts +429 -0
  239. package/Tests/Types/Workflow/Components/BaseModel.test.ts +225 -0
  240. package/Tests/Types/Workflow/IntegrationCredentialMetadata.test.ts +100 -0
  241. package/Tests/Types/Workflow/TemplateSyntax.test.ts +112 -0
  242. package/Tests/Types/Workflow/Templates.test.ts +916 -101
  243. package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
  244. package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
  245. package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
  246. package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
  247. package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
  248. package/Tests/UI/Components/PendingProjectInvitations.test.tsx +913 -0
  249. package/Tests/UI/Components/SimpleLogViewer.test.tsx +228 -0
  250. package/Tests/UI/Components/TableRowSelectability.test.tsx +312 -0
  251. package/Tests/UI/Components/Workflow/GraphLint.test.ts +72 -0
  252. package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
  253. package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +242 -7
  254. package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +15 -7
  255. package/Tests/UI/Components/Workflow/ModelSchema.test.ts +242 -28
  256. package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +59 -1
  257. package/Tests/UI/Components/Workflow/StepTraceViewer.test.tsx +256 -0
  258. package/Tests/UI/Components/Workflow/UseRunWatch.test.tsx +665 -0
  259. package/Tests/UI/Components/Workflow/WorkflowIssuesModal.test.tsx +485 -0
  260. package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +478 -0
  261. package/Tests/UI/Components/Workflow/WorkflowStatusBar.test.tsx +379 -0
  262. package/Tests/UI/EsbuildConfig.test.ts +607 -0
  263. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  264. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +11 -4
  265. package/Tests/UI/Utils/ModelAPICreateMiscData.test.ts +94 -0
  266. package/Tests/UI/Utils/Platform.test.ts +147 -0
  267. package/Tests/UI/Utils/ProjectInvitationDisplay.test.ts +357 -0
  268. package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +198 -0
  269. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +487 -0
  270. package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
  271. package/Tests/__mocks__/bullmq.js +55 -0
  272. package/Types/AI/AIChatMessageStatus.ts +8 -1
  273. package/Types/AI/AIChatTypes.ts +12 -0
  274. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +88 -0
  275. package/Types/Exception/ExceptionCode.ts +2 -0
  276. package/Types/Exception/ServiceUnavailableException.ts +8 -0
  277. package/Types/Exception/TooManyRequestsException.ts +8 -0
  278. package/Types/IP/IP.ts +93 -47
  279. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +49 -3
  280. package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +55 -0
  281. package/Types/OnCallDutyPolicy/Layer.ts +203 -149
  282. package/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +13 -0
  283. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +155 -10
  284. package/Types/Permission.ts +193 -0
  285. package/Types/Telemetry/EntityRelationshipType.ts +1 -1
  286. package/Types/Telemetry/EntitySource.ts +1 -1
  287. package/Types/Telemetry/EntityType.ts +1 -1
  288. package/Types/Telemetry/EntityTypeGroups.ts +1 -1
  289. package/Types/Workflow/Components/BaseModel.ts +75 -29
  290. package/Types/Workflow/Components/Discord.ts +1 -0
  291. package/Types/Workflow/Components/Email.ts +12 -3
  292. package/Types/Workflow/Components/JavaScript.ts +7 -0
  293. package/Types/Workflow/Components/MicrosoftTeams.ts +3 -2
  294. package/Types/Workflow/Components/Slack.ts +1 -0
  295. package/Types/Workflow/Components/Telegram.ts +1 -0
  296. package/Types/Workflow/TemplateSyntax.ts +44 -0
  297. package/Types/Workflow/Templates.ts +2097 -45
  298. package/UI/Components/Calendar/Calendar.css +43 -0
  299. package/UI/Components/Calendar/Calendar.tsx +8 -0
  300. package/UI/Components/Card/Card.tsx +2 -2
  301. package/UI/Components/Checkbox/Checkbox.tsx +16 -0
  302. package/UI/Components/Dictionary/Dictionary.tsx +48 -9
  303. package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
  304. package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
  305. package/UI/Components/Input/Input.tsx +1 -0
  306. package/UI/Components/KeyboardShortcut/KeyboardKey.ts +185 -0
  307. package/UI/Components/KeyboardShortcut/KeyboardShortcut.tsx +87 -0
  308. package/UI/Components/LogsViewer/LogsViewer.tsx +28 -0
  309. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +31 -0
  310. package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +18 -18
  311. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +363 -14
  312. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +11 -0
  313. package/UI/Components/LogsViewer/components/LogsTable.tsx +155 -12
  314. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +29 -0
  315. package/UI/Components/LogsViewer/types.ts +23 -0
  316. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +9 -2
  317. package/UI/Components/Navbar/NavBarMenuModal.tsx +15 -30
  318. package/UI/Components/ProjectInvitations/PendingProjectInvitations.tsx +442 -0
  319. package/UI/Components/SimpleLogViewer/SimpleLogViewer.tsx +23 -1
  320. package/UI/Components/Table/Table.tsx +49 -16
  321. package/UI/Components/Table/TableBody.tsx +53 -28
  322. package/UI/Components/Table/TableHeader.tsx +13 -0
  323. package/UI/Components/Table/TableRow.tsx +58 -26
  324. package/UI/Components/Workflow/ArgumentsForm.tsx +155 -9
  325. package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
  326. package/UI/Components/Workflow/ComponentSettingsModal.tsx +10 -1
  327. package/UI/Components/Workflow/ComponentValuePickerModal.tsx +135 -7
  328. package/UI/Components/Workflow/ComponentsModal.tsx +116 -22
  329. package/UI/Components/Workflow/DocumentationViewer.tsx +59 -9
  330. package/UI/Components/Workflow/GraphLint.ts +42 -5
  331. package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
  332. package/UI/Components/Workflow/ModelColumnEditor.tsx +154 -22
  333. package/UI/Components/Workflow/ModelSchema.ts +115 -33
  334. package/UI/Components/Workflow/RunStatusWatcher.ts +1 -1
  335. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  336. package/UI/Components/Workflow/UseRunWatch.ts +212 -0
  337. package/UI/Components/Workflow/VariableModal.tsx +6 -2
  338. package/UI/Components/Workflow/Workflow.tsx +49 -3
  339. package/UI/Components/Workflow/WorkflowIssuesModal.tsx +255 -0
  340. package/UI/Components/Workflow/WorkflowLogModal.tsx +128 -0
  341. package/UI/Components/Workflow/WorkflowStatusBar.tsx +224 -0
  342. package/UI/Utils/AIChatExport/ConversationMarkdown.ts +10 -0
  343. package/UI/Utils/ModelAPI/ModelAPI.ts +7 -1
  344. package/UI/Utils/Platform.ts +149 -0
  345. package/UI/Utils/ProjectInvitationDisplay.ts +118 -0
  346. package/UI/esbuild-config.js +22 -1
  347. package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +187 -0
  348. package/Utils/Monitor/NetworkTopologyUtil.ts +888 -164
  349. package/Utils/Telemetry/CrossSignalScope.ts +502 -0
  350. package/Utils/Telemetry/EntityKey.ts +5 -5
  351. package/Utils/Telemetry/EntityRelationship.ts +1 -1
  352. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +1 -1
  353. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +1 -1
  354. package/build/dist/Models/DatabaseModels/AIConversation.js +32 -0
  355. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  356. package/build/dist/Models/DatabaseModels/AIConversationMessage.js +42 -0
  357. package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -1
  358. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +28 -0
  359. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
  360. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +29 -0
  361. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
  362. package/build/dist/Models/DatabaseModels/Index.js +12 -4
  363. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  364. package/build/dist/Models/DatabaseModels/{TelemetryEntity.js → InventoryItem.js} +167 -31
  365. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -0
  366. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +454 -0
  367. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -0
  368. package/build/dist/Models/DatabaseModels/{TelemetryEntityRelationship.js → InventoryItemRelationship.js} +27 -27
  369. package/build/dist/Models/DatabaseModels/InventoryItemRelationship.js.map +1 -0
  370. package/build/dist/Models/DatabaseModels/NetworkDevice.js +141 -0
  371. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  372. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +719 -0
  373. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -0
  374. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +475 -0
  375. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -0
  376. package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js +446 -0
  377. package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js.map +1 -0
  378. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js +9 -0
  379. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js.map +1 -1
  380. package/build/dist/Models/DatabaseModels/Project.js +80 -0
  381. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  382. package/build/dist/Models/DatabaseModels/UserCall.js +46 -2
  383. package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
  384. package/build/dist/Models/DatabaseModels/UserEmail.js +48 -2
  385. package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
  386. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +424 -55
  387. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  388. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +24 -2
  389. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  390. package/build/dist/Models/DatabaseModels/UserPush.js +51 -1
  391. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  392. package/build/dist/Models/DatabaseModels/UserSMS.js +47 -2
  393. package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
  394. package/build/dist/Models/DatabaseModels/UserTelegram.js +65 -3
  395. package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
  396. package/build/dist/Models/DatabaseModels/UserWebhook.js +56 -2
  397. package/build/dist/Models/DatabaseModels/UserWebhook.js.map +1 -1
  398. package/build/dist/Models/DatabaseModels/UserWhatsApp.js +45 -2
  399. package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
  400. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +12 -0
  401. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  402. package/build/dist/Server/API/AIChatAPI.js +237 -1
  403. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  404. package/build/dist/Server/API/DashboardAPI.js +165 -13
  405. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  406. package/build/dist/Server/API/OnCallReadinessAPI.js +599 -0
  407. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -0
  408. package/build/dist/Server/API/TeamComplianceAPI.js +68 -9
  409. package/build/dist/Server/API/TeamComplianceAPI.js.map +1 -1
  410. package/build/dist/Server/API/TelemetryAPI.js +129 -18
  411. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  412. package/build/dist/Server/EnvironmentConfig.js +45 -0
  413. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  414. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +22 -0
  415. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  416. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +4 -4
  417. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js +18 -0
  418. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js.map +1 -0
  419. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js +39 -0
  420. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js.map +1 -0
  421. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js +38 -0
  422. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js.map +1 -0
  423. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js +22 -0
  424. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js.map +1 -0
  425. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js +150 -0
  426. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js.map +1 -0
  427. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js +57 -0
  428. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js.map +1 -0
  429. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js +69 -0
  430. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js.map +1 -0
  431. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js +32 -0
  432. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js.map +1 -0
  433. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js +26 -0
  434. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js.map +1 -0
  435. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js +48 -0
  436. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js.map +1 -0
  437. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  438. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  439. package/build/dist/Server/Infrastructure/Queue.js +72 -13
  440. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  441. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +399 -0
  442. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -0
  443. package/build/dist/Server/Services/AIService.js +1 -0
  444. package/build/dist/Server/Services/AIService.js.map +1 -1
  445. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +24 -3
  446. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  447. package/build/dist/Server/Services/AlertSeverityService.js +54 -0
  448. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  449. package/build/dist/Server/Services/DashboardService.js +10 -9
  450. package/build/dist/Server/Services/DashboardService.js.map +1 -1
  451. package/build/dist/Server/Services/DatabaseService.js +24 -2
  452. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  453. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +24 -3
  454. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  455. package/build/dist/Server/Services/IncidentSeverityService.js +67 -0
  456. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  457. package/build/dist/Server/Services/Index.js +12 -4
  458. package/build/dist/Server/Services/Index.js.map +1 -1
  459. package/build/dist/Server/Services/InventoryItemCustomFieldService.js +9 -0
  460. package/build/dist/Server/Services/InventoryItemCustomFieldService.js.map +1 -0
  461. package/build/dist/Server/Services/{TelemetryEntityRelationshipService.js → InventoryItemRelationshipService.js} +6 -6
  462. package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
  463. package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +68 -23
  464. package/build/dist/Server/Services/InventoryItemService.js.map +1 -0
  465. package/build/dist/Server/Services/LogAggregationService.js +27 -8
  466. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  467. package/build/dist/Server/Services/MetricAggregationService.js +80 -0
  468. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  469. package/build/dist/Server/Services/MetricService.js +6 -6
  470. package/build/dist/Server/Services/MetricService.js.map +1 -1
  471. package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js +9 -0
  472. package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js.map +1 -0
  473. package/build/dist/Server/Services/NetworkDeviceLinkService.js +71 -0
  474. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -0
  475. package/build/dist/Server/Services/NetworkDeviceService.js +113 -0
  476. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  477. package/build/dist/Server/Services/NetworkSiteService.js +53 -13
  478. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  479. package/build/dist/Server/Services/NetworkTopologySuppressionService.js +85 -0
  480. package/build/dist/Server/Services/NetworkTopologySuppressionService.js.map +1 -0
  481. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +48 -32
  482. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  483. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +8 -0
  484. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  485. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -12
  486. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
  487. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +57 -13
  488. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  489. package/build/dist/Server/Services/OnCallNotificationAlertingService.js +548 -0
  490. package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -0
  491. package/build/dist/Server/Services/OnCallReadinessService.js +1961 -0
  492. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -0
  493. package/build/dist/Server/Services/OnCallSetupReminderService.js +738 -0
  494. package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -0
  495. package/build/dist/Server/Services/ProfileAggregationService.js +68 -4
  496. package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
  497. package/build/dist/Server/Services/StatusPageService.js +11 -10
  498. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  499. package/build/dist/Server/Services/TeamComplianceService.js +312 -160
  500. package/build/dist/Server/Services/TeamComplianceService.js.map +1 -1
  501. package/build/dist/Server/Services/UserCallService.js +24 -1
  502. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  503. package/build/dist/Server/Services/UserEmailService.js +24 -1
  504. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  505. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +858 -0
  506. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -0
  507. package/build/dist/Server/Services/UserNotificationRuleService.js +2830 -175
  508. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  509. package/build/dist/Server/Services/UserOnCallLogService.js +488 -43
  510. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  511. package/build/dist/Server/Services/UserPushService.js +26 -0
  512. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  513. package/build/dist/Server/Services/UserService.js +10 -0
  514. package/build/dist/Server/Services/UserService.js.map +1 -1
  515. package/build/dist/Server/Services/UserSmsService.js +24 -1
  516. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  517. package/build/dist/Server/Services/UserTelegramService.js +22 -1
  518. package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
  519. package/build/dist/Server/Services/UserWebhookService.js +25 -1
  520. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  521. package/build/dist/Server/Services/UserWhatsAppService.js +22 -1
  522. package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
  523. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +12 -1
  524. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  525. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +126 -0
  526. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  527. package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js +254 -0
  528. package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js.map +1 -0
  529. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +47 -2
  530. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
  531. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +7 -0
  532. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  533. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +1 -1
  534. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  535. package/build/dist/Server/Types/Workflow/Components/API/Get.js +1 -1
  536. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  537. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +1 -1
  538. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  539. package/build/dist/Server/Types/Workflow/Components/API/Post.js +1 -1
  540. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  541. package/build/dist/Server/Types/Workflow/Components/API/Put.js +1 -1
  542. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  543. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +21 -5
  544. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
  545. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +14 -10
  546. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
  547. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +31 -0
  548. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -1
  549. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +3 -9
  550. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  551. package/build/dist/Server/Types/Workflow/Components/Email.js +15 -4
  552. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  553. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +7 -2
  554. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  555. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +1 -1
  556. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
  557. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +514 -56
  558. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  559. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +20 -3
  560. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  561. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +19 -6
  562. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  563. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +7 -0
  564. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  565. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +2 -2
  566. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
  567. package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js +692 -0
  568. package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js.map +1 -0
  569. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +148 -12
  570. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
  571. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +157 -10
  572. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  573. package/build/dist/Server/Utils/AI/Toolbox/Index.js +37 -0
  574. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  575. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +259 -14
  576. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -1
  577. package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js +235 -0
  578. package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js.map +1 -0
  579. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +1000 -0
  580. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -0
  581. package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js +356 -0
  582. package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js.map +1 -0
  583. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +394 -0
  584. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -0
  585. package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js +465 -0
  586. package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js.map +1 -0
  587. package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js +280 -0
  588. package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js.map +1 -0
  589. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +527 -0
  590. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -0
  591. package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js +548 -0
  592. package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js.map +1 -0
  593. package/build/dist/Server/Utils/ClientIp.js +137 -0
  594. package/build/dist/Server/Utils/ClientIp.js.map +1 -0
  595. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +38 -0
  596. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  597. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +89 -0
  598. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -0
  599. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +77 -0
  600. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -0
  601. package/build/dist/Server/Utils/Express.js +12 -12
  602. package/build/dist/Server/Utils/Express.js.map +1 -1
  603. package/build/dist/Server/Utils/LLM/LLMService.js +70 -7
  604. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  605. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +121 -11
  606. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  607. package/build/dist/Server/Utils/SSRFProtection.js +82 -21
  608. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  609. package/build/dist/Server/Utils/StartServer.js +12 -4
  610. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  611. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +165 -18
  612. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  613. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +11 -11
  614. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  615. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +122 -47
  616. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  617. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +7 -3
  618. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  619. package/build/dist/Types/AI/AIChatMessageStatus.js +8 -1
  620. package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -1
  621. package/build/dist/Types/AI/AIChatTypes.js +12 -0
  622. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  623. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +60 -0
  624. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -0
  625. package/build/dist/Types/Exception/ExceptionCode.js +2 -0
  626. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  627. package/build/dist/Types/Exception/ServiceUnavailableException.js +8 -0
  628. package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -0
  629. package/build/dist/Types/Exception/TooManyRequestsException.js +8 -0
  630. package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -0
  631. package/build/dist/Types/IP/IP.js +87 -43
  632. package/build/dist/Types/IP/IP.js.map +1 -1
  633. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +50 -0
  634. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -0
  635. package/build/dist/Types/OnCallDutyPolicy/Layer.js +186 -123
  636. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  637. package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js +13 -0
  638. package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js.map +1 -1
  639. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +105 -11
  640. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  641. package/build/dist/Types/Permission.js +174 -0
  642. package/build/dist/Types/Permission.js.map +1 -1
  643. package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
  644. package/build/dist/Types/Telemetry/EntitySource.js +1 -1
  645. package/build/dist/Types/Telemetry/EntityType.js +1 -1
  646. package/build/dist/Types/Telemetry/EntityTypeGroups.js +1 -1
  647. package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -1
  648. package/build/dist/Types/Workflow/Components/BaseModel.js +66 -28
  649. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  650. package/build/dist/Types/Workflow/Components/Discord.js +1 -0
  651. package/build/dist/Types/Workflow/Components/Discord.js.map +1 -1
  652. package/build/dist/Types/Workflow/Components/Email.js +12 -3
  653. package/build/dist/Types/Workflow/Components/Email.js.map +1 -1
  654. package/build/dist/Types/Workflow/Components/JavaScript.js +7 -0
  655. package/build/dist/Types/Workflow/Components/JavaScript.js.map +1 -1
  656. package/build/dist/Types/Workflow/Components/MicrosoftTeams.js +3 -2
  657. package/build/dist/Types/Workflow/Components/MicrosoftTeams.js.map +1 -1
  658. package/build/dist/Types/Workflow/Components/Slack.js +1 -0
  659. package/build/dist/Types/Workflow/Components/Slack.js.map +1 -1
  660. package/build/dist/Types/Workflow/Components/Telegram.js +1 -0
  661. package/build/dist/Types/Workflow/Components/Telegram.js.map +1 -1
  662. package/build/dist/Types/Workflow/TemplateSyntax.js +12 -0
  663. package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -1
  664. package/build/dist/Types/Workflow/Templates.js +1931 -38
  665. package/build/dist/Types/Workflow/Templates.js.map +1 -1
  666. package/build/dist/UI/Components/Calendar/Calendar.js +1 -1
  667. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  668. package/build/dist/UI/Components/Checkbox/Checkbox.js +1 -1
  669. package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
  670. package/build/dist/UI/Components/Dictionary/Dictionary.js +25 -11
  671. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  672. package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
  673. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +27 -4
  674. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  675. package/build/dist/UI/Components/Input/Input.js +1 -0
  676. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  677. package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js +163 -0
  678. package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js.map +1 -0
  679. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js +47 -0
  680. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js.map +1 -0
  681. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +4 -4
  682. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  683. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +11 -1
  684. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  685. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +10 -8
  686. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
  687. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +227 -14
  688. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  689. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +5 -0
  690. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  691. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +69 -5
  692. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
  693. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +8 -0
  694. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  695. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  696. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +9 -2
  697. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  698. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +8 -21
  699. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  700. package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js +251 -0
  701. package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js.map +1 -0
  702. package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js +9 -2
  703. package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js.map +1 -1
  704. package/build/dist/UI/Components/Table/Table.js +27 -15
  705. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  706. package/build/dist/UI/Components/Table/TableBody.js +24 -18
  707. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  708. package/build/dist/UI/Components/Table/TableHeader.js +9 -1
  709. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  710. package/build/dist/UI/Components/Table/TableRow.js +29 -21
  711. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  712. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +130 -12
  713. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  714. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +10 -1
  715. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
  716. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +4 -4
  717. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
  718. package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js +57 -7
  719. package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js.map +1 -1
  720. package/build/dist/UI/Components/Workflow/ComponentsModal.js +53 -18
  721. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  722. package/build/dist/UI/Components/Workflow/DocumentationViewer.js +19 -6
  723. package/build/dist/UI/Components/Workflow/DocumentationViewer.js.map +1 -1
  724. package/build/dist/UI/Components/Workflow/GraphLint.js +33 -4
  725. package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -1
  726. package/build/dist/UI/Components/Workflow/GraphLintSummary.js +231 -0
  727. package/build/dist/UI/Components/Workflow/GraphLintSummary.js.map +1 -0
  728. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +116 -21
  729. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
  730. package/build/dist/UI/Components/Workflow/ModelSchema.js +72 -34
  731. package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -1
  732. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +1 -1
  733. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -1
  734. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  735. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  736. package/build/dist/UI/Components/Workflow/UseRunWatch.js +123 -0
  737. package/build/dist/UI/Components/Workflow/UseRunWatch.js.map +1 -0
  738. package/build/dist/UI/Components/Workflow/VariableModal.js +3 -2
  739. package/build/dist/UI/Components/Workflow/VariableModal.js.map +1 -1
  740. package/build/dist/UI/Components/Workflow/Workflow.js +41 -1
  741. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  742. package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js +99 -0
  743. package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js.map +1 -0
  744. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +56 -0
  745. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -0
  746. package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js +92 -0
  747. package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js.map +1 -0
  748. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +9 -0
  749. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -1
  750. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +1 -1
  751. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  752. package/build/dist/UI/Utils/Platform.js +118 -0
  753. package/build/dist/UI/Utils/Platform.js.map +1 -0
  754. package/build/dist/UI/Utils/ProjectInvitationDisplay.js +106 -0
  755. package/build/dist/UI/Utils/ProjectInvitationDisplay.js.map +1 -0
  756. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +108 -0
  757. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -0
  758. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +642 -136
  759. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  760. package/build/dist/Utils/Telemetry/CrossSignalScope.js +328 -0
  761. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -0
  762. package/build/dist/Utils/Telemetry/EntityKey.js +5 -5
  763. package/build/dist/Utils/Telemetry/EntityRelationship.js +1 -1
  764. package/jest.config.json +1 -0
  765. package/package.json +1 -1
  766. package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +0 -1
  767. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +0 -1
  768. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +0 -1
  769. package/build/dist/Server/Services/TelemetryEntityService.js.map +0 -1
@@ -0,0 +1,1021 @@
1
+ import DatabaseConfig from "../../../Server/DatabaseConfig";
2
+ import OnCallDutyPolicyService from "../../../Server/Services/OnCallDutyPolicyService";
3
+ import OnCallReadinessService, {
4
+ ReadinessStatus,
5
+ ReadinessSummary,
6
+ ResponderSource,
7
+ UserReadiness,
8
+ } from "../../../Server/Services/OnCallReadinessService";
9
+ import ProjectService from "../../../Server/Services/ProjectService";
10
+ import logger from "../../../Server/Utils/Logger";
11
+ import URL from "../../../Types/API/URL";
12
+ import Includes from "../../../Types/BaseDatabase/Includes";
13
+ import ObjectID from "../../../Types/ObjectID";
14
+ import { EVERY_WEEK } from "../../../Utils/CronTime";
15
+ import OnCallDutyPolicy from "../../../Models/DatabaseModels/OnCallDutyPolicy";
16
+ import Project from "../../../Models/DatabaseModels/Project";
17
+ import User from "../../../Models/DatabaseModels/User";
18
+ import fs from "fs";
19
+ import Path from "path";
20
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
21
+
22
+ /*
23
+ * PHASE 4, the weekly digest.
24
+ *
25
+ * Everything else built for on-call readiness is either reactive (the fallback,
26
+ * which rescues a page at the moment it would otherwise be dropped) or
27
+ * on-demand (the readiness page, which answers the question for whoever thinks
28
+ * to ask it). Nobody thinks to ask it. This job is the only thing in the system
29
+ * that tells a project owner about an unreachable responder WITHOUT being
30
+ * asked, which puts an unusual amount of weight on properties that are easy to
31
+ * get wrong and invisible when they are:
32
+ *
33
+ * 1. IT IS SILENT WHEN THERE IS NOTHING TO SAY. A weekly "everything is fine"
34
+ * mail teaches its recipients to filter it, and the filter is still there
35
+ * the week it says something different. The silence is pinned here as hard
36
+ * as the sending is.
37
+ *
38
+ * 2. IT NAMES THE RIGHT PEOPLE. A digest that lists a Ready responder among
39
+ * the broken ones is a digest an owner learns to distrust, and an owner
40
+ * who distrusts it is an owner who does not read the one that matters.
41
+ *
42
+ * 3. IT NEVER FAILS SILENTLY. ProjectService.getOwners returns [] for a
43
+ * project where no team holds ProjectOwner, and sendEmailToProjectOwners
44
+ * then sends nothing and says nothing - so the last-resort warning about
45
+ * responders nobody can page would itself reach nobody, with no trace
46
+ * anywhere that it had not. That case has to be LOUD in the log even
47
+ * though it is silent in the inbox.
48
+ *
49
+ * 4. IT ESCAPES WHAT IT INTERPOLATES. sendEmailToProjectOwners hands the
50
+ * message to SimpleMessage.hbs, which renders it through a triple stache -
51
+ * i.e. as RAW HTML. Responder names, reason sentences and project names
52
+ * are all user-controlled strings.
53
+ *
54
+ * 5. IT IS REGISTERED. RunCron registers a job purely as a module side
55
+ * effect, so a job file nothing imports is never scheduled. That exact
56
+ * omission was a Phase 1 blocker, and it is invisible to every test that
57
+ * imports the job directly - which is why one test below reads
58
+ * App/FeatureSet/Workers/Index.ts as text.
59
+ *
60
+ * This suite lives beside SeverityRuleBackfill.test.ts, the Phase 1 worker
61
+ * tests, for the same reason they do: the job is an App module that imports
62
+ * Common through the `Common/*` specifier, and Common's tsconfig is the one
63
+ * that maps that specifier back to this tree.
64
+ */
65
+
66
+ /*
67
+ * The job registers itself with RunCron at import time, which would otherwise
68
+ * reach for the Worker queue (and therefore Redis) as a side effect of
69
+ * importing this file. Mocked before the job module is imported, both to keep
70
+ * the import inert and to capture the registration itself.
71
+ */
72
+ type CronHandler = () => Promise<void>;
73
+
74
+ interface CapturedCronOptions {
75
+ schedule: string;
76
+ runOnStartup: boolean;
77
+ }
78
+
79
+ const mockCapturedJobs: Record<string, CronHandler> = {};
80
+ const mockCapturedOptions: Record<string, CapturedCronOptions> = {};
81
+
82
+ jest.mock("../../../../App/FeatureSet/Workers/Utils/Cron", () => {
83
+ return {
84
+ __esModule: true,
85
+ default: jest.fn(
86
+ (
87
+ jobName: string,
88
+ options: CapturedCronOptions,
89
+ runFunction: CronHandler,
90
+ ): void => {
91
+ mockCapturedJobs[jobName] = runFunction;
92
+ mockCapturedOptions[jobName] = options;
93
+ },
94
+ ),
95
+ };
96
+ });
97
+
98
+ // Imported AFTER the mock above so the registration lands in it.
99
+ import {
100
+ JOB_NAME,
101
+ MAX_RESPONDERS_LISTED_PER_SECTION,
102
+ ProjectDigest,
103
+ buildProjectDigest,
104
+ findProjectsWithOnCallPolicies,
105
+ sendDigestForProject,
106
+ sendWeeklyReadinessDigests,
107
+ } from "../../../../App/FeatureSet/Workers/Jobs/OnCallDutyPolicy/WeeklyReadinessDigest";
108
+
109
+ const WORKERS_INDEX_PATH: string = Path.resolve(
110
+ __dirname,
111
+ "../../../../App/FeatureSet/Workers/Index.ts",
112
+ );
113
+
114
+ const PROJECT_ID: ObjectID = new ObjectID("project-1");
115
+ const OTHER_PROJECT_ID: ObjectID = new ObjectID("project-2");
116
+
117
+ const DASHBOARD_URL: string = "https://oneuptime.example.com/dashboard";
118
+
119
+ type MakeProjectFunction = (data?: {
120
+ id?: ObjectID;
121
+ // null means "this project has no name", which is a different case from "default".
122
+ name?: string | null;
123
+ }) => Project;
124
+
125
+ const makeProject: MakeProjectFunction = (data?: {
126
+ id?: ObjectID;
127
+ name?: string | null;
128
+ }): Project => {
129
+ const project: Project = new Project();
130
+ project.id = data?.id || PROJECT_ID;
131
+
132
+ const name: string | null =
133
+ data && "name" in data ? data.name ?? null : "Acme Production";
134
+
135
+ if (name) {
136
+ project.name = name;
137
+ }
138
+
139
+ return project;
140
+ };
141
+
142
+ type MakePolicyFunction = (
143
+ policyProjectId: ObjectID | undefined,
144
+ ) => OnCallDutyPolicy;
145
+
146
+ const makePolicy: MakePolicyFunction = (
147
+ policyProjectId: ObjectID | undefined,
148
+ ): OnCallDutyPolicy => {
149
+ const policy: OnCallDutyPolicy = new OnCallDutyPolicy();
150
+ policy.id = new ObjectID(`policy-${policyProjectId?.toString() || "none"}`);
151
+
152
+ if (policyProjectId) {
153
+ policy.projectId = policyProjectId;
154
+ }
155
+
156
+ return policy;
157
+ };
158
+
159
+ type MakeUserReadinessFunction = (
160
+ overrides?: Partial<UserReadiness>,
161
+ ) => UserReadiness;
162
+
163
+ const makeUserReadiness: MakeUserReadinessFunction = (
164
+ overrides?: Partial<UserReadiness>,
165
+ ): UserReadiness => {
166
+ return {
167
+ userId: new ObjectID("user-1"),
168
+ userName: "Ada Lovelace",
169
+ userEmail: "ada@example.com",
170
+ status: ReadinessStatus.NotReachable,
171
+ methods: [],
172
+ coverage: [],
173
+ reasons: ["No verified notification method - cannot be paged"],
174
+ reachedVia: [ResponderSource.Direct],
175
+ teams: [],
176
+ ...overrides,
177
+ };
178
+ };
179
+
180
+ type MakeSummaryFunction = (
181
+ overrides?: Partial<ReadinessSummary>,
182
+ ) => ReadinessSummary;
183
+
184
+ const makeSummary: MakeSummaryFunction = (
185
+ overrides?: Partial<ReadinessSummary>,
186
+ ): ReadinessSummary => {
187
+ const users: Array<UserReadiness> = overrides?.users || [];
188
+
189
+ const countOf: (status: ReadinessStatus) => number = (
190
+ status: ReadinessStatus,
191
+ ): number => {
192
+ return users.filter((user: UserReadiness): boolean => {
193
+ return user.status === status;
194
+ }).length;
195
+ };
196
+
197
+ return {
198
+ projectId: PROJECT_ID,
199
+ readyCount: countOf(ReadinessStatus.Ready),
200
+ partiallyReadyCount: countOf(ReadinessStatus.PartiallyReady),
201
+ notReachableCount: countOf(ReadinessStatus.NotReachable),
202
+ isFallbackEnabled: true,
203
+ isTruncated: false,
204
+ ...overrides,
205
+ users: users,
206
+ };
207
+ };
208
+
209
+ type MakeOwnerFunction = (name: string) => User;
210
+
211
+ const makeOwner: MakeOwnerFunction = (name: string): User => {
212
+ const user: User = new User();
213
+ user.id = new ObjectID(`owner-${name}`);
214
+
215
+ return user;
216
+ };
217
+
218
+ /* Every spy the suite drives, refreshed per test. */
219
+ let policyFindAllBySpy: jest.SpyInstance;
220
+ let policyFindBySpy: jest.SpyInstance;
221
+ let projectFindAllBySpy: jest.SpyInstance;
222
+ let projectFindBySpy: jest.SpyInstance;
223
+ let getOwnersSpy: jest.SpyInstance;
224
+ let sendEmailSpy: jest.SpyInstance;
225
+ let readinessSpy: jest.SpyInstance;
226
+ let loggerWarnSpy: jest.SpyInstance;
227
+ let loggerErrorSpy: jest.SpyInstance;
228
+
229
+ /* The (projectId, subject, message) of the nth mail the job asked to send. */
230
+ type SentMail = {
231
+ projectId: ObjectID;
232
+ subject: string;
233
+ message: string;
234
+ };
235
+
236
+ type GetSentMailFunction = (index: number) => SentMail;
237
+
238
+ const getSentMail: GetSentMailFunction = (index: number): SentMail => {
239
+ const call: Array<unknown> = sendEmailSpy.mock.calls[index] as Array<unknown>;
240
+
241
+ return {
242
+ projectId: call[0] as ObjectID,
243
+ subject: call[1] as string,
244
+ message: call[2] as string,
245
+ };
246
+ };
247
+
248
+ beforeEach(() => {
249
+ policyFindAllBySpy = jest
250
+ .spyOn(OnCallDutyPolicyService, "findAllBy")
251
+ .mockResolvedValue([]);
252
+
253
+ policyFindBySpy = jest
254
+ .spyOn(OnCallDutyPolicyService, "findBy")
255
+ .mockResolvedValue([]);
256
+
257
+ projectFindAllBySpy = jest
258
+ .spyOn(ProjectService, "findAllBy")
259
+ .mockResolvedValue([]);
260
+
261
+ projectFindBySpy = jest.spyOn(ProjectService, "findBy").mockResolvedValue([]);
262
+
263
+ getOwnersSpy = jest
264
+ .spyOn(ProjectService, "getOwners")
265
+ .mockResolvedValue([makeOwner("first")]);
266
+
267
+ sendEmailSpy = jest
268
+ .spyOn(ProjectService, "sendEmailToProjectOwners")
269
+ .mockResolvedValue(undefined);
270
+
271
+ readinessSpy = jest
272
+ .spyOn(OnCallReadinessService, "getReadinessForProject")
273
+ .mockResolvedValue(makeSummary());
274
+
275
+ jest
276
+ .spyOn(DatabaseConfig, "getDashboardUrl")
277
+ .mockResolvedValue(URL.fromString(DASHBOARD_URL));
278
+
279
+ // Silenced, and asserted on: the log IS the product in the no-owners case.
280
+ jest.spyOn(logger, "debug").mockImplementation((): void => {});
281
+ loggerWarnSpy = jest.spyOn(logger, "warn").mockImplementation((): void => {});
282
+ loggerErrorSpy = jest
283
+ .spyOn(logger, "error")
284
+ .mockImplementation((): void => {});
285
+ });
286
+
287
+ afterEach(() => {
288
+ jest.restoreAllMocks();
289
+ });
290
+
291
+ describe("WeeklyReadinessDigest - registration", () => {
292
+ test("registers itself with RunCron on a weekly schedule, and never on startup", () => {
293
+ expect(JOB_NAME).toBe("OnCallDutyPolicy:WeeklyReadinessDigest");
294
+ expect(mockCapturedJobs[JOB_NAME]).toBeDefined();
295
+
296
+ const options: CapturedCronOptions | undefined =
297
+ mockCapturedOptions[JOB_NAME];
298
+
299
+ expect(options?.schedule).toBe(EVERY_WEEK);
300
+
301
+ /*
302
+ * runOnStartup would email every project owner in the instance on every
303
+ * worker boot - every deploy, every crash-loop, every scale-up. An owner
304
+ * whose inbox gets three digests during one rollout writes a filter rule,
305
+ * and that filter rule is still there the week the digest matters.
306
+ */
307
+ expect(options?.runOnStartup).toBe(false);
308
+ });
309
+
310
+ test("the registered cron handler runs the sweep", async () => {
311
+ const handler: CronHandler | undefined = mockCapturedJobs[JOB_NAME];
312
+
313
+ expect(handler).toBeDefined();
314
+
315
+ await handler!();
316
+
317
+ // The sweep starts by asking which projects have an on-call policy at all.
318
+ expect(policyFindAllBySpy).toHaveBeenCalledTimes(1);
319
+ });
320
+
321
+ test("is imported by App/FeatureSet/Workers/Index.ts, without which it never runs", () => {
322
+ /*
323
+ * Not a stylistic check. RunCron registers a job purely as a module side
324
+ * effect, so a job file that nothing imports is never scheduled and cannot
325
+ * be enqueued by name either. Every other test in this file imports the job
326
+ * directly, so every other test would still pass with the import missing;
327
+ * reading the index as TEXT is the only assertion that can fail for the
328
+ * right reason. Phase 1 shipped this exact omission.
329
+ */
330
+ const indexSource: string = fs.readFileSync(WORKERS_INDEX_PATH, {
331
+ encoding: "utf-8",
332
+ });
333
+
334
+ expect(indexSource).toContain(
335
+ 'import "./Jobs/OnCallDutyPolicy/WeeklyReadinessDigest";',
336
+ );
337
+ });
338
+ });
339
+
340
+ describe("WeeklyReadinessDigest - finding the projects to check", () => {
341
+ test("collapses many policies into one read of their distinct projects", async () => {
342
+ policyFindAllBySpy.mockResolvedValue([
343
+ makePolicy(PROJECT_ID),
344
+ makePolicy(PROJECT_ID),
345
+ makePolicy(OTHER_PROJECT_ID),
346
+ ]);
347
+
348
+ projectFindAllBySpy.mockResolvedValue([
349
+ makeProject({ id: PROJECT_ID }),
350
+ makeProject({ id: OTHER_PROJECT_ID, name: "Acme Staging" }),
351
+ ]);
352
+
353
+ const projects: Array<Project> = await findProjectsWithOnCallPolicies();
354
+
355
+ expect(projects).toHaveLength(2);
356
+ expect(projectFindAllBySpy).toHaveBeenCalledTimes(1);
357
+
358
+ const findBy: Record<string, unknown> = projectFindAllBySpy.mock
359
+ .calls[0]![0] as Record<string, unknown>;
360
+ const query: Record<string, unknown> = findBy["query"] as Record<
361
+ string,
362
+ unknown
363
+ >;
364
+ const includes: Includes = query["_id"] as Includes;
365
+
366
+ expect(includes).toBeInstanceOf(Includes);
367
+ expect(
368
+ (includes.values as Array<ObjectID>).map((value: ObjectID): string => {
369
+ return value.toString();
370
+ }),
371
+ ).toEqual([PROJECT_ID.toString(), OTHER_PROJECT_ID.toString()]);
372
+ });
373
+
374
+ test("reads nothing about projects when no project has an on-call policy", async () => {
375
+ policyFindAllBySpy.mockResolvedValue([]);
376
+
377
+ const projects: Array<Project> = await findProjectsWithOnCallPolicies();
378
+
379
+ expect(projects).toEqual([]);
380
+ expect(projectFindAllBySpy).not.toHaveBeenCalled();
381
+ });
382
+
383
+ test("skips policy rows with no project id rather than querying for undefined", async () => {
384
+ policyFindAllBySpy.mockResolvedValue([
385
+ makePolicy(undefined),
386
+ makePolicy(PROJECT_ID),
387
+ ]);
388
+
389
+ await findProjectsWithOnCallPolicies();
390
+
391
+ const findBy: Record<string, unknown> = projectFindAllBySpy.mock
392
+ .calls[0]![0] as Record<string, unknown>;
393
+ const query: Record<string, unknown> = findBy["query"] as Record<
394
+ string,
395
+ unknown
396
+ >;
397
+ const includes: Includes = query["_id"] as Includes;
398
+
399
+ expect(includes.values).toHaveLength(1);
400
+ });
401
+
402
+ test("pages both reads instead of taking whatever the first page returns", async () => {
403
+ /*
404
+ * findAllBy pages until the table is exhausted; findBy stops at its limit.
405
+ * A project whose only policy row falls off the end of a single page is a
406
+ * project whose unreachable responders are reported to nobody - which is
407
+ * indistinguishable from a project that is fine. Same reasoning as
408
+ * OnCallReadinessService's readEveryPage, and the same defect that made
409
+ * TeamComplianceService's `limit: 100` a comfortable lie.
410
+ */
411
+ policyFindAllBySpy.mockResolvedValue([makePolicy(PROJECT_ID)]);
412
+ projectFindAllBySpy.mockResolvedValue([makeProject()]);
413
+
414
+ await findProjectsWithOnCallPolicies();
415
+
416
+ expect(policyFindAllBySpy).toHaveBeenCalledTimes(1);
417
+ expect(projectFindAllBySpy).toHaveBeenCalledTimes(1);
418
+ expect(policyFindBySpy).not.toHaveBeenCalled();
419
+ expect(projectFindBySpy).not.toHaveBeenCalled();
420
+
421
+ const policyFindBy: Record<string, any> = policyFindAllBySpy.mock
422
+ .calls[0]![0] as Record<string, any>;
423
+ const projectFindBy: Record<string, any> = projectFindAllBySpy.mock
424
+ .calls[0]![0] as Record<string, any>;
425
+
426
+ expect(policyFindBy["props"].isRoot).toBe(true);
427
+ expect(projectFindBy["props"].isRoot).toBe(true);
428
+ });
429
+ });
430
+
431
+ describe("WeeklyReadinessDigest - saying nothing", () => {
432
+ test("sends no digest when every responder is ready", async () => {
433
+ readinessSpy.mockResolvedValue(
434
+ makeSummary({
435
+ users: [
436
+ makeUserReadiness({ status: ReadinessStatus.Ready, reasons: [] }),
437
+ makeUserReadiness({
438
+ userId: new ObjectID("user-2"),
439
+ userName: "Grace Hopper",
440
+ status: ReadinessStatus.Ready,
441
+ reasons: [],
442
+ }),
443
+ ],
444
+ }),
445
+ );
446
+
447
+ await sendDigestForProject(makeProject());
448
+
449
+ expect(sendEmailSpy).not.toHaveBeenCalled();
450
+
451
+ /*
452
+ * Not even the owner lookup happens: a healthy project costs one readiness
453
+ * computation and nothing else, which is what makes running this over every
454
+ * project in the instance affordable.
455
+ */
456
+ expect(getOwnersSpy).not.toHaveBeenCalled();
457
+ });
458
+
459
+ test("sends no digest when the project's policies have no responders at all", async () => {
460
+ readinessSpy.mockResolvedValue(makeSummary({ users: [] }));
461
+
462
+ await sendDigestForProject(makeProject());
463
+
464
+ expect(sendEmailSpy).not.toHaveBeenCalled();
465
+ });
466
+
467
+ test("warns in the log, and still sends nothing, when a clean result came from a truncated read", async () => {
468
+ /*
469
+ * "We found no problems" and "we could not finish looking" are the same
470
+ * silence in an inbox. There is nobody to name, so there is no digest to
471
+ * send - but the difference has to survive somewhere, and the worker log is
472
+ * the only place left.
473
+ */
474
+ readinessSpy.mockResolvedValue(
475
+ makeSummary({ users: [], isTruncated: true }),
476
+ );
477
+
478
+ await sendDigestForProject(makeProject());
479
+
480
+ expect(sendEmailSpy).not.toHaveBeenCalled();
481
+ expect(loggerWarnSpy).toHaveBeenCalledTimes(1);
482
+ expect(loggerWarnSpy.mock.calls[0]![0]).toContain(PROJECT_ID.toString());
483
+ });
484
+ });
485
+
486
+ describe("WeeklyReadinessDigest - saying something", () => {
487
+ test("emails the owners, naming the unreachable responder and their reasons", async () => {
488
+ readinessSpy.mockResolvedValue(
489
+ makeSummary({
490
+ users: [
491
+ makeUserReadiness({
492
+ userName: "Ada Lovelace",
493
+ userEmail: "ada@example.com",
494
+ reasons: [
495
+ "No verified notification method - cannot be paged",
496
+ "Added SMS but never verified - unverified methods are never used",
497
+ ],
498
+ }),
499
+ ],
500
+ }),
501
+ );
502
+
503
+ await sendDigestForProject(makeProject());
504
+
505
+ expect(sendEmailSpy).toHaveBeenCalledTimes(1);
506
+
507
+ const mail: SentMail = getSentMail(0);
508
+
509
+ expect(mail.projectId.toString()).toBe(PROJECT_ID.toString());
510
+ expect(mail.subject).toContain("[Action Required]");
511
+ expect(mail.subject).toContain("1 on-call responder");
512
+ expect(mail.subject).toContain("Acme Production");
513
+ expect(mail.subject).toContain("cannot be paged");
514
+
515
+ expect(mail.message).toContain("Ada Lovelace");
516
+ expect(mail.message).toContain("ada@example.com");
517
+ expect(mail.message).toContain(
518
+ "No verified notification method - cannot be paged",
519
+ );
520
+ expect(mail.message).toContain(
521
+ "Added SMS but never verified - unverified methods are never used",
522
+ );
523
+ });
524
+
525
+ test("links to that project's On-Call > Readiness page", async () => {
526
+ readinessSpy.mockResolvedValue(
527
+ makeSummary({ users: [makeUserReadiness()] }),
528
+ );
529
+
530
+ await sendDigestForProject(makeProject());
531
+
532
+ const mail: SentMail = getSentMail(0);
533
+
534
+ expect(mail.message).toContain(
535
+ `${DASHBOARD_URL}/${PROJECT_ID.toString()}/on-call-duty/readiness`,
536
+ );
537
+ expect(mail.message).toContain("<a href=");
538
+ });
539
+
540
+ test("never names a responder who is ready", async () => {
541
+ readinessSpy.mockResolvedValue(
542
+ makeSummary({
543
+ users: [
544
+ makeUserReadiness({ userName: "Ada Lovelace" }),
545
+ makeUserReadiness({
546
+ userId: new ObjectID("user-2"),
547
+ userName: "Grace Hopper",
548
+ status: ReadinessStatus.Ready,
549
+ reasons: [],
550
+ }),
551
+ ],
552
+ }),
553
+ );
554
+
555
+ await sendDigestForProject(makeProject());
556
+
557
+ const mail: SentMail = getSentMail(0);
558
+
559
+ expect(mail.message).toContain("Ada Lovelace");
560
+ expect(mail.message).not.toContain("Grace Hopper");
561
+ });
562
+
563
+ test("separates 'cannot be paged at all' from 'reachable, but with gaps', worst first", async () => {
564
+ readinessSpy.mockResolvedValue(
565
+ makeSummary({
566
+ users: [
567
+ makeUserReadiness({
568
+ userName: "Ada Lovelace",
569
+ status: ReadinessStatus.NotReachable,
570
+ }),
571
+ makeUserReadiness({
572
+ userId: new ObjectID("user-2"),
573
+ userName: "Grace Hopper",
574
+ status: ReadinessStatus.PartiallyReady,
575
+ reasons: [
576
+ "No rules for Sev1 incidents - pages fall back to Email, Push",
577
+ ],
578
+ }),
579
+ ],
580
+ }),
581
+ );
582
+
583
+ await sendDigestForProject(makeProject());
584
+
585
+ const mail: SentMail = getSentMail(0);
586
+
587
+ expect(mail.message).toContain("Cannot be paged at all (1)");
588
+ expect(mail.message).toContain("Reachable, but with gaps (1)");
589
+ expect(mail.message.indexOf("Cannot be paged at all")).toBeLessThan(
590
+ mail.message.indexOf("Reachable, but with gaps"),
591
+ );
592
+ });
593
+
594
+ test("drops the [Action Required] prefix when nobody is fully unreachable", async () => {
595
+ readinessSpy.mockResolvedValue(
596
+ makeSummary({
597
+ users: [
598
+ makeUserReadiness({
599
+ status: ReadinessStatus.PartiallyReady,
600
+ reasons: ["No rules for Sev1 incidents - pages fall back to Email"],
601
+ }),
602
+ ],
603
+ }),
604
+ );
605
+
606
+ await sendDigestForProject(makeProject());
607
+
608
+ const mail: SentMail = getSentMail(0);
609
+
610
+ expect(mail.subject).not.toContain("[Action Required]");
611
+ expect(mail.subject).toContain("gaps in their notification rules");
612
+ });
613
+
614
+ test("calls the gaps DROPPED pages, not fallback pages, when the project has the fallback switched off", async () => {
615
+ /*
616
+ * The same coverage gap means two different things depending on one project
617
+ * setting: with the fallback on it is an untidy page on the wrong channel,
618
+ * with it off it is silence. Those must not share a sentence.
619
+ */
620
+ readinessSpy.mockResolvedValue(
621
+ makeSummary({
622
+ isFallbackEnabled: false,
623
+ users: [
624
+ makeUserReadiness({
625
+ status: ReadinessStatus.PartiallyReady,
626
+ reasons: [
627
+ "No rules for Sev1 incidents - pages are dropped because on-call fallback is disabled for this project",
628
+ ],
629
+ }),
630
+ ],
631
+ }),
632
+ );
633
+
634
+ await sendDigestForProject(makeProject());
635
+
636
+ const mail: SentMail = getSentMail(0);
637
+
638
+ expect(mail.message).toContain("switched OFF");
639
+ expect(mail.message).toContain("silent pages");
640
+ expect(mail.message).not.toContain(
641
+ "fall back to whatever they have verified",
642
+ );
643
+ });
644
+
645
+ test("admits in the mail when the readiness check did not finish reading the project", async () => {
646
+ readinessSpy.mockResolvedValue(
647
+ makeSummary({ isTruncated: true, users: [makeUserReadiness()] }),
648
+ );
649
+
650
+ await sendDigestForProject(makeProject());
651
+
652
+ expect(getSentMail(0).message).toContain("did not finish reading");
653
+ });
654
+
655
+ test("falls back to a generic project name rather than emailing about 'undefined'", async () => {
656
+ readinessSpy.mockResolvedValue(
657
+ makeSummary({ users: [makeUserReadiness()] }),
658
+ );
659
+
660
+ await sendDigestForProject(makeProject({ id: PROJECT_ID, name: null }));
661
+
662
+ const mail: SentMail = getSentMail(0);
663
+
664
+ expect(mail.subject).toContain("your project");
665
+ expect(mail.message).toContain("your project");
666
+ expect(mail.subject).not.toContain("undefined");
667
+ expect(mail.message).not.toContain("undefined");
668
+ });
669
+
670
+ test("does nothing for a project row with no id", async () => {
671
+ /*
672
+ * Built without an id rather than by clearing one: the model's id setter
673
+ * ignores a null, so an "unset" id can only be produced by never setting
674
+ * it - which is exactly the shape a partial select would hand back.
675
+ */
676
+ const project: Project = new Project();
677
+ project.name = "Acme Production";
678
+
679
+ await sendDigestForProject(project);
680
+
681
+ expect(readinessSpy).not.toHaveBeenCalled();
682
+ expect(sendEmailSpy).not.toHaveBeenCalled();
683
+ });
684
+ });
685
+
686
+ describe("WeeklyReadinessDigest - nobody to tell", () => {
687
+ test("logs an error, loudly, when the project has no owners to send to", async () => {
688
+ /*
689
+ * getOwners returns [] when no team in the project holds ProjectOwner, and
690
+ * sendEmailToProjectOwners then returns without sending and without
691
+ * complaining. Returning silently here would mean the last-resort warning
692
+ * about responders nobody can page itself reaches nobody, and nothing
693
+ * anywhere records that it did not.
694
+ */
695
+ getOwnersSpy.mockResolvedValue([]);
696
+
697
+ readinessSpy.mockResolvedValue(
698
+ makeSummary({
699
+ users: [
700
+ makeUserReadiness(),
701
+ makeUserReadiness({
702
+ userId: new ObjectID("user-2"),
703
+ userName: "Grace Hopper",
704
+ status: ReadinessStatus.PartiallyReady,
705
+ reasons: ["No rules for Sev1 incidents - pages fall back to Email"],
706
+ }),
707
+ ],
708
+ }),
709
+ );
710
+
711
+ await sendDigestForProject(makeProject());
712
+
713
+ expect(sendEmailSpy).not.toHaveBeenCalled();
714
+ expect(loggerErrorSpy).toHaveBeenCalledTimes(1);
715
+
716
+ const logged: string = loggerErrorSpy.mock.calls[0]![0] as string;
717
+
718
+ expect(logged).toContain(PROJECT_ID.toString());
719
+ expect(logged).toContain("ProjectOwner");
720
+
721
+ // The counts have to survive into the log, because they survive nowhere else.
722
+ expect(logged).toContain("1 responder(s) who cannot be paged");
723
+ expect(logged).toContain("1 with gaps");
724
+ });
725
+
726
+ test("does not look for owners at all when there is nothing to tell them", async () => {
727
+ getOwnersSpy.mockResolvedValue([]);
728
+
729
+ readinessSpy.mockResolvedValue(
730
+ makeSummary({
731
+ users: [
732
+ makeUserReadiness({ status: ReadinessStatus.Ready, reasons: [] }),
733
+ ],
734
+ }),
735
+ );
736
+
737
+ await sendDigestForProject(makeProject());
738
+
739
+ expect(getOwnersSpy).not.toHaveBeenCalled();
740
+ expect(loggerErrorSpy).not.toHaveBeenCalled();
741
+ });
742
+
743
+ test("sends through sendEmailToProjectOwners once the owners exist", async () => {
744
+ getOwnersSpy.mockResolvedValue([makeOwner("first"), makeOwner("second")]);
745
+
746
+ readinessSpy.mockResolvedValue(
747
+ makeSummary({ users: [makeUserReadiness()] }),
748
+ );
749
+
750
+ await sendDigestForProject(makeProject());
751
+
752
+ /*
753
+ * One call, not one per owner: fanning the message out to each owner is
754
+ * ProjectService's job, and duplicating that loop here would duplicate its
755
+ * per-owner error handling too.
756
+ */
757
+ expect(sendEmailSpy).toHaveBeenCalledTimes(1);
758
+ expect(loggerErrorSpy).not.toHaveBeenCalled();
759
+ });
760
+ });
761
+
762
+ describe("WeeklyReadinessDigest - building the message", () => {
763
+ test("escapes every user-controlled value it interpolates into the raw-HTML body", () => {
764
+ /*
765
+ * ProjectService.sendEmailToProjectOwners hands `message` to
766
+ * SimpleMessage.hbs, which renders it through a triple stache - as RAW
767
+ * HTML. A responder called `<script>` must read as a strange name.
768
+ */
769
+ const digest: ProjectDigest = buildProjectDigest({
770
+ projectName: "<b>Acme</b> & Co",
771
+ notReachable: [
772
+ makeUserReadiness({
773
+ userName: '<script>alert("xss")</script>',
774
+ userEmail: "evil@example.com",
775
+ reasons: ["<img src=x onerror=alert(1)>"],
776
+ }),
777
+ ],
778
+ partiallyReady: [],
779
+ isFallbackEnabled: true,
780
+ isTruncated: false,
781
+ readinessLink: `${DASHBOARD_URL}/${PROJECT_ID.toString()}/on-call-duty/readiness`,
782
+ });
783
+
784
+ expect(digest.messageHtml).not.toContain("<script>");
785
+ expect(digest.messageHtml).not.toContain("<img src=x");
786
+ expect(digest.messageHtml).toContain("&lt;script&gt;");
787
+ expect(digest.messageHtml).toContain("&lt;img src=x");
788
+ expect(digest.messageHtml).toContain("&lt;b&gt;Acme&lt;/b&gt; &amp; Co");
789
+ });
790
+
791
+ test("escapes a hostile responder name in the PARTIALLY READY section too", () => {
792
+ const digest: ProjectDigest = buildProjectDigest({
793
+ projectName: "Acme Production",
794
+ notReachable: [],
795
+ partiallyReady: [
796
+ makeUserReadiness({
797
+ status: ReadinessStatus.PartiallyReady,
798
+ userName: "<svg onload=alert(1)>",
799
+ reasons: ["<b>not a tag</b>"],
800
+ }),
801
+ ],
802
+ isFallbackEnabled: true,
803
+ isTruncated: false,
804
+ readinessLink: DASHBOARD_URL,
805
+ });
806
+
807
+ expect(digest.messageHtml).not.toContain("<svg");
808
+ expect(digest.messageHtml).toContain("&lt;svg onload=alert(1)&gt;");
809
+ expect(digest.messageHtml).toContain("&lt;b&gt;not a tag&lt;/b&gt;");
810
+ });
811
+
812
+ test("leaves the subject RAW, because Handlebars escapes it and SMTP does not want it escaped", () => {
813
+ /*
814
+ * The subject is rendered through {{title}}, which Handlebars escapes on
815
+ * its own, and is also used verbatim as the SMTP subject header. Escaping
816
+ * here would double-escape the first and corrupt the second.
817
+ */
818
+ const digest: ProjectDigest = buildProjectDigest({
819
+ projectName: "Acme & Co",
820
+ notReachable: [makeUserReadiness()],
821
+ partiallyReady: [],
822
+ isFallbackEnabled: true,
823
+ isTruncated: false,
824
+ readinessLink: DASHBOARD_URL,
825
+ });
826
+
827
+ expect(digest.subject).toContain("Acme & Co");
828
+ expect(digest.subject).not.toContain("&amp;");
829
+ });
830
+
831
+ test("caps each section but never understates the count", () => {
832
+ const many: Array<UserReadiness> = [];
833
+
834
+ for (let index: number = 0; index < 25; index++) {
835
+ many.push(
836
+ makeUserReadiness({
837
+ userId: new ObjectID(`user-${index}`),
838
+ // Zero-padded so alphabetical order is also numeric order here.
839
+ userName: `Responder ${index.toString().padStart(2, "0")}`,
840
+ }),
841
+ );
842
+ }
843
+
844
+ const digest: ProjectDigest = buildProjectDigest({
845
+ projectName: "Acme Production",
846
+ notReachable: many,
847
+ partiallyReady: [],
848
+ isFallbackEnabled: true,
849
+ isTruncated: false,
850
+ readinessLink: DASHBOARD_URL,
851
+ });
852
+
853
+ expect(MAX_RESPONDERS_LISTED_PER_SECTION).toBe(20);
854
+
855
+ // The heading counts all 25, even though the list stops at 20.
856
+ expect(digest.messageHtml).toContain("Cannot be paged at all (25)");
857
+ expect(digest.subject).toContain("25 on-call responders");
858
+
859
+ expect(digest.messageHtml).toContain("Responder 00");
860
+ expect(digest.messageHtml).toContain("Responder 19");
861
+ expect(digest.messageHtml).not.toContain("Responder 20");
862
+ expect(digest.messageHtml).toContain("and 5 more");
863
+ });
864
+
865
+ test("names one responder in the singular and several in the plural", () => {
866
+ const one: ProjectDigest = buildProjectDigest({
867
+ projectName: "Acme Production",
868
+ notReachable: [makeUserReadiness()],
869
+ partiallyReady: [],
870
+ isFallbackEnabled: true,
871
+ isTruncated: false,
872
+ readinessLink: DASHBOARD_URL,
873
+ });
874
+
875
+ const two: ProjectDigest = buildProjectDigest({
876
+ projectName: "Acme Production",
877
+ notReachable: [
878
+ makeUserReadiness(),
879
+ makeUserReadiness({ userId: new ObjectID("user-2") }),
880
+ ],
881
+ partiallyReady: [],
882
+ isFallbackEnabled: true,
883
+ isTruncated: false,
884
+ readinessLink: DASHBOARD_URL,
885
+ });
886
+
887
+ expect(one.subject).toContain("1 on-call responder in");
888
+ expect(two.subject).toContain("2 on-call responders in");
889
+ });
890
+
891
+ test("omits the parenthesised email when the responder has none", () => {
892
+ const digest: ProjectDigest = buildProjectDigest({
893
+ projectName: "Acme Production",
894
+ notReachable: [
895
+ makeUserReadiness({ userName: "Ada Lovelace", userEmail: "" }),
896
+ ],
897
+ partiallyReady: [],
898
+ isFallbackEnabled: true,
899
+ isTruncated: false,
900
+ readinessLink: DASHBOARD_URL,
901
+ });
902
+
903
+ expect(digest.messageHtml).toContain("<b>Ada Lovelace</b>");
904
+ expect(digest.messageHtml).not.toContain("Ada Lovelace</b> (");
905
+ });
906
+
907
+ test("says out loud that silence means everything is fine", () => {
908
+ /*
909
+ * The framing is load-bearing: an owner who knows this only arrives when
910
+ * something is broken reads it differently from one who assumes it is a
911
+ * routine weekly report they can skim.
912
+ */
913
+ const digest: ProjectDigest = buildProjectDigest({
914
+ projectName: "Acme Production",
915
+ notReachable: [makeUserReadiness()],
916
+ partiallyReady: [],
917
+ isFallbackEnabled: true,
918
+ isTruncated: false,
919
+ readinessLink: DASHBOARD_URL,
920
+ });
921
+
922
+ expect(digest.messageHtml).toContain(
923
+ "a week with nothing wrong is a week with no email",
924
+ );
925
+ });
926
+ });
927
+
928
+ describe("WeeklyReadinessDigest - the weekly sweep", () => {
929
+ test("lists responders alphabetically so an unchanged week looks unchanged", async () => {
930
+ readinessSpy.mockResolvedValue(
931
+ makeSummary({
932
+ users: [
933
+ makeUserReadiness({
934
+ userId: new ObjectID("user-1"),
935
+ userName: "Zoe Zhang",
936
+ }),
937
+ makeUserReadiness({
938
+ userId: new ObjectID("user-2"),
939
+ userName: "Ada Lovelace",
940
+ }),
941
+ makeUserReadiness({
942
+ userId: new ObjectID("user-3"),
943
+ userName: "Marie Curie",
944
+ }),
945
+ ],
946
+ }),
947
+ );
948
+
949
+ await sendDigestForProject(makeProject());
950
+
951
+ const message: string = getSentMail(0).message;
952
+
953
+ expect(message.indexOf("Ada Lovelace")).toBeLessThan(
954
+ message.indexOf("Marie Curie"),
955
+ );
956
+ expect(message.indexOf("Marie Curie")).toBeLessThan(
957
+ message.indexOf("Zoe Zhang"),
958
+ );
959
+ });
960
+
961
+ test("walks every project with a policy, and asks about each one exactly once", async () => {
962
+ policyFindAllBySpy.mockResolvedValue([
963
+ makePolicy(PROJECT_ID),
964
+ makePolicy(OTHER_PROJECT_ID),
965
+ ]);
966
+
967
+ projectFindAllBySpy.mockResolvedValue([
968
+ makeProject({ id: PROJECT_ID }),
969
+ makeProject({ id: OTHER_PROJECT_ID, name: "Acme Staging" }),
970
+ ]);
971
+
972
+ readinessSpy.mockResolvedValue(
973
+ makeSummary({ users: [makeUserReadiness()] }),
974
+ );
975
+
976
+ await sendWeeklyReadinessDigests();
977
+
978
+ expect(readinessSpy).toHaveBeenCalledTimes(2);
979
+ expect(sendEmailSpy).toHaveBeenCalledTimes(2);
980
+ expect(getSentMail(1).subject).toContain("Acme Staging");
981
+ });
982
+
983
+ test("does nothing at all when no project has an on-call policy", async () => {
984
+ policyFindAllBySpy.mockResolvedValue([]);
985
+
986
+ await sendWeeklyReadinessDigests();
987
+
988
+ expect(readinessSpy).not.toHaveBeenCalled();
989
+ expect(sendEmailSpy).not.toHaveBeenCalled();
990
+ });
991
+
992
+ test("one project that throws does not stop the rest being told", async () => {
993
+ policyFindAllBySpy.mockResolvedValue([
994
+ makePolicy(PROJECT_ID),
995
+ makePolicy(OTHER_PROJECT_ID),
996
+ ]);
997
+
998
+ projectFindAllBySpy.mockResolvedValue([
999
+ makeProject({ id: PROJECT_ID }),
1000
+ makeProject({ id: OTHER_PROJECT_ID, name: "Acme Staging" }),
1001
+ ]);
1002
+
1003
+ readinessSpy.mockImplementation(
1004
+ async (askedProjectId: ObjectID): Promise<ReadinessSummary> => {
1005
+ if (askedProjectId.toString() === PROJECT_ID.toString()) {
1006
+ throw new Error("readiness read blew up");
1007
+ }
1008
+
1009
+ return makeSummary({ users: [makeUserReadiness()] });
1010
+ },
1011
+ );
1012
+
1013
+ await sendWeeklyReadinessDigests();
1014
+
1015
+ expect(sendEmailSpy).toHaveBeenCalledTimes(1);
1016
+ expect(getSentMail(0).projectId.toString()).toBe(
1017
+ OTHER_PROJECT_ID.toString(),
1018
+ );
1019
+ expect(loggerErrorSpy).toHaveBeenCalled();
1020
+ });
1021
+ });