@oneuptime/common 12.0.13 → 12.0.14

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 (376) hide show
  1. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +14 -0
  2. package/Models/DatabaseModels/UserCall.ts +94 -3
  3. package/Models/DatabaseModels/UserEmail.ts +94 -3
  4. package/Models/DatabaseModels/UserIncomingCallNumber.ts +94 -3
  5. package/Models/DatabaseModels/UserPush.ts +37 -0
  6. package/Models/DatabaseModels/UserSMS.ts +94 -3
  7. package/Models/DatabaseModels/UserWhatsApp.ts +93 -2
  8. package/Server/API/BaseAPI.ts +79 -14
  9. package/Server/API/UserCallAPI.ts +53 -45
  10. package/Server/API/UserEmailAPI.ts +53 -44
  11. package/Server/API/UserIncomingCallNumberAPI.ts +48 -38
  12. package/Server/API/UserNotificationMethodAdminAPI.ts +481 -0
  13. package/Server/API/UserPushAPI.ts +126 -2
  14. package/Server/API/UserSmsAPI.ts +53 -44
  15. package/Server/API/UserWhatsAppAPI.ts +53 -50
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.ts +154 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.ts +17 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.ts +45 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/VerificationCodeRateLimit.ts +586 -0
  21. package/Server/Services/AnalyticsDatabaseService.ts +169 -0
  22. package/Server/Services/DatabaseService.ts +101 -0
  23. package/Server/Services/LogAggregationService.ts +5 -0
  24. package/Server/Services/PushNotificationService.ts +127 -12
  25. package/Server/Services/TeamMemberService.ts +53 -13
  26. package/Server/Services/UserCallService.ts +55 -24
  27. package/Server/Services/UserEmailService.ts +83 -17
  28. package/Server/Services/UserIncomingCallNumberService.ts +49 -85
  29. package/Server/Services/UserNotificationMethodAdminService.ts +1527 -0
  30. package/Server/Services/UserNotificationRuleService.ts +26 -0
  31. package/Server/Services/UserPushService.ts +89 -0
  32. package/Server/Services/UserSmsService.ts +50 -17
  33. package/Server/Services/UserWhatsAppService.ts +48 -15
  34. package/Server/Types/Database/QueryHelper.ts +30 -0
  35. package/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.ts +59 -0
  36. package/Server/Utils/ChannelVerification.ts +510 -0
  37. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +13 -16
  38. package/Server/Utils/Monitor/MonitorAlert.ts +24 -21
  39. package/Server/Utils/Monitor/MonitorIncident.ts +21 -18
  40. package/Server/Utils/Monitor/MonitorResourceContext.ts +107 -0
  41. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +442 -0
  42. package/Server/Utils/Monitor/SeriesResourceLabels.ts +3 -3
  43. package/Server/Utils/Monitor/SeriesResourceLinker.ts +96 -28
  44. package/Server/Utils/TotpAuth.ts +137 -9
  45. package/Server/Utils/UserRegistrationToken.ts +182 -0
  46. package/Server/Utils/VerificationCode.ts +134 -0
  47. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +173 -94
  48. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +1151 -0
  49. package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +905 -0
  50. package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +338 -0
  51. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1 -1
  52. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +28 -0
  53. package/Tests/Models/UserPushCriticalAlertColumn.test.ts +172 -0
  54. package/Tests/Server/API/BaseAPIGetListParallel.test.ts +519 -0
  55. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +543 -0
  56. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +108 -18
  57. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +963 -0
  58. package/Tests/Server/API/UserPushCriticalAlertsApi.test.ts +114 -0
  59. package/Tests/Server/API/UserSmsApi.test.ts +107 -14
  60. package/Tests/Server/API/UserTotpAuthAPI.test.ts +427 -0
  61. package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +315 -0
  62. package/Tests/Server/Middleware/VerificationCodeRateLimit.test.ts +775 -0
  63. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +10 -62
  64. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +33 -13
  65. package/Tests/Server/Services/AnalyticsDatabaseSortKeyBoundary.test.ts +658 -0
  66. package/Tests/Server/Services/CriticalOnCallAlertDelivery.test.ts +458 -0
  67. package/Tests/Server/Services/CriticalPushAlertPayload.test.ts +301 -0
  68. package/Tests/Server/Services/DatabaseServiceAtomicIncrement.test.ts +143 -0
  69. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1 -0
  70. package/Tests/Server/Services/LogAggregationScanMemory.test.ts +421 -0
  71. package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
  72. package/Tests/Server/Services/NotificationChannelCodeIssuance.test.ts +401 -0
  73. package/Tests/Server/Services/PushNotificationDeliveryOptions.test.ts +204 -0
  74. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +21 -1
  75. package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +290 -0
  76. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +948 -0
  77. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1 -0
  78. package/Tests/Server/Services/UserPushCriticalAlertToggle.test.ts +255 -0
  79. package/Tests/Server/Services/UserTotpAuthEnrolment.test.ts +218 -0
  80. package/Tests/Server/TestingUtils/AuthenticatorApp.ts +248 -0
  81. package/Tests/Server/Types/Database/Permissions/NotificationChannelVerificationColumns.test.ts +135 -0
  82. package/Tests/Server/Types/Database/Permissions/OnCallScheduleRelationSelect.test.ts +504 -0
  83. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAnyOf.test.ts +91 -0
  84. package/Tests/Server/Types/Workflow/Components/JsonToText.test.ts +384 -0
  85. package/Tests/Server/Types/Workflow/Components/MergeJson.test.ts +403 -0
  86. package/Tests/Server/Utils/Browser.test.ts +162 -0
  87. package/Tests/Server/Utils/ChannelVerification.test.ts +689 -0
  88. package/Tests/Server/Utils/Express.test.ts +371 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +707 -0
  90. package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +698 -0
  91. package/Tests/Server/Utils/Monitor/Criteria/IncomingEmailBodyCriteria.test.ts +243 -0
  92. package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestBodyCriteria.test.ts +278 -0
  93. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +160 -20
  94. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +13 -8
  95. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +162 -18
  96. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +349 -0
  97. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +690 -0
  98. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +7 -2
  99. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +92 -18
  100. package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +267 -0
  101. package/Tests/Server/Utils/Telemetry/OneuptimeLabel.test.ts +439 -0
  102. package/Tests/Server/Utils/TotpAuth.test.ts +719 -0
  103. package/Tests/Server/Utils/UserRegistrationToken.test.ts +322 -0
  104. package/Tests/Server/Utils/VerificationCode.test.ts +296 -0
  105. package/Tests/Types/AutoRemediation/AutoRemediationSuggestionStatus.test.ts +192 -0
  106. package/Tests/Types/Call/CallRequest.test.ts +189 -0
  107. package/Tests/Types/Dashboard/DashboardSize.test.ts +226 -0
  108. package/Tests/Types/Incident/IncidentSlaStatus.test.ts +191 -0
  109. package/Tests/Types/Metrics/MetricDashboardMetricType.test.ts +278 -0
  110. package/Tests/Types/Metrics/MetricPipelineRuleType.test.ts +517 -0
  111. package/Tests/Types/Monitor/IncomingMonitorDefaultCriteria.test.ts +470 -0
  112. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +6 -4
  113. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +262 -5
  114. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
  115. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
  116. package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +905 -0
  117. package/Tests/Types/Service/ServiceLanguage.test.ts +255 -0
  118. package/Tests/Types/TextRandomGeneration.test.ts +211 -0
  119. package/Tests/UI/Components/Button.test.tsx +175 -1
  120. package/Tests/UI/Components/CodeBlockLanguages.test.tsx +236 -0
  121. package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +568 -0
  122. package/Tests/UI/Components/CommandPalette/CommandPaletteKeyboard.test.tsx +239 -0
  123. package/Tests/UI/Components/CommandPalette/CommandPaletteProviders.test.tsx +400 -0
  124. package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +234 -0
  125. package/Tests/UI/Components/CommandPalette/RecentCommands.test.ts +97 -0
  126. package/Tests/UI/Components/CountModelSideMenuItemGuard.test.tsx +198 -0
  127. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +1 -1
  128. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +267 -0
  129. package/Tests/UI/Components/List.test.tsx +9 -2
  130. package/Tests/UI/Components/ListLoadingStates.test.tsx +173 -0
  131. package/Tests/UI/Components/MarkdownLazy.test.tsx +113 -0
  132. package/Tests/UI/Components/MarkdownMermaidRetry.test.tsx +110 -0
  133. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +8 -0
  134. package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +692 -0
  135. package/Tests/UI/Components/MoreMenuMotion.test.tsx +118 -0
  136. package/Tests/UI/Components/NavBarCommandKGating.test.tsx +178 -0
  137. package/Tests/UI/Components/OrderedStatesList.test.tsx +19 -2
  138. package/Tests/UI/Components/SideOverMotion.test.tsx +109 -0
  139. package/Tests/UI/Components/Skeleton.test.tsx +71 -0
  140. package/Tests/UI/Components/TableLoadingStates.test.tsx +279 -0
  141. package/Tests/UI/Components/Tabs.test.tsx +24 -0
  142. package/Tests/UI/Components/Toast.test.tsx +130 -7
  143. package/Tests/UI/Components/ToastStacking.test.tsx +198 -0
  144. package/Tests/UI/Utils/Dropdown.test.ts +423 -0
  145. package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +122 -0
  146. package/Tests/UI/Utils/ModelListCache.test.ts +338 -0
  147. package/Tests/UI/Utils/ProjectListCacheInvalidation.test.ts +225 -0
  148. package/Tests/Utils/API.test.ts +16 -2
  149. package/Tests/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.test.ts +347 -0
  150. package/Tests/Utils/Dashboard/Components/DashboardListSharedArgs.test.ts +259 -0
  151. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +466 -0
  152. package/Tests/Utils/Rum/UrlScrubberSegments.test.ts +362 -0
  153. package/Types/Email/EmailTemplateType.ts +1 -0
  154. package/Types/Monitor/MonitorCriteriaInstance.ts +41 -20
  155. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +131 -8
  156. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +48 -8
  157. package/Types/Monitor/ServiceAlertTemplates.ts +1352 -0
  158. package/Types/Permission.ts +35 -0
  159. package/Types/PushNotification/AndroidNotificationChannel.ts +26 -0
  160. package/Types/PushNotification/PushNotificationMessage.ts +15 -0
  161. package/Types/PushNotification/PushNotificationRequest.ts +8 -16
  162. package/Types/Service/ServiceLanguage.ts +144 -0
  163. package/Types/Text.ts +111 -19
  164. package/UI/Components/Button/Button.tsx +27 -14
  165. package/UI/Components/CodeBlock/CodeBlock.tsx +54 -7
  166. package/UI/Components/CodeBlock/LanguageRegistry.ts +103 -0
  167. package/UI/Components/CommandPalette/CommandPalette.tsx +790 -0
  168. package/UI/Components/CommandPalette/PaletteFilter.ts +196 -0
  169. package/UI/Components/CommandPalette/PaletteRow.tsx +173 -0
  170. package/UI/Components/CommandPalette/RecentCommands.ts +57 -0
  171. package/UI/Components/CommandPalette/Types.ts +49 -0
  172. package/UI/Components/CommandPalette/UseProviderSearch.ts +129 -0
  173. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  174. package/UI/Components/Feed/FeedItem.tsx +1 -1
  175. package/UI/Components/List/List.tsx +37 -5
  176. package/UI/Components/List/ListSkeleton.tsx +54 -0
  177. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +6 -1
  178. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +71 -26
  179. package/UI/Components/Modal/Modal.tsx +2 -2
  180. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  181. package/UI/Components/ModelTable/useCustomFieldColumns.ts +11 -5
  182. package/UI/Components/MoreMenu/MoreMenu.tsx +39 -1
  183. package/UI/Components/Navbar/NavBar.tsx +14 -1
  184. package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +50 -5
  185. package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +40 -1
  186. package/UI/Components/SideOver/SideOver.tsx +37 -2
  187. package/UI/Components/Skeleton/Skeleton.tsx +53 -0
  188. package/UI/Components/Table/Table.tsx +74 -44
  189. package/UI/Components/Table/TableSkeletonRows.tsx +141 -0
  190. package/UI/Components/Tabs/Tab.tsx +1 -1
  191. package/UI/Components/Toast/Toast.tsx +118 -53
  192. package/UI/Components/Toast/ToastInit.tsx +82 -32
  193. package/UI/Styles/Theme.css +40 -0
  194. package/UI/Utils/ModelAPI/ModelAPI.ts +20 -10
  195. package/UI/Utils/ModelListCache.ts +188 -0
  196. package/UI/Utils/Project.ts +29 -0
  197. package/Utils/API.ts +6 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +14 -0
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/UserCall.js +101 -3
  201. package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/UserEmail.js +101 -3
  203. package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js +101 -3
  205. package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/UserPush.js +38 -0
  207. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/UserSMS.js +101 -3
  209. package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/UserWhatsApp.js +100 -2
  211. package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
  212. package/build/dist/Server/API/BaseAPI.js +69 -13
  213. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  214. package/build/dist/Server/API/UserCallAPI.js +42 -38
  215. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  216. package/build/dist/Server/API/UserEmailAPI.js +42 -38
  217. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  218. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +37 -32
  219. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  220. package/build/dist/Server/API/UserNotificationMethodAdminAPI.js +341 -0
  221. package/build/dist/Server/API/UserNotificationMethodAdminAPI.js.map +1 -0
  222. package/build/dist/Server/API/UserPushAPI.js +95 -2
  223. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  224. package/build/dist/Server/API/UserSmsAPI.js +42 -38
  225. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  226. package/build/dist/Server/API/UserWhatsAppAPI.js +42 -40
  227. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js +89 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js.map +1 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js +12 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js.map +1 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js +40 -0
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js.map +1 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  236. package/build/dist/Server/Middleware/VerificationCodeRateLimit.js +393 -0
  237. package/build/dist/Server/Middleware/VerificationCodeRateLimit.js.map +1 -0
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js +136 -0
  239. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  240. package/build/dist/Server/Services/DatabaseService.js +74 -0
  241. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  242. package/build/dist/Server/Services/LogAggregationService.js +5 -0
  243. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  244. package/build/dist/Server/Services/PushNotificationService.js +69 -30
  245. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  246. package/build/dist/Server/Services/TeamMemberService.js +43 -7
  247. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  248. package/build/dist/Server/Services/UserCallService.js +54 -22
  249. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  250. package/build/dist/Server/Services/UserEmailService.js +78 -17
  251. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  252. package/build/dist/Server/Services/UserIncomingCallNumberService.js +45 -74
  253. package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
  254. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1117 -0
  255. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -0
  256. package/build/dist/Server/Services/UserNotificationRuleService.js +20 -0
  257. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  258. package/build/dist/Server/Services/UserPushService.js +77 -0
  259. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  260. package/build/dist/Server/Services/UserSmsService.js +50 -17
  261. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  262. package/build/dist/Server/Services/UserWhatsAppService.js +48 -15
  263. package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
  264. package/build/dist/Server/Types/Database/QueryHelper.js +27 -0
  265. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  266. package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js +46 -0
  267. package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js.map +1 -1
  268. package/build/dist/Server/Utils/ChannelVerification.js +348 -0
  269. package/build/dist/Server/Utils/ChannelVerification.js.map +1 -0
  270. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +13 -12
  271. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  272. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +20 -17
  273. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  274. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -14
  275. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  276. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +95 -0
  277. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -0
  278. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +313 -0
  279. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -0
  280. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +3 -3
  281. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +67 -26
  282. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  283. package/build/dist/Server/Utils/TotpAuth.js +127 -8
  284. package/build/dist/Server/Utils/TotpAuth.js.map +1 -1
  285. package/build/dist/Server/Utils/UserRegistrationToken.js +170 -0
  286. package/build/dist/Server/Utils/UserRegistrationToken.js.map +1 -0
  287. package/build/dist/Server/Utils/VerificationCode.js +120 -0
  288. package/build/dist/Server/Utils/VerificationCode.js.map +1 -0
  289. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  290. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  291. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +42 -21
  292. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  293. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +77 -5
  294. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  295. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +18 -6
  296. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  297. package/build/dist/Types/Monitor/ServiceAlertTemplates.js +1028 -0
  298. package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -0
  299. package/build/dist/Types/Permission.js +33 -0
  300. package/build/dist/Types/Permission.js.map +1 -1
  301. package/build/dist/Types/PushNotification/AndroidNotificationChannel.js +27 -0
  302. package/build/dist/Types/PushNotification/AndroidNotificationChannel.js.map +1 -0
  303. package/build/dist/Types/Service/ServiceLanguage.js +96 -0
  304. package/build/dist/Types/Service/ServiceLanguage.js.map +1 -0
  305. package/build/dist/Types/Text.js +82 -17
  306. package/build/dist/Types/Text.js.map +1 -1
  307. package/build/dist/UI/Components/Button/Button.js +22 -14
  308. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  309. package/build/dist/UI/Components/CodeBlock/CodeBlock.js +35 -3
  310. package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
  311. package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js +57 -0
  312. package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js.map +1 -0
  313. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +439 -0
  314. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -0
  315. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +135 -0
  316. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -0
  317. package/build/dist/UI/Components/CommandPalette/PaletteRow.js +83 -0
  318. package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -0
  319. package/build/dist/UI/Components/CommandPalette/RecentCommands.js +47 -0
  320. package/build/dist/UI/Components/CommandPalette/RecentCommands.js.map +1 -0
  321. package/build/dist/UI/Components/CommandPalette/Types.js +2 -0
  322. package/build/dist/UI/Components/CommandPalette/Types.js.map +1 -0
  323. package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js +88 -0
  324. package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js.map +1 -0
  325. package/build/dist/UI/Components/Dropdown/Dropdown.js +6 -1
  326. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  327. package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
  328. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  329. package/build/dist/UI/Components/List/List.js +19 -5
  330. package/build/dist/UI/Components/List/List.js.map +1 -1
  331. package/build/dist/UI/Components/List/ListSkeleton.js +21 -0
  332. package/build/dist/UI/Components/List/ListSkeleton.js.map +1 -0
  333. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  334. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +52 -25
  335. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  336. package/build/dist/UI/Components/Modal/Modal.js +2 -2
  337. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  338. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  339. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +9 -2
  340. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  341. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +32 -1
  342. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  343. package/build/dist/UI/Components/Navbar/NavBar.js +5 -1
  344. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  345. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +29 -5
  346. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
  347. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -1
  348. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
  349. package/build/dist/UI/Components/SideOver/SideOver.js +23 -2
  350. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  351. package/build/dist/UI/Components/Skeleton/Skeleton.js +23 -0
  352. package/build/dist/UI/Components/Skeleton/Skeleton.js.map +1 -0
  353. package/build/dist/UI/Components/Table/Table.js +35 -24
  354. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  355. package/build/dist/UI/Components/Table/TableSkeletonRows.js +52 -0
  356. package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -0
  357. package/build/dist/UI/Components/Tabs/Tab.js +1 -1
  358. package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
  359. package/build/dist/UI/Components/Toast/Toast.js +77 -23
  360. package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
  361. package/build/dist/UI/Components/Toast/ToastInit.js +37 -15
  362. package/build/dist/UI/Components/Toast/ToastInit.js.map +1 -1
  363. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -9
  364. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  365. package/build/dist/UI/Utils/ModelListCache.js +126 -0
  366. package/build/dist/UI/Utils/ModelListCache.js.map +1 -0
  367. package/build/dist/UI/Utils/Project.js +24 -1
  368. package/build/dist/UI/Utils/Project.js.map +1 -1
  369. package/build/dist/Utils/API.js +6 -1
  370. package/build/dist/Utils/API.js.map +1 -1
  371. package/jest.config.json +1 -1
  372. package/package.json +2 -3
  373. package/Server/Utils/Monitor/MonitorClusterContext.ts +0 -182
  374. package/Tests/__mocks__/otpauth.js +0 -30
  375. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +0 -141
  376. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +0 -1
@@ -0,0 +1,134 @@
1
+ import { EncryptionSecret } from "../EnvironmentConfig";
2
+ import ObjectID from "../../Types/ObjectID";
3
+ import crypto from "crypto";
4
+
5
+ /*
6
+ * The cryptography behind notification-channel ownership checks: the codes
7
+ * mailed/texted/read out to a user to prove they hold the address or number
8
+ * they just added.
9
+ *
10
+ * This is deliberately server-only. Common/Types/Text is bundled into the
11
+ * dashboard and therefore has to tolerate a runtime with no CSPRNG; a code
12
+ * minted here has no such excuse, so nothing in this file falls back to
13
+ * Math.random. If the platform cannot produce secure randomness, generation
14
+ * throws and the channel simply cannot be added.
15
+ *
16
+ * WHY THE STORED VALUE IS A DIGEST
17
+ *
18
+ * A six-digit code is only a secret for as long as it is in flight. Storing
19
+ * the plaintext puts a live account-takeover token in a database column, a
20
+ * backup, a replica and every log line that ever dumps the row - and the
21
+ * column sits on a table (UserEmail and friends) whose entire access-control
22
+ * design exists because reading somebody else's row IS the attack. Keeping a
23
+ * keyed digest means a database dump alone yields nothing: the attacker also
24
+ * needs the deployment's EncryptionSecret, and even holding both, recovering
25
+ * the code means re-running the HMAC over the code space per row, because...
26
+ *
27
+ * WHY THE ROW ID IS IN THE MESSAGE
28
+ *
29
+ * ...the digest is domain-separated by the row it belongs to. Without that,
30
+ * one HMAC table of all 10^6 codes inverts every row in the table at once,
31
+ * and two rows that happened to draw the same code would be visibly equal.
32
+ * With it, the same code on two rows produces two unrelated digests.
33
+ *
34
+ * A fast hash is right here, unlike a password: the input is a value the
35
+ * server minted at random, so there is nothing to guess offline that the
36
+ * attempt counter is not already bounding online. The scheme version prefix
37
+ * is there so a future change of construction can be told apart from this one
38
+ * rather than silently invalidating or, worse, colliding with it.
39
+ */
40
+
41
+ const HASH_SCHEME_VERSION: string = "v1";
42
+
43
+ /*
44
+ * Six digits, matching what the notification templates and the dashboard's
45
+ * code input already expect. The code space is small on purpose - it has to
46
+ * be readable off a phone screen - which is exactly why every control around
47
+ * it (expiry, attempt counter, lockout, rate limit) is load-bearing rather
48
+ * than defence in depth.
49
+ */
50
+ export const VERIFICATION_CODE_LENGTH: number = 6;
51
+
52
+ export default class VerificationCode {
53
+ /*
54
+ * A fresh code, drawn uniformly from the whole code space.
55
+ *
56
+ * crypto.randomInt does the rejection sampling itself, so there is no modulo
57
+ * bias here and no need to hand-roll one. Leading zeros are kept: "000123"
58
+ * is a perfectly good code, and dropping them would quietly shrink the
59
+ * space by 10%.
60
+ */
61
+ public static generate(length: number = VERIFICATION_CODE_LENGTH): string {
62
+ let code: string = "";
63
+
64
+ for (let i: number = 0; i < length; i++) {
65
+ code += crypto.randomInt(0, 10).toString();
66
+ }
67
+
68
+ return code;
69
+ }
70
+
71
+ /*
72
+ * The digest to store for `code` on the row identified by `channelId`.
73
+ *
74
+ * Both inputs are length-prefixed into the message rather than merely
75
+ * concatenated, so no pair of (id, code) values can be rearranged into the
76
+ * same byte string as another pair.
77
+ */
78
+ public static hashCode(data: { code: string; channelId: ObjectID }): string {
79
+ const message: string = [
80
+ HASH_SCHEME_VERSION,
81
+ data.channelId.toString(),
82
+ data.code,
83
+ ]
84
+ .map((part: string) => {
85
+ return `${part.length}:${part}`;
86
+ })
87
+ .join("");
88
+
89
+ return crypto
90
+ .createHmac("sha256", EncryptionSecret.toString())
91
+ .update(message)
92
+ .digest("hex");
93
+ }
94
+
95
+ /*
96
+ * Compare two digests without leaking, through timing, how many leading
97
+ * bytes matched.
98
+ *
99
+ * `===` on strings short-circuits at the first differing character, and the
100
+ * attacker here controls the plaintext that produces one side of the
101
+ * comparison. That is a much weaker oracle than it is for a password check -
102
+ * they would be recovering a digest of a value that expires in minutes - but
103
+ * a constant-time compare costs nothing and removes the question.
104
+ *
105
+ * Lengths are compared first. Both digests are always 64 hex characters, so
106
+ * an early return only happens on malformed input, where the length is not
107
+ * the secret.
108
+ */
109
+ public static isHashEqual(a: string, b: string): boolean {
110
+ if (!a || !b || a.length !== b.length) {
111
+ return false;
112
+ }
113
+
114
+ return crypto.timingSafeEqual(
115
+ Buffer.from(a, "utf8"),
116
+ Buffer.from(b, "utf8"),
117
+ );
118
+ }
119
+
120
+ /*
121
+ * A digest that no code can ever produce, for burning the outstanding
122
+ * challenge.
123
+ *
124
+ * Used when a row is locked out or successfully verified. The column is NOT
125
+ * NULL, so "there is no live code" cannot be expressed as null; it is
126
+ * expressed as a digest drawn at random from a 256-bit space, which the
127
+ * 10^6 possible codes cannot hit. Rotating rather than leaving the old
128
+ * digest in place matters: it means a lockout is not merely a pause an
129
+ * attacker can wait out with the same target still valid behind it.
130
+ */
131
+ public static generateUnusableHash(): string {
132
+ return crypto.randomBytes(32).toString("hex");
133
+ }
134
+ }
@@ -15,6 +15,11 @@ import {
15
15
  waitFor,
16
16
  within,
17
17
  } from "@testing-library/react";
18
+ import {
19
+ MemoryRouter,
20
+ Route as RouterRoute,
21
+ Routes as RouterRoutes,
22
+ } from "react-router-dom";
18
23
  import React, { FunctionComponent, ReactElement } from "react";
19
24
  import getJestMockFunction, { MockFunction } from "../../MockType";
20
25
 
@@ -365,7 +370,14 @@ jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
365
370
  });
366
371
 
367
372
  import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
368
- import UserViewNotificationRules from "../../../../App/FeatureSet/Dashboard/src/Pages/Users/View/NotificationRules";
373
+ import UserViewOnCallLayout from "../../../../App/FeatureSet/Dashboard/src/Pages/Users/View/OnCall/Layout";
374
+ import UserViewOnCallReadiness from "../../../../App/FeatureSet/Dashboard/src/Pages/Users/View/OnCall/Readiness";
375
+ import UserViewOnCallRules, {
376
+ ALERT_EPISODE_RULES_PROPS,
377
+ ALERT_RULES_PROPS,
378
+ INCIDENT_EPISODE_RULES_PROPS,
379
+ INCIDENT_RULES_PROPS,
380
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Users/View/OnCall/Rules";
369
381
  import AlertOnCallRules from "../../../../App/FeatureSet/Dashboard/src/Pages/UserSettings/AlertOnCallRules";
370
382
  import EpisodeOnCallRules from "../../../../App/FeatureSet/Dashboard/src/Pages/UserSettings/EpisodeOnCallRules";
371
383
  import IncidentEpisodeOnCallRules from "../../../../App/FeatureSet/Dashboard/src/Pages/UserSettings/IncidentEpisodeOnCallRules";
@@ -758,17 +770,81 @@ type RenderAdminPageFunction = (
758
770
  * offset yields an ObjectID of the literal string "notification-rules", which
759
771
  * every assertion about whose rules these are would then fail on.
760
772
  */
773
+
774
+ /*
775
+ * Every page the On-Call section holds that has a rule table or a readiness
776
+ * surface on it, mounted at once under ONE section layout.
777
+ *
778
+ * The section used to be a single route and is six now, and these assertions
779
+ * are about the shared machinery underneath rather than about which URL a card
780
+ * ends up on: the severity axes, the masked dropdown, the on-behalf-of context,
781
+ * the coverage grid. Mounting the readiness page beside all four rule pages
782
+ * keeps every one of them pointed at a single load of identity and readiness -
783
+ * which is exactly what the layout does in the product - so what is asserted
784
+ * here is what the section actually composes to.
785
+ *
786
+ * That each ROUTE renders only its own page is a different claim, and it is
787
+ * asserted where it belongs: AdminUserOnCallPages.test.tsx walks the real route
788
+ * table.
789
+ */
790
+ const AdminOnCallSection: FunctionComponent = (): ReactElement => {
791
+ return (
792
+ <>
793
+ <UserViewOnCallReadiness {...pageProps} />
794
+ <UserViewOnCallRules {...INCIDENT_RULES_PROPS} />
795
+ <UserViewOnCallRules {...INCIDENT_EPISODE_RULES_PROPS} />
796
+ <UserViewOnCallRules {...ALERT_RULES_PROPS} />
797
+ <UserViewOnCallRules {...ALERT_EPISODE_RULES_PROPS} />
798
+ </>
799
+ );
800
+ };
801
+
802
+ type RenderSectionFunction = (
803
+ targetUserId: string,
804
+ children: ReactElement,
805
+ ) => HTMLElement;
806
+
807
+ /*
808
+ * The real nesting, through a real router.
809
+ *
810
+ * The section layout reads the target user from `useParams`, not by counting
811
+ * segments off window.location, so a stubbed location would tell it nothing.
812
+ * Driving it through MemoryRouter with the same `:id` parent the app uses is
813
+ * what makes "whose configuration is this?" answered here the same way it is
814
+ * answered in the product - and reading the wrong param would leave every
815
+ * assertion about the target user failing on an id of "on-call-readiness".
816
+ */
817
+ const renderSection: RenderSectionFunction = (
818
+ targetUserId: string,
819
+ children: ReactElement,
820
+ ): HTMLElement => {
821
+ const { container } = render(
822
+ <MemoryRouter
823
+ initialEntries={[
824
+ `/dashboard/${PROJECT_ID_STRING}/users/${targetUserId}/on-call-readiness`,
825
+ ]}
826
+ >
827
+ <RouterRoutes>
828
+ <RouterRoute path="/dashboard/:projectId/users/:id">
829
+ <RouterRoute element={<UserViewOnCallLayout />}>
830
+ <RouterRoute path="on-call-readiness" element={children} />
831
+ </RouterRoute>
832
+ </RouterRoute>
833
+ </RouterRoutes>
834
+ </MemoryRouter>,
835
+ );
836
+
837
+ return container;
838
+ };
839
+
761
840
  const renderAdminPage: RenderAdminPageFunction = async (
762
841
  targetUserId: string = TARGET_USER_ID_STRING,
763
842
  ): Promise<HTMLElement> => {
764
- window.history.pushState(
765
- {},
766
- "",
767
- `/dashboard/${PROJECT_ID_STRING}/users/${targetUserId}/notification-rules`,
843
+ const container: HTMLElement = renderSection(
844
+ targetUserId,
845
+ <AdminOnCallSection />,
768
846
  );
769
847
 
770
- const { container } = render(<UserViewNotificationRules {...pageProps} />);
771
-
772
848
  await waitForSettledPage(ADMIN_TABLE_COUNT);
773
849
 
774
850
  return container;
@@ -1852,7 +1928,7 @@ describe("the on-behalf-of banner", () => {
1852
1928
  const container: HTMLElement = await renderAdminPage();
1853
1929
 
1854
1930
  expect(container.textContent).toContain(
1855
- "These are your own notification rules.",
1931
+ "This is your own on-call configuration.",
1856
1932
  );
1857
1933
  expect(container.textContent).not.toContain("on behalf of");
1858
1934
 
@@ -1869,13 +1945,7 @@ describe("the on-behalf-of banner", () => {
1869
1945
  test("a member with no permission is refused, and the page reads nothing about the user", async () => {
1870
1946
  jest.spyOn(PermissionUtil, "getAllPermissions").mockReturnValue([]);
1871
1947
 
1872
- window.history.pushState(
1873
- {},
1874
- "",
1875
- `/dashboard/${PROJECT_ID_STRING}/users/${TARGET_USER_ID_STRING}/notification-rules`,
1876
- );
1877
-
1878
- render(<UserViewNotificationRules {...pageProps} />);
1948
+ renderSection(TARGET_USER_ID_STRING, <AdminOnCallSection />);
1879
1949
 
1880
1950
  expect(
1881
1951
  await screen.findByText(/do not have permission to view this user/),
@@ -1913,111 +1983,111 @@ describe("the on-behalf-of banner", () => {
1913
1983
  });
1914
1984
 
1915
1985
  /*
1916
- * And it must not invent an empty method list out of a failed request. "No
1917
- * methods" is the most alarming thing this page can print about somebody,
1918
- * and printing it because a fetch failed sends an admin chasing an account
1919
- * that is perfectly well configured.
1986
+ * And it must not invent a diagnosis out of a failed request. An unreported
1987
+ * coverage matrix and a matrix full of holes look identical once drawn, and
1988
+ * only one of them is a reason to go and add rules - so a failed readiness
1989
+ * read says so rather than rendering an empty grid.
1920
1990
  */
1921
- expect(container.textContent).toContain(
1922
- "Notification methods could not be loaded",
1923
- );
1991
+ expect(container.textContent).toContain("Coverage could not be loaded");
1924
1992
  expect(container.textContent).not.toContain(
1925
1993
  "has no notification methods at all",
1926
1994
  );
1927
1995
  });
1928
1996
  });
1929
1997
 
1930
- describe("notification methods are read-only", () => {
1931
- test("lists each method masked, with its verification state", async () => {
1932
- await renderAdminPage();
1933
-
1934
- const methods: HTMLElement = cardNamed("Notification methods");
1998
+ describe("methods are a page of their own now, not a card on this one", () => {
1999
+ /*
2000
+ * WHAT THIS BLOCK USED TO SAY, AND WHY IT NO LONGER DOES.
2001
+ *
2002
+ * It asserted that an administrator was offered NO control for adding a
2003
+ * notification method to somebody else's account: a masked, read-only list
2004
+ * and a prefilled "please add one yourself" email. That was correct for what
2005
+ * the server could do at the time - the seven method models are scoped to
2006
+ * their owner and the attempt to widen them leaked every raw column behind
2007
+ * them - but it left the commonest broken responder, a new joiner with no
2008
+ * method at all, fixable by nobody but themselves.
2009
+ *
2010
+ * An administrator can now add one, through a narrow server-side capability
2011
+ * that never hands them the row, and CANNOT MAKE IT LIVE: the method is
2012
+ * written unverified, the code goes to the device, and every verify endpoint
2013
+ * refuses a caller who is not the row's owner. That capability and its
2014
+ * limits are tested in AdminUserNotificationMethodsPage.test.tsx and in the
2015
+ * server suites; what matters HERE is that the rule surfaces did not quietly
2016
+ * inherit it.
2017
+ */
2018
+ test("the readiness page no longer carries the method list", async () => {
2019
+ const container: HTMLElement = await renderAdminPage();
1935
2020
 
1936
- expect(methods.textContent).toContain(MASKED_EMAIL);
1937
- expect(methods.textContent).toContain(MASKED_PHONE);
1938
- expect(methods.textContent).toContain(MASKED_TELEGRAM);
1939
- expect(methods.textContent).toContain("Verified");
1940
- expect(methods.textContent).toContain("Unverified");
2021
+ /*
2022
+ * The masked identifiers are what the RULE dropdown is built from, so they
2023
+ * are still in the section's props - but the standing list of a colleague's
2024
+ * devices belongs on the page that manages them, not under a diagnosis.
2025
+ */
2026
+ expect(
2027
+ screen.queryByTestId("admin-notification-method-list"),
2028
+ ).not.toBeInTheDocument();
1941
2029
 
1942
- // The asymmetry stated out loud, where somebody looking for a button is.
1943
- expect(methods.textContent).toContain(
2030
+ expect(container.textContent).not.toContain(
1944
2031
  "Identifiers are masked and cannot be edited from here",
1945
2032
  );
1946
2033
  });
1947
2034
 
1948
- test("offers no control for adding a method on the user's behalf", async () => {
1949
- await renderAdminPage();
1950
-
1951
- const methods: HTMLElement = cardNamed("Notification methods");
2035
+ test("points at the page that does manage them", async () => {
2036
+ const container: HTMLElement = await renderAdminPage();
1952
2037
 
1953
2038
  /*
1954
- * Structural rather than copy-deep, because the next person to be tempted
1955
- * will add a button, not a sentence. Letting an admin type in a phone
1956
- * number would point another person's pages at a device the admin controls,
1957
- * and it would not solve the case it appears to solve: a responder with no
1958
- * methods needs a device THEY hold and can verify.
2039
+ * A diagnosis with no route to the repair is how the single page this
2040
+ * replaced came to be scrolled past. The readiness page names every page
2041
+ * that can change the answer it just gave.
1959
2042
  */
1960
- expect(
1961
- methods.querySelectorAll("button, input, select, textarea, form"),
1962
- ).toHaveLength(0);
2043
+ expect(container.textContent).toContain("Notification methods");
2044
+ expect(container.textContent).toContain("Incident on-call rules");
2045
+ expect(container.textContent).toContain("Alert episode on-call rules");
2046
+ });
2047
+
2048
+ test("the rule tables stay editable for a user with no method at all", async () => {
2049
+ respondWithReadiness(NO_METHODS_READINESS);
2050
+
2051
+ await renderAdminPage();
1963
2052
 
1964
2053
  /*
1965
- * The only link out of this card is the mail draft. A link INTO somebody
1966
- * else's notification-method settings would be the same capability wearing
1967
- * a different hat.
2054
+ * A responder with no verified method still needs rules waiting for the
2055
+ * moment one is added - by them, or now by an admin on the methods page.
2056
+ * Folding "cannot be reached" into "cannot be configured" would make the
2057
+ * broken case the one that is hardest to repair.
1968
2058
  */
1969
- Array.from(methods.querySelectorAll("a")).forEach((anchor: HTMLElement) => {
1970
- expect(anchor.getAttribute("href") || "").toMatch(/^mailto:/);
2059
+ capturedTables.forEach((table: CapturedTableProps) => {
2060
+ expect(table.isCreateable).toBe(true);
2061
+ expect(table.isDeleteable).toBe(true);
2062
+ expect(table.modelType).toBe(UserNotificationRule);
1971
2063
  });
1972
2064
  });
1973
2065
 
1974
- test("the no-methods empty state offers a reminder rather than a form", async () => {
2066
+ test("still asks the reader to chase the one thing an admin cannot do", async () => {
1975
2067
  respondWithReadiness(NO_METHODS_READINESS);
1976
2068
 
1977
2069
  await renderAdminPage();
1978
2070
 
1979
- const methods: HTMLElement = cardNamed("Notification methods");
1980
-
1981
- expect(methods.textContent).toContain("has no notification methods at all");
1982
- expect(methods.textContent).toContain(
1983
- `Only ${TARGET_USER_FIRST_NAME} can add one`,
1984
- );
1985
-
1986
- // A nudge, with the link already in it - and still not a single control.
1987
- expect(
1988
- methods.querySelectorAll("button, input, select, textarea, form"),
1989
- ).toHaveLength(0);
1990
-
1991
- const href: string = mailtoHrefIn(methods);
2071
+ const href: string = mailtoHrefIn(cardNamed("On-call readiness"));
1992
2072
 
1993
2073
  expect(href).toContain(TARGET_LOGIN_EMAIL);
1994
2074
  expect(href).toContain("notification-methods");
1995
- expect(href).toContain(
1996
- "only you can add the device or address they send to",
1997
- );
1998
2075
 
1999
2076
  /*
2000
- * The draft has to say what is actually wrong, or it is a form letter. With
2001
- * no method at all the consequence is not a late page, it is no page.
2077
+ * The draft's ask moved with the capability. An admin can add a method now,
2078
+ * so asking the responder to add one would be asking them to do work that
2079
+ * has already been offered to the admin; what is still theirs alone is
2080
+ * VERIFYING it, because the code goes to their device.
2002
2081
  */
2003
- expect(href).toContain("dropped");
2004
- });
2005
-
2006
- test("the rule tables stay editable while the methods stay locked", async () => {
2007
- respondWithReadiness(NO_METHODS_READINESS);
2008
-
2009
- await renderAdminPage();
2082
+ expect(href).toContain(
2083
+ "only you can verify the device or address it sends to",
2084
+ );
2010
2085
 
2011
2086
  /*
2012
- * The whole design of this phase in one assertion: rules are the admin's to
2013
- * repair, methods are not. A responder with no verified method still needs
2014
- * rules waiting for the moment they add one.
2087
+ * And it has to say what is actually wrong, or it is a form letter. With no
2088
+ * method at all the consequence is not a late page, it is no page.
2015
2089
  */
2016
- capturedTables.forEach((table: CapturedTableProps) => {
2017
- expect(table.isCreateable).toBe(true);
2018
- expect(table.isDeleteable).toBe(true);
2019
- expect(table.modelType).toBe(UserNotificationRule);
2020
- });
2090
+ expect(href).toContain("dropped");
2021
2091
  });
2022
2092
  });
2023
2093
 
@@ -2377,24 +2447,33 @@ describe("no unmasked identifier reaches the DOM", () => {
2377
2447
  expect(container.textContent || "").not.toMatch(UNMASKED_PHONE_PATTERN);
2378
2448
  };
2379
2449
 
2380
- test("the admin page renders masked identifiers only", async () => {
2450
+ test("the admin rule surfaces render no identifier at all", async () => {
2381
2451
  const container: HTMLElement = await renderAdminPage();
2382
2452
 
2383
- // The masked forms are present, so this is not passing by rendering nothing.
2384
- expect(container.textContent).toContain(MASKED_EMAIL);
2385
- expect(container.textContent).toContain(MASKED_PHONE);
2453
+ /*
2454
+ * Not vacuous: readiness DID hand this section three methods, and their
2455
+ * masked labels are on the rule dropdown's props a few tests below. What
2456
+ * the rule surfaces render is none of them - the standing list of somebody
2457
+ * else's devices belongs on the page that manages them, and a page that
2458
+ * prints a colleague's channels under a diagnosis was one of the reasons
2459
+ * the old combined page was too long to read.
2460
+ */
2461
+ expect(capturedTables.length).toBeGreaterThan(0);
2462
+ expect(methodOptionsFor(capturedTables[0]!).length).toBeGreaterThan(0);
2463
+
2464
+ expect(container.textContent).not.toContain(MASKED_EMAIL);
2465
+ expect(container.textContent).not.toContain(MASKED_PHONE);
2386
2466
 
2387
2467
  assertNoRawIdentifiers(container);
2388
2468
  });
2389
2469
 
2390
- test("an unreachable user's empty state leaks nothing either", async () => {
2470
+ test("an unreachable user's readiness page leaks nothing either", async () => {
2391
2471
  respondWithReadiness(NO_METHODS_READINESS);
2392
2472
 
2393
2473
  const container: HTMLElement = await renderAdminPage();
2394
2474
 
2395
- expect(container.textContent).toContain(
2396
- "has no notification methods at all",
2397
- );
2475
+ // Not vacuous: the page really is reporting the unreachable state.
2476
+ expect(container.textContent).toContain("Unreachable");
2398
2477
 
2399
2478
  assertNoRawIdentifiers(container);
2400
2479
  });
@@ -2443,7 +2522,7 @@ describe("no unmasked identifier reaches the DOM", () => {
2443
2522
 
2444
2523
  await renderAdminPage();
2445
2524
 
2446
- const href: string = mailtoHrefIn(cardNamed("Notification methods"));
2525
+ const href: string = mailtoHrefIn(cardNamed("On-call readiness"));
2447
2526
 
2448
2527
  expect(href).toContain(TARGET_LOGIN_EMAIL);
2449
2528