@oneuptime/common 14.0.1 → 14.0.2

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 (1090) hide show
  1. package/Models/DatabaseModels/AlertEpisodeMember.ts +8 -1
  2. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +63 -0
  3. package/Models/DatabaseModels/IncidentEpisodeMember.ts +8 -1
  4. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +9 -2
  5. package/Models/DatabaseModels/IncidentPublicNote.ts +9 -2
  6. package/Models/DatabaseModels/KubernetesCluster.ts +429 -0
  7. package/Models/DatabaseModels/Monitor.ts +48 -0
  8. package/Models/DatabaseModels/Runner.ts +5 -4
  9. package/Models/DatabaseModels/RunnerJob.ts +66 -0
  10. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +10 -2
  11. package/Models/DatabaseModels/WorkflowVariable.ts +479 -2
  12. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +79 -25
  13. package/Server/API/AIInvestigationAPI.ts +341 -0
  14. package/Server/API/AutoRemediationAPI.ts +184 -2
  15. package/Server/API/CommonAPI.ts +70 -0
  16. package/Server/API/KubernetesClusterAiAccessAPI.ts +793 -0
  17. package/Server/API/MicrosoftTeamsAPI.ts +770 -315
  18. package/Server/API/MonitorAPI.ts +140 -0
  19. package/Server/API/OnCallScheduleTimelineAPI.ts +5 -21
  20. package/Server/API/SlackAPI.ts +447 -99
  21. package/Server/API/StatusPageAPI.ts +146 -26
  22. package/Server/API/TelemetryAPI.ts +216 -5
  23. package/Server/API/UserTwoFactorBackupCodeAPI.ts +1 -0
  24. package/Server/API/WorkflowVariableAPI.ts +156 -0
  25. package/Server/Infrastructure/GlobalCache.ts +35 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.ts +97 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.ts +129 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.ts +25 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.ts +20 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.ts +38 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  32. package/Server/Middleware/MasterAdminAuthorization.ts +28 -0
  33. package/Server/Middleware/TelemetryIngest.ts +47 -2
  34. package/Server/Middleware/UserAuthorization.ts +56 -2
  35. package/Server/Services/AIAgentService.ts +1 -0
  36. package/Server/Services/AccessTokenService.ts +121 -48
  37. package/Server/Services/AlertEpisodeMemberService.ts +9 -0
  38. package/Server/Services/AlertEpisodeService.ts +14 -1
  39. package/Server/Services/AlertService.ts +134 -22
  40. package/Server/Services/AutoRemediationRuleEngineService.ts +968 -39
  41. package/Server/Services/AutoRemediationRuleService.ts +118 -0
  42. package/Server/Services/BillingService.ts +1 -0
  43. package/Server/Services/InboundEmail/Providers/SendGridInboundProvider.ts +16 -22
  44. package/Server/Services/IncidentEpisodeMemberService.ts +9 -0
  45. package/Server/Services/IncidentEpisodePublicNoteService.ts +66 -0
  46. package/Server/Services/IncidentEpisodeRoleMemberService.ts +27 -0
  47. package/Server/Services/IncidentEpisodeService.ts +14 -1
  48. package/Server/Services/IncidentGroupingEngineService.ts +11 -0
  49. package/Server/Services/IncidentMemberService.ts +21 -0
  50. package/Server/Services/IncidentPublicNoteService.ts +63 -0
  51. package/Server/Services/IncidentService.ts +120 -20
  52. package/Server/Services/IncidentStateTimelineService.ts +15 -0
  53. package/Server/Services/IncidentTemplateService.ts +83 -3
  54. package/Server/Services/KubernetesClusterAiAccessService.ts +1943 -0
  55. package/Server/Services/KubernetesClusterService.ts +1570 -1
  56. package/Server/Services/LogAggregationService.ts +14 -6
  57. package/Server/Services/MonitorService.ts +651 -52
  58. package/Server/Services/MonitorStatusTimelineService.ts +639 -2
  59. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -0
  60. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -0
  61. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -0
  62. package/Server/Services/OnCallDutyPolicyScheduleService.ts +3 -0
  63. package/Server/Services/OnCallNotificationAlertingService.ts +36 -12
  64. package/Server/Services/OnCallSetupReminderService.ts +1 -0
  65. package/Server/Services/ProbeService.ts +7 -1
  66. package/Server/Services/ProjectService.ts +11 -1
  67. package/Server/Services/RunbookCredentialService.ts +26 -1
  68. package/Server/Services/RunbookSecretService.ts +36 -0
  69. package/Server/Services/RunnerJobService.ts +941 -23
  70. package/Server/Services/RunnerService.ts +439 -6
  71. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +66 -0
  72. package/Server/Services/ScheduledMaintenanceService.ts +124 -19
  73. package/Server/Services/ScheduledMaintenanceTemplateService.ts +82 -3
  74. package/Server/Services/ServiceLevelObjectiveService.ts +14 -1
  75. package/Server/Services/StatusPagePrivateUserService.ts +1 -0
  76. package/Server/Services/StatusPageService.ts +301 -74
  77. package/Server/Services/StatusPageSubscriberService.ts +4 -0
  78. package/Server/Services/TeamMemberService.ts +309 -7
  79. package/Server/Services/TraceAggregationService.ts +10 -6
  80. package/Server/Services/UserEmailService.ts +1 -0
  81. package/Server/Services/UserNotificationMethodAdminService.ts +1 -0
  82. package/Server/Services/UserNotificationRuleAdminService.ts +1 -0
  83. package/Server/Services/UserNotificationRuleService.ts +4 -0
  84. package/Server/Services/UserService.ts +122 -1
  85. package/Server/Services/WorkflowVariableService.ts +593 -8
  86. package/Server/Services/WorkspaceNotificationRuleService.ts +588 -8
  87. package/Server/Services/WorkspaceProjectAuthTokenService.ts +170 -1
  88. package/Server/Types/Database/QueryHelper.ts +22 -0
  89. package/Server/Types/Markdown.ts +502 -27
  90. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +182 -0
  91. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +525 -0
  92. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +854 -0
  93. package/Server/Utils/AI/CodeFix/CodeFixAgentAuth.ts +22 -2
  94. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1916 -307
  95. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +1601 -74
  96. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +139 -7
  97. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +58 -1
  98. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +54 -1
  99. package/Server/Utils/AI/Toolbox/AIActionTools.ts +14 -6
  100. package/Server/Utils/AI/Toolbox/ResourceTools.ts +4 -2
  101. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +1341 -150
  102. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +375 -4
  103. package/Server/Utils/Database/AffectedResourceRelations.ts +127 -0
  104. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +240 -1
  105. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +1 -0
  106. package/Server/Utils/Monitor/MonitorAlert.ts +83 -9
  107. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +5 -1
  108. package/Server/Utils/Monitor/MonitorIncident.ts +104 -9
  109. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +124 -7
  110. package/Server/Utils/Monitor/ProbeMonitorsNotification.ts +407 -0
  111. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +1 -0
  112. package/Server/Utils/PushNotificationUtil.ts +45 -0
  113. package/Server/Utils/Rules/OwnerRuleAssignment.ts +40 -3
  114. package/Server/Utils/Runbook/RunbookExecutePermission.ts +56 -6
  115. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +223 -33
  116. package/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.ts +559 -0
  117. package/Server/Utils/StartServer.ts +4 -3
  118. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +528 -0
  119. package/Server/Utils/TwoFactorBackupCodeNotification.ts +1 -0
  120. package/Server/Utils/UserPermission/UserPermission.ts +16 -0
  121. package/Server/Utils/Workflow/OAuth2TokenClient.ts +704 -0
  122. package/Server/Utils/Workflow/WorkflowVariableOAuthToken.ts +318 -0
  123. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +47 -7
  124. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +57 -16
  125. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +193 -214
  126. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +57 -13
  127. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.ts +54 -17
  128. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +179 -161
  129. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +281 -167
  130. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.ts +114 -0
  131. package/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.ts +895 -0
  132. package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +7 -9
  133. package/Server/Utils/Workspace/Slack/Actions/Alert.ts +96 -227
  134. package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +93 -214
  135. package/Server/Utils/Workspace/Slack/Actions/Auth.ts +30 -3
  136. package/Server/Utils/Workspace/Slack/Actions/Authorization.ts +99 -0
  137. package/Server/Utils/Workspace/Slack/Actions/Incident.ts +117 -283
  138. package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +101 -196
  139. package/Server/Utils/Workspace/Slack/Actions/ReactionNote.ts +329 -0
  140. package/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.ts +111 -287
  141. package/Server/Utils/Workspace/Slack/Slack.ts +86 -17
  142. package/Server/Utils/Workspace/Slack/app-manifest.json +2 -0
  143. package/Server/Utils/Workspace/Workspace.ts +171 -54
  144. package/Server/Utils/Workspace/WorkspaceActionAuthorization.ts +252 -0
  145. package/Server/Utils/Workspace/WorkspaceBase.ts +39 -0
  146. package/Server/Utils/Workspace/WorkspaceOAuthState.ts +280 -0
  147. package/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.ts +96 -0
  148. package/Server/Utils/Workspace/WorkspaceReactionNote.ts +620 -0
  149. package/Tests/App/Dashboard/AttributesJSONDashboardSurfaces.test.tsx +496 -0
  150. package/Tests/App/Dashboard/AutoRemediationRulesTableCommandRunners.test.tsx +152 -0
  151. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +32 -0
  152. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +794 -0
  153. package/Tests/App/Dashboard/CreateOAuthWorkflowVariableModal.test.tsx +840 -0
  154. package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +653 -0
  155. package/Tests/App/Dashboard/DatabaseMonitorStepForm.test.tsx +197 -0
  156. package/Tests/App/Dashboard/DatabaseMonitorView.test.tsx +376 -0
  157. package/Tests/App/Dashboard/DisabledWarningRobustness.test.tsx +262 -0
  158. package/Tests/App/Dashboard/EventNotes.test.tsx +2080 -0
  159. package/Tests/App/Dashboard/EventNotesPages.test.tsx +486 -0
  160. package/Tests/App/Dashboard/EventNotesUtil.test.ts +933 -0
  161. package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +218 -0
  162. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +665 -0
  163. package/Tests/App/Dashboard/IncidentEpisodeFeedPublicNoteNotifyDefault.test.tsx +494 -0
  164. package/Tests/App/Dashboard/IncidentEpisodeOverviewNotifyDefault.test.tsx +706 -0
  165. package/Tests/App/Dashboard/IncidentFeedPublicNoteNotifyDefault.test.tsx +472 -0
  166. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +858 -0
  167. package/Tests/App/Dashboard/IncomingEmailAddressSettings.test.tsx +567 -0
  168. package/Tests/App/Dashboard/InvestigationClusterToolFormat.test.ts +241 -0
  169. package/Tests/App/Dashboard/InvestigationEvidenceKubectl.test.tsx +322 -0
  170. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +12 -0
  171. package/Tests/App/Dashboard/InvestigationPanelClusterAccess.test.tsx +476 -0
  172. package/Tests/App/Dashboard/InvestigationRunDetailsKubectl.test.tsx +383 -0
  173. package/Tests/App/Dashboard/KubectlActivitySummary.test.tsx +366 -0
  174. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +2886 -0
  175. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +2688 -0
  176. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +915 -0
  177. package/Tests/App/Dashboard/LayerDateTimeFieldTimezoneName.test.tsx +66 -0
  178. package/Tests/App/Dashboard/LogMonitorPreview.test.tsx +174 -0
  179. package/Tests/App/Dashboard/MonitorFeedRefresh.test.tsx +279 -0
  180. package/Tests/App/Dashboard/MonitorMetricQueryConfig.test.ts +244 -0
  181. package/Tests/App/Dashboard/MonitorOpenWorkCard.test.tsx +381 -0
  182. package/Tests/App/Dashboard/MonitorOpenWorkHook.test.tsx +784 -0
  183. package/Tests/App/Dashboard/MonitorOverviewData.test.tsx +2199 -0
  184. package/Tests/App/Dashboard/MonitorOverviewHero.test.tsx +1169 -0
  185. package/Tests/App/Dashboard/MonitorOverviewInput.test.ts +1127 -0
  186. package/Tests/App/Dashboard/MonitorOverviewPage.test.tsx +2136 -0
  187. package/Tests/App/Dashboard/MonitorOverviewSelectReadable.test.ts +368 -0
  188. package/Tests/App/Dashboard/MonitorOverviewStatBar.test.tsx +526 -0
  189. package/Tests/App/Dashboard/MonitorOverviewSupportingCards.test.tsx +542 -0
  190. package/Tests/App/Dashboard/MonitorOwnersCombine.test.ts +299 -0
  191. package/Tests/App/Dashboard/MonitorProbesCard.test.tsx +602 -0
  192. package/Tests/App/Dashboard/MonitorResponseTimeCard.test.tsx +386 -0
  193. package/Tests/App/Dashboard/MonitorSetupAndConnectionCards.test.tsx +612 -0
  194. package/Tests/App/Dashboard/MonitorStatusChangesCard.test.tsx +257 -0
  195. package/Tests/App/Dashboard/MonitorSummaryTestMonitorButton.test.tsx +360 -1
  196. package/Tests/App/Dashboard/MonitorUptimeHistoryCard.test.tsx +835 -0
  197. package/Tests/App/Dashboard/MonitorUptimeSummaryHook.test.tsx +971 -0
  198. package/Tests/App/Dashboard/MonitorViewLayout.test.tsx +580 -0
  199. package/Tests/App/Dashboard/OnCallLayerRestrictionTimesPreserved.test.tsx +441 -0
  200. package/Tests/App/Dashboard/OnCallNavbar.test.tsx +214 -0
  201. package/Tests/App/Dashboard/OnCallSchedulePreviewCalendarDisplay.test.tsx +1389 -0
  202. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +205 -1
  203. package/Tests/App/Dashboard/OnCallScheduleTimeInputsSettingsZone.test.tsx +198 -0
  204. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +524 -0
  205. package/Tests/App/Dashboard/RemediationSuggestionCardApproval.test.tsx +702 -0
  206. package/Tests/App/Dashboard/RemoveUserFromProject.test.tsx +427 -0
  207. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +4 -0
  208. package/Tests/App/Dashboard/RumSessionReplaySideMenu.test.tsx +3 -1
  209. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +166 -0
  210. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +622 -0
  211. package/Tests/App/Dashboard/ScheduledMaintenanceChangeStateNotifyDefault.test.tsx +1491 -0
  212. package/Tests/App/Dashboard/ScheduledMaintenanceFeedPublicNoteNotifyDefault.test.tsx +489 -0
  213. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewNotifyDefault.test.tsx +909 -0
  214. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +14 -0
  215. package/Tests/App/Dashboard/ScheduledMaintenanceWindowStatsTranslation.test.tsx +307 -0
  216. package/Tests/App/Dashboard/SecurityEventAttributeColumnPicker.test.tsx +542 -0
  217. package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +174 -0
  218. package/Tests/App/Dashboard/SecurityEventConnectionsEmptyState.test.tsx +973 -212
  219. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +491 -52
  220. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +301 -0
  221. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +158 -1
  222. package/Tests/App/Dashboard/SecurityEventProviderTile.test.tsx +869 -0
  223. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +959 -94
  224. package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +2 -0
  225. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +274 -1
  226. package/Tests/App/Dashboard/TeamMembershipRemoval.test.ts +900 -0
  227. package/Tests/App/Dashboard/TeamMembershipRemovalPages.test.tsx +460 -0
  228. package/Tests/App/Dashboard/TimezoneElements.test.tsx +321 -0
  229. package/Tests/App/Dashboard/TimezoneSelectButton.test.tsx +634 -0
  230. package/Tests/App/Dashboard/TraceExplorer.test.tsx +260 -0
  231. package/Tests/App/Dashboard/TraceMonitorPreview.test.tsx +182 -0
  232. package/Tests/App/Dashboard/UserTimezoneInit.test.tsx +544 -0
  233. package/Tests/App/Dashboard/WorkflowVariableModals.test.tsx +1630 -0
  234. package/Tests/App/Dashboard/WorkflowVariableRoutes.test.tsx +603 -0
  235. package/Tests/App/Dashboard/WorkflowVariableTokenStatus.test.tsx +589 -0
  236. package/Tests/App/Dashboard/WorkflowVariableUtil.test.ts +1543 -0
  237. package/Tests/App/Dashboard/WorkflowVariableView.test.tsx +3227 -0
  238. package/Tests/App/Dashboard/WorkflowVariablesTable.test.tsx +1277 -395
  239. package/Tests/App/StatusPage/StatusPageOverviewMonitorGroupUptime.test.tsx +714 -0
  240. package/Tests/App/StatusPage/StatusPageOverviewUptimeBars.NewYork.test.tsx +1009 -0
  241. package/Tests/App/StatusPage/StatusPageOverviewUptimeBars.test.tsx +1092 -0
  242. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +5 -0
  243. package/Tests/Models/DatabaseModels/KubernetesClusterAiAccessColumns.test.ts +520 -0
  244. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +66 -2
  245. package/Tests/Models/DatabaseModels/WorkspaceProjectAuthTokenAccessControl.test.ts +236 -0
  246. package/Tests/Models/IncidentEpisodePublicNoteNotifyColumnContract.test.ts +103 -0
  247. package/Tests/Models/IncidentPublicNoteNotifyColumnContract.test.ts +96 -0
  248. package/Tests/Models/ScheduledMaintenancePublicNoteNotifyColumnContract.test.ts +111 -0
  249. package/Tests/Server/API/AIInvestigationAPI.test.ts +976 -0
  250. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +836 -0
  251. package/Tests/Server/API/CommonAPIAssertCanCreateTable.test.ts +565 -0
  252. package/Tests/Server/API/CommonAPIAssertCanReadTable.test.ts +282 -0
  253. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +1744 -0
  254. package/Tests/Server/API/LogHistogramAPI.test.ts +371 -0
  255. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +125 -30
  256. package/Tests/Server/API/MicrosoftTeamsOAuthState.test.ts +1121 -0
  257. package/Tests/Server/API/MonitorUptimeSummaryAPI.test.ts +685 -0
  258. package/Tests/Server/API/SessionReplayAPI.test.ts +602 -1
  259. package/Tests/Server/API/SlackChannelCacheAPI.test.ts +358 -0
  260. package/Tests/Server/API/SlackOAuthState.test.ts +688 -0
  261. package/Tests/Server/API/StatusPageOverviewMonitorGroupUptime.test.ts +527 -0
  262. package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +2 -0
  263. package/Tests/Server/API/StatusPageUptimeAPI.test.ts +427 -0
  264. package/Tests/Server/API/UserProjectsAPI.test.ts +4 -0
  265. package/Tests/Server/API/WorkflowVariableAPI.test.ts +404 -0
  266. package/Tests/Server/API/WorkspaceOAuthTestHelpers.ts +289 -0
  267. package/Tests/Server/API/WorkspaceSendTestNotificationRoutes.test.ts +2064 -0
  268. package/Tests/Server/Infrastructure/GlobalCache.test.ts +86 -0
  269. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +589 -0
  270. package/Tests/Server/Infrastructure/Postgres/AddOAuth2WorkflowVariablesMigration.test.ts +254 -0
  271. package/Tests/Server/Middleware/BlockedUserMiddleware.test.ts +403 -0
  272. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +13 -0
  273. package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +2 -0
  274. package/Tests/Server/Middleware/RemovedProjectMemberAuthorization.test.ts +469 -0
  275. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +77 -1
  276. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +211 -0
  277. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +358 -0
  278. package/Tests/Server/Middleware/UserAuthorization.test.ts +97 -0
  279. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +2 -0
  280. package/Tests/Server/Services/AccessTokenProjectUserGrant.test.ts +14 -2
  281. package/Tests/Server/Services/AccessTokenRemovedMemberAccess.test.ts +1044 -0
  282. package/Tests/Server/Services/AddOwnersIdempotency.test.ts +5 -0
  283. package/Tests/Server/Services/AlertEpisodeMemberCreatePermission.test.ts +139 -0
  284. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +1361 -0
  285. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +375 -0
  286. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +2384 -0
  287. package/Tests/Server/Services/FindOwnersProjectMembership.test.ts +267 -0
  288. package/Tests/Server/Services/InboundEmail/SendGridInboundProviderAddress.test.ts +90 -0
  289. package/Tests/Server/Services/IncidentEpisodeMemberCreatePermission.test.ts +143 -0
  290. package/Tests/Server/Services/IncidentEpisodePublicNoteNotifyDefault.test.ts +805 -0
  291. package/Tests/Server/Services/IncidentPublicNoteNotifyDefault.test.ts +724 -0
  292. package/Tests/Server/Services/IncidentRoleProjectMembership.test.ts +233 -0
  293. package/Tests/Server/Services/KubernetesClusterAiAccessAgentRunnerAssignments.test.ts +372 -0
  294. package/Tests/Server/Services/KubernetesClusterAiAccessBindingGuard.test.ts +1876 -0
  295. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +620 -0
  296. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +421 -0
  297. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +3662 -0
  298. package/Tests/Server/Services/KubernetesClusterAiAccessSubjectClusters.test.ts +405 -0
  299. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +975 -0
  300. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +497 -0
  301. package/Tests/Server/Services/KubernetesClusterServiceDeleteSettlesRemediation.test.ts +328 -0
  302. package/Tests/Server/Services/LogAggregationService.test.ts +99 -0
  303. package/Tests/Server/Services/MonitorServiceIncomingEmailCustomAddress.test.ts +447 -0
  304. package/Tests/Server/Services/MonitorServiceProbeFlagSync.test.ts +1334 -0
  305. package/Tests/Server/Services/MonitorServiceProbeStatusGrouping.test.ts +1640 -0
  306. package/Tests/Server/Services/MonitorServiceRefreshCurrentStatus.test.ts +225 -0
  307. package/Tests/Server/Services/MonitorStatusBridgeProbePath.test.ts +8 -8
  308. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +264 -1
  309. package/Tests/Server/Services/MonitorStatusTimelineMergedDowntime.test.ts +284 -0
  310. package/Tests/Server/Services/MonitorUptimeSummaryService.test.ts +1043 -0
  311. package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +5 -0
  312. package/Tests/Server/Services/OnCallUndeliverablePageOwnerMembership.test.ts +359 -0
  313. package/Tests/Server/Services/ProbeServiceDashboardLink.test.ts +191 -0
  314. package/Tests/Server/Services/ProjectOwnersProjectMembership.test.ts +221 -0
  315. package/Tests/Server/Services/RuleRun/IncidentAlertMaintenanceRuleEnginesRun.test.ts +5 -0
  316. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupARun.test.ts +28 -0
  317. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +9 -1
  318. package/Tests/Server/Services/RunnerJobEnqueueAiCommandAgentRunner.test.ts +230 -0
  319. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +978 -0
  320. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +866 -0
  321. package/Tests/Server/Services/RunnerJobSubmitResultRedaction.test.ts +322 -0
  322. package/Tests/Server/Services/RunnerJobSubmitResultUnstorableText.test.ts +357 -0
  323. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +227 -0
  324. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +581 -0
  325. package/Tests/Server/Services/ScheduledMaintenancePublicNoteNotifyDefault.test.ts +802 -0
  326. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +53 -0
  327. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +5 -0
  328. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +12 -0
  329. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +1176 -1
  330. package/Tests/Server/Services/TeamMemberBillingSeatSync.test.ts +9 -0
  331. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +18 -0
  332. package/Tests/Server/Services/TeamMemberProjectMembership.test.ts +255 -0
  333. package/Tests/Server/Services/TeamMemberRemovalRevokesProjectAccess.test.ts +600 -0
  334. package/Tests/Server/Services/TeamMemberResourceCleanup.test.ts +731 -0
  335. package/Tests/Server/Services/TeamMemberWorkspaceAccountCleanup.test.ts +164 -0
  336. package/Tests/Server/Services/TraceAggregationService.test.ts +64 -0
  337. package/Tests/Server/Services/UserServiceBlockedUser.test.ts +293 -0
  338. package/Tests/Server/Services/WorkflowVariableOAuthHooks.test.ts +1016 -0
  339. package/Tests/Server/Services/WorkflowVariableOAuthPostgres.test.ts +543 -0
  340. package/Tests/Server/Services/WorkflowVariableRenameUniqueness.test.ts +16 -5
  341. package/Tests/Server/Services/WorkspaceNotificationRuleNoteDelivery.test.ts +473 -0
  342. package/Tests/Server/Services/WorkspaceNotificationRuleSendTestNotification.test.ts +3690 -0
  343. package/Tests/Server/Services/WorkspaceProjectAuthTokenMiscDataSecrets.test.ts +374 -0
  344. package/Tests/Server/Services/WorkspaceProjectAuthTokenSlackChannelCache.test.ts +143 -0
  345. package/Tests/Server/Types/Database/Permissions/WorkflowVariableOAuthColumnPermission.test.ts +250 -0
  346. package/Tests/Server/Types/Database/QueryHelperJsonContains.test.ts +96 -0
  347. package/Tests/Server/Types/Markdown.test.ts +1033 -0
  348. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +285 -0
  349. package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAuth.test.ts +159 -1
  350. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +752 -0
  351. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +1840 -0
  352. package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +1067 -0
  353. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +355 -0
  354. package/Tests/Server/Utils/AI/PostIncidentStatusUpdateTool.test.ts +496 -0
  355. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +305 -0
  356. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +1365 -0
  357. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +639 -0
  358. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +1585 -0
  359. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +1065 -0
  360. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +2138 -1
  361. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +1035 -0
  362. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +517 -0
  363. package/Tests/Server/Utils/AI/ResourceTools.test.ts +27 -1
  364. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +1371 -24
  365. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorRollbackRace.test.ts +962 -0
  366. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +518 -0
  367. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +939 -2
  368. package/Tests/Server/Utils/Database/ProjectScopedRelationReferences.test.ts +371 -0
  369. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +64 -0
  370. package/Tests/Server/Utils/Monitor/DatabaseMetricObservationRemediation.test.ts +102 -0
  371. package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +362 -0
  372. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +55 -0
  373. package/Tests/Server/Utils/Monitor/MonitorEmailAddressRedaction.test.ts +255 -0
  374. package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +347 -0
  375. package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +203 -0
  376. package/Tests/Server/Utils/Monitor/ProbeMonitorsNotification.test.ts +1147 -0
  377. package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +79 -0
  378. package/Tests/Server/Utils/ResourceFeed/ResourceFeedUtil.test.ts +21 -1
  379. package/Tests/Server/Utils/Rules/OwnerRuleAssignment.test.ts +155 -1
  380. package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +288 -0
  381. package/Tests/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.test.ts +304 -0
  382. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +40 -3
  383. package/Tests/Server/Utils/Workflow/OAuth2TokenClient.test.ts +702 -0
  384. package/Tests/Server/Utils/Workflow/OAuth2TokenClientHttpIntegration.test.ts +507 -0
  385. package/Tests/Server/Utils/Workflow/WorkflowVariableOAuthToken.test.ts +831 -0
  386. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +63 -1
  387. package/Tests/Server/Utils/Workspace/MicrosoftTeamsAppAccessToken.test.ts +256 -0
  388. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +60 -0
  389. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChatActionAuthorization.test.ts +442 -0
  390. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +44 -2
  391. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +55 -0
  392. package/Tests/Server/Utils/Workspace/MicrosoftTeamsReactionNoteSync.test.ts +1522 -0
  393. package/Tests/Server/Utils/Workspace/MicrosoftTeamsServiceUrl.test.ts +130 -0
  394. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +4 -4
  395. package/Tests/Server/Utils/Workspace/Slack/SlackActionAuthorization.test.ts +691 -0
  396. package/Tests/Server/Utils/Workspace/Slack/SlackMessageByTimestamp.test.ts +253 -0
  397. package/Tests/Server/Utils/Workspace/Slack/SlackReactionNote.test.ts +574 -0
  398. package/Tests/Server/Utils/Workspace/WorkspaceActionAuthorization.test.ts +488 -0
  399. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +861 -0
  400. package/Tests/Server/Utils/Workspace/WorkspaceMessageAuthor.test.ts +635 -0
  401. package/Tests/Server/Utils/Workspace/WorkspaceOAuthState.test.ts +548 -0
  402. package/Tests/Server/Utils/Workspace/WorkspaceReactionNote.test.ts +839 -0
  403. package/Tests/Server/Utils/Workspace/WorkspaceSendErrorMessage.test.ts +725 -0
  404. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +283 -9
  405. package/Tests/Types/DateBrowserLocalWallClock.test.ts +280 -0
  406. package/Tests/Types/DateLocalShortMonthNameFallback.test.ts +389 -0
  407. package/Tests/Types/DateUserTimezone.test.ts +177 -4
  408. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +306 -0
  409. package/Tests/Types/Kubernetes/KubernetesClusterAiAccess.test.ts +283 -0
  410. package/Tests/Types/Kubernetes/KubernetesRunnerPostureWriteScope.test.ts +68 -0
  411. package/Tests/Types/Monitor/DatabaseMonitor/SqlServerPlatform.test.ts +144 -0
  412. package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +160 -0
  413. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +53 -0
  414. package/Tests/Types/Runbook/RunbookStepType.test.ts +91 -16
  415. package/Tests/Types/Runner/RunnerLiveStatus.test.ts +14 -3
  416. package/Tests/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.test.ts +15 -0
  417. package/Tests/Types/StatusPage/PublicNoteSubscriberNotificationDefault.test.ts +1868 -0
  418. package/Tests/Types/Timezone.test.ts +5 -0
  419. package/Tests/Types/TimezoneAlias.test.ts +1034 -0
  420. package/Tests/Types/Workflow/JiraTemplateFixtures.ts +833 -0
  421. package/Tests/Types/Workflow/JiraTemplateScripts.test.ts +5414 -0
  422. package/Tests/Types/Workflow/JiraTemplateSimulation.test.ts +4803 -0
  423. package/Tests/Types/Workflow/JiraTemplates.test.ts +5429 -0
  424. package/Tests/Types/Workflow/Templates.test.ts +79 -4
  425. package/Tests/Types/Workflow/WorkflowVariableOAuth.test.ts +520 -0
  426. package/Tests/Types/Workspace/WorkspaceNoteReaction.test.ts +177 -0
  427. package/Tests/UI/Components/AttributesJSON/AttributesJSONView.test.tsx +390 -0
  428. package/Tests/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.test.tsx +566 -0
  429. package/Tests/UI/Components/AttributesJSON/JSONHighlight.test.ts +131 -0
  430. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +276 -1
  431. package/Tests/UI/Components/Calendar/Calendar.test.tsx +1611 -0
  432. package/Tests/UI/Components/Calendar/CalendarDayLayout.test.ts +1486 -0
  433. package/Tests/UI/Components/Calendar/CalendarDayLayoutDst.test.ts +632 -0
  434. package/Tests/UI/Components/Calendar/CalendarDst.test.tsx +388 -0
  435. package/Tests/UI/Components/Charts/ChartGroupPlotFloor.test.tsx +170 -0
  436. package/Tests/UI/Components/Charts/HistogramSelection.test.ts +282 -0
  437. package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +6 -0
  438. package/Tests/UI/Components/CustomTimeRangeModalTimezoneTranslation.test.tsx +285 -0
  439. package/Tests/UI/Components/Dropdown.test.tsx +999 -2
  440. package/Tests/UI/Components/Forms/FormFieldCheckboxTranslation.test.tsx +216 -0
  441. package/Tests/UI/Components/Forms/FormFieldTimezoneDescriptionTranslation.test.tsx +461 -0
  442. package/Tests/UI/Components/Forms/IncidentEpisodePublicNoteNotifyPayload.test.tsx +641 -0
  443. package/Tests/UI/Components/Forms/IncidentPublicNoteNotifyPayload.test.tsx +346 -0
  444. package/Tests/UI/Components/Forms/ScheduledMaintenancePublicNoteNotifyPayload.test.tsx +493 -0
  445. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +911 -2
  446. package/Tests/UI/Components/Graphs/DayUptimeGraphTimezone.NewYork.test.tsx +730 -0
  447. package/Tests/UI/Components/Graphs/DayUptimeGraphTimezone.Tokyo.test.tsx +506 -0
  448. package/Tests/UI/Components/HistogramClickToZoom.test.tsx +619 -0
  449. package/Tests/UI/Components/LogDetailsPanelAttributesJSON.test.tsx +248 -0
  450. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +47 -1
  451. package/Tests/UI/Components/LogsViewerSmallScreens.test.tsx +620 -0
  452. package/Tests/UI/Components/ModelPageModelChange.test.tsx +242 -3
  453. package/Tests/UI/Components/ModelTable/BaseModelTableDeleteConfirmation.test.tsx +521 -0
  454. package/Tests/UI/Components/MonitorGraphs/Uptime.test.tsx +406 -2
  455. package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +169 -2
  456. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +266 -0
  457. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerSmallScreens.test.tsx +439 -0
  458. package/Tests/UI/Components/TimezoneDropdownLegacyValues.test.tsx +704 -0
  459. package/Tests/UI/Components/UseHistogramZoom.test.tsx +89 -1
  460. package/Tests/UI/Components/UseLogsHistogram.test.tsx +175 -0
  461. package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +5 -5
  462. package/Tests/UI/Telemetry/TelemetryDetailPanel.test.tsx +28 -0
  463. package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +51 -0
  464. package/Tests/UI/Utils/Timezone.test.ts +747 -33
  465. package/Tests/UI/Workspace/SendTestNotificationButton.test.tsx +1854 -0
  466. package/Tests/UI/Workspace/WorkspaceCardsSendTestNotification.test.tsx +1864 -0
  467. package/Tests/Utils/AI/InvestigationReport.test.ts +435 -7
  468. package/Tests/Utils/AiRemediation/KubectlAllowlistValidatorParity.test.ts +1084 -0
  469. package/Tests/Utils/AiRemediation/KubectlOutputRedactor.test.ts +2531 -0
  470. package/Tests/Utils/AiRemediation/KubectlPolicy.test.ts +1895 -0
  471. package/Tests/Utils/AiRemediation/KubectlPolicyAllowlist.test.ts +428 -0
  472. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +828 -0
  473. package/Tests/Utils/AiRemediation/KubectlPolicyCustomResourceScope.test.ts +573 -0
  474. package/Tests/Utils/AiRemediation/KubectlPolicyDenials.test.ts +1525 -0
  475. package/Tests/Utils/AiRemediation/KubectlPolicyExposeOverrides.test.ts +275 -0
  476. package/Tests/Utils/AiRemediation/KubectlPolicyNodePatch.test.ts +428 -0
  477. package/Tests/Utils/AiRemediation/KubectlPolicyObjectWords.test.ts +195 -0
  478. package/Tests/Utils/AiRemediation/KubectlPolicyPatchParents.test.ts +637 -0
  479. package/Tests/Utils/AiRemediation/KubectlPolicyPodSecurityBaseline.test.ts +322 -0
  480. package/Tests/Utils/AiRemediation/KubectlPolicySafeWriteScope.test.ts +1026 -0
  481. package/Tests/Utils/AiRemediation/KubectlWaitBudget.test.ts +267 -0
  482. package/Tests/Utils/AiRemediation/KubectlWriteScope.test.ts +390 -0
  483. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +777 -0
  484. package/Tests/Utils/AiRemediation/KubectlWriteScopeClusterScoped.test.ts +613 -0
  485. package/Tests/Utils/AiRemediation/KubectlWriteScopeEntryPoint.test.ts +576 -0
  486. package/Tests/Utils/AiRemediation/KubectlWriteScopeKubectlSpellings.test.ts +752 -0
  487. package/Tests/Utils/AiRemediation/KubectlWriteScopeObjectWords.test.ts +358 -0
  488. package/Tests/Utils/AiRemediation/KubectlWriteScopeOverrides.test.ts +293 -0
  489. package/Tests/Utils/AiRemediation/KubectlWriteScopeParityCases.ts +598 -0
  490. package/Tests/Utils/AiRemediation/KubectlWriteScopePolicyParity.test.ts +403 -0
  491. package/Tests/Utils/Dashboard/DashboardClockComponent.test.ts +154 -5
  492. package/Tests/Utils/Monitor/IncomingEmailMonitorAddress.test.ts +478 -0
  493. package/Tests/Utils/Monitor/MonitorCheckScheduleUtil.test.ts +646 -0
  494. package/Tests/Utils/Monitor/MonitorOverviewCriteriaUtil.test.ts +292 -0
  495. package/Tests/Utils/Monitor/MonitorOverviewFamily.test.ts +274 -0
  496. package/Tests/Utils/Monitor/MonitorOverviewPresentationUtil.test.ts +3100 -0
  497. package/Tests/Utils/Monitor/MonitorOverviewProbeUtil.test.ts +1412 -0
  498. package/Tests/Utils/Monitor/MonitorOverviewTargetUtil.test.ts +374 -0
  499. package/Tests/Utils/Monitor/MonitorStatusHistoryUtil.test.ts +332 -0
  500. package/Tests/Utils/Monitor/MonitorUptimeSummaryUtil.test.ts +946 -0
  501. package/Tests/Utils/Rum/SessionIdentity.test.ts +94 -0
  502. package/Tests/Utils/Rum/SessionReplayWafCompatibility.test.ts +840 -0
  503. package/Tests/Utils/Rum/SessionReplayWireEncoding.test.ts +208 -0
  504. package/Tests/Utils/Rum/UserFlow.test.ts +1010 -0
  505. package/Tests/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.test.ts +217 -0
  506. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +1272 -1
  507. package/Tests/Utils/StatusPage/UptimeDailyAggregateTotals.test.ts +298 -0
  508. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +649 -1
  509. package/Tests/Utils/StatusPage/UptimeDailyAggregateWindow.test.ts +314 -0
  510. package/Tests/Utils/Telemetry/AttributesJSON.test.ts +454 -0
  511. package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +418 -2
  512. package/Tests/Utils/Uptime/DayUptimeGraphUtilMidnightDst.test.ts +160 -0
  513. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Auckland.test.ts +45 -0
  514. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Kolkata.test.ts +40 -0
  515. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.London.test.ts +147 -0
  516. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.NewYork.test.ts +48 -0
  517. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Tokyo.test.ts +39 -0
  518. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezoneCases.ts +1015 -0
  519. package/Tests/Utils/Uptime/UptimeUtil.test.ts +75 -0
  520. package/Tests/Utils/Uptime/UptimeUtilChronologicalOrder.test.ts +1279 -0
  521. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +203 -5
  522. package/Types/Date.ts +121 -7
  523. package/Types/Email/EmailMessage.ts +8 -0
  524. package/Types/Email/EmailTemplateType.ts +1 -0
  525. package/Types/Exception/ExceptionMessages.ts +1 -0
  526. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +488 -0
  527. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +54 -0
  528. package/Types/Kubernetes/KubernetesClusterAiAccessToolNames.ts +17 -0
  529. package/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.ts +9 -0
  530. package/Types/Monitor/DatabaseMonitor/SqlServerPlatform.ts +117 -0
  531. package/Types/Monitor/KubernetesAlertTemplates.ts +6 -5
  532. package/Types/Monitor/MonitorStepExceptionMonitor.ts +56 -0
  533. package/Types/Monitor/MonitorUptimeSummary.ts +113 -0
  534. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +11 -0
  535. package/Types/Rum/SessionReplay.ts +55 -2
  536. package/Types/Rum/SessionReplayApi.ts +44 -1
  537. package/Types/Rum/UserFlow.ts +187 -0
  538. package/Types/Runbook/RunbookStepType.ts +10 -0
  539. package/Types/Runbook/RunnerJobOrigin.ts +15 -0
  540. package/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.ts +6 -2
  541. package/Types/StatusPage/MergedDowntimeTotals.ts +17 -0
  542. package/Types/StatusPage/PublicNoteSubscriberNotificationDefault.ts +148 -0
  543. package/Types/StatusPage/UptimeDailyAggregate.ts +15 -0
  544. package/Types/Telemetry/TelemetryIngestSurface.ts +9 -0
  545. package/Types/Timezone.ts +5 -0
  546. package/Types/TimezoneAlias.ts +385 -0
  547. package/Types/Workflow/Templates.ts +3041 -2
  548. package/Types/Workflow/WorkflowVariableOAuth.ts +476 -0
  549. package/Types/Workspace/WorkspaceNoteReaction.ts +132 -0
  550. package/Typings/Index.d.ts +24 -0
  551. package/UI/Components/AttributesJSON/AttributesJSONPreferences.ts +175 -0
  552. package/UI/Components/AttributesJSON/AttributesJSONView.tsx +138 -0
  553. package/UI/Components/AttributesJSON/AttributesViewToggle.tsx +79 -0
  554. package/UI/Components/AttributesJSON/BracesGlyph.tsx +30 -0
  555. package/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.tsx +491 -0
  556. package/UI/Components/AttributesJSON/JSONHighlight.ts +77 -0
  557. package/UI/Components/Calendar/Calendar.css +83 -0
  558. package/UI/Components/Calendar/Calendar.tsx +136 -7
  559. package/UI/Components/Calendar/CalendarDayLayout.ts +236 -0
  560. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +23 -1
  561. package/UI/Components/Charts/Utils/HistogramSelection.ts +117 -0
  562. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +269 -0
  563. package/UI/Components/Charts/Utils/useHistogramZoom.ts +26 -2
  564. package/UI/Components/Date/CustomTimeRangeModal.tsx +26 -3
  565. package/UI/Components/Dropdown/Dropdown.tsx +58 -9
  566. package/UI/Components/Forms/Fields/FormField.tsx +49 -14
  567. package/UI/Components/Graphs/DayUptimeGraph.tsx +166 -25
  568. package/UI/Components/Graphs/UptimeBarTooltip.tsx +10 -2
  569. package/UI/Components/Graphs/UptimeDaySummary.tsx +8 -0
  570. package/UI/Components/LogsViewer/LogsViewer.tsx +52 -5
  571. package/UI/Components/LogsViewer/components/ColumnSelector.tsx +13 -2
  572. package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +13 -1
  573. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +91 -76
  574. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +15 -1
  575. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +28 -106
  576. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +54 -6
  577. package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +8 -2
  578. package/UI/Components/LogsViewer/useLogsHistogram.ts +39 -6
  579. package/UI/Components/ModelTable/BaseModelTable.tsx +66 -14
  580. package/UI/Components/MonitorGraphs/Uptime.tsx +12 -0
  581. package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +10 -2
  582. package/UI/Components/Page/ModelPage.tsx +59 -3
  583. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +69 -5
  584. package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +12 -1
  585. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +15 -1
  586. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +36 -104
  587. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +9 -1
  588. package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +19 -1
  589. package/UI/Utils/Timezone.ts +69 -28
  590. package/Utils/AI/InvestigationReport.ts +195 -25
  591. package/Utils/AiRemediation/KubectlOutputRedactor.ts +1724 -0
  592. package/Utils/AiRemediation/KubectlPolicy.ts +4136 -0
  593. package/Utils/AiRemediation/KubectlWaitBudget.ts +156 -0
  594. package/Utils/AiRemediation/KubectlWriteScope.ts +1964 -0
  595. package/Utils/Dashboard/Components/DashboardClockComponent.ts +7 -6
  596. package/Utils/Monitor/IncomingEmailMonitorAddress.ts +292 -0
  597. package/Utils/Monitor/MonitorCheckScheduleUtil.ts +654 -0
  598. package/Utils/Monitor/MonitorOverviewCriteriaUtil.ts +188 -0
  599. package/Utils/Monitor/MonitorOverviewFamily.ts +263 -0
  600. package/Utils/Monitor/MonitorOverviewPresentationUtil.ts +1958 -0
  601. package/Utils/Monitor/MonitorOverviewProbeUtil.ts +979 -0
  602. package/Utils/Monitor/MonitorOverviewTargetUtil.ts +299 -0
  603. package/Utils/Monitor/MonitorStatusHistoryUtil.ts +201 -0
  604. package/Utils/Monitor/MonitorUptimeSummaryUtil.ts +638 -0
  605. package/Utils/Rum/SessionIdentity.ts +44 -0
  606. package/Utils/Rum/SessionReplayWireEncoding.ts +137 -0
  607. package/Utils/Rum/UserFlow.ts +1527 -0
  608. package/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.ts +107 -0
  609. package/Utils/StatusPage/ResourceUptime.ts +99 -0
  610. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +197 -0
  611. package/Utils/Telemetry/AttributesJSON.ts +367 -0
  612. package/Utils/Uptime/DayUptimeGraphUtil.ts +165 -0
  613. package/Utils/Uptime/UptimeUtil.ts +122 -38
  614. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +8 -1
  615. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
  616. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  617. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  618. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +8 -1
  619. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
  620. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +10 -3
  621. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  622. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +10 -3
  623. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  624. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +434 -0
  625. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  626. package/build/dist/Models/DatabaseModels/Monitor.js +49 -0
  627. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  628. package/build/dist/Models/DatabaseModels/Runner.js +4 -4
  629. package/build/dist/Models/DatabaseModels/Runner.js.map +1 -1
  630. package/build/dist/Models/DatabaseModels/RunnerJob.js +66 -0
  631. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  632. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +11 -3
  633. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  634. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +487 -3
  635. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  636. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js +64 -16
  637. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  638. package/build/dist/Server/API/AIInvestigationAPI.js +205 -0
  639. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  640. package/build/dist/Server/API/AutoRemediationAPI.js +115 -2
  641. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  642. package/build/dist/Server/API/CommonAPI.js +49 -0
  643. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  644. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +564 -0
  645. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -0
  646. package/build/dist/Server/API/MicrosoftTeamsAPI.js +465 -176
  647. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  648. package/build/dist/Server/API/MonitorAPI.js +107 -1
  649. package/build/dist/Server/API/MonitorAPI.js.map +1 -1
  650. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +5 -16
  651. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -1
  652. package/build/dist/Server/API/SlackAPI.js +322 -78
  653. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  654. package/build/dist/Server/API/StatusPageAPI.js +123 -17
  655. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  656. package/build/dist/Server/API/TelemetryAPI.js +144 -5
  657. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  658. package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js +1 -0
  659. package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js.map +1 -1
  660. package/build/dist/Server/API/WorkflowVariableAPI.js +109 -0
  661. package/build/dist/Server/API/WorkflowVariableAPI.js.map +1 -0
  662. package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
  663. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  664. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.js +38 -0
  665. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.js.map +1 -0
  666. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.js +54 -0
  667. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.js.map +1 -0
  668. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.js +14 -0
  669. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.js.map +1 -0
  670. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.js +12 -0
  671. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.js.map +1 -0
  672. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.js +32 -0
  673. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.js.map +1 -0
  674. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  675. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  676. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +21 -0
  677. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  678. package/build/dist/Server/Middleware/TelemetryIngest.js +32 -2
  679. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  680. package/build/dist/Server/Middleware/UserAuthorization.js +39 -6
  681. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  682. package/build/dist/Server/Services/AIAgentService.js +1 -0
  683. package/build/dist/Server/Services/AIAgentService.js.map +1 -1
  684. package/build/dist/Server/Services/AccessTokenService.js +87 -42
  685. package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
  686. package/build/dist/Server/Services/AlertEpisodeMemberService.js +4 -0
  687. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  688. package/build/dist/Server/Services/AlertEpisodeService.js +12 -1
  689. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  690. package/build/dist/Server/Services/AlertService.js +110 -23
  691. package/build/dist/Server/Services/AlertService.js.map +1 -1
  692. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +716 -30
  693. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  694. package/build/dist/Server/Services/AutoRemediationRuleService.js +100 -0
  695. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  696. package/build/dist/Server/Services/BillingService.js +1 -0
  697. package/build/dist/Server/Services/BillingService.js.map +1 -1
  698. package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js +11 -16
  699. package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js.map +1 -1
  700. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +4 -0
  701. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  702. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +48 -0
  703. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  704. package/build/dist/Server/Services/IncidentEpisodeRoleMemberService.js +21 -0
  705. package/build/dist/Server/Services/IncidentEpisodeRoleMemberService.js.map +1 -1
  706. package/build/dist/Server/Services/IncidentEpisodeService.js +12 -1
  707. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  708. package/build/dist/Server/Services/IncidentGroupingEngineService.js +8 -0
  709. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  710. package/build/dist/Server/Services/IncidentMemberService.js +14 -0
  711. package/build/dist/Server/Services/IncidentMemberService.js.map +1 -1
  712. package/build/dist/Server/Services/IncidentPublicNoteService.js +48 -0
  713. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  714. package/build/dist/Server/Services/IncidentService.js +99 -21
  715. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  716. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  717. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  718. package/build/dist/Server/Services/IncidentTemplateService.js +68 -4
  719. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  720. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +1470 -0
  721. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -0
  722. package/build/dist/Server/Services/KubernetesClusterService.js +1097 -0
  723. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  724. package/build/dist/Server/Services/LogAggregationService.js +14 -6
  725. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  726. package/build/dist/Server/Services/MonitorService.js +487 -48
  727. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  728. package/build/dist/Server/Services/MonitorStatusTimelineService.js +540 -3
  729. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  730. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -0
  731. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  732. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -0
  733. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  734. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -0
  735. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  736. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +3 -0
  737. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  738. package/build/dist/Server/Services/OnCallNotificationAlertingService.js +29 -7
  739. package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -1
  740. package/build/dist/Server/Services/OnCallSetupReminderService.js +1 -0
  741. package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -1
  742. package/build/dist/Server/Services/ProbeService.js +7 -1
  743. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  744. package/build/dist/Server/Services/ProjectService.js +11 -1
  745. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  746. package/build/dist/Server/Services/RunbookCredentialService.js +24 -0
  747. package/build/dist/Server/Services/RunbookCredentialService.js.map +1 -1
  748. package/build/dist/Server/Services/RunbookSecretService.js +44 -0
  749. package/build/dist/Server/Services/RunbookSecretService.js.map +1 -1
  750. package/build/dist/Server/Services/RunnerJobService.js +641 -21
  751. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  752. package/build/dist/Server/Services/RunnerService.js +344 -5
  753. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  754. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +48 -0
  755. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  756. package/build/dist/Server/Services/ScheduledMaintenanceService.js +102 -20
  757. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  758. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +67 -4
  759. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  760. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +12 -1
  761. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  762. package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -0
  763. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  764. package/build/dist/Server/Services/StatusPageService.js +230 -32
  765. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  766. package/build/dist/Server/Services/StatusPageSubscriberService.js +4 -0
  767. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  768. package/build/dist/Server/Services/TeamMemberService.js +270 -11
  769. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  770. package/build/dist/Server/Services/TraceAggregationService.js +10 -6
  771. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  772. package/build/dist/Server/Services/UserEmailService.js +1 -0
  773. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  774. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1 -0
  775. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  776. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +1 -0
  777. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  778. package/build/dist/Server/Services/UserNotificationRuleService.js +4 -0
  779. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  780. package/build/dist/Server/Services/UserService.js +107 -1
  781. package/build/dist/Server/Services/UserService.js.map +1 -1
  782. package/build/dist/Server/Services/WorkflowVariableService.js +396 -7
  783. package/build/dist/Server/Services/WorkflowVariableService.js.map +1 -1
  784. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +463 -7
  785. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  786. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +144 -2
  787. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  788. package/build/dist/Server/Types/Database/QueryHelper.js +20 -0
  789. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  790. package/build/dist/Server/Types/Markdown.js +394 -27
  791. package/build/dist/Server/Types/Markdown.js.map +1 -1
  792. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +123 -0
  793. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -0
  794. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +344 -0
  795. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -0
  796. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +587 -0
  797. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -0
  798. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js +18 -2
  799. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js.map +1 -1
  800. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1380 -239
  801. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  802. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +1099 -68
  803. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  804. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +88 -6
  805. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  806. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +45 -11
  807. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  808. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +41 -11
  809. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  810. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +11 -4
  811. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
  812. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +4 -2
  813. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  814. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +997 -141
  815. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  816. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +299 -4
  817. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  818. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +120 -0
  819. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -0
  820. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +160 -1
  821. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  822. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +1 -0
  823. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  824. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +59 -12
  825. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  826. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +5 -1
  827. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  828. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +84 -22
  829. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  830. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +70 -5
  831. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -1
  832. package/build/dist/Server/Utils/Monitor/ProbeMonitorsNotification.js +249 -0
  833. package/build/dist/Server/Utils/Monitor/ProbeMonitorsNotification.js.map +1 -0
  834. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1 -0
  835. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -1
  836. package/build/dist/Server/Utils/PushNotificationUtil.js +27 -0
  837. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  838. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js +26 -3
  839. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js.map +1 -1
  840. package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js +38 -3
  841. package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js.map +1 -1
  842. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +143 -23
  843. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  844. package/build/dist/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.js +406 -0
  845. package/build/dist/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.js.map +1 -0
  846. package/build/dist/Server/Utils/StartServer.js +4 -3
  847. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  848. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +382 -0
  849. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -0
  850. package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js +1 -0
  851. package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js.map +1 -1
  852. package/build/dist/Server/Utils/UserPermission/UserPermission.js +10 -0
  853. package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
  854. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +447 -0
  855. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -0
  856. package/build/dist/Server/Utils/Workflow/WorkflowVariableOAuthToken.js +202 -0
  857. package/build/dist/Server/Utils/Workflow/WorkflowVariableOAuthToken.js.map +1 -0
  858. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +39 -1
  859. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  860. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +44 -4
  861. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  862. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +150 -191
  863. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  864. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +44 -4
  865. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  866. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.js +39 -17
  867. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.js.map +1 -1
  868. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +151 -145
  869. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  870. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +221 -127
  871. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  872. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.js +91 -0
  873. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.js.map +1 -0
  874. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.js +673 -0
  875. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.js.map +1 -0
  876. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +3 -9
  877. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -1
  878. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +65 -166
  879. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
  880. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +63 -157
  881. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -1
  882. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +23 -3
  883. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
  884. package/build/dist/Server/Utils/Workspace/Slack/Actions/Authorization.js +92 -0
  885. package/build/dist/Server/Utils/Workspace/Slack/Actions/Authorization.js.map +1 -0
  886. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +81 -210
  887. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
  888. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +68 -138
  889. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -1
  890. package/build/dist/Server/Utils/Workspace/Slack/Actions/ReactionNote.js +240 -0
  891. package/build/dist/Server/Utils/Workspace/Slack/Actions/ReactionNote.js.map +1 -0
  892. package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js +85 -212
  893. package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js.map +1 -1
  894. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +60 -17
  895. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  896. package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +2 -0
  897. package/build/dist/Server/Utils/Workspace/Workspace.js +134 -41
  898. package/build/dist/Server/Utils/Workspace/Workspace.js.map +1 -1
  899. package/build/dist/Server/Utils/Workspace/WorkspaceActionAuthorization.js +197 -0
  900. package/build/dist/Server/Utils/Workspace/WorkspaceActionAuthorization.js.map +1 -0
  901. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js +32 -0
  902. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
  903. package/build/dist/Server/Utils/Workspace/WorkspaceOAuthState.js +199 -0
  904. package/build/dist/Server/Utils/Workspace/WorkspaceOAuthState.js.map +1 -0
  905. package/build/dist/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.js +91 -0
  906. package/build/dist/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.js.map +1 -0
  907. package/build/dist/Server/Utils/Workspace/WorkspaceReactionNote.js +450 -0
  908. package/build/dist/Server/Utils/Workspace/WorkspaceReactionNote.js.map +1 -0
  909. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +103 -5
  910. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  911. package/build/dist/Types/Date.js +97 -7
  912. package/build/dist/Types/Date.js.map +1 -1
  913. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  914. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  915. package/build/dist/Types/Exception/ExceptionMessages.js +1 -0
  916. package/build/dist/Types/Exception/ExceptionMessages.js.map +1 -1
  917. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +245 -0
  918. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -0
  919. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +49 -0
  920. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -0
  921. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessToolNames.js +16 -0
  922. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessToolNames.js.map +1 -0
  923. package/build/dist/Types/Monitor/DatabaseMonitor/SqlServerPlatform.js +104 -0
  924. package/build/dist/Types/Monitor/DatabaseMonitor/SqlServerPlatform.js.map +1 -0
  925. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +6 -5
  926. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  927. package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js +32 -0
  928. package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js.map +1 -1
  929. package/build/dist/Types/Monitor/MonitorUptimeSummary.js +52 -0
  930. package/build/dist/Types/Monitor/MonitorUptimeSummary.js.map +1 -0
  931. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +9 -1
  932. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  933. package/build/dist/Types/Rum/SessionReplay.js +52 -2
  934. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  935. package/build/dist/Types/Rum/SessionReplayApi.js +11 -0
  936. package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
  937. package/build/dist/Types/Rum/UserFlow.js +115 -0
  938. package/build/dist/Types/Rum/UserFlow.js.map +1 -0
  939. package/build/dist/Types/Runbook/RunbookStepType.js +10 -0
  940. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  941. package/build/dist/Types/Runbook/RunnerJobOrigin.js +14 -0
  942. package/build/dist/Types/Runbook/RunnerJobOrigin.js.map +1 -1
  943. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js +2 -2
  944. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js.map +1 -1
  945. package/build/dist/Types/StatusPage/MergedDowntimeTotals.js +2 -0
  946. package/build/dist/Types/StatusPage/MergedDowntimeTotals.js.map +1 -0
  947. package/build/dist/Types/StatusPage/PublicNoteSubscriberNotificationDefault.js +65 -0
  948. package/build/dist/Types/StatusPage/PublicNoteSubscriberNotificationDefault.js.map +1 -0
  949. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +8 -0
  950. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  951. package/build/dist/Types/Timezone.js +5 -0
  952. package/build/dist/Types/Timezone.js.map +1 -1
  953. package/build/dist/Types/TimezoneAlias.js +340 -0
  954. package/build/dist/Types/TimezoneAlias.js.map +1 -0
  955. package/build/dist/Types/Workflow/Templates.js +2694 -2
  956. package/build/dist/Types/Workflow/Templates.js.map +1 -1
  957. package/build/dist/Types/Workflow/WorkflowVariableOAuth.js +314 -0
  958. package/build/dist/Types/Workflow/WorkflowVariableOAuth.js.map +1 -0
  959. package/build/dist/Types/Workspace/WorkspaceNoteReaction.js +98 -0
  960. package/build/dist/Types/Workspace/WorkspaceNoteReaction.js.map +1 -0
  961. package/build/dist/UI/Components/AttributesJSON/AttributesJSONPreferences.js +95 -0
  962. package/build/dist/UI/Components/AttributesJSON/AttributesJSONPreferences.js.map +1 -0
  963. package/build/dist/UI/Components/AttributesJSON/AttributesJSONView.js +44 -0
  964. package/build/dist/UI/Components/AttributesJSON/AttributesJSONView.js.map +1 -0
  965. package/build/dist/UI/Components/AttributesJSON/AttributesViewToggle.js +27 -0
  966. package/build/dist/UI/Components/AttributesJSON/AttributesViewToggle.js.map +1 -0
  967. package/build/dist/UI/Components/AttributesJSON/BracesGlyph.js +9 -0
  968. package/build/dist/UI/Components/AttributesJSON/BracesGlyph.js.map +1 -0
  969. package/build/dist/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.js +260 -0
  970. package/build/dist/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.js.map +1 -0
  971. package/build/dist/UI/Components/AttributesJSON/JSONHighlight.js +56 -0
  972. package/build/dist/UI/Components/AttributesJSON/JSONHighlight.js.map +1 -0
  973. package/build/dist/UI/Components/Calendar/Calendar.js +94 -7
  974. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  975. package/build/dist/UI/Components/Calendar/CalendarDayLayout.js +136 -0
  976. package/build/dist/UI/Components/Calendar/CalendarDayLayout.js.map +1 -0
  977. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +1 -1
  978. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  979. package/build/dist/UI/Components/Charts/Utils/HistogramSelection.js +72 -0
  980. package/build/dist/UI/Components/Charts/Utils/HistogramSelection.js.map +1 -0
  981. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +178 -0
  982. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -0
  983. package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js +13 -1
  984. package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js.map +1 -1
  985. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +20 -4
  986. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -1
  987. package/build/dist/UI/Components/Dropdown/Dropdown.js +33 -9
  988. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  989. package/build/dist/UI/Components/Forms/Fields/FormField.js +36 -13
  990. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  991. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +117 -11
  992. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  993. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -1
  994. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
  995. package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -1
  996. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +36 -8
  997. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  998. package/build/dist/UI/Components/LogsViewer/components/ColumnSelector.js +10 -2
  999. package/build/dist/UI/Components/LogsViewer/components/ColumnSelector.js.map +1 -1
  1000. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +9 -1
  1001. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
  1002. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +16 -15
  1003. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  1004. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
  1005. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  1006. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +14 -74
  1007. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  1008. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +22 -5
  1009. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  1010. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +9 -2
  1011. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
  1012. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +16 -5
  1013. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -1
  1014. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +23 -8
  1015. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  1016. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +7 -1
  1017. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  1018. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +5 -1
  1019. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
  1020. package/build/dist/UI/Components/Page/ModelPage.js +37 -3
  1021. package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
  1022. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +27 -6
  1023. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1024. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +7 -1
  1025. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -1
  1026. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
  1027. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  1028. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +17 -77
  1029. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  1030. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +10 -1
  1031. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
  1032. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +15 -1
  1033. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -1
  1034. package/build/dist/UI/Utils/Timezone.js +52 -17
  1035. package/build/dist/UI/Utils/Timezone.js.map +1 -1
  1036. package/build/dist/Utils/AI/InvestigationReport.js +116 -21
  1037. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  1038. package/build/dist/Utils/AiRemediation/KubectlOutputRedactor.js +1209 -0
  1039. package/build/dist/Utils/AiRemediation/KubectlOutputRedactor.js.map +1 -0
  1040. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +2973 -0
  1041. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -0
  1042. package/build/dist/Utils/AiRemediation/KubectlWaitBudget.js +99 -0
  1043. package/build/dist/Utils/AiRemediation/KubectlWaitBudget.js.map +1 -0
  1044. package/build/dist/Utils/AiRemediation/KubectlWriteScope.js +1429 -0
  1045. package/build/dist/Utils/AiRemediation/KubectlWriteScope.js.map +1 -0
  1046. package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js +7 -6
  1047. package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js.map +1 -1
  1048. package/build/dist/Utils/Monitor/IncomingEmailMonitorAddress.js +214 -0
  1049. package/build/dist/Utils/Monitor/IncomingEmailMonitorAddress.js.map +1 -0
  1050. package/build/dist/Utils/Monitor/MonitorCheckScheduleUtil.js +471 -0
  1051. package/build/dist/Utils/Monitor/MonitorCheckScheduleUtil.js.map +1 -0
  1052. package/build/dist/Utils/Monitor/MonitorOverviewCriteriaUtil.js +107 -0
  1053. package/build/dist/Utils/Monitor/MonitorOverviewCriteriaUtil.js.map +1 -0
  1054. package/build/dist/Utils/Monitor/MonitorOverviewFamily.js +191 -0
  1055. package/build/dist/Utils/Monitor/MonitorOverviewFamily.js.map +1 -0
  1056. package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js +1259 -0
  1057. package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js.map +1 -0
  1058. package/build/dist/Utils/Monitor/MonitorOverviewProbeUtil.js +593 -0
  1059. package/build/dist/Utils/Monitor/MonitorOverviewProbeUtil.js.map +1 -0
  1060. package/build/dist/Utils/Monitor/MonitorOverviewTargetUtil.js +195 -0
  1061. package/build/dist/Utils/Monitor/MonitorOverviewTargetUtil.js.map +1 -0
  1062. package/build/dist/Utils/Monitor/MonitorStatusHistoryUtil.js +127 -0
  1063. package/build/dist/Utils/Monitor/MonitorStatusHistoryUtil.js.map +1 -0
  1064. package/build/dist/Utils/Monitor/MonitorUptimeSummaryUtil.js +464 -0
  1065. package/build/dist/Utils/Monitor/MonitorUptimeSummaryUtil.js.map +1 -0
  1066. package/build/dist/Utils/Rum/SessionIdentity.js +31 -1
  1067. package/build/dist/Utils/Rum/SessionIdentity.js.map +1 -1
  1068. package/build/dist/Utils/Rum/SessionReplayWireEncoding.js +128 -0
  1069. package/build/dist/Utils/Rum/SessionReplayWireEncoding.js.map +1 -0
  1070. package/build/dist/Utils/Rum/UserFlow.js +941 -0
  1071. package/build/dist/Utils/Rum/UserFlow.js.map +1 -0
  1072. package/build/dist/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.js +82 -0
  1073. package/build/dist/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.js.map +1 -0
  1074. package/build/dist/Utils/StatusPage/ResourceUptime.js +46 -0
  1075. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  1076. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  1077. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -1
  1078. package/build/dist/Utils/Telemetry/AttributesJSON.js +245 -0
  1079. package/build/dist/Utils/Telemetry/AttributesJSON.js.map +1 -0
  1080. package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +103 -0
  1081. package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -1
  1082. package/build/dist/Utils/Uptime/UptimeUtil.js +91 -27
  1083. package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
  1084. package/jest.config.json +2 -1
  1085. package/package.json +2 -1
  1086. package/tsconfig.tests.json +36 -0
  1087. package/Tests/UI/Components/EmptyState/EmptyStateGuideLinks.test.tsx +0 -337
  1088. package/UI/Components/EmptyState/EmptyStateGuideLinks.tsx +0 -124
  1089. package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js +0 -47
  1090. package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js.map +0 -1
@@ -0,0 +1,3100 @@
1
+ import MonitorProbe, {
2
+ MonitorStepProbeResponse,
3
+ } from "../../../Models/DatabaseModels/MonitorProbe";
4
+ import Probe, {
5
+ ProbeConnectionStatus,
6
+ } from "../../../Models/DatabaseModels/Probe";
7
+ import OneUptimeDate from "../../../Types/Date";
8
+ import { JSONObject } from "../../../Types/JSON";
9
+ import { CheckOn, FilterType } from "../../../Types/Monitor/CriteriaFilter";
10
+ import MonitorMetricType from "../../../Types/Monitor/MonitorMetricType";
11
+ import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
12
+ import MonitorType from "../../../Types/Monitor/MonitorType";
13
+ import ObjectID from "../../../Types/ObjectID";
14
+ import Timezone from "../../../Types/Timezone";
15
+ import CronTab from "../../../Utils/CronTab";
16
+ import { MonitorCheckFreshness } from "../../../Utils/Monitor/MonitorCheckScheduleUtil";
17
+ import MonitorOverviewFamilyUtil, {
18
+ MonitorOverviewFamily,
19
+ MonitorOverviewSetupKind,
20
+ } from "../../../Utils/Monitor/MonitorOverviewFamily";
21
+ import MonitorOverviewPresentationUtil, {
22
+ MONITOR_LOG_MINIMUM_RETENTION_SECONDS,
23
+ MonitorOverviewFact,
24
+ MonitorOverviewPresentation,
25
+ MonitorOverviewPresentationInput,
26
+ MonitorOverviewRunState,
27
+ MonitorOverviewStatusRef,
28
+ } from "../../../Utils/Monitor/MonitorOverviewPresentationUtil";
29
+ import MonitorOverviewProbeUtil, {
30
+ MonitorOverviewProbeHealth,
31
+ MonitorOverviewProbeRow,
32
+ MonitorOverviewProbeSummary,
33
+ } from "../../../Utils/Monitor/MonitorOverviewProbeUtil";
34
+ import { afterEach, describe, expect, it, jest } from "@jest/globals";
35
+ import type { MockInstance } from "jest-mock";
36
+
37
+ /*
38
+ * The hero, facts, pulse and section switches of the monitor overview for
39
+ * every family and run state. The invariants at the top are the reason the
40
+ * page was redesigned: a monitor that is paused, not being checked, not set
41
+ * up or still waiting for data must never look healthy, and an unknown must
42
+ * never be presented as a zero or a "none".
43
+ */
44
+
45
+ const NOW: Date = new Date("2026-09-21T12:00:00.000Z");
46
+ const STEP_ID: string = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
47
+ const DEVICE_ID: string = "66666666-6666-4666-8666-666666666666";
48
+ const OTHER_DEVICE_ID: string = "77777777-7777-4777-8777-777777777777";
49
+
50
+ const secondsAgo: (seconds: number) => Date = (seconds: number): Date => {
51
+ return new Date(NOW.getTime() - seconds * 1000);
52
+ };
53
+
54
+ const DAY: number = 86400;
55
+
56
+ const OPERATIONAL: MonitorOverviewStatusRef = {
57
+ id: "22222222-2222-4222-8222-222222222222",
58
+ name: "Operational",
59
+ color: "#10B981",
60
+ isOperationalState: true,
61
+ isOfflineState: false,
62
+ };
63
+
64
+ const DEGRADED: MonitorOverviewStatusRef = {
65
+ id: "33333333-3333-4333-8333-333333333333",
66
+ name: "Degraded",
67
+ color: "#F59E0B",
68
+ isOperationalState: false,
69
+ isOfflineState: false,
70
+ };
71
+
72
+ const OFFLINE: MonitorOverviewStatusRef = {
73
+ id: "44444444-4444-4444-8444-444444444444",
74
+ name: "Offline",
75
+ color: "#EF4444",
76
+ isOperationalState: false,
77
+ isOfflineState: true,
78
+ };
79
+
80
+ const ALL_TYPES: Array<MonitorType> = Object.values(MonitorType);
81
+
82
+ const stepsOf: (...stepData: Array<JSONObject>) => MonitorSteps = (
83
+ ...stepData: Array<JSONObject>
84
+ ): MonitorSteps => {
85
+ return {
86
+ data: {
87
+ monitorStepsInstanceArray: stepData.map((data: JSONObject) => {
88
+ return { data: { id: STEP_ID, ...data } };
89
+ }),
90
+ },
91
+ } as unknown as MonitorSteps;
92
+ };
93
+
94
+ const NO_STEPS: MonitorSteps = stepsOf();
95
+
96
+ const missingWindowSteps: (
97
+ checkOn: CheckOn,
98
+ minutes: number,
99
+ ) => MonitorSteps = (checkOn: CheckOn, minutes: number): MonitorSteps => {
100
+ return stepsOf({
101
+ monitorCriteria: {
102
+ data: {
103
+ monitorCriteriaInstanceArray: [
104
+ {
105
+ data: {
106
+ filters: [
107
+ {
108
+ checkOn: checkOn,
109
+ filterType: FilterType.NotRecievedInMinutes,
110
+ value: minutes,
111
+ },
112
+ ],
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ },
118
+ });
119
+ };
120
+
121
+ const probes: (
122
+ overrides?: Partial<MonitorOverviewProbeSummary>,
123
+ ) => MonitorOverviewProbeSummary = (
124
+ overrides?: Partial<MonitorOverviewProbeSummary>,
125
+ ): MonitorOverviewProbeSummary => {
126
+ return {
127
+ rows: [],
128
+ attachedCount: 2,
129
+ enabledCount: 2,
130
+ reportingCount: 2,
131
+ disabledCount: 0,
132
+ disconnectedCount: 0,
133
+ lastResultAt: secondsAgo(60),
134
+ nextCheckAt: secondsAgo(-240),
135
+ latestResult: {
136
+ probeId: "11111111-1111-4111-8111-111111111111",
137
+ probeName: "London",
138
+ monitoredAt: secondsAgo(60),
139
+ isOnline: true,
140
+ responseTimeInMs: 120,
141
+ responseCode: 200,
142
+ },
143
+ responseTime: {
144
+ medianMs: 120,
145
+ minMs: 100,
146
+ maxMs: 140,
147
+ respondedCount: 2,
148
+ totalCount: 2,
149
+ },
150
+ ...overrides,
151
+ };
152
+ };
153
+
154
+ const NO_RESULTS: Partial<MonitorOverviewProbeSummary> = {
155
+ reportingCount: 0,
156
+ lastResultAt: undefined,
157
+ latestResult: undefined,
158
+ responseTime: null,
159
+ };
160
+
161
+ /*
162
+ * A healthy, running monitor of the given type: the starting point every
163
+ * test changes one thing about.
164
+ */
165
+ const running: (
166
+ monitorType: MonitorType,
167
+ overrides?: Partial<MonitorOverviewPresentationInput>,
168
+ ) => MonitorOverviewPresentationInput = (
169
+ monitorType: MonitorType,
170
+ overrides?: Partial<MonitorOverviewPresentationInput>,
171
+ ): MonitorOverviewPresentationInput => {
172
+ const family: MonitorOverviewFamily =
173
+ MonitorOverviewFamilyUtil.getFamily(monitorType);
174
+
175
+ const base: MonitorOverviewPresentationInput = {
176
+ now: NOW,
177
+ monitorType: monitorType,
178
+ monitorSteps: stepsOf({
179
+ monitorDestination: "https://api.example.com/health",
180
+ networkDeviceMonitor: { networkDeviceId: DEVICE_ID },
181
+ }),
182
+ monitoringInterval: "*/5 * * * *",
183
+ createdAt: secondsAgo(30 * DAY),
184
+ currentStatus: OPERATIONAL,
185
+ statusSince: secondsAgo(3 * DAY + 4 * 3600),
186
+ pause: {
187
+ isDisabled: false,
188
+ byManualIncident: false,
189
+ byScheduledMaintenance: false,
190
+ },
191
+ probeFlags: { isNoProbeEnabled: false, isAllProbesDisconnected: false },
192
+ probes: family === MonitorOverviewFamily.ProbeCheck ? probes() : null,
193
+ heartbeat: {
194
+ lastReceivedAt: secondsAgo(120),
195
+ lastCheckedAt: secondsAgo(30),
196
+ requestMethod: "POST",
197
+ },
198
+ email: { lastReceivedAt: secondsAgo(300), lastCheckedAt: secondsAgo(40) },
199
+ agent: {
200
+ lastReportAt: secondsAgo(45),
201
+ hostname: "web-01.example.com",
202
+ cpuPercent: 42.4,
203
+ memoryPercent: 63.6,
204
+ },
205
+ telemetry: {
206
+ lastScheduledAt: secondsAgo(30),
207
+ nextEvaluationAt: secondsAgo(-270),
208
+ },
209
+ latestEvaluationAt: secondsAgo(90),
210
+ evaluationStatus: "loaded",
211
+ };
212
+
213
+ return { ...base, ...overrides };
214
+ };
215
+
216
+ const build: (
217
+ input: MonitorOverviewPresentationInput,
218
+ ) => MonitorOverviewPresentation = (
219
+ input: MonitorOverviewPresentationInput,
220
+ ): MonitorOverviewPresentation => {
221
+ return MonitorOverviewPresentationUtil.build(input);
222
+ };
223
+
224
+ const factKeys: (presentation: MonitorOverviewPresentation) => Array<string> = (
225
+ presentation: MonitorOverviewPresentation,
226
+ ): Array<string> => {
227
+ return presentation.facts.map((fact: MonitorOverviewFact) => {
228
+ return fact.key;
229
+ });
230
+ };
231
+
232
+ const fact: (
233
+ presentation: MonitorOverviewPresentation,
234
+ key: string,
235
+ ) => MonitorOverviewFact = (
236
+ presentation: MonitorOverviewPresentation,
237
+ key: string,
238
+ ): MonitorOverviewFact => {
239
+ const found: MonitorOverviewFact | undefined = presentation.facts.find(
240
+ (item: MonitorOverviewFact) => {
241
+ return item.key === key;
242
+ },
243
+ );
244
+
245
+ if (!found) {
246
+ throw new Error(`no ${key} fact in ${factKeys(presentation).join(", ")}`);
247
+ }
248
+
249
+ return found;
250
+ };
251
+
252
+ interface Scenario {
253
+ name: string;
254
+ input: MonitorOverviewPresentationInput;
255
+ runState: MonitorOverviewRunState;
256
+ // Awaiting, but already judged by the server, so its history is shown.
257
+ hasPushVerdict?: boolean;
258
+ }
259
+
260
+ /*
261
+ * One monitor per interesting state, across the families. The invariant
262
+ * tests run over all of them.
263
+ */
264
+ const SCENARIOS: Array<Scenario> = [
265
+ {
266
+ name: "manual with a status",
267
+ input: running(MonitorType.Manual),
268
+ runState: MonitorOverviewRunState.Manual,
269
+ },
270
+ {
271
+ name: "API disabled",
272
+ input: running(MonitorType.API, {
273
+ pause: {
274
+ isDisabled: true,
275
+ byManualIncident: false,
276
+ byScheduledMaintenance: false,
277
+ },
278
+ }),
279
+ runState: MonitorOverviewRunState.Paused,
280
+ },
281
+ {
282
+ name: "Logs paused by an incident",
283
+ input: running(MonitorType.Logs, {
284
+ currentStatus: OFFLINE,
285
+ pause: {
286
+ isDisabled: false,
287
+ byManualIncident: true,
288
+ byScheduledMaintenance: false,
289
+ },
290
+ }),
291
+ runState: MonitorOverviewRunState.Paused,
292
+ },
293
+ {
294
+ name: "incoming request in maintenance",
295
+ input: running(MonitorType.IncomingRequest, {
296
+ pause: {
297
+ isDisabled: false,
298
+ byManualIncident: false,
299
+ byScheduledMaintenance: true,
300
+ },
301
+ }),
302
+ runState: MonitorOverviewRunState.Paused,
303
+ },
304
+ {
305
+ name: "API with every probe switched off",
306
+ input: running(MonitorType.API, {
307
+ probeFlags: { isNoProbeEnabled: true, isAllProbesDisconnected: false },
308
+ probes: probes({
309
+ ...NO_RESULTS,
310
+ attachedCount: 2,
311
+ enabledCount: 0,
312
+ disabledCount: 2,
313
+ }),
314
+ }),
315
+ runState: MonitorOverviewRunState.NotChecking,
316
+ },
317
+ {
318
+ name: "Website with every probe disconnected",
319
+ input: running(MonitorType.Website, {
320
+ currentStatus: OPERATIONAL,
321
+ probeFlags: { isNoProbeEnabled: false, isAllProbesDisconnected: true },
322
+ }),
323
+ runState: MonitorOverviewRunState.NotChecking,
324
+ },
325
+ {
326
+ name: "DNS with no criteria",
327
+ input: running(MonitorType.DNS, { monitorSteps: NO_STEPS }),
328
+ runState: MonitorOverviewRunState.NotConfigured,
329
+ },
330
+ {
331
+ name: "network device with no device",
332
+ input: running(MonitorType.NetworkDevice, {
333
+ monitorSteps: stepsOf({ networkDeviceMonitor: {} }),
334
+ }),
335
+ runState: MonitorOverviewRunState.NotConfigured,
336
+ },
337
+ {
338
+ name: "new API monitor",
339
+ input: running(MonitorType.API, {
340
+ createdAt: secondsAgo(120),
341
+ statusSince: undefined,
342
+ probes: probes(NO_RESULTS),
343
+ }),
344
+ runState: MonitorOverviewRunState.AwaitingFirstData,
345
+ },
346
+ {
347
+ name: "heartbeat that never received a request",
348
+ input: running(MonitorType.IncomingRequest, {
349
+ heartbeat: {},
350
+ }),
351
+ runState: MonitorOverviewRunState.AwaitingFirstData,
352
+ },
353
+ {
354
+ name: "inbound email that never received one",
355
+ input: running(MonitorType.IncomingEmail, { email: {} }),
356
+ runState: MonitorOverviewRunState.AwaitingFirstData,
357
+ },
358
+ {
359
+ name: "server whose agent never reported",
360
+ input: running(MonitorType.Server, { agent: {} }),
361
+ runState: MonitorOverviewRunState.AwaitingFirstData,
362
+ },
363
+ {
364
+ name: "kubernetes never evaluated",
365
+ input: running(MonitorType.Kubernetes, {
366
+ createdAt: secondsAgo(120),
367
+ telemetry: {},
368
+ latestEvaluationAt: undefined,
369
+ }),
370
+ runState: MonitorOverviewRunState.AwaitingFirstData,
371
+ },
372
+ {
373
+ name: "API with a stale result",
374
+ input: running(MonitorType.API, {
375
+ probes: probes({ lastResultAt: secondsAgo(2 * 3600) }),
376
+ }),
377
+ runState: MonitorOverviewRunState.Overdue,
378
+ },
379
+ {
380
+ name: "offline API with a stale result",
381
+ input: running(MonitorType.API, {
382
+ currentStatus: OFFLINE,
383
+ probes: probes({ lastResultAt: secondsAgo(2 * 3600) }),
384
+ }),
385
+ runState: MonitorOverviewRunState.Overdue,
386
+ },
387
+ {
388
+ /*
389
+ * The worker keeps stamping "last scheduled" every minute, but nothing
390
+ * has been evaluated for an hour: the queue is stuck, or every
391
+ * evaluation fails.
392
+ */
393
+ name: "metrics monitor with a stale evaluation",
394
+ input: running(MonitorType.Metrics, {
395
+ monitoringInterval: "* * * * *",
396
+ telemetry: {
397
+ lastScheduledAt: secondsAgo(10),
398
+ nextEvaluationAt: secondsAgo(-50),
399
+ },
400
+ latestEvaluationAt: secondsAgo(3600),
401
+ }),
402
+ runState: MonitorOverviewRunState.Overdue,
403
+ },
404
+ {
405
+ name: "running API",
406
+ input: running(MonitorType.API),
407
+ runState: MonitorOverviewRunState.Running,
408
+ },
409
+ {
410
+ name: "running heartbeat",
411
+ input: running(MonitorType.IncomingRequest),
412
+ runState: MonitorOverviewRunState.Running,
413
+ },
414
+ {
415
+ name: "running network device",
416
+ input: running(MonitorType.NetworkDevice),
417
+ runState: MonitorOverviewRunState.Running,
418
+ },
419
+ {
420
+ name: "running server with no status",
421
+ input: running(MonitorType.Server, { currentStatus: undefined }),
422
+ runState: MonitorOverviewRunState.Running,
423
+ },
424
+ {
425
+ name: "heartbeat that never received a request, judged offline",
426
+ input: running(MonitorType.IncomingRequest, {
427
+ currentStatus: OFFLINE,
428
+ heartbeat: {},
429
+ }),
430
+ runState: MonitorOverviewRunState.AwaitingFirstData,
431
+ hasPushVerdict: true,
432
+ },
433
+ {
434
+ name: "API that never reported, created two days ago",
435
+ input: running(MonitorType.API, {
436
+ createdAt: secondsAgo(2 * DAY),
437
+ statusSince: undefined,
438
+ probes: probes({ ...NO_RESULTS, nextCheckAt: undefined }),
439
+ }),
440
+ runState: MonitorOverviewRunState.Overdue,
441
+ },
442
+ {
443
+ name: "logs monitor with nothing in its evaluation log's last day",
444
+ input: running(MonitorType.Logs, {
445
+ createdAt: secondsAgo(10 * DAY),
446
+ latestEvaluationAt: undefined,
447
+ }),
448
+ runState: MonitorOverviewRunState.Overdue,
449
+ },
450
+ {
451
+ name: "new network device whose log is loaded and empty",
452
+ input: running(MonitorType.NetworkDevice, {
453
+ createdAt: secondsAgo(3600),
454
+ latestEvaluationAt: undefined,
455
+ }),
456
+ runState: MonitorOverviewRunState.AwaitingFirstData,
457
+ },
458
+ {
459
+ // Its rows expired: the log only covers the last day or two.
460
+ name: "network device with nothing in its evaluation log's last day",
461
+ input: running(MonitorType.NetworkDevice, {
462
+ currentStatus: OFFLINE,
463
+ latestEvaluationAt: undefined,
464
+ }),
465
+ runState: MonitorOverviewRunState.Running,
466
+ },
467
+ {
468
+ name: "weekly logs monitor whose last evaluation has left the log",
469
+ input: running(MonitorType.Logs, {
470
+ // Fridays at noon; now is Monday noon.
471
+ monitoringInterval: "0 12 * * 5",
472
+ createdAt: secondsAgo(90 * DAY),
473
+ telemetry: {
474
+ lastScheduledAt: secondsAgo(3 * DAY),
475
+ nextEvaluationAt: secondsAgo(-4 * DAY),
476
+ },
477
+ latestEvaluationAt: undefined,
478
+ }),
479
+ runState: MonitorOverviewRunState.Running,
480
+ },
481
+ ];
482
+
483
+ describe("MonitorOverviewPresentationUtil run states", () => {
484
+ it.each(SCENARIOS)("$name is $runState", (scenario: Scenario) => {
485
+ expect(MonitorOverviewPresentationUtil.getRunState(scenario.input)).toBe(
486
+ scenario.runState,
487
+ );
488
+ expect(build(scenario.input).runState).toBe(scenario.runState);
489
+ });
490
+
491
+ it("run-state precedence table", () => {
492
+ const rows: Array<
493
+ [string, MonitorOverviewPresentationInput, MonitorOverviewRunState]
494
+ > = [
495
+ [
496
+ "Manual beats disabled",
497
+ running(MonitorType.Manual, {
498
+ pause: {
499
+ isDisabled: true,
500
+ byManualIncident: true,
501
+ byScheduledMaintenance: true,
502
+ },
503
+ }),
504
+ MonitorOverviewRunState.Manual,
505
+ ],
506
+ [
507
+ "disabled beats not checking",
508
+ running(MonitorType.API, {
509
+ pause: {
510
+ isDisabled: true,
511
+ byManualIncident: false,
512
+ byScheduledMaintenance: false,
513
+ },
514
+ probeFlags: {
515
+ isNoProbeEnabled: true,
516
+ isAllProbesDisconnected: true,
517
+ },
518
+ }),
519
+ MonitorOverviewRunState.Paused,
520
+ ],
521
+ [
522
+ "paused with no probes",
523
+ running(MonitorType.API, {
524
+ pause: {
525
+ isDisabled: false,
526
+ byManualIncident: false,
527
+ byScheduledMaintenance: true,
528
+ },
529
+ probes: probes({
530
+ ...NO_RESULTS,
531
+ attachedCount: 0,
532
+ enabledCount: 0,
533
+ }),
534
+ }),
535
+ MonitorOverviewRunState.Paused,
536
+ ],
537
+ [
538
+ "not checking beats awaiting",
539
+ running(MonitorType.API, {
540
+ createdAt: secondsAgo(60),
541
+ probeFlags: {
542
+ isNoProbeEnabled: true,
543
+ isAllProbesDisconnected: false,
544
+ },
545
+ probes: probes(NO_RESULTS),
546
+ }),
547
+ MonitorOverviewRunState.NotChecking,
548
+ ],
549
+ [
550
+ "not checking beats not configured",
551
+ running(MonitorType.API, {
552
+ monitorSteps: NO_STEPS,
553
+ probeFlags: {
554
+ isNoProbeEnabled: false,
555
+ isAllProbesDisconnected: true,
556
+ },
557
+ }),
558
+ MonitorOverviewRunState.NotChecking,
559
+ ],
560
+ [
561
+ "not configured beats awaiting",
562
+ running(MonitorType.API, {
563
+ monitorSteps: NO_STEPS,
564
+ createdAt: secondsAgo(60),
565
+ probes: probes(NO_RESULTS),
566
+ }),
567
+ MonitorOverviewRunState.NotConfigured,
568
+ ],
569
+ [
570
+ "awaiting beats overdue while only the scheduler's stamp is known",
571
+ running(MonitorType.Logs, {
572
+ createdAt: secondsAgo(10 * DAY),
573
+ telemetry: { nextEvaluationAt: secondsAgo(DAY) },
574
+ latestEvaluationAt: undefined,
575
+ evaluationStatus: "error",
576
+ }),
577
+ MonitorOverviewRunState.AwaitingFirstData,
578
+ ],
579
+ [
580
+ "overdue beats running",
581
+ running(MonitorType.Ping, {
582
+ probes: probes({ lastResultAt: secondsAgo(3600) }),
583
+ }),
584
+ MonitorOverviewRunState.Overdue,
585
+ ],
586
+ [
587
+ "probe flags only matter for probe checks",
588
+ running(MonitorType.IncomingRequest, {
589
+ probeFlags: {
590
+ isNoProbeEnabled: true,
591
+ isAllProbesDisconnected: true,
592
+ },
593
+ }),
594
+ MonitorOverviewRunState.Running,
595
+ ],
596
+ [
597
+ "the probe rows say no probe is enabled before the flag does",
598
+ running(MonitorType.API, {
599
+ probes: probes({
600
+ ...NO_RESULTS,
601
+ enabledCount: 0,
602
+ disabledCount: 2,
603
+ }),
604
+ }),
605
+ MonitorOverviewRunState.NotChecking,
606
+ ],
607
+ [
608
+ "the probe rows say every enabled probe is disconnected",
609
+ running(MonitorType.API, {
610
+ probes: probes({ disconnectedCount: 2, reportingCount: 0 }),
611
+ }),
612
+ MonitorOverviewRunState.NotChecking,
613
+ ],
614
+ [
615
+ "unknown probes are never overdue or awaiting",
616
+ running(MonitorType.API, { probes: null }),
617
+ MonitorOverviewRunState.Running,
618
+ ],
619
+ [
620
+ "a network device is not awaiting while its log is unknown",
621
+ running(MonitorType.NetworkDevice, {
622
+ latestEvaluationAt: undefined,
623
+ evaluationStatus: undefined,
624
+ createdAt: secondsAgo(60),
625
+ }),
626
+ MonitorOverviewRunState.Running,
627
+ ],
628
+ [
629
+ "a network device is not awaiting while its log is unreadable",
630
+ running(MonitorType.NetworkDevice, {
631
+ latestEvaluationAt: undefined,
632
+ evaluationStatus: "forbidden",
633
+ }),
634
+ MonitorOverviewRunState.Running,
635
+ ],
636
+ ];
637
+
638
+ for (const [name, input, expected] of rows) {
639
+ expect({ name: name, runState: build(input).runState }).toEqual({
640
+ name: name,
641
+ runState: expected,
642
+ });
643
+ }
644
+ });
645
+ });
646
+
647
+ describe("MonitorOverviewPresentationUtil invariants", () => {
648
+ it.each(ALL_TYPES)(
649
+ "%s never produces more than 4 facts, minimal and full input",
650
+ (monitorType: MonitorType) => {
651
+ const minimal: MonitorOverviewPresentationInput = {
652
+ now: NOW,
653
+ monitorType: monitorType,
654
+ pause: {
655
+ isDisabled: false,
656
+ byManualIncident: false,
657
+ byScheduledMaintenance: false,
658
+ },
659
+ probeFlags: {
660
+ isNoProbeEnabled: false,
661
+ isAllProbesDisconnected: false,
662
+ },
663
+ probes: null,
664
+ heartbeat: {},
665
+ email: {},
666
+ agent: {},
667
+ telemetry: {},
668
+ };
669
+
670
+ for (const input of [minimal, running(monitorType)]) {
671
+ const presentation: MonitorOverviewPresentation = build(input);
672
+
673
+ expect(presentation.facts.length).toBeGreaterThanOrEqual(1);
674
+ expect(presentation.facts.length).toBeLessThanOrEqual(4);
675
+ expect(presentation.facts[presentation.facts.length - 1]!.key).toBe(
676
+ "owners",
677
+ );
678
+ expect(new Set(factKeys(presentation)).size).toBe(
679
+ presentation.facts.length,
680
+ );
681
+ }
682
+ },
683
+ );
684
+
685
+ it("states Paused/NotChecking/NotConfigured/AwaitingFirstData are never good and never show the status name as the primary badge", () => {
686
+ const guarded: Array<MonitorOverviewRunState> = [
687
+ MonitorOverviewRunState.Paused,
688
+ MonitorOverviewRunState.NotChecking,
689
+ MonitorOverviewRunState.NotConfigured,
690
+ MonitorOverviewRunState.AwaitingFirstData,
691
+ ];
692
+ let checked: number = 0;
693
+
694
+ for (const scenario of SCENARIOS) {
695
+ const presentation: MonitorOverviewPresentation = build(scenario.input);
696
+
697
+ if (!guarded.includes(presentation.runState)) {
698
+ continue;
699
+ }
700
+
701
+ checked++;
702
+ expect({ name: scenario.name, tone: presentation.tone }).not.toEqual({
703
+ name: scenario.name,
704
+ tone: "good",
705
+ });
706
+ expect(presentation.badge.tone).not.toBe("good");
707
+ expect(presentation.badge.text).not.toBe(
708
+ scenario.input.currentStatus?.name,
709
+ );
710
+ expect(presentation.badge.statusColor).toBeUndefined();
711
+ }
712
+
713
+ expect(checked).toBeGreaterThanOrEqual(10);
714
+ });
715
+
716
+ it("Overdue is never good", () => {
717
+ for (const status of [OPERATIONAL, DEGRADED, OFFLINE, undefined]) {
718
+ const presentation: MonitorOverviewPresentation = build(
719
+ running(MonitorType.API, {
720
+ currentStatus: status,
721
+ probes: probes({ lastResultAt: secondsAgo(2 * 3600) }),
722
+ }),
723
+ );
724
+
725
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Overdue);
726
+ expect(presentation.tone).not.toBe("good");
727
+ expect(presentation.secondaryBadges).toEqual([
728
+ { text: "Checks overdue", tone: "warning" },
729
+ ]);
730
+ }
731
+
732
+ expect(
733
+ build(
734
+ running(MonitorType.API, {
735
+ currentStatus: OPERATIONAL,
736
+ probes: probes({ lastResultAt: secondsAgo(2 * 3600) }),
737
+ }),
738
+ ).tone,
739
+ ).toBe("warning");
740
+ expect(
741
+ build(
742
+ running(MonitorType.API, {
743
+ currentStatus: OFFLINE,
744
+ probes: probes({ lastResultAt: secondsAgo(2 * 3600) }),
745
+ }),
746
+ ).tone,
747
+ ).toBe("danger");
748
+ });
749
+
750
+ it("showUptime is false only while awaiting first data that nothing has judged", () => {
751
+ for (const scenario of SCENARIOS) {
752
+ const presentation: MonitorOverviewPresentation = build(scenario.input);
753
+
754
+ expect({
755
+ name: scenario.name,
756
+ showUptime: presentation.sections.showUptime,
757
+ }).toEqual({
758
+ name: scenario.name,
759
+ showUptime:
760
+ presentation.runState !== MonitorOverviewRunState.AwaitingFirstData ||
761
+ scenario.hasPushVerdict === true,
762
+ });
763
+ }
764
+ });
765
+
766
+ it("owners is always the last fact", () => {
767
+ for (const scenario of SCENARIOS) {
768
+ const presentation: MonitorOverviewPresentation = build(scenario.input);
769
+ const last: MonitorOverviewFact =
770
+ presentation.facts[presentation.facts.length - 1]!;
771
+
772
+ expect(last).toEqual({ key: "owners", label: "Owners", value: "" });
773
+ }
774
+ });
775
+ });
776
+
777
+ describe("MonitorOverviewPresentationUtil hero", () => {
778
+ it("running shows the status, its colour and how long it has held", () => {
779
+ const presentation: MonitorOverviewPresentation = build(
780
+ running(MonitorType.API),
781
+ );
782
+
783
+ expect(presentation.tone).toBe("good");
784
+ expect(presentation.badge).toEqual({
785
+ text: "Operational",
786
+ tone: "good",
787
+ statusColor: "#10B981",
788
+ });
789
+ expect(presentation.headline).toEqual({
790
+ text: "Operational",
791
+ since: secondsAgo(3 * DAY + 4 * 3600),
792
+ });
793
+ expect(presentation.secondaryBadges).toEqual([]);
794
+ expect(presentation.explanation).toBeUndefined();
795
+ expect(presentation.callToAction).toBeUndefined();
796
+ expect(presentation.freshness).toBe(MonitorCheckFreshness.Fresh);
797
+ });
798
+
799
+ it('since is used only when given; no status gives "No status recorded yet"', () => {
800
+ expect(
801
+ build(running(MonitorType.API, { statusSince: undefined })).headline,
802
+ ).toEqual({ text: "Operational", since: undefined });
803
+
804
+ const noStatus: MonitorOverviewPresentation = build(
805
+ running(MonitorType.API, {
806
+ currentStatus: undefined,
807
+ statusSince: secondsAgo(60),
808
+ }),
809
+ );
810
+
811
+ expect(noStatus.headline).toEqual({ text: "No status recorded yet" });
812
+ expect(noStatus.badge).toEqual({ text: "Unknown status", tone: "neutral" });
813
+ expect(noStatus.tone).toBe("neutral");
814
+ });
815
+
816
+ it("offline status is danger, operational is good, any other is warning", () => {
817
+ expect(MonitorOverviewPresentationUtil.getStatusTone(OPERATIONAL)).toBe(
818
+ "good",
819
+ );
820
+ expect(MonitorOverviewPresentationUtil.getStatusTone(OFFLINE)).toBe(
821
+ "danger",
822
+ );
823
+ expect(MonitorOverviewPresentationUtil.getStatusTone(DEGRADED)).toBe(
824
+ "warning",
825
+ );
826
+ expect(MonitorOverviewPresentationUtil.getStatusTone(undefined)).toBe(
827
+ "neutral",
828
+ );
829
+ // A status flagged both ways must not read as good.
830
+ expect(
831
+ MonitorOverviewPresentationUtil.getStatusTone({
832
+ ...OFFLINE,
833
+ isOperationalState: true,
834
+ }),
835
+ ).toBe("danger");
836
+
837
+ expect(
838
+ build(running(MonitorType.API, { currentStatus: OFFLINE })).badge,
839
+ ).toEqual({ text: "Offline", tone: "danger", statusColor: "#EF4444" });
840
+ expect(
841
+ build(running(MonitorType.API, { currentStatus: DEGRADED })).tone,
842
+ ).toBe("warning");
843
+ });
844
+
845
+ it("an unreadable status colour is dropped, not passed to a style", () => {
846
+ expect(
847
+ build(
848
+ running(MonitorType.API, {
849
+ currentStatus: { ...OPERATIONAL, color: "url(javascript:x)" },
850
+ }),
851
+ ).badge.statusColor,
852
+ ).toBeUndefined();
853
+ });
854
+
855
+ it("manual monitors say who sets the status", () => {
856
+ const presentation: MonitorOverviewPresentation = build(
857
+ running(MonitorType.Manual, { currentStatus: DEGRADED }),
858
+ );
859
+
860
+ expect(presentation.badge.text).toBe("Degraded");
861
+ expect(presentation.tone).toBe("warning");
862
+ expect(presentation.secondaryBadges).toEqual([
863
+ { text: "Manual", tone: "neutral" },
864
+ ]);
865
+ expect(presentation.explanation).toBe(
866
+ "Manual monitor: OneUptime runs no checks. The status changes when someone sets it, or when an incident or scheduled maintenance event changes it.",
867
+ );
868
+ expect(presentation.callToAction).toEqual({
869
+ text: "Change status",
870
+ linkKey: "statusTimeline",
871
+ });
872
+
873
+ const unknown: MonitorOverviewPresentation = build(
874
+ running(MonitorType.Manual, { currentStatus: undefined }),
875
+ );
876
+
877
+ expect(unknown.badge).toEqual({ text: "Unknown status", tone: "neutral" });
878
+ expect(unknown.headline.text).toBe("No status recorded yet");
879
+ });
880
+
881
+ it("paused copy per reason", () => {
882
+ const disabled: MonitorOverviewPresentation = build(SCENARIOS[1]!.input);
883
+
884
+ expect(disabled.badge).toEqual({ text: "Disabled", tone: "neutral" });
885
+ expect(disabled.headline.text).toBe("Monitoring is turned off");
886
+ expect(disabled.explanation).toBe(
887
+ "No checks run while monitoring is off, so the status stays at the last one recorded.",
888
+ );
889
+ expect(disabled.lastKnownStatus).toBe("Last recorded status: Operational");
890
+ expect(disabled.callToAction).toEqual({
891
+ text: "Open settings",
892
+ linkKey: "settings",
893
+ });
894
+
895
+ const byIncident: MonitorOverviewPresentation = build(SCENARIOS[2]!.input);
896
+
897
+ expect(byIncident.badge).toEqual({ text: "Paused", tone: "neutral" });
898
+ expect(byIncident.headline.text).toBe(
899
+ "Monitoring is paused by an incident",
900
+ );
901
+ expect(byIncident.lastKnownStatus).toBe("Last recorded status: Offline");
902
+ expect(byIncident.callToAction).toEqual({
903
+ text: "View incidents",
904
+ linkKey: "incidents",
905
+ });
906
+
907
+ const maintenance: MonitorOverviewPresentation = build(SCENARIOS[3]!.input);
908
+
909
+ expect(maintenance.badge.text).toBe("Paused");
910
+ expect(maintenance.headline.text).toBe(
911
+ "Monitoring is paused for scheduled maintenance",
912
+ );
913
+ expect(maintenance.explanation).toBe(
914
+ "Checks resume automatically when the maintenance event ends.",
915
+ );
916
+ expect(maintenance.callToAction).toBeUndefined();
917
+ });
918
+
919
+ it('paused with no probe enabled keeps a "Probes Not Enabled" secondary badge', () => {
920
+ const presentation: MonitorOverviewPresentation = build(
921
+ running(MonitorType.API, {
922
+ pause: {
923
+ isDisabled: true,
924
+ byManualIncident: false,
925
+ byScheduledMaintenance: false,
926
+ },
927
+ probeFlags: { isNoProbeEnabled: true, isAllProbesDisconnected: true },
928
+ }),
929
+ );
930
+
931
+ expect(presentation.badge.text).toBe("Disabled");
932
+ expect(presentation.secondaryBadges).toEqual([
933
+ { text: "Probes Not Enabled", tone: "danger" },
934
+ ]);
935
+
936
+ expect(
937
+ build(
938
+ running(MonitorType.API, {
939
+ pause: {
940
+ isDisabled: false,
941
+ byManualIncident: false,
942
+ byScheduledMaintenance: true,
943
+ },
944
+ probeFlags: {
945
+ isNoProbeEnabled: false,
946
+ isAllProbesDisconnected: true,
947
+ },
948
+ }),
949
+ ).secondaryBadges,
950
+ ).toEqual([{ text: "Probes Disconnected", tone: "danger" }]);
951
+
952
+ // Only probe checks have probes to complain about.
953
+ expect(
954
+ build(
955
+ running(MonitorType.Logs, {
956
+ pause: {
957
+ isDisabled: true,
958
+ byManualIncident: false,
959
+ byScheduledMaintenance: false,
960
+ },
961
+ probeFlags: {
962
+ isNoProbeEnabled: true,
963
+ isAllProbesDisconnected: false,
964
+ },
965
+ }),
966
+ ).secondaryBadges,
967
+ ).toEqual([]);
968
+ });
969
+
970
+ it("NotChecking wording for attached > 0, attached = 0, and unknown attachment", () => {
971
+ const switchedOff: (
972
+ attachedCount: number,
973
+ ) => MonitorOverviewPresentation = (
974
+ attachedCount: number,
975
+ ): MonitorOverviewPresentation => {
976
+ return build(
977
+ running(MonitorType.API, {
978
+ probeFlags: {
979
+ isNoProbeEnabled: true,
980
+ isAllProbesDisconnected: false,
981
+ },
982
+ probes: probes({
983
+ ...NO_RESULTS,
984
+ attachedCount: attachedCount,
985
+ enabledCount: 0,
986
+ disabledCount: attachedCount,
987
+ }),
988
+ }),
989
+ );
990
+ };
991
+
992
+ const three: MonitorOverviewPresentation = switchedOff(3);
993
+
994
+ expect(three.badge).toEqual({ text: "Probes Not Enabled", tone: "danger" });
995
+ expect(three.tone).toBe("danger");
996
+ expect(three.headline.text).toBe(
997
+ "Every probe is turned off for this monitor",
998
+ );
999
+ expect(three.explanation).toBe(
1000
+ "3 probes are attached but switched off, so nothing is checking this resource.",
1001
+ );
1002
+ expect(three.callToAction).toEqual({
1003
+ text: "Manage probes",
1004
+ linkKey: "probes",
1005
+ });
1006
+ expect(three.lastKnownStatus).toBe("Last recorded status: Operational");
1007
+
1008
+ expect(switchedOff(1).explanation).toBe(
1009
+ "1 probe is attached but switched off, so nothing is checking this resource.",
1010
+ );
1011
+
1012
+ const none: MonitorOverviewPresentation = switchedOff(0);
1013
+
1014
+ expect(none.headline.text).toBe("No probes are attached to this monitor");
1015
+ expect(none.explanation).toBe(
1016
+ "Add a probe to start checking this resource.",
1017
+ );
1018
+ expect(none.callToAction).toEqual({
1019
+ text: "Add a probe",
1020
+ linkKey: "probes",
1021
+ });
1022
+
1023
+ const unknown: MonitorOverviewPresentation = build(
1024
+ running(MonitorType.API, {
1025
+ probeFlags: { isNoProbeEnabled: true, isAllProbesDisconnected: false },
1026
+ probes: null,
1027
+ }),
1028
+ );
1029
+
1030
+ expect(unknown.headline.text).toBe("Nothing is checking this monitor");
1031
+ expect(unknown.explanation).toBe(
1032
+ "No probe is enabled for this monitor, so no checks run.",
1033
+ );
1034
+ expect(unknown.callToAction?.text).toBe("Manage probes");
1035
+
1036
+ const disconnected: MonitorOverviewPresentation = build(
1037
+ SCENARIOS[5]!.input,
1038
+ );
1039
+
1040
+ expect(disconnected.badge).toEqual({
1041
+ text: "Probes Disconnected",
1042
+ tone: "danger",
1043
+ });
1044
+ expect(disconnected.headline.text).toBe(
1045
+ "Every probe checking this monitor is disconnected",
1046
+ );
1047
+ expect(disconnected.callToAction).toEqual({
1048
+ text: "Check probes",
1049
+ linkKey: "probes",
1050
+ });
1051
+ });
1052
+
1053
+ it('unknown probes never produce "No probes are attached"', () => {
1054
+ for (const flags of [
1055
+ { isNoProbeEnabled: true, isAllProbesDisconnected: false },
1056
+ { isNoProbeEnabled: true, isAllProbesDisconnected: true },
1057
+ { isNoProbeEnabled: false, isAllProbesDisconnected: false },
1058
+ ]) {
1059
+ const presentation: MonitorOverviewPresentation = build(
1060
+ running(MonitorType.API, { probeFlags: flags, probes: null }),
1061
+ );
1062
+
1063
+ expect(JSON.stringify(presentation)).not.toContain(
1064
+ "No probes are attached",
1065
+ );
1066
+ expect(fact(presentation, "probes").value).toBe("Unavailable");
1067
+ expect(fact(presentation, "probes").isMuted).toBe(true);
1068
+ }
1069
+ });
1070
+
1071
+ it("not configured copy", () => {
1072
+ const noCriteria: MonitorOverviewPresentation = build(SCENARIOS[6]!.input);
1073
+
1074
+ expect(noCriteria.badge).toEqual({ text: "Not set up", tone: "neutral" });
1075
+ expect(noCriteria.headline.text).toBe("This monitor has no criteria yet");
1076
+ expect(noCriteria.callToAction).toEqual({
1077
+ text: "Set up criteria",
1078
+ linkKey: "criteria",
1079
+ });
1080
+ expect(noCriteria.lastKnownStatus).toBeUndefined();
1081
+
1082
+ const noDevice: MonitorOverviewPresentation = build(SCENARIOS[7]!.input);
1083
+
1084
+ expect(noDevice.headline.text).toBe("No network device is selected");
1085
+ expect(noDevice.explanation).toBe(
1086
+ "Choose the device this monitor alerts on.",
1087
+ );
1088
+ expect(noDevice.callToAction).toEqual({
1089
+ text: "Choose a device",
1090
+ linkKey: "criteria",
1091
+ });
1092
+ });
1093
+
1094
+ it("awaiting copy per family", () => {
1095
+ const probeCheck: MonitorOverviewPresentation = build(SCENARIOS[8]!.input);
1096
+
1097
+ expect(probeCheck.badge).toEqual({
1098
+ text: "Waiting for data",
1099
+ tone: "info",
1100
+ });
1101
+ expect(probeCheck.tone).toBe("info");
1102
+ expect(probeCheck.headline.text).toBe("Waiting for the first check");
1103
+ expect(probeCheck.explanation).toBe(
1104
+ "2 probes check this every 5 minutes. The first result usually arrives within a few minutes.",
1105
+ );
1106
+ expect(probeCheck.callToAction).toBeUndefined();
1107
+ expect(probeCheck.lastKnownStatus).toBe(
1108
+ "Last recorded status: Operational",
1109
+ );
1110
+
1111
+ expect(
1112
+ build(
1113
+ running(MonitorType.Ping, {
1114
+ createdAt: secondsAgo(60),
1115
+ monitoringInterval: "* * * * *",
1116
+ probes: probes({ ...NO_RESULTS, enabledCount: 1, attachedCount: 1 }),
1117
+ }),
1118
+ ).explanation,
1119
+ ).toBe(
1120
+ "1 probe checks this every minute. The first result usually arrives within a few minutes.",
1121
+ );
1122
+
1123
+ const heartbeat: MonitorOverviewPresentation = build(SCENARIOS[9]!.input);
1124
+
1125
+ expect(heartbeat.headline.text).toBe("Waiting for the first heartbeat");
1126
+ expect(heartbeat.explanation).toBe(
1127
+ "Send a GET or POST request to this monitor's heartbeat URL to start tracking it.",
1128
+ );
1129
+ expect(heartbeat.callToAction).toEqual({
1130
+ text: "Setup instructions",
1131
+ linkKey: "documentation",
1132
+ });
1133
+
1134
+ const email: MonitorOverviewPresentation = build(SCENARIOS[10]!.input);
1135
+
1136
+ expect(email.headline.text).toBe("Waiting for the first email");
1137
+ expect(email.explanation).toBe(
1138
+ "Send an email to this monitor's address to start tracking it.",
1139
+ );
1140
+ expect(email.callToAction?.linkKey).toBe("documentation");
1141
+
1142
+ const agent: MonitorOverviewPresentation = build(SCENARIOS[11]!.input);
1143
+
1144
+ expect(agent.headline.text).toBe("Waiting for the agent to report");
1145
+ expect(agent.explanation).toBe(
1146
+ "Install the server agent to start sending reports.",
1147
+ );
1148
+ expect(agent.callToAction?.linkKey).toBe("documentation");
1149
+
1150
+ const telemetry: MonitorOverviewPresentation = build(
1151
+ running(MonitorType.Traces, {
1152
+ monitoringInterval: "* * * * *",
1153
+ createdAt: secondsAgo(30),
1154
+ telemetry: {},
1155
+ latestEvaluationAt: undefined,
1156
+ currentStatus: undefined,
1157
+ }),
1158
+ );
1159
+
1160
+ expect(telemetry.headline.text).toBe("Waiting for the first evaluation");
1161
+ expect(telemetry.explanation).toBe(
1162
+ "This monitor is evaluated every minute once saved.",
1163
+ );
1164
+ expect(telemetry.callToAction).toBeUndefined();
1165
+ expect(telemetry.lastKnownStatus).toBeUndefined();
1166
+ });
1167
+
1168
+ it("overdue explanation uses compact durations and the interval phrase", () => {
1169
+ const probeCheck: MonitorOverviewPresentation = build(
1170
+ running(MonitorType.API, {
1171
+ probes: probes({ lastResultAt: secondsAgo(2 * 3600 + 5 * 60) }),
1172
+ }),
1173
+ );
1174
+
1175
+ expect(probeCheck.explanation).toBe(
1176
+ "No result for 2h 5m, but this monitor checks every 5 minutes. A probe may be overloaded or offline.",
1177
+ );
1178
+ expect(probeCheck.callToAction).toEqual({
1179
+ text: "Check probes",
1180
+ linkKey: "probes",
1181
+ });
1182
+ // The pulse says how late: the age less one cadence.
1183
+ expect(probeCheck.pulse.overdueSeconds).toBe(2 * 3600);
1184
+
1185
+ const telemetry: MonitorOverviewPresentation = build(SCENARIOS[15]!.input);
1186
+
1187
+ expect(telemetry.explanation).toBe(
1188
+ "No evaluation for 1h, but this monitor is evaluated every minute.",
1189
+ );
1190
+ expect(telemetry.callToAction).toBeUndefined();
1191
+ });
1192
+
1193
+ it("never-reported old monitor reads as overdue with the creation age", () => {
1194
+ const presentation: MonitorOverviewPresentation = build(
1195
+ running(MonitorType.API, {
1196
+ createdAt: secondsAgo(2 * DAY + 3 * 3600),
1197
+ statusSince: undefined,
1198
+ probes: probes(NO_RESULTS),
1199
+ }),
1200
+ );
1201
+
1202
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Overdue);
1203
+ expect(presentation.explanation).toBe(
1204
+ "No check result has arrived since this monitor was created 2d 3h ago.",
1205
+ );
1206
+ expect(presentation.tone).toBe("warning");
1207
+ expect(presentation.isNeverReported).toBe(true);
1208
+ });
1209
+
1210
+ /*
1211
+ * Nothing was ever measured, so the stored status is only the default the
1212
+ * monitor was created with: it must not be the badge, and it has not
1213
+ * "held" for 30 days.
1214
+ */
1215
+ it("never-reported monitors lead with the missing results, not the default status", () => {
1216
+ const presentation: MonitorOverviewPresentation = build(
1217
+ running(MonitorType.API, {
1218
+ createdAt: secondsAgo(30 * DAY),
1219
+ probes: probes({ ...NO_RESULTS, nextCheckAt: undefined }),
1220
+ }),
1221
+ );
1222
+
1223
+ expect(presentation.badge).toEqual({
1224
+ text: "No results yet",
1225
+ tone: "warning",
1226
+ });
1227
+ expect(presentation.headline).toEqual({
1228
+ text: "No check has completed yet",
1229
+ });
1230
+ expect(presentation.lastKnownStatus).toBe(
1231
+ "Last recorded status: Operational",
1232
+ );
1233
+ expect(presentation.secondaryBadges).toEqual([
1234
+ { text: "Checks overdue", tone: "warning" },
1235
+ ]);
1236
+
1237
+ // Danger stays danger.
1238
+ expect(
1239
+ build(
1240
+ running(MonitorType.API, {
1241
+ currentStatus: OFFLINE,
1242
+ createdAt: secondsAgo(30 * DAY),
1243
+ probes: probes({ ...NO_RESULTS, nextCheckAt: undefined }),
1244
+ }),
1245
+ ).tone,
1246
+ ).toBe("danger");
1247
+
1248
+ // Overdue with results keeps leading with the status it measured.
1249
+ const late: MonitorOverviewPresentation = build(
1250
+ running(MonitorType.API, {
1251
+ probes: probes({ lastResultAt: secondsAgo(2 * 3600) }),
1252
+ }),
1253
+ );
1254
+
1255
+ expect(late.badge.text).toBe("Operational");
1256
+ expect(late.headline.since).toEqual(secondsAgo(3 * DAY + 4 * 3600));
1257
+ expect(late.lastKnownStatus).toBeUndefined();
1258
+ expect(late.isNeverReported).toBe(false);
1259
+ });
1260
+ });
1261
+
1262
+ describe("MonitorOverviewPresentationUtil facts", () => {
1263
+ it("probe checks show the latest result, the probes and the owners", () => {
1264
+ const presentation: MonitorOverviewPresentation = build(
1265
+ running(MonitorType.API),
1266
+ );
1267
+
1268
+ expect(factKeys(presentation)).toEqual([
1269
+ "latest-result",
1270
+ "probes",
1271
+ "owners",
1272
+ ]);
1273
+ expect(fact(presentation, "latest-result")).toEqual({
1274
+ key: "latest-result",
1275
+ label: "Latest result",
1276
+ value: "Up · 120 ms · HTTP 200",
1277
+ secondary: "from London",
1278
+ tone: "good",
1279
+ });
1280
+ expect(fact(presentation, "probes")).toEqual({
1281
+ key: "probes",
1282
+ label: "Probes",
1283
+ linkKey: "probes",
1284
+ value: "2 of 2 reporting",
1285
+ secondary: undefined,
1286
+ tone: "neutral",
1287
+ });
1288
+ });
1289
+
1290
+ it("latest result per outcome and type", () => {
1291
+ const latest: (
1292
+ monitorType: MonitorType,
1293
+ result: Partial<NonNullable<MonitorOverviewProbeSummary["latestResult"]>>,
1294
+ ) => MonitorOverviewFact = (
1295
+ monitorType: MonitorType,
1296
+ result: Partial<NonNullable<MonitorOverviewProbeSummary["latestResult"]>>,
1297
+ ): MonitorOverviewFact => {
1298
+ return fact(
1299
+ build(
1300
+ running(monitorType, {
1301
+ probes: probes({
1302
+ latestResult: {
1303
+ probeId: "11111111-1111-4111-8111-111111111111",
1304
+ probeName: "London",
1305
+ monitoredAt: secondsAgo(60),
1306
+ ...result,
1307
+ },
1308
+ }),
1309
+ }),
1310
+ ),
1311
+ "latest-result",
1312
+ );
1313
+ };
1314
+
1315
+ const down: MonitorOverviewFact = latest(MonitorType.Website, {
1316
+ isOnline: false,
1317
+ responseTimeInMs: 30000.4,
1318
+ responseCode: 503,
1319
+ failureCause: "Service Unavailable",
1320
+ });
1321
+
1322
+ expect(down.value).toBe("Down · 30000 ms · HTTP 503");
1323
+ expect(down.secondary).toBe("Service Unavailable");
1324
+ expect(down.tone).toBe("danger");
1325
+
1326
+ // Only HTTP checks quote a status code.
1327
+ expect(
1328
+ latest(MonitorType.Port, {
1329
+ isOnline: true,
1330
+ responseTimeInMs: 12,
1331
+ responseCode: 200,
1332
+ }).value,
1333
+ ).toBe("Up · 12 ms");
1334
+
1335
+ expect(
1336
+ latest(MonitorType.SyntheticMonitor, {
1337
+ isOnline: true,
1338
+ responseTimeInMs: 4200,
1339
+ }).value,
1340
+ ).toBe("Passed · 4200 ms");
1341
+ expect(
1342
+ latest(MonitorType.CustomJavaScriptCode, { isOnline: false }).value,
1343
+ ).toBe("Failed");
1344
+
1345
+ const reported: MonitorOverviewFact = latest(MonitorType.DNS, {});
1346
+
1347
+ expect(reported.value).toBe("Reported");
1348
+ expect(reported.tone).toBe("neutral");
1349
+ expect(reported.secondary).toBe("from London");
1350
+ });
1351
+
1352
+ it("latest result when there is none, and when probes are unknown", () => {
1353
+ expect(
1354
+ fact(
1355
+ build(running(MonitorType.API, { probes: probes(NO_RESULTS) })),
1356
+ "latest-result",
1357
+ ),
1358
+ ).toEqual({
1359
+ key: "latest-result",
1360
+ label: "Latest result",
1361
+ value: "—",
1362
+ secondary: "No results yet",
1363
+ isMuted: true,
1364
+ });
1365
+ expect(
1366
+ fact(build(running(MonitorType.API, { probes: null })), "latest-result"),
1367
+ ).toEqual({
1368
+ key: "latest-result",
1369
+ label: "Latest result",
1370
+ value: "Unavailable",
1371
+ isMuted: true,
1372
+ });
1373
+ });
1374
+
1375
+ it("probes fact tones and secondary line", () => {
1376
+ const probesFact: (
1377
+ overrides: Partial<MonitorOverviewProbeSummary>,
1378
+ ) => MonitorOverviewFact = (
1379
+ overrides: Partial<MonitorOverviewProbeSummary>,
1380
+ ): MonitorOverviewFact => {
1381
+ return fact(
1382
+ build(running(MonitorType.API, { probes: probes(overrides) })),
1383
+ "probes",
1384
+ );
1385
+ };
1386
+
1387
+ const partly: MonitorOverviewFact = probesFact({
1388
+ attachedCount: 5,
1389
+ enabledCount: 3,
1390
+ reportingCount: 2,
1391
+ disabledCount: 2,
1392
+ disconnectedCount: 1,
1393
+ });
1394
+
1395
+ expect(partly.value).toBe("2 of 3 reporting");
1396
+ expect(partly.secondary).toBe("2 disabled · 1 disconnected");
1397
+ expect(partly.tone).toBe("warning");
1398
+
1399
+ expect(
1400
+ probesFact({ reportingCount: 0, lastResultAt: secondsAgo(60) }).tone,
1401
+ ).toBe("danger");
1402
+ expect(probesFact({ disabledCount: 1, attachedCount: 3 }).secondary).toBe(
1403
+ "1 disabled",
1404
+ );
1405
+ });
1406
+
1407
+ it("certificate and domain expiry tones at 31, 30, 8, 7, 0 and −1 days, and an invalid certificate", () => {
1408
+ const expiresIn: (seconds: number) => Date = (seconds: number): Date => {
1409
+ return new Date(NOW.getTime() + seconds * 1000);
1410
+ };
1411
+
1412
+ const cases: Array<[number, string, string]> = [
1413
+ [31 * DAY, "in 31 days", "neutral"],
1414
+ [30 * DAY, "in 30 days", "warning"],
1415
+ [8 * DAY, "in 8 days", "warning"],
1416
+ [7 * DAY, "in 7 days", "danger"],
1417
+ [DAY, "in 1 day", "danger"],
1418
+ [3600, "Expires in 1 hour", "danger"],
1419
+ [-DAY, "Expired 1 day ago", "danger"],
1420
+ [-3 * DAY, "Expired 3 days ago", "danger"],
1421
+ ];
1422
+
1423
+ for (const [seconds, value, tone] of cases) {
1424
+ const ssl: MonitorOverviewFact = fact(
1425
+ build(
1426
+ running(MonitorType.SSLCertificate, {
1427
+ probes: probes({
1428
+ latestResult: {
1429
+ probeId: "11111111-1111-4111-8111-111111111111",
1430
+ probeName: "London",
1431
+ monitoredAt: secondsAgo(60),
1432
+ isOnline: true,
1433
+ sslExpiresAt: expiresIn(seconds),
1434
+ isValidCertificate: true,
1435
+ },
1436
+ }),
1437
+ }),
1438
+ ),
1439
+ "certificate-expiry",
1440
+ );
1441
+
1442
+ expect({ seconds: seconds, value: ssl.value, tone: ssl.tone }).toEqual({
1443
+ seconds: seconds,
1444
+ value: value,
1445
+ tone: tone,
1446
+ });
1447
+ expect(ssl.label).toBe("Certificate expires");
1448
+ expect(ssl.secondary).toBe(
1449
+ OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
1450
+ expiresIn(seconds),
1451
+ true,
1452
+ ),
1453
+ );
1454
+
1455
+ const domain: MonitorOverviewFact = fact(
1456
+ build(
1457
+ running(MonitorType.Domain, {
1458
+ probes: probes({
1459
+ latestResult: {
1460
+ probeId: "11111111-1111-4111-8111-111111111111",
1461
+ probeName: "London",
1462
+ monitoredAt: secondsAgo(60),
1463
+ isOnline: true,
1464
+ domainExpiresAt: expiresIn(seconds),
1465
+ },
1466
+ }),
1467
+ }),
1468
+ ),
1469
+ "domain-expiry",
1470
+ );
1471
+
1472
+ expect({
1473
+ seconds: seconds,
1474
+ value: domain.value,
1475
+ tone: domain.tone,
1476
+ }).toEqual({ seconds: seconds, value: value, tone: tone });
1477
+ expect(domain.label).toBe("Domain expires");
1478
+ }
1479
+
1480
+ const invalid: MonitorOverviewFact =
1481
+ MonitorOverviewPresentationUtil.getExpiryFact({
1482
+ key: "certificate-expiry",
1483
+ expiresAt: expiresIn(200 * DAY),
1484
+ isValidCertificate: false,
1485
+ now: NOW,
1486
+ });
1487
+
1488
+ expect(invalid.value).toBe("Not valid");
1489
+ expect(invalid.tone).toBe("danger");
1490
+
1491
+ expect(
1492
+ MonitorOverviewPresentationUtil.getExpiryFact({
1493
+ key: "domain-expiry",
1494
+ expiresAt: undefined,
1495
+ now: NOW,
1496
+ }),
1497
+ ).toEqual({
1498
+ key: "domain-expiry",
1499
+ label: "Domain expires",
1500
+ value: "—",
1501
+ isMuted: true,
1502
+ });
1503
+
1504
+ // Facts are ordered result, expiry, probes, owners.
1505
+ expect(factKeys(build(running(MonitorType.SSLCertificate)))).toEqual([
1506
+ "latest-result",
1507
+ "certificate-expiry",
1508
+ "probes",
1509
+ "owners",
1510
+ ]);
1511
+ expect(
1512
+ fact(
1513
+ build(running(MonitorType.Domain, { probes: null })),
1514
+ "domain-expiry",
1515
+ ).value,
1516
+ ).toBe("Unavailable");
1517
+ });
1518
+
1519
+ it('heartbeat missing-window falls back to "Set in Criteria"', () => {
1520
+ const withWindow: MonitorOverviewPresentation = build(
1521
+ running(MonitorType.IncomingRequest, {
1522
+ monitorSteps: missingWindowSteps(CheckOn.IncomingRequest, 10),
1523
+ }),
1524
+ );
1525
+
1526
+ expect(factKeys(withWindow)).toEqual([
1527
+ "missing-window",
1528
+ "heartbeat-check",
1529
+ "owners",
1530
+ ]);
1531
+ expect(fact(withWindow, "missing-window")).toEqual({
1532
+ key: "missing-window",
1533
+ label: "Missing-request window",
1534
+ value: "10 minutes",
1535
+ secondary: "From this monitor's criteria",
1536
+ });
1537
+ expect(fact(withWindow, "heartbeat-check")).toEqual({
1538
+ key: "heartbeat-check",
1539
+ label: "Last missing-request check",
1540
+ value: "",
1541
+ valueDate: secondsAgo(30),
1542
+ });
1543
+
1544
+ const withoutWindow: MonitorOverviewPresentation = build(
1545
+ running(MonitorType.IncomingRequest),
1546
+ );
1547
+
1548
+ expect(fact(withoutWindow, "missing-window")).toEqual({
1549
+ key: "missing-window",
1550
+ label: "Missing-request window",
1551
+ value: "Set in Criteria",
1552
+ linkKey: "criteria",
1553
+ });
1554
+
1555
+ const email: MonitorOverviewPresentation = build(
1556
+ running(MonitorType.IncomingEmail, {
1557
+ monitorSteps: missingWindowSteps(CheckOn.EmailReceivedAt, 1),
1558
+ email: { lastReceivedAt: secondsAgo(60) },
1559
+ }),
1560
+ );
1561
+
1562
+ expect(fact(email, "missing-window").label).toBe("Missing-email window");
1563
+ expect(fact(email, "missing-window").value).toBe("1 minute");
1564
+ expect(fact(email, "heartbeat-check")).toEqual({
1565
+ key: "heartbeat-check",
1566
+ label: "Last missing-email check",
1567
+ value: "Not run yet",
1568
+ isMuted: true,
1569
+ });
1570
+ });
1571
+
1572
+ it("agent facts before and after the first report", () => {
1573
+ const before: MonitorOverviewPresentation = build(
1574
+ running(MonitorType.Server, { agent: {} }),
1575
+ );
1576
+
1577
+ expect(factKeys(before)).toEqual(["host", "cpu", "memory", "owners"]);
1578
+ expect(fact(before, "host")).toEqual({
1579
+ key: "host",
1580
+ label: "Host",
1581
+ value: "Not reported yet",
1582
+ isMuted: true,
1583
+ });
1584
+ expect(fact(before, "cpu")).toEqual({
1585
+ key: "cpu",
1586
+ label: "CPU",
1587
+ value: "—",
1588
+ isMuted: true,
1589
+ });
1590
+ expect(fact(before, "memory").value).toBe("—");
1591
+
1592
+ const after: MonitorOverviewPresentation = build(
1593
+ running(MonitorType.Server),
1594
+ );
1595
+
1596
+ expect(fact(after, "host")).toEqual({
1597
+ key: "host",
1598
+ label: "Host",
1599
+ value: "web-01.example.com",
1600
+ isMono: true,
1601
+ });
1602
+ expect(fact(after, "cpu").value).toBe("42%");
1603
+ expect(fact(after, "memory").value).toBe("64%");
1604
+ expect(after.target).toEqual({
1605
+ value: "web-01.example.com",
1606
+ isMono: true,
1607
+ extraStepCount: 0,
1608
+ });
1609
+ });
1610
+
1611
+ it("telemetry and infrastructure show the evaluation cadence and criteria", () => {
1612
+ for (const monitorType of [MonitorType.Logs, MonitorType.Kubernetes]) {
1613
+ const presentation: MonitorOverviewPresentation = build(
1614
+ running(monitorType, {
1615
+ monitoringInterval: "* * * * *",
1616
+ monitorSteps: stepsOf({}, {}),
1617
+ }),
1618
+ );
1619
+
1620
+ expect(factKeys(presentation)).toEqual([
1621
+ "evaluates",
1622
+ "criteria",
1623
+ "owners",
1624
+ ]);
1625
+ expect(fact(presentation, "evaluates").value).toBe("Every minute");
1626
+ expect(fact(presentation, "criteria")).toEqual({
1627
+ key: "criteria",
1628
+ label: "Criteria",
1629
+ value: "2 steps",
1630
+ linkKey: "criteria",
1631
+ });
1632
+ }
1633
+
1634
+ expect(fact(build(running(MonitorType.Metrics)), "criteria").value).toBe(
1635
+ "1 step",
1636
+ );
1637
+ });
1638
+
1639
+ it("network device fact links the step's networkDeviceId, not autoProvisionedNetworkDeviceId", () => {
1640
+ const presentation: MonitorOverviewPresentation = build(
1641
+ running(MonitorType.NetworkDevice, {
1642
+ monitorSteps: stepsOf(
1643
+ {
1644
+ networkDeviceMonitor: { networkDeviceId: DEVICE_ID },
1645
+ autoProvisionedNetworkDeviceId: OTHER_DEVICE_ID,
1646
+ },
1647
+ { networkDeviceMonitor: { networkDeviceId: OTHER_DEVICE_ID } },
1648
+ ),
1649
+ }),
1650
+ );
1651
+
1652
+ expect(factKeys(presentation)).toEqual(["device", "evaluated", "owners"]);
1653
+ expect(fact(presentation, "device")).toEqual({
1654
+ key: "device",
1655
+ label: "Device",
1656
+ value: "View device",
1657
+ linkKey: "networkDevice",
1658
+ linkId: DEVICE_ID,
1659
+ });
1660
+ expect(fact(presentation, "evaluated").value).toBe(
1661
+ "On every poll and matching trap",
1662
+ );
1663
+ expect(presentation.target).toBeNull();
1664
+
1665
+ const unselected: MonitorOverviewPresentation = build(
1666
+ running(MonitorType.NetworkDevice, {
1667
+ monitorSteps: stepsOf({
1668
+ networkDeviceMonitor: {},
1669
+ autoProvisionedNetworkDeviceId: OTHER_DEVICE_ID,
1670
+ }),
1671
+ }),
1672
+ );
1673
+
1674
+ expect(fact(unselected, "device")).toEqual({
1675
+ key: "device",
1676
+ label: "Device",
1677
+ value: "Not selected",
1678
+ isMuted: true,
1679
+ });
1680
+ });
1681
+
1682
+ it("manual monitors have one fact about checks", () => {
1683
+ const presentation: MonitorOverviewPresentation = build(
1684
+ running(MonitorType.Manual),
1685
+ );
1686
+
1687
+ expect(presentation.facts).toEqual([
1688
+ {
1689
+ key: "checks",
1690
+ label: "Checks",
1691
+ value: "None",
1692
+ secondary: "Status is set by hand or by incidents",
1693
+ },
1694
+ { key: "owners", label: "Owners", value: "" },
1695
+ ]);
1696
+ });
1697
+ });
1698
+
1699
+ describe("MonitorOverviewPresentationUtil pulse", () => {
1700
+ it('pulse per family, including "Last checked: unavailable" when probes are unknown', () => {
1701
+ expect(build(running(MonitorType.API)).pulse).toEqual({
1702
+ label: "Last checked",
1703
+ at: secondsAgo(60),
1704
+ emptyText: "Not checked yet",
1705
+ cadenceText: "Every 5 minutes",
1706
+ nextAt: secondsAgo(-240),
1707
+ overdueSeconds: undefined,
1708
+ isUnavailable: false,
1709
+ });
1710
+
1711
+ const unknown: MonitorOverviewPresentation = build(
1712
+ running(MonitorType.API, { probes: null }),
1713
+ );
1714
+
1715
+ expect(unknown.pulse.label).toBe("Last checked");
1716
+ expect(unknown.pulse.isUnavailable).toBe(true);
1717
+ expect(unknown.pulse.at).toBeUndefined();
1718
+
1719
+ expect(build(running(MonitorType.IncomingRequest)).pulse).toEqual({
1720
+ label: "Last request",
1721
+ at: secondsAgo(120),
1722
+ emptyText: "No request yet",
1723
+ isUnavailable: false,
1724
+ });
1725
+ expect(build(running(MonitorType.IncomingEmail)).pulse).toEqual({
1726
+ label: "Last email",
1727
+ at: secondsAgo(300),
1728
+ emptyText: "No email yet",
1729
+ isUnavailable: false,
1730
+ });
1731
+ expect(build(running(MonitorType.Server)).pulse).toEqual({
1732
+ label: "Last report",
1733
+ at: secondsAgo(45),
1734
+ emptyText: "No report yet",
1735
+ isUnavailable: false,
1736
+ });
1737
+ expect(
1738
+ build(running(MonitorType.Logs, { monitoringInterval: "* * * * *" }))
1739
+ .pulse,
1740
+ ).toEqual({
1741
+ label: "Last evaluated",
1742
+ at: secondsAgo(90),
1743
+ emptyText: "Not evaluated yet",
1744
+ cadenceText: "Every minute",
1745
+ nextAt: secondsAgo(-270),
1746
+ overdueSeconds: undefined,
1747
+ isUnavailable: false,
1748
+ });
1749
+ expect(build(running(MonitorType.NetworkDevice)).pulse).toEqual({
1750
+ label: "Last evaluated",
1751
+ at: secondsAgo(90),
1752
+ emptyText: "Not evaluated yet",
1753
+ isUnavailable: false,
1754
+ });
1755
+ expect(build(running(MonitorType.Manual)).pulse).toEqual({
1756
+ label: null,
1757
+ emptyText: "No automated checks",
1758
+ isUnavailable: false,
1759
+ });
1760
+ });
1761
+
1762
+ it("a next check in the past is not promised, and overdue carries its lateness", () => {
1763
+ const overdue: MonitorOverviewPresentation = build(
1764
+ running(MonitorType.API, {
1765
+ probes: probes({
1766
+ lastResultAt: secondsAgo(1000),
1767
+ nextCheckAt: secondsAgo(700),
1768
+ }),
1769
+ }),
1770
+ );
1771
+
1772
+ expect(overdue.pulse.nextAt).toBeUndefined();
1773
+ // The result came at 11:43:20, so the next was due at the 11:45 run.
1774
+ expect(overdue.pulse.overdueSeconds).toBe(900);
1775
+ expect(overdue.freshness).toBe(MonitorCheckFreshness.Stale);
1776
+ });
1777
+
1778
+ it("a paused or unchecked monitor promises no cadence", () => {
1779
+ for (const scenario of [SCENARIOS[1]!, SCENARIOS[4]!, SCENARIOS[6]!]) {
1780
+ const presentation: MonitorOverviewPresentation = build(scenario.input);
1781
+
1782
+ expect(presentation.pulse.cadenceText).toBeUndefined();
1783
+ expect(presentation.pulse.nextAt).toBeUndefined();
1784
+ }
1785
+
1786
+ expect(build(SCENARIOS[1]!.input).freshness).toBe(
1787
+ MonitorCheckFreshness.NotScheduled,
1788
+ );
1789
+ });
1790
+ });
1791
+
1792
+ describe("MonitorOverviewPresentationUtil sections", () => {
1793
+ it("probe checks: uptime, response time, summary and the probes card", () => {
1794
+ expect(build(running(MonitorType.API)).sections).toEqual({
1795
+ showUptime: true,
1796
+ setup: null,
1797
+ connection: null,
1798
+ summary: {
1799
+ isShown: true,
1800
+ description: "What each probe saw on its most recent check.",
1801
+ },
1802
+ sideCard: "probes",
1803
+ responseTimeMetric: MonitorMetricType.ResponseTime,
1804
+ telemetryPreview: null,
1805
+ stepCount: 1,
1806
+ });
1807
+
1808
+ expect(
1809
+ build(running(MonitorType.SyntheticMonitor)).sections.responseTimeMetric,
1810
+ ).toBe(MonitorMetricType.ExecutionTime);
1811
+ });
1812
+
1813
+ it("an awaiting probe check has no uptime and no response time yet", () => {
1814
+ const sections: MonitorOverviewPresentation["sections"] = build(
1815
+ SCENARIOS[8]!.input,
1816
+ ).sections;
1817
+
1818
+ expect(sections.showUptime).toBe(false);
1819
+ expect(sections.responseTimeMetric).toBeNull();
1820
+ expect(sections.summary.isShown).toBe(true);
1821
+ expect(sections.sideCard).toBe("probes");
1822
+ });
1823
+
1824
+ it("push types show setup while awaiting and the connection card after", () => {
1825
+ const awaiting: MonitorOverviewPresentation["sections"] = build(
1826
+ SCENARIOS[9]!.input,
1827
+ ).sections;
1828
+
1829
+ expect(awaiting.setup).toBe(MonitorOverviewSetupKind.HeartbeatUrl);
1830
+ expect(awaiting.connection).toBeNull();
1831
+ expect(awaiting.sideCard).toBeNull();
1832
+ expect(awaiting.summary.isShown).toBe(false);
1833
+
1834
+ const after: MonitorOverviewPresentation["sections"] = build(
1835
+ running(MonitorType.IncomingRequest),
1836
+ ).sections;
1837
+
1838
+ expect(after.setup).toBeNull();
1839
+ expect(after.connection).toBe(MonitorOverviewSetupKind.HeartbeatUrl);
1840
+ expect(after.sideCard).toBe("connection");
1841
+ expect(after.summary).toEqual({
1842
+ isShown: true,
1843
+ description: "The most recent request and how the criteria judged it.",
1844
+ });
1845
+
1846
+ expect(build(SCENARIOS[10]!.input).sections.setup).toBe(
1847
+ MonitorOverviewSetupKind.InboundEmail,
1848
+ );
1849
+ expect(build(SCENARIOS[11]!.input).sections.setup).toBe(
1850
+ MonitorOverviewSetupKind.ServerAgent,
1851
+ );
1852
+ expect(build(running(MonitorType.Server)).sections.connection).toBe(
1853
+ MonitorOverviewSetupKind.ServerAgent,
1854
+ );
1855
+ });
1856
+
1857
+ it("previews need a step", () => {
1858
+ expect(build(running(MonitorType.Logs)).sections.telemetryPreview).toBe(
1859
+ "Logs",
1860
+ );
1861
+ expect(
1862
+ build(running(MonitorType.Logs, { monitorSteps: NO_STEPS })).sections
1863
+ .telemetryPreview,
1864
+ ).toBeNull();
1865
+ expect(
1866
+ build(running(MonitorType.Exceptions)).sections.telemetryPreview,
1867
+ ).toBeNull();
1868
+ });
1869
+
1870
+ it("manual monitors have no summary and the manual guide", () => {
1871
+ const sections: MonitorOverviewPresentation["sections"] = build(
1872
+ running(MonitorType.Manual),
1873
+ ).sections;
1874
+
1875
+ expect(sections.summary).toEqual({ isShown: false, description: "" });
1876
+ expect(sections.sideCard).toBe("manual");
1877
+ expect(sections.showUptime).toBe(true);
1878
+ });
1879
+
1880
+ it("the target is the redacted first step", () => {
1881
+ expect(
1882
+ build(
1883
+ running(MonitorType.API, {
1884
+ monitorSteps: stepsOf(
1885
+ {
1886
+ requestType: "GET",
1887
+ monitorDestination: "https://ops:pw@api.example.com/health?k=1",
1888
+ },
1889
+ {},
1890
+ ),
1891
+ }),
1892
+ ).target,
1893
+ ).toEqual({
1894
+ value: "GET https://api.example.com/health",
1895
+ isMono: true,
1896
+ extraStepCount: 1,
1897
+ });
1898
+ });
1899
+ });
1900
+
1901
+ /*
1902
+ * Probe rows as the server stores them, summarised the way the page does,
1903
+ * so these tests cover the whole path from MonitorProbe to the hero.
1904
+ */
1905
+ const probeRow: (data: {
1906
+ probeId: string;
1907
+ name: string;
1908
+ resultAgeSeconds?: number;
1909
+ isOnline?: boolean;
1910
+ nextPingInSeconds?: number;
1911
+ connectionStatus?: ProbeConnectionStatus;
1912
+ isEnabled?: boolean;
1913
+ }) => MonitorProbe = (data: {
1914
+ probeId: string;
1915
+ name: string;
1916
+ resultAgeSeconds?: number;
1917
+ isOnline?: boolean;
1918
+ nextPingInSeconds?: number;
1919
+ connectionStatus?: ProbeConnectionStatus;
1920
+ isEnabled?: boolean;
1921
+ }): MonitorProbe => {
1922
+ const row: MonitorProbe = new MonitorProbe();
1923
+ const probe: Probe = new Probe();
1924
+
1925
+ probe._id = data.probeId;
1926
+ probe.name = data.name;
1927
+ probe.connectionStatus =
1928
+ data.connectionStatus || ProbeConnectionStatus.Connected;
1929
+
1930
+ row.probeId = new ObjectID(data.probeId);
1931
+ row.probe = probe;
1932
+ row.isEnabled = data.isEnabled ?? true;
1933
+ row.nextPingAt = secondsAgo(-(data.nextPingInSeconds ?? 60));
1934
+
1935
+ if (data.resultAgeSeconds !== undefined) {
1936
+ row.lastPingAt = secondsAgo(data.resultAgeSeconds + 2);
1937
+ row.lastMonitoringLog = {
1938
+ [STEP_ID]: {
1939
+ monitoredAt: secondsAgo(data.resultAgeSeconds).toISOString(),
1940
+ isOnline: data.isOnline ?? true,
1941
+ responseTimeInMs: 120,
1942
+ },
1943
+ } as unknown as MonitorStepProbeResponse;
1944
+ }
1945
+
1946
+ return row;
1947
+ };
1948
+
1949
+ const summaryOf: (
1950
+ rows: Array<MonitorProbe>,
1951
+ isScheduled?: boolean,
1952
+ ) => MonitorOverviewProbeSummary = (
1953
+ rows: Array<MonitorProbe>,
1954
+ isScheduled?: boolean,
1955
+ ): MonitorOverviewProbeSummary => {
1956
+ const monitorSteps: MonitorSteps | undefined = running(
1957
+ MonitorType.API,
1958
+ ).monitorSteps;
1959
+
1960
+ return MonitorOverviewProbeUtil.summarizeProbes({
1961
+ monitorProbes: rows,
1962
+ validStepIds: MonitorOverviewProbeUtil.getValidStepIds(monitorSteps),
1963
+ primaryStepId: MonitorOverviewProbeUtil.getPrimaryStepId(monitorSteps),
1964
+ cadenceSeconds: 300,
1965
+ now: NOW,
1966
+ monitoringInterval: "*/5 * * * *",
1967
+ isScheduled: isScheduled,
1968
+ });
1969
+ };
1970
+
1971
+ const FRANKFURT: string = "11111111-1111-4111-8111-111111111111";
1972
+ const VIRGINIA: string = "22222222-2222-4222-8222-222222222222";
1973
+ const SINGAPORE: string = "33333333-3333-4333-8333-333333333333";
1974
+
1975
+ describe("MonitorOverviewPresentationUtil probes that stop checking", () => {
1976
+ /*
1977
+ * Only a probe's own claim moves its nextPingAt, so a probe that lost its
1978
+ * connection keeps a due time that sinks further into the past. The
1979
+ * monitor is still checked on time by the others.
1980
+ */
1981
+ it("one disconnected probe does not make a monitor others check on time overdue", () => {
1982
+ const presentation: MonitorOverviewPresentation = build(
1983
+ running(MonitorType.Website, {
1984
+ probes: summaryOf([
1985
+ probeRow({
1986
+ probeId: FRANKFURT,
1987
+ name: "Frankfurt",
1988
+ resultAgeSeconds: 60,
1989
+ nextPingInSeconds: 240,
1990
+ }),
1991
+ probeRow({
1992
+ probeId: VIRGINIA,
1993
+ name: "Virginia",
1994
+ resultAgeSeconds: 30,
1995
+ nextPingInSeconds: 270,
1996
+ }),
1997
+ probeRow({
1998
+ probeId: SINGAPORE,
1999
+ name: "Singapore",
2000
+ resultAgeSeconds: 3 * DAY,
2001
+ nextPingInSeconds: -3 * DAY,
2002
+ connectionStatus: ProbeConnectionStatus.Disconnected,
2003
+ }),
2004
+ ]),
2005
+ }),
2006
+ );
2007
+
2008
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Running);
2009
+ expect(presentation.freshness).toBe(MonitorCheckFreshness.Fresh);
2010
+ expect(presentation.secondaryBadges).toEqual([]);
2011
+ expect(presentation.pulse.nextAt).toEqual(secondsAgo(-240));
2012
+ expect(presentation.pulse.overdueSeconds).toBeUndefined();
2013
+
2014
+ // The lost probe is still reported where probes are reported.
2015
+ expect(fact(presentation, "probes")).toMatchObject({
2016
+ value: "2 of 3 reporting",
2017
+ secondary: "1 disconnected",
2018
+ tone: "warning",
2019
+ });
2020
+ });
2021
+
2022
+ it("one probe that stopped claiming does not either", () => {
2023
+ const presentation: MonitorOverviewPresentation = build(
2024
+ running(MonitorType.API, {
2025
+ probes: summaryOf([
2026
+ probeRow({
2027
+ probeId: FRANKFURT,
2028
+ name: "Frankfurt",
2029
+ resultAgeSeconds: 60,
2030
+ nextPingInSeconds: 240,
2031
+ }),
2032
+ probeRow({
2033
+ probeId: VIRGINIA,
2034
+ name: "Virginia",
2035
+ resultAgeSeconds: 2 * 3600,
2036
+ nextPingInSeconds: -2 * 3600,
2037
+ }),
2038
+ ]),
2039
+ }),
2040
+ );
2041
+
2042
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Running);
2043
+ expect(presentation.pulse.nextAt).toEqual(secondsAgo(-240));
2044
+ });
2045
+
2046
+ it("is overdue when every probe that is checking is past due", () => {
2047
+ const presentation: MonitorOverviewPresentation = build(
2048
+ running(MonitorType.API, {
2049
+ probes: summaryOf([
2050
+ probeRow({
2051
+ probeId: FRANKFURT,
2052
+ name: "Frankfurt",
2053
+ resultAgeSeconds: 100,
2054
+ nextPingInSeconds: -1200,
2055
+ }),
2056
+ probeRow({
2057
+ probeId: VIRGINIA,
2058
+ name: "Virginia",
2059
+ resultAgeSeconds: 100,
2060
+ nextPingInSeconds: -1100,
2061
+ }),
2062
+ ]),
2063
+ }),
2064
+ );
2065
+
2066
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Overdue);
2067
+ // Past due since the later of the two.
2068
+ expect(presentation.pulse.overdueSeconds).toBe(1100);
2069
+ });
2070
+ });
2071
+
2072
+ describe("MonitorOverviewPresentationUtil paused probes", () => {
2073
+ const MAINTENANCE: MonitorOverviewPresentationInput["pause"] = {
2074
+ isDisabled: false,
2075
+ byManualIncident: false,
2076
+ byScheduledMaintenance: true,
2077
+ };
2078
+
2079
+ it("a paused monitor's probes are neither late nor a fault", () => {
2080
+ const summary: MonitorOverviewProbeSummary = summaryOf(
2081
+ [
2082
+ probeRow({
2083
+ probeId: FRANKFURT,
2084
+ name: "Frankfurt",
2085
+ resultAgeSeconds: 25 * 60,
2086
+ }),
2087
+ probeRow({
2088
+ probeId: VIRGINIA,
2089
+ name: "Virginia",
2090
+ resultAgeSeconds: 25 * 60,
2091
+ isOnline: false,
2092
+ }),
2093
+ ],
2094
+ false,
2095
+ );
2096
+
2097
+ expect(
2098
+ summary.rows.map((row: MonitorOverviewProbeRow) => {
2099
+ return row.health;
2100
+ }),
2101
+ ).toEqual([MonitorOverviewProbeHealth.Down, MonitorOverviewProbeHealth.Up]);
2102
+
2103
+ const presentation: MonitorOverviewPresentation = build(
2104
+ running(MonitorType.API, { pause: MAINTENANCE, probes: summary }),
2105
+ );
2106
+
2107
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Paused);
2108
+ expect(fact(presentation, "probes")).toEqual({
2109
+ key: "probes",
2110
+ label: "Probes",
2111
+ linkKey: "probes",
2112
+ value: "2 enabled",
2113
+ secondary: "Checks paused",
2114
+ tone: "neutral",
2115
+ });
2116
+ });
2117
+
2118
+ it("keeps saying which probes are switched off or disconnected", () => {
2119
+ const presentation: MonitorOverviewPresentation = build(
2120
+ running(MonitorType.API, {
2121
+ pause: {
2122
+ isDisabled: true,
2123
+ byManualIncident: false,
2124
+ byScheduledMaintenance: false,
2125
+ },
2126
+ probes: probes({
2127
+ attachedCount: 4,
2128
+ enabledCount: 3,
2129
+ reportingCount: 0,
2130
+ disabledCount: 1,
2131
+ disconnectedCount: 1,
2132
+ }),
2133
+ }),
2134
+ );
2135
+
2136
+ expect(fact(presentation, "probes")).toMatchObject({
2137
+ value: "3 enabled",
2138
+ secondary: "Checks paused · 1 disabled · 1 disconnected",
2139
+ tone: "neutral",
2140
+ });
2141
+
2142
+ // No probe enabled is still a problem when monitoring resumes.
2143
+ expect(
2144
+ fact(
2145
+ build(
2146
+ running(MonitorType.API, {
2147
+ pause: MAINTENANCE,
2148
+ probes: probes({
2149
+ ...NO_RESULTS,
2150
+ enabledCount: 0,
2151
+ disabledCount: 2,
2152
+ }),
2153
+ }),
2154
+ ),
2155
+ "probes",
2156
+ ),
2157
+ ).toMatchObject({ value: "None enabled", tone: "danger" });
2158
+ });
2159
+ });
2160
+
2161
+ describe("MonitorOverviewPresentationUtil probes that disagree", () => {
2162
+ it("down probes are never neutral in the probes fact", () => {
2163
+ const probesFact: (
2164
+ overrides: Partial<MonitorOverviewProbeSummary>,
2165
+ monitorType?: MonitorType,
2166
+ ) => MonitorOverviewFact = (
2167
+ overrides: Partial<MonitorOverviewProbeSummary>,
2168
+ monitorType?: MonitorType,
2169
+ ): MonitorOverviewFact => {
2170
+ return fact(
2171
+ build(
2172
+ running(monitorType || MonitorType.API, {
2173
+ probes: probes(overrides),
2174
+ }),
2175
+ ),
2176
+ "probes",
2177
+ );
2178
+ };
2179
+
2180
+ expect(
2181
+ probesFact({
2182
+ attachedCount: 3,
2183
+ enabledCount: 3,
2184
+ reportingCount: 3,
2185
+ upCount: 1,
2186
+ downCount: 2,
2187
+ }),
2188
+ ).toMatchObject({
2189
+ value: "3 of 3 reporting",
2190
+ secondary: "2 down",
2191
+ tone: "warning",
2192
+ });
2193
+
2194
+ // Every reporting probe sees it down.
2195
+ expect(
2196
+ probesFact({
2197
+ attachedCount: 3,
2198
+ enabledCount: 3,
2199
+ reportingCount: 2,
2200
+ upCount: 0,
2201
+ downCount: 2,
2202
+ disconnectedCount: 1,
2203
+ }),
2204
+ ).toMatchObject({
2205
+ secondary: "2 down · 1 disconnected",
2206
+ tone: "danger",
2207
+ });
2208
+
2209
+ expect(
2210
+ probesFact(
2211
+ { reportingCount: 2, upCount: 1, downCount: 1 },
2212
+ MonitorType.SyntheticMonitor,
2213
+ ).secondary,
2214
+ ).toBe("1 failing");
2215
+ });
2216
+
2217
+ it("the latest result says the probes disagree instead of quoting whichever reported last", () => {
2218
+ const latest: (
2219
+ monitorType: MonitorType,
2220
+ overrides: Partial<MonitorOverviewProbeSummary>,
2221
+ ) => MonitorOverviewFact = (
2222
+ monitorType: MonitorType,
2223
+ overrides: Partial<MonitorOverviewProbeSummary>,
2224
+ ): MonitorOverviewFact => {
2225
+ return fact(
2226
+ build(running(monitorType, { probes: probes(overrides) })),
2227
+ "latest-result",
2228
+ );
2229
+ };
2230
+
2231
+ expect(
2232
+ latest(MonitorType.API, {
2233
+ attachedCount: 3,
2234
+ enabledCount: 3,
2235
+ reportingCount: 3,
2236
+ upCount: 1,
2237
+ downCount: 2,
2238
+ }),
2239
+ ).toEqual({
2240
+ key: "latest-result",
2241
+ label: "Latest result",
2242
+ value: "Mixed · 1 up, 2 down",
2243
+ tone: "warning",
2244
+ });
2245
+ expect(
2246
+ latest(MonitorType.SyntheticMonitor, { upCount: 1, downCount: 1 }).value,
2247
+ ).toBe("Mixed · 1 passed, 1 failed");
2248
+
2249
+ // Agreement keeps the newest result.
2250
+ expect(latest(MonitorType.API, { upCount: 2, downCount: 0 }).value).toBe(
2251
+ "Up · 120 ms · HTTP 200",
2252
+ );
2253
+ });
2254
+
2255
+ it("from real probe rows", () => {
2256
+ const presentation: MonitorOverviewPresentation = build(
2257
+ running(MonitorType.API, {
2258
+ probes: summaryOf([
2259
+ probeRow({
2260
+ probeId: FRANKFURT,
2261
+ name: "Frankfurt",
2262
+ resultAgeSeconds: 20,
2263
+ }),
2264
+ probeRow({
2265
+ probeId: VIRGINIA,
2266
+ name: "Virginia",
2267
+ resultAgeSeconds: 40,
2268
+ isOnline: false,
2269
+ }),
2270
+ probeRow({
2271
+ probeId: SINGAPORE,
2272
+ name: "Singapore",
2273
+ resultAgeSeconds: 50,
2274
+ isOnline: false,
2275
+ }),
2276
+ ]),
2277
+ }),
2278
+ );
2279
+
2280
+ expect(fact(presentation, "latest-result").value).toBe(
2281
+ "Mixed · 1 up, 2 down",
2282
+ );
2283
+ expect(fact(presentation, "probes")).toMatchObject({
2284
+ value: "3 of 3 reporting",
2285
+ secondary: "2 down",
2286
+ tone: "warning",
2287
+ });
2288
+ });
2289
+ });
2290
+
2291
+ /*
2292
+ * The worker stamps telemetryMonitorLastMonitorAt when it QUEUES an
2293
+ * evaluation. Only the evaluation log says one ran.
2294
+ */
2295
+ describe("MonitorOverviewPresentationUtil telemetry evaluations", () => {
2296
+ it("a stuck evaluator is overdue even while the scheduler keeps stamping", () => {
2297
+ const presentation: MonitorOverviewPresentation = build(
2298
+ running(MonitorType.Logs, {
2299
+ monitoringInterval: "* * * * *",
2300
+ telemetry: {
2301
+ lastScheduledAt: secondsAgo(5),
2302
+ nextEvaluationAt: secondsAgo(-55),
2303
+ },
2304
+ latestEvaluationAt: secondsAgo(20 * 60),
2305
+ evaluationStatus: "loaded",
2306
+ }),
2307
+ );
2308
+
2309
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Overdue);
2310
+ expect(presentation.pulse).toMatchObject({
2311
+ label: "Last evaluated",
2312
+ at: secondsAgo(20 * 60),
2313
+ });
2314
+ expect(presentation.explanation).toBe(
2315
+ "No evaluation for 20m, but this monitor is evaluated every minute.",
2316
+ );
2317
+ });
2318
+
2319
+ it("without the evaluation log, the scheduler's stamp is called what it is", () => {
2320
+ const statuses: Array<"loading" | "error" | "forbidden"> = [
2321
+ "loading",
2322
+ "error",
2323
+ "forbidden",
2324
+ ];
2325
+
2326
+ for (const evaluationStatus of statuses) {
2327
+ const presentation: MonitorOverviewPresentation = build(
2328
+ running(MonitorType.Kubernetes, {
2329
+ telemetry: {
2330
+ lastScheduledAt: secondsAgo(30),
2331
+ nextEvaluationAt: secondsAgo(-270),
2332
+ },
2333
+ latestEvaluationAt: undefined,
2334
+ evaluationStatus: evaluationStatus,
2335
+ }),
2336
+ );
2337
+
2338
+ expect({
2339
+ evaluationStatus: evaluationStatus,
2340
+ runState: presentation.runState,
2341
+ label: presentation.pulse.label,
2342
+ at: presentation.pulse.at,
2343
+ }).toEqual({
2344
+ evaluationStatus: evaluationStatus,
2345
+ runState: MonitorOverviewRunState.Running,
2346
+ label: "Last scheduled",
2347
+ at: secondsAgo(30),
2348
+ });
2349
+ }
2350
+
2351
+ // The old name for the stamp is still read.
2352
+ expect(
2353
+ build(
2354
+ running(MonitorType.Logs, {
2355
+ telemetry: { lastEvaluatedAt: secondsAgo(40) },
2356
+ latestEvaluationAt: undefined,
2357
+ evaluationStatus: undefined,
2358
+ }),
2359
+ ).pulse,
2360
+ ).toMatchObject({ label: "Last scheduled", at: secondsAgo(40) });
2361
+ });
2362
+
2363
+ /*
2364
+ * The log still holds every evaluation of a monitor younger than a day,
2365
+ * so its emptiness means none has completed: the worker's stamp, which it
2366
+ * writes when it only queues one, must not end the wait.
2367
+ */
2368
+ it("never evaluated: waiting while young, overdue after", () => {
2369
+ const young: MonitorOverviewPresentation = build(
2370
+ running(MonitorType.Metrics, {
2371
+ createdAt: secondsAgo(120),
2372
+ telemetry: {
2373
+ lastScheduledAt: secondsAgo(30),
2374
+ nextEvaluationAt: secondsAgo(-270),
2375
+ },
2376
+ latestEvaluationAt: undefined,
2377
+ }),
2378
+ );
2379
+
2380
+ expect(young.runState).toBe(MonitorOverviewRunState.AwaitingFirstData);
2381
+ expect(young.pulse).toMatchObject({
2382
+ label: "Last evaluated",
2383
+ at: undefined,
2384
+ emptyText: "Not evaluated yet",
2385
+ });
2386
+
2387
+ const stuck: MonitorOverviewPresentation = build(
2388
+ running(MonitorType.Metrics, {
2389
+ createdAt: secondsAgo(5 * 3600),
2390
+ latestEvaluationAt: undefined,
2391
+ }),
2392
+ );
2393
+
2394
+ expect(stuck.runState).toBe(MonitorOverviewRunState.Overdue);
2395
+ expect(stuck.isNeverReported).toBe(true);
2396
+ expect(stuck.badge).toEqual({ text: "No results yet", tone: "warning" });
2397
+ expect(stuck.headline).toEqual({ text: "No evaluation has completed yet" });
2398
+ expect(stuck.explanation).toBe(
2399
+ "No evaluation has run since this monitor was created 5h ago.",
2400
+ );
2401
+
2402
+ // The worker stamps every evaluation it queues, so none was ever queued.
2403
+ const neverQueued: MonitorOverviewPresentation = build(
2404
+ running(MonitorType.Metrics, {
2405
+ createdAt: secondsAgo(10 * DAY),
2406
+ telemetry: {},
2407
+ latestEvaluationAt: undefined,
2408
+ }),
2409
+ );
2410
+
2411
+ expect(neverQueued.runState).toBe(MonitorOverviewRunState.Overdue);
2412
+ expect(neverQueued.isNeverReported).toBe(true);
2413
+ expect(neverQueued.explanation).toBe(
2414
+ "No evaluation has run since this monitor was created 10d ago.",
2415
+ );
2416
+ });
2417
+ });
2418
+
2419
+ /*
2420
+ * MonitorLog keeps a row for a day (MonitorLogUtil.DEFAULT_RETENTION_DAYS)
2421
+ * and drops it with its daily partition, so a row is gone within about two
2422
+ * days. For a monitor older than that, an empty log is not "never
2423
+ * evaluated".
2424
+ */
2425
+ describe("MonitorOverviewPresentationUtil evaluation log retention", () => {
2426
+ it("an empty log is judged against the day every row is guaranteed to survive", () => {
2427
+ expect(MONITOR_LOG_MINIMUM_RETENTION_SECONDS).toBe(DAY);
2428
+ });
2429
+
2430
+ it("an old monitor evaluated every minute with nothing in the log's last day is overdue, not 'never evaluated'", () => {
2431
+ const presentation: MonitorOverviewPresentation = build(
2432
+ running(MonitorType.Metrics, {
2433
+ monitoringInterval: "* * * * *",
2434
+ createdAt: secondsAgo(90 * DAY),
2435
+ // The worker keeps queueing; nothing completes.
2436
+ telemetry: {
2437
+ lastScheduledAt: secondsAgo(5),
2438
+ nextEvaluationAt: secondsAgo(-55),
2439
+ },
2440
+ latestEvaluationAt: undefined,
2441
+ evaluationStatus: "loaded",
2442
+ }),
2443
+ );
2444
+
2445
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Overdue);
2446
+ expect(presentation.isNeverReported).toBe(false);
2447
+ // What it last measured leads, as for any overdue monitor with results.
2448
+ expect(presentation.badge.text).toBe("Operational");
2449
+ expect(presentation.headline).toEqual({
2450
+ text: "Operational",
2451
+ since: secondsAgo(3 * DAY + 4 * 3600),
2452
+ });
2453
+ expect(presentation.secondaryBadges).toEqual([
2454
+ { text: "Checks overdue", tone: "warning" },
2455
+ ]);
2456
+ expect(presentation.tone).toBe("warning");
2457
+ expect(presentation.explanation).toBe(
2458
+ "No evaluation recorded in the last day, but this monitor is evaluated every minute.",
2459
+ );
2460
+ expect(presentation.lastKnownStatus).toBeUndefined();
2461
+ expect(MonitorOverviewPresentationUtil.getUptimeCaveat(presentation)).toBe(
2462
+ null,
2463
+ );
2464
+ expect(presentation.sections.showUptime).toBe(true);
2465
+ // How late is unknown: the log only says "not in the last day".
2466
+ expect(presentation.pulse).toEqual({
2467
+ label: "Last evaluated",
2468
+ at: undefined,
2469
+ emptyText: "Not evaluated in the last day",
2470
+ cadenceText: "Every minute",
2471
+ nextAt: secondsAgo(-55),
2472
+ overdueSeconds: undefined,
2473
+ isUnavailable: false,
2474
+ });
2475
+ });
2476
+
2477
+ it("a weekly monitor between evaluations shows its last scheduled run, not an overdue or a wait", () => {
2478
+ const presentation: MonitorOverviewPresentation = build(
2479
+ running(MonitorType.Kubernetes, {
2480
+ // Fridays at noon; now is Monday noon.
2481
+ monitoringInterval: "0 12 * * 5",
2482
+ createdAt: secondsAgo(90 * DAY),
2483
+ telemetry: {
2484
+ lastScheduledAt: secondsAgo(3 * DAY),
2485
+ nextEvaluationAt: secondsAgo(-4 * DAY),
2486
+ },
2487
+ latestEvaluationAt: undefined,
2488
+ evaluationStatus: "loaded",
2489
+ }),
2490
+ );
2491
+
2492
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Running);
2493
+ expect(presentation.isNeverReported).toBe(false);
2494
+ expect(presentation.badge.text).toBe("Operational");
2495
+ expect(presentation.sections.showUptime).toBe(true);
2496
+ expect(presentation.pulse).toMatchObject({
2497
+ label: "Last scheduled",
2498
+ at: secondsAgo(3 * DAY),
2499
+ nextAt: secondsAgo(-4 * DAY),
2500
+ });
2501
+ });
2502
+
2503
+ it("a weekly monitor a day or two old, whose first evaluation has left the log, is not still waiting", () => {
2504
+ // Saturdays at 13:00; created just before Saturday's run, 47 hours ago.
2505
+ const presentation: MonitorOverviewPresentation = build(
2506
+ running(MonitorType.Logs, {
2507
+ monitoringInterval: "0 13 * * 6",
2508
+ createdAt: secondsAgo(47 * 3600 + 300),
2509
+ telemetry: {
2510
+ lastScheduledAt: secondsAgo(47 * 3600),
2511
+ nextEvaluationAt: secondsAgo(-(5 * DAY + 3600)),
2512
+ },
2513
+ latestEvaluationAt: undefined,
2514
+ evaluationStatus: "loaded",
2515
+ }),
2516
+ );
2517
+
2518
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Running);
2519
+ expect(presentation.pulse).toMatchObject({
2520
+ label: "Last scheduled",
2521
+ at: secondsAgo(47 * 3600),
2522
+ });
2523
+ });
2524
+
2525
+ it("a schedule with gaps is not overdue through the weekend, and is once Monday's runs are missing", () => {
2526
+ // Every 5 minutes, 09:00 to 17:55 UTC, Monday to Friday.
2527
+ const businessHours: (now: Date) => MonitorOverviewPresentation = (
2528
+ now: Date,
2529
+ ): MonitorOverviewPresentation => {
2530
+ return build(
2531
+ running(MonitorType.Logs, {
2532
+ now: now,
2533
+ monitoringInterval: "*/5 9-17 * * 1-5",
2534
+ createdAt: secondsAgo(90 * DAY),
2535
+ telemetry: {
2536
+ // Friday's last run; the worker queues nothing until Monday.
2537
+ lastScheduledAt: new Date("2026-09-18T17:55:00.000Z"),
2538
+ nextEvaluationAt: new Date("2026-09-21T09:00:00.000Z"),
2539
+ },
2540
+ latestEvaluationAt: undefined,
2541
+ evaluationStatus: "loaded",
2542
+ }),
2543
+ );
2544
+ };
2545
+
2546
+ /*
2547
+ * Friday's rows expire through the weekend. An empty log then proves
2548
+ * only that nothing ran in the last day, which is true of a weekend, so
2549
+ * it is judged from a day ago and not from the two days a row can
2550
+ * outlive its retention.
2551
+ */
2552
+ for (const weekend of [
2553
+ "2026-09-19T20:00:00.000Z",
2554
+ "2026-09-20T12:00:00.000Z",
2555
+ "2026-09-20T23:00:00.000Z",
2556
+ ]) {
2557
+ const presentation: MonitorOverviewPresentation = businessHours(
2558
+ new Date(weekend),
2559
+ );
2560
+
2561
+ expect({ weekend, runState: presentation.runState }).toEqual({
2562
+ weekend,
2563
+ runState: MonitorOverviewRunState.Running,
2564
+ });
2565
+ expect(presentation.pulse.label).toBe("Last scheduled");
2566
+ }
2567
+
2568
+ const mondayMorning: MonitorOverviewPresentation = businessHours(
2569
+ new Date("2026-09-21T08:00:00.000Z"),
2570
+ );
2571
+
2572
+ expect(mondayMorning.runState).toBe(MonitorOverviewRunState.Running);
2573
+ expect(mondayMorning.pulse.label).toBe("Last scheduled");
2574
+
2575
+ const mondayTen: MonitorOverviewPresentation = businessHours(
2576
+ new Date("2026-09-21T10:00:00.000Z"),
2577
+ );
2578
+
2579
+ expect(mondayTen.runState).toBe(MonitorOverviewRunState.Overdue);
2580
+ expect(mondayTen.isNeverReported).toBe(false);
2581
+ });
2582
+
2583
+ it("a monitor a day or two old with nothing in the log's last day is not called 'never evaluated'", () => {
2584
+ const presentation: MonitorOverviewPresentation = build(
2585
+ running(MonitorType.Metrics, {
2586
+ monitoringInterval: "* * * * *",
2587
+ createdAt: secondsAgo(36 * 3600),
2588
+ latestEvaluationAt: undefined,
2589
+ }),
2590
+ );
2591
+
2592
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Overdue);
2593
+ expect(presentation.isNeverReported).toBe(false);
2594
+ expect(presentation.explanation).toBe(
2595
+ "No evaluation recorded in the last day, but this monitor is evaluated every minute.",
2596
+ );
2597
+ });
2598
+
2599
+ it("an impossible schedule is judged as the every-minute one it gets, without CronTab's search", () => {
2600
+ // The 30th of February: CronTab takes over a second to find no run.
2601
+ const search: MockInstance<typeof CronTab.getNextExecutionTimes> =
2602
+ jest.spyOn(CronTab, "getNextExecutionTimes");
2603
+
2604
+ try {
2605
+ const probeCheck: MonitorOverviewPresentation = build(
2606
+ running(MonitorType.API, { monitoringInterval: "0 0 30 2 *" }),
2607
+ );
2608
+ const telemetry: MonitorOverviewPresentation = build(
2609
+ running(MonitorType.Logs, {
2610
+ monitoringInterval: "0 0 30 2 *",
2611
+ createdAt: secondsAgo(90 * DAY),
2612
+ latestEvaluationAt: undefined,
2613
+ }),
2614
+ );
2615
+
2616
+ expect(probeCheck.runState).toBe(MonitorOverviewRunState.Running);
2617
+ expect(probeCheck.pulse.cadenceText).toBe("Every minute");
2618
+ // Nothing in the last day, on a schedule the scheduler runs every minute.
2619
+ expect(telemetry.runState).toBe(MonitorOverviewRunState.Overdue);
2620
+ expect(telemetry.explanation).toBe(
2621
+ "No evaluation recorded in the last day, but this monitor is evaluated every minute.",
2622
+ );
2623
+ expect(search).not.toHaveBeenCalled();
2624
+ } finally {
2625
+ search.mockRestore();
2626
+ }
2627
+ });
2628
+
2629
+ it("paused, it names the last scheduled run instead of 'not evaluated yet'", () => {
2630
+ const presentation: MonitorOverviewPresentation = build(
2631
+ running(MonitorType.Logs, {
2632
+ createdAt: secondsAgo(90 * DAY),
2633
+ pause: {
2634
+ isDisabled: true,
2635
+ byManualIncident: false,
2636
+ byScheduledMaintenance: false,
2637
+ },
2638
+ telemetry: { lastScheduledAt: secondsAgo(5 * DAY) },
2639
+ latestEvaluationAt: undefined,
2640
+ }),
2641
+ );
2642
+
2643
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Paused);
2644
+ expect(presentation.pulse).toMatchObject({
2645
+ label: "Last scheduled",
2646
+ at: secondsAgo(5 * DAY),
2647
+ });
2648
+ });
2649
+ });
2650
+
2651
+ describe("MonitorOverviewPresentationUtil network device evaluations", () => {
2652
+ const pulseFor: (
2653
+ overrides: Partial<MonitorOverviewPresentationInput>,
2654
+ ) => MonitorOverviewPresentation["pulse"] = (
2655
+ overrides: Partial<MonitorOverviewPresentationInput>,
2656
+ ): MonitorOverviewPresentation["pulse"] => {
2657
+ return build(running(MonitorType.NetworkDevice, overrides)).pulse;
2658
+ };
2659
+
2660
+ it("an unreadable log is unavailable, never 'not evaluated yet'", () => {
2661
+ const statuses: Array<"error" | "forbidden"> = ["error", "forbidden"];
2662
+
2663
+ for (const evaluationStatus of statuses) {
2664
+ expect(
2665
+ pulseFor({
2666
+ latestEvaluationAt: undefined,
2667
+ evaluationStatus: evaluationStatus,
2668
+ }),
2669
+ ).toEqual({
2670
+ label: "Last evaluated",
2671
+ at: undefined,
2672
+ emptyText: "Not evaluated yet",
2673
+ isUnavailable: true,
2674
+ });
2675
+ }
2676
+ });
2677
+
2678
+ it("a log still loading says so", () => {
2679
+ expect(
2680
+ pulseFor({ latestEvaluationAt: undefined, evaluationStatus: "loading" }),
2681
+ ).toEqual({
2682
+ label: "Last evaluated",
2683
+ at: undefined,
2684
+ emptyText: "Loading…",
2685
+ isUnavailable: false,
2686
+ });
2687
+ });
2688
+
2689
+ it("a loaded, empty log is waiting for the first poll, not a measured status", () => {
2690
+ const presentation: MonitorOverviewPresentation = build(
2691
+ running(MonitorType.NetworkDevice, {
2692
+ createdAt: secondsAgo(2 * 3600),
2693
+ latestEvaluationAt: undefined,
2694
+ evaluationStatus: "loaded",
2695
+ }),
2696
+ );
2697
+
2698
+ expect(presentation.runState).toBe(
2699
+ MonitorOverviewRunState.AwaitingFirstData,
2700
+ );
2701
+ expect(presentation.badge).toEqual({
2702
+ text: "Waiting for data",
2703
+ tone: "info",
2704
+ });
2705
+ expect(presentation.headline.text).toBe("Waiting for the first poll");
2706
+ expect(presentation.lastKnownStatus).toBe(
2707
+ "Last recorded status: Operational",
2708
+ );
2709
+ expect(presentation.sections.showUptime).toBe(false);
2710
+ expect(presentation.pulse.isUnavailable).toBe(false);
2711
+ });
2712
+
2713
+ /*
2714
+ * Its polling stopped (turned off, or its probe went offline) more than
2715
+ * a day ago, and the log's rows have expired since. It has been judged
2716
+ * for months: that history, and its status, stay.
2717
+ */
2718
+ it("an older device with nothing in the log's last day keeps its status and history", () => {
2719
+ const presentation: MonitorOverviewPresentation = build(
2720
+ running(MonitorType.NetworkDevice, {
2721
+ createdAt: secondsAgo(90 * DAY),
2722
+ currentStatus: OFFLINE,
2723
+ latestEvaluationAt: undefined,
2724
+ evaluationStatus: "loaded",
2725
+ }),
2726
+ );
2727
+
2728
+ expect(presentation.runState).toBe(MonitorOverviewRunState.Running);
2729
+ expect(presentation.badge).toMatchObject({
2730
+ text: "Offline",
2731
+ tone: "danger",
2732
+ });
2733
+ expect(presentation.headline).toEqual({
2734
+ text: "Offline",
2735
+ since: secondsAgo(3 * DAY + 4 * 3600),
2736
+ });
2737
+ expect(presentation.explanation).toBe(
2738
+ "No poll or trap from this device has been evaluated in the last day, so the status shown is the last one recorded.",
2739
+ );
2740
+ expect(presentation.lastKnownStatus).toBeUndefined();
2741
+ expect(presentation.sections.showUptime).toBe(true);
2742
+ expect(presentation.pulse).toEqual({
2743
+ label: "Last evaluated",
2744
+ at: undefined,
2745
+ emptyText: "Not evaluated in the last day",
2746
+ isUnavailable: false,
2747
+ });
2748
+
2749
+ // A device that is being evaluated needs no such note.
2750
+ expect(build(running(MonitorType.NetworkDevice)).explanation).toBe(
2751
+ undefined,
2752
+ );
2753
+ });
2754
+
2755
+ it("a device just under a day old with an empty log is still waiting", () => {
2756
+ expect(
2757
+ build(
2758
+ running(MonitorType.NetworkDevice, {
2759
+ createdAt: secondsAgo(DAY - 60),
2760
+ latestEvaluationAt: undefined,
2761
+ evaluationStatus: "loaded",
2762
+ }),
2763
+ ).runState,
2764
+ ).toBe(MonitorOverviewRunState.AwaitingFirstData);
2765
+ expect(
2766
+ build(
2767
+ running(MonitorType.NetworkDevice, {
2768
+ createdAt: secondsAgo(DAY + 60),
2769
+ latestEvaluationAt: undefined,
2770
+ evaluationStatus: "loaded",
2771
+ }),
2772
+ ).runState,
2773
+ ).toBe(MonitorOverviewRunState.Running);
2774
+ });
2775
+ });
2776
+
2777
+ /*
2778
+ * The heartbeat and agent checks judge a monitor that never reported as if
2779
+ * its last signal came at creation, so it can be Offline with an incident
2780
+ * open while the page still waits for the first signal.
2781
+ */
2782
+ describe("MonitorOverviewPresentationUtil push monitors the server has judged", () => {
2783
+ it("a heartbeat judged offline leads with that verdict and keeps the setup", () => {
2784
+ const presentation: MonitorOverviewPresentation = build(
2785
+ running(MonitorType.IncomingRequest, {
2786
+ currentStatus: OFFLINE,
2787
+ createdAt: secondsAgo(3 * DAY),
2788
+ heartbeat: {},
2789
+ }),
2790
+ );
2791
+
2792
+ expect(presentation.runState).toBe(
2793
+ MonitorOverviewRunState.AwaitingFirstData,
2794
+ );
2795
+ expect(presentation.tone).toBe("danger");
2796
+ expect(presentation.badge).toEqual({
2797
+ text: "Waiting for data",
2798
+ tone: "info",
2799
+ });
2800
+ expect(presentation.secondaryBadges).toEqual([
2801
+ { text: "Offline", tone: "danger" },
2802
+ ]);
2803
+ expect(presentation.headline).toEqual({
2804
+ text: "No heartbeat has arrived since this monitor was created 3d ago",
2805
+ });
2806
+ expect(presentation.explanation).toBe(
2807
+ "Send a GET or POST request to this monitor's heartbeat URL to start tracking it.",
2808
+ );
2809
+ expect(presentation.callToAction).toEqual({
2810
+ text: "Setup instructions",
2811
+ linkKey: "documentation",
2812
+ });
2813
+ expect(presentation.lastKnownStatus).toBeUndefined();
2814
+ expect(presentation.sections.showUptime).toBe(true);
2815
+ expect(presentation.sections.setup).toBe(
2816
+ MonitorOverviewSetupKind.HeartbeatUrl,
2817
+ );
2818
+ });
2819
+
2820
+ it("an email or agent monitor judged degraded or offline leads with its tone", () => {
2821
+ const email: MonitorOverviewPresentation = build(
2822
+ running(MonitorType.IncomingEmail, {
2823
+ currentStatus: DEGRADED,
2824
+ createdAt: secondsAgo(2 * 3600),
2825
+ email: {},
2826
+ }),
2827
+ );
2828
+
2829
+ expect(email.tone).toBe("warning");
2830
+ expect(email.secondaryBadges).toEqual([
2831
+ { text: "Degraded", tone: "warning" },
2832
+ ]);
2833
+ expect(email.headline.text).toBe(
2834
+ "No email has arrived since this monitor was created 2h ago",
2835
+ );
2836
+
2837
+ const agent: MonitorOverviewPresentation = build(
2838
+ running(MonitorType.Server, {
2839
+ currentStatus: OFFLINE,
2840
+ createdAt: secondsAgo(DAY),
2841
+ agent: {},
2842
+ }),
2843
+ );
2844
+
2845
+ expect(agent.headline.text).toBe(
2846
+ "The agent has not reported since this monitor was created 1d ago",
2847
+ );
2848
+ expect(agent.sections.showUptime).toBe(true);
2849
+ expect(agent.sections.setup).toBe(MonitorOverviewSetupKind.ServerAgent);
2850
+ });
2851
+
2852
+ it("history shows once the missing-signal window since creation has passed", () => {
2853
+ const showsUptime: (
2854
+ overrides: Partial<MonitorOverviewPresentationInput>,
2855
+ ) => boolean = (
2856
+ overrides: Partial<MonitorOverviewPresentationInput>,
2857
+ ): boolean => {
2858
+ return build(
2859
+ running(MonitorType.IncomingEmail, { email: {}, ...overrides }),
2860
+ ).sections.showUptime;
2861
+ };
2862
+
2863
+ const steps: MonitorSteps = missingWindowSteps(CheckOn.EmailReceivedAt, 30);
2864
+
2865
+ expect(
2866
+ showsUptime({ monitorSteps: steps, createdAt: secondsAgo(31 * 60) }),
2867
+ ).toBe(true);
2868
+ expect(
2869
+ showsUptime({ monitorSteps: steps, createdAt: secondsAgo(29 * 60) }),
2870
+ ).toBe(false);
2871
+ // No missing-email criterion: nothing will judge it.
2872
+ expect(showsUptime({ createdAt: secondsAgo(DAY) })).toBe(false);
2873
+
2874
+ // Operational means nothing has said otherwise: calm, not a verdict.
2875
+ const calm: MonitorOverviewPresentation = build(
2876
+ running(MonitorType.IncomingEmail, {
2877
+ email: {},
2878
+ monitorSteps: steps,
2879
+ createdAt: secondsAgo(31 * 60),
2880
+ }),
2881
+ );
2882
+
2883
+ expect(calm.tone).toBe("info");
2884
+ expect(calm.secondaryBadges).toEqual([]);
2885
+ expect(calm.headline.text).toBe("Waiting for the first email");
2886
+ });
2887
+
2888
+ it("an agent's window is three minutes, and only with an Is Online criterion", () => {
2889
+ const isOnlineSteps: MonitorSteps = stepsOf({
2890
+ monitorCriteria: {
2891
+ data: {
2892
+ monitorCriteriaInstanceArray: [
2893
+ {
2894
+ data: {
2895
+ filters: [
2896
+ {
2897
+ checkOn: CheckOn.IsOnline,
2898
+ filterType: FilterType.False,
2899
+ },
2900
+ ],
2901
+ },
2902
+ },
2903
+ ],
2904
+ },
2905
+ },
2906
+ });
2907
+ const showsUptime: (
2908
+ overrides: Partial<MonitorOverviewPresentationInput>,
2909
+ ) => boolean = (
2910
+ overrides: Partial<MonitorOverviewPresentationInput>,
2911
+ ): boolean => {
2912
+ return build(running(MonitorType.Server, { agent: {}, ...overrides }))
2913
+ .sections.showUptime;
2914
+ };
2915
+
2916
+ expect(
2917
+ showsUptime({ monitorSteps: isOnlineSteps, createdAt: secondsAgo(200) }),
2918
+ ).toBe(true);
2919
+ expect(
2920
+ showsUptime({ monitorSteps: isOnlineSteps, createdAt: secondsAgo(170) }),
2921
+ ).toBe(false);
2922
+ expect(showsUptime({ createdAt: secondsAgo(DAY) })).toBe(false);
2923
+ });
2924
+ });
2925
+
2926
+ describe("MonitorOverviewPresentationUtil.getUptimeCaveat", () => {
2927
+ const caveatOf: (input: MonitorOverviewPresentationInput) => string | null = (
2928
+ input: MonitorOverviewPresentationInput,
2929
+ ): string | null => {
2930
+ return MonitorOverviewPresentationUtil.getUptimeCaveat(build(input));
2931
+ };
2932
+
2933
+ it("names the unmeasured time per run state", () => {
2934
+ const rows: Array<
2935
+ [string, MonitorOverviewPresentationInput, string | null]
2936
+ > = [
2937
+ ["running", running(MonitorType.API), null],
2938
+ ["disabled", SCENARIOS[1]!.input, "paused"],
2939
+ ["paused by an incident", SCENARIOS[2]!.input, "paused"],
2940
+ ["no probe enabled", SCENARIOS[4]!.input, "not-checking"],
2941
+ ["every probe disconnected", SCENARIOS[5]!.input, "not-checking"],
2942
+ ["no criteria", SCENARIOS[6]!.input, "not-checking"],
2943
+ [
2944
+ "overdue with results",
2945
+ running(MonitorType.API, {
2946
+ probes: probes({ lastResultAt: secondsAgo(2 * 3600) }),
2947
+ }),
2948
+ null,
2949
+ ],
2950
+ [
2951
+ "never reported",
2952
+ running(MonitorType.API, {
2953
+ createdAt: secondsAgo(2 * DAY),
2954
+ probes: probes({ ...NO_RESULTS, nextCheckAt: undefined }),
2955
+ }),
2956
+ "no-results",
2957
+ ],
2958
+ [
2959
+ "judged heartbeat",
2960
+ running(MonitorType.IncomingRequest, {
2961
+ currentStatus: OFFLINE,
2962
+ heartbeat: {},
2963
+ }),
2964
+ null,
2965
+ ],
2966
+ /*
2967
+ * Months of evaluations back these windows; the log just no longer
2968
+ * holds them.
2969
+ */
2970
+ [
2971
+ "telemetry with nothing in its evaluation log's last day",
2972
+ running(MonitorType.Metrics, {
2973
+ monitoringInterval: "* * * * *",
2974
+ createdAt: secondsAgo(90 * DAY),
2975
+ latestEvaluationAt: undefined,
2976
+ }),
2977
+ null,
2978
+ ],
2979
+ ];
2980
+
2981
+ for (const [name, input, expected] of rows) {
2982
+ expect({ name: name, caveat: caveatOf(input) }).toEqual({
2983
+ name: name,
2984
+ caveat: expected,
2985
+ });
2986
+ }
2987
+ });
2988
+
2989
+ it("a manual monitor has no checks to pause", () => {
2990
+ expect(
2991
+ caveatOf(
2992
+ running(MonitorType.Manual, {
2993
+ pause: {
2994
+ isDisabled: false,
2995
+ byManualIncident: true,
2996
+ byScheduledMaintenance: true,
2997
+ },
2998
+ }),
2999
+ ),
3000
+ ).toBeNull();
3001
+ });
3002
+ });
3003
+
3004
+ describe("MonitorOverviewPresentationUtil expiry wording", () => {
3005
+ afterEach(() => {
3006
+ // setUserTimezone is process-wide static state; never leak it.
3007
+ OneUptimeDate.setUserTimezone(null);
3008
+ });
3009
+
3010
+ const expiry: (seconds: number) => MonitorOverviewFact = (
3011
+ seconds: number,
3012
+ ): MonitorOverviewFact => {
3013
+ return MonitorOverviewPresentationUtil.getExpiryFact({
3014
+ key: "certificate-expiry",
3015
+ expiresAt: new Date(NOW.getTime() + seconds * 1000),
3016
+ isValidCertificate: true,
3017
+ now: NOW,
3018
+ });
3019
+ };
3020
+
3021
+ it("hours within a day, either way", () => {
3022
+ OneUptimeDate.setUserTimezone(Timezone.UTC);
3023
+
3024
+ const cases: Array<[number, string]> = [
3025
+ [20 * 3600, "Expires in 20 hours"],
3026
+ [3600, "Expires in 1 hour"],
3027
+ [20 * 60, "Expires in 20 minutes"],
3028
+ [5, "Expires in 1 minute"],
3029
+ [-5, "Expired 1 minute ago"],
3030
+ [-3600, "Expired 1 hour ago"],
3031
+ [-23 * 3600, "Expired 23 hours ago"],
3032
+ ];
3033
+
3034
+ for (const [seconds, value] of cases) {
3035
+ expect({ seconds: seconds, fact: expiry(seconds) }).toMatchObject({
3036
+ seconds: seconds,
3037
+ fact: { value: value, tone: "danger" },
3038
+ });
3039
+ }
3040
+ });
3041
+
3042
+ it("calendar days beyond that, in the zone the date is printed in", () => {
3043
+ OneUptimeDate.setUserTimezone(Timezone.UTC);
3044
+
3045
+ // Noon on the 21st to midnight on the 23rd: two dates away.
3046
+ expect(expiry(36 * 3600)).toMatchObject({
3047
+ value: "in 2 days",
3048
+ tone: "danger",
3049
+ });
3050
+ // Midnight on the 19th, seen at noon on the 21st: two dates back.
3051
+ expect(expiry(-60 * 3600).value).toBe("Expired 2 days ago");
3052
+ // The tone still counts whole days left: 7 days and 20 hours is danger.
3053
+ expect(expiry(7 * DAY + 20 * 3600)).toMatchObject({
3054
+ value: "in 8 days",
3055
+ tone: "danger",
3056
+ });
3057
+
3058
+ // 08:00 on the 21st to 20:00 on the 22nd in New York: one date away.
3059
+ OneUptimeDate.setUserTimezone(Timezone.AmericaNew_York);
3060
+
3061
+ const newYork: MonitorOverviewFact = expiry(36 * 3600);
3062
+
3063
+ expect(newYork.value).toBe("in 1 day");
3064
+ expect(newYork.secondary).toBe("Sep 22, 2026");
3065
+ });
3066
+ });
3067
+
3068
+ /*
3069
+ * `now` is the page's single clock, corrected to the server's by the
3070
+ * caller. Nothing may read the machine clock behind its back, or a fast
3071
+ * browser clock would make every check look late.
3072
+ */
3073
+ describe("MonitorOverviewPresentationUtil clock", () => {
3074
+ afterEach(() => {
3075
+ jest.useRealTimers();
3076
+ });
3077
+
3078
+ it("reads no clock but input.now", () => {
3079
+ const inputs: Array<MonitorOverviewPresentationInput> = SCENARIOS.map(
3080
+ (scenario: Scenario) => {
3081
+ return scenario.input;
3082
+ },
3083
+ );
3084
+ const before: Array<MonitorOverviewPresentation> = inputs.map(
3085
+ (input: MonitorOverviewPresentationInput) => {
3086
+ return build(input);
3087
+ },
3088
+ );
3089
+
3090
+ jest.useFakeTimers();
3091
+ // Seven minutes fast, and three days ahead besides.
3092
+ jest.setSystemTime(new Date(NOW.getTime() + (7 * 60 + 3 * DAY) * 1000));
3093
+
3094
+ expect(
3095
+ inputs.map((input: MonitorOverviewPresentationInput) => {
3096
+ return build(input);
3097
+ }),
3098
+ ).toEqual(before);
3099
+ });
3100
+ });