@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,905 @@
1
+ import {
2
+ ServiceAlertTemplate,
3
+ ServiceAlertTemplateArgs,
4
+ getAllServiceAlertTemplates,
5
+ getLanguagesWithServiceAlertTemplates,
6
+ getServiceAlertTemplateById,
7
+ getServiceAlertTemplates,
8
+ } from "../../../Types/Monitor/ServiceAlertTemplates";
9
+ import MonitorStep from "../../../Types/Monitor/MonitorStep";
10
+ import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance";
11
+ import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
12
+ import MonitorType from "../../../Types/Monitor/MonitorType";
13
+ import MonitorStepMetricMonitor from "../../../Types/Monitor/MonitorStepMetricMonitor";
14
+ import MonitorStepTraceMonitor from "../../../Types/Monitor/MonitorStepTraceMonitor";
15
+ import MonitorStepExceptionMonitor from "../../../Types/Monitor/MonitorStepExceptionMonitor";
16
+ import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData";
17
+ import MetricFormulaConfigData from "../../../Types/Metrics/MetricFormulaConfigData";
18
+ import {
19
+ CheckOn,
20
+ CriteriaFilter,
21
+ FilterType,
22
+ } from "../../../Types/Monitor/CriteriaFilter";
23
+ import { ServiceLanguage } from "../../../Types/Service/ServiceLanguage";
24
+ import ObjectID from "../../../Types/ObjectID";
25
+
26
+ /*
27
+ * The service alert library, and the only template module whose answer depends
28
+ * on the resource rather than only on its type.
29
+ *
30
+ * The failure modes worth spending a test file on are all silent — a template
31
+ * that is registered, renders a card, creates a monitor, and then never fires:
32
+ *
33
+ * 1. A `checkOn` the evaluator does not implement for that monitor type.
34
+ * Only `MetricValue` / `SpanCount` / `ExceptionCount` are evaluated, one
35
+ * per monitor type, and anything else returns null forever.
36
+ * 2. A `metricAlias` that matches no query and no formula. The evaluator
37
+ * silently falls back to result slot 0, so a ratio template with a typo'd
38
+ * alias thresholds raw bytes against "90" and fires permanently.
39
+ * 3. A monitor step that is not scoped to the service, which would evaluate
40
+ * against every service in the project.
41
+ * 4. The language filter letting one runtime's templates through to another
42
+ * runtime, which is how you get a JVM monitor on a Go service.
43
+ */
44
+
45
+ const ARGS: ServiceAlertTemplateArgs = {
46
+ serviceId: ObjectID.generate().toString(),
47
+ onlineMonitorStatusId: ObjectID.generate(),
48
+ offlineMonitorStatusId: ObjectID.generate(),
49
+ defaultIncidentSeverityId: ObjectID.generate(),
50
+ defaultAlertSeverityId: ObjectID.generate(),
51
+ monitorName: "payments-api",
52
+ };
53
+
54
+ // The counts are canaries. Update them deliberately when adding a template.
55
+ const AGNOSTIC_COUNT: number = 8;
56
+
57
+ const TEMPLATE_COUNT_BY_LANGUAGE: Partial<Record<ServiceLanguage, number>> = {
58
+ java: 6,
59
+ dotnet: 4,
60
+ nodejs: 4,
61
+ python: 3,
62
+ go: 4,
63
+ };
64
+
65
+ /*
66
+ * Languages the module deliberately ships nothing extra for. They are not an
67
+ * oversight: none of these ecosystems has a default OpenTelemetry runtime
68
+ * metrics source, so any runtime template written for them would query a
69
+ * metric name that nobody emits.
70
+ */
71
+ const LANGUAGES_WITHOUT_TEMPLATES: Array<ServiceLanguage> = [
72
+ "ruby",
73
+ "php",
74
+ "rust",
75
+ "erlang",
76
+ "swift",
77
+ "cpp",
78
+ "webjs",
79
+ ];
80
+
81
+ const ALL_TEMPLATES: Array<ServiceAlertTemplate> =
82
+ getAllServiceAlertTemplates();
83
+
84
+ /*
85
+ * The one CheckOn the evaluator implements for each monitor type. Anything
86
+ * else is accepted by the form, saved to the database, and then never
87
+ * compared against anything.
88
+ */
89
+ const CHECK_ON_BY_MONITOR_TYPE: Record<string, CheckOn> = {
90
+ [MonitorType.Metrics]: CheckOn.MetricValue,
91
+ [MonitorType.Traces]: CheckOn.SpanCount,
92
+ [MonitorType.Exceptions]: CheckOn.ExceptionCount,
93
+ };
94
+
95
+ // The comparators `CompareCriteria.compareCriteriaNumbers` actually implements.
96
+ const NUMERIC_FILTER_TYPES: Array<FilterType> = [
97
+ FilterType.GreaterThan,
98
+ FilterType.LessThan,
99
+ FilterType.EqualTo,
100
+ FilterType.NotEqualTo,
101
+ FilterType.GreaterThanOrEqualTo,
102
+ FilterType.LessThanOrEqualTo,
103
+ ];
104
+
105
+ function getCriteriaInstances(
106
+ step: MonitorStep,
107
+ ): Array<MonitorCriteriaInstance> {
108
+ return step.data?.monitorCriteria?.data?.monitorCriteriaInstanceArray || [];
109
+ }
110
+
111
+ function getFilters(step: MonitorStep): Array<CriteriaFilter> {
112
+ return getCriteriaInstances(step).flatMap(
113
+ (instance: MonitorCriteriaInstance) => {
114
+ return instance.data?.filters || [];
115
+ },
116
+ );
117
+ }
118
+
119
+ function buildStep(template: ServiceAlertTemplate): MonitorStep {
120
+ return template.getMonitorStep(ARGS);
121
+ }
122
+
123
+ describe("ServiceAlertTemplates", () => {
124
+ describe("the library itself", () => {
125
+ it("ships the expected number of templates", () => {
126
+ const languageCount: number = (
127
+ Object.values(TEMPLATE_COUNT_BY_LANGUAGE) as Array<number>
128
+ ).reduce((total: number, count: number) => {
129
+ return total + count;
130
+ }, 0);
131
+
132
+ expect(ALL_TEMPLATES.length).toBe(AGNOSTIC_COUNT + languageCount);
133
+ });
134
+
135
+ it("gives every template a unique id", () => {
136
+ const ids: Array<string> = ALL_TEMPLATES.map(
137
+ (template: ServiceAlertTemplate) => {
138
+ return template.id;
139
+ },
140
+ );
141
+
142
+ expect(new Set<string>(ids).size).toBe(ids.length);
143
+ });
144
+
145
+ /*
146
+ * Template ids are only guaranteed unique WITHIN a module, but the
147
+ * registry-wide id is `<resourceType>:<templateId>` and every module
148
+ * self-prefixes by convention. Keeping the convention here means a
149
+ * `service-` id can never be confused for anyone else's in a log line or a
150
+ * dismissal row.
151
+ */
152
+ it("prefixes every id with service-", () => {
153
+ for (const template of ALL_TEMPLATES) {
154
+ expect(template.id.startsWith("service-")).toBe(true);
155
+ }
156
+ });
157
+
158
+ it("gives every template a name and a description", () => {
159
+ for (const template of ALL_TEMPLATES) {
160
+ expect(template.name.trim().length).toBeGreaterThan(0);
161
+ expect(template.description.trim().length).toBeGreaterThan(0);
162
+ }
163
+ });
164
+
165
+ it("uses only the three telemetry monitor types", () => {
166
+ for (const template of ALL_TEMPLATES) {
167
+ expect([
168
+ MonitorType.Metrics,
169
+ MonitorType.Traces,
170
+ MonitorType.Exceptions,
171
+ ]).toContain(template.monitorType);
172
+ }
173
+ });
174
+
175
+ it("uses only the two recommendation severities", () => {
176
+ for (const template of ALL_TEMPLATES) {
177
+ expect(["Critical", "Warning"]).toContain(template.severity);
178
+ }
179
+ });
180
+
181
+ /*
182
+ * The accessors hand out copies. A caller that sorts or splices the result
183
+ * — the recommendations page sorts for display — must not be able to
184
+ * reorder the library for everyone else, which would make the category
185
+ * order the page renders depend on who rendered first.
186
+ */
187
+ it("does not leak the internal array", () => {
188
+ const first: Array<ServiceAlertTemplate> = getAllServiceAlertTemplates();
189
+ first.pop();
190
+
191
+ expect(getAllServiceAlertTemplates().length).toBe(ALL_TEMPLATES.length);
192
+ });
193
+
194
+ it("looks a template up by id, and answers undefined for an unknown one", () => {
195
+ for (const template of ALL_TEMPLATES) {
196
+ expect(getServiceAlertTemplateById(template.id)).toBe(template);
197
+ }
198
+
199
+ expect(
200
+ getServiceAlertTemplateById("service-does-not-exist"),
201
+ ).toBeUndefined();
202
+ });
203
+ });
204
+
205
+ describe("selecting by language", () => {
206
+ it("returns exactly the agnostic set when the language is unknown", () => {
207
+ const forNull: Array<ServiceAlertTemplate> =
208
+ getServiceAlertTemplates(null);
209
+
210
+ expect(forNull.length).toBe(AGNOSTIC_COUNT);
211
+ expect(getServiceAlertTemplates(undefined).length).toBe(AGNOSTIC_COUNT);
212
+ expect(getServiceAlertTemplates().length).toBe(AGNOSTIC_COUNT);
213
+
214
+ for (const template of forNull) {
215
+ expect(template.language).toBeUndefined();
216
+ }
217
+ });
218
+
219
+ it.each(
220
+ Object.entries(TEMPLATE_COUNT_BY_LANGUAGE) as Array<
221
+ [ServiceLanguage, number]
222
+ >,
223
+ )(
224
+ "gives %s the agnostic set plus %s of its own",
225
+ (language: ServiceLanguage, ownCount: number) => {
226
+ const templates: Array<ServiceAlertTemplate> =
227
+ getServiceAlertTemplates(language);
228
+
229
+ expect(templates.length).toBe(AGNOSTIC_COUNT + ownCount);
230
+
231
+ const own: Array<ServiceAlertTemplate> = templates.filter(
232
+ (template: ServiceAlertTemplate) => {
233
+ return Boolean(template.language);
234
+ },
235
+ );
236
+
237
+ expect(own.length).toBe(ownCount);
238
+
239
+ for (const template of own) {
240
+ expect(template.language).toBe(language);
241
+ }
242
+ },
243
+ );
244
+
245
+ /*
246
+ * The whole point of the feature: one runtime's templates must never
247
+ * appear for another. A leak here is not a rendering bug — it creates
248
+ * monitors that query metrics the service does not emit and sit green
249
+ * forever.
250
+ */
251
+ it("never mixes one runtime's templates into another's", () => {
252
+ const languages: Array<ServiceLanguage> =
253
+ getLanguagesWithServiceAlertTemplates();
254
+
255
+ expect(languages.length).toBe(
256
+ Object.keys(TEMPLATE_COUNT_BY_LANGUAGE).length,
257
+ );
258
+
259
+ for (const language of languages) {
260
+ for (const template of getServiceAlertTemplates(language)) {
261
+ if (template.language) {
262
+ expect(template.language).toBe(language);
263
+ }
264
+ }
265
+ }
266
+ });
267
+
268
+ it.each(LANGUAGES_WITHOUT_TEMPLATES)(
269
+ "gives %s exactly the agnostic set, rather than nothing",
270
+ (language: ServiceLanguage) => {
271
+ const templates: Array<ServiceAlertTemplate> =
272
+ getServiceAlertTemplates(language);
273
+
274
+ expect(templates.length).toBe(AGNOSTIC_COUNT);
275
+
276
+ for (const template of templates) {
277
+ expect(template.language).toBeUndefined();
278
+ }
279
+ },
280
+ );
281
+
282
+ /*
283
+ * Order matters downstream: `MonitorRecommendationCatalog.getCategories`
284
+ * derives its section order from the order templates are returned in, so
285
+ * the agnostic sections have to stay on top for every language.
286
+ */
287
+ it("returns the agnostic set first, in a stable order", () => {
288
+ const agnosticIds: Array<string> = getServiceAlertTemplates(null).map(
289
+ (template: ServiceAlertTemplate) => {
290
+ return template.id;
291
+ },
292
+ );
293
+
294
+ for (const language of getLanguagesWithServiceAlertTemplates()) {
295
+ const ids: Array<string> = getServiceAlertTemplates(language).map(
296
+ (template: ServiceAlertTemplate) => {
297
+ return template.id;
298
+ },
299
+ );
300
+
301
+ expect(ids.slice(0, agnosticIds.length)).toEqual(agnosticIds);
302
+ }
303
+ });
304
+
305
+ it("reports only languages that actually have templates", () => {
306
+ for (const language of getLanguagesWithServiceAlertTemplates()) {
307
+ expect(LANGUAGES_WITHOUT_TEMPLATES).not.toContain(language);
308
+ expect(TEMPLATE_COUNT_BY_LANGUAGE[language]).toBeGreaterThan(0);
309
+ }
310
+ });
311
+ });
312
+
313
+ describe("the monitor step each template builds", () => {
314
+ it("produces a step that passes monitor-step validation", () => {
315
+ for (const template of ALL_TEMPLATES) {
316
+ const monitorSteps: MonitorSteps = new MonitorSteps();
317
+
318
+ monitorSteps.data = {
319
+ monitorStepsInstanceArray: [buildStep(template)],
320
+ defaultMonitorStatusId: ARGS.onlineMonitorStatusId,
321
+ };
322
+
323
+ expect(
324
+ MonitorSteps.getValidationError(monitorSteps, template.monitorType),
325
+ ).toBeNull();
326
+ }
327
+ });
328
+
329
+ it("seeds the sub-config its monitor type is evaluated through", () => {
330
+ /*
331
+ * `getDefaultMonitorStep` only seeds the config matching the monitor
332
+ * type, and the metrics worker THROWS when `metricMonitor` is missing
333
+ * rather than falling back — so a Metrics template that forgets
334
+ * `setMetricMonitor` is a monitor that errors every cycle.
335
+ */
336
+ for (const template of ALL_TEMPLATES) {
337
+ const step: MonitorStep = buildStep(template);
338
+
339
+ if (template.monitorType === MonitorType.Metrics) {
340
+ expect(step.data?.metricMonitor).toBeDefined();
341
+ }
342
+
343
+ if (template.monitorType === MonitorType.Traces) {
344
+ expect(step.data?.traceMonitor).toBeDefined();
345
+ }
346
+
347
+ if (template.monitorType === MonitorType.Exceptions) {
348
+ expect(step.data?.exceptionMonitor).toBeDefined();
349
+ }
350
+ }
351
+ });
352
+
353
+ it("scopes every step to the service it was built for", () => {
354
+ for (const template of ALL_TEMPLATES) {
355
+ const step: MonitorStep = buildStep(template);
356
+
357
+ const config:
358
+ | MonitorStepMetricMonitor
359
+ | MonitorStepTraceMonitor
360
+ | MonitorStepExceptionMonitor
361
+ | undefined =
362
+ step.data?.metricMonitor ||
363
+ step.data?.traceMonitor ||
364
+ step.data?.exceptionMonitor;
365
+
366
+ const telemetryServiceIds: Array<ObjectID> =
367
+ config?.telemetryServiceIds || [];
368
+
369
+ expect(telemetryServiceIds.length).toBe(1);
370
+ expect(telemetryServiceIds[0]!.toString()).toBe(ARGS.serviceId);
371
+ }
372
+ });
373
+
374
+ it("builds an unhealthy and a healthy criteria instance for every template", () => {
375
+ /*
376
+ * Both, always. A step with no healthy criteria opens an incident,
377
+ * auto-resolves it, and then leaves the monitor in its offline status
378
+ * with nothing that can ever move it back.
379
+ */
380
+ for (const template of ALL_TEMPLATES) {
381
+ const instances: Array<MonitorCriteriaInstance> = getCriteriaInstances(
382
+ buildStep(template),
383
+ );
384
+
385
+ expect(instances.length).toBe(2);
386
+
387
+ const [unhealthy, healthy]: Array<MonitorCriteriaInstance> = instances;
388
+
389
+ expect(unhealthy!.data?.monitorStatusId?.toString()).toBe(
390
+ ARGS.offlineMonitorStatusId.toString(),
391
+ );
392
+ expect(unhealthy!.data?.createIncidents).toBe(true);
393
+ expect(unhealthy!.data?.createAlerts).toBe(true);
394
+ expect(unhealthy!.data?.incidents?.length).toBe(1);
395
+ expect(unhealthy!.data?.alerts?.length).toBe(1);
396
+
397
+ expect(healthy!.data?.monitorStatusId?.toString()).toBe(
398
+ ARGS.onlineMonitorStatusId.toString(),
399
+ );
400
+ expect(healthy!.data?.createIncidents).toBe(false);
401
+ expect(healthy!.data?.createAlerts).toBe(false);
402
+ expect(healthy!.data?.incidents?.length).toBe(0);
403
+ expect(healthy!.data?.alerts?.length).toBe(0);
404
+ }
405
+ });
406
+
407
+ it("names and describes every criteria instance", () => {
408
+ for (const template of ALL_TEMPLATES) {
409
+ for (const instance of getCriteriaInstances(buildStep(template))) {
410
+ expect((instance.data?.name || "").trim().length).toBeGreaterThan(0);
411
+ expect(
412
+ (instance.data?.description || "").trim().length,
413
+ ).toBeGreaterThan(0);
414
+ }
415
+ }
416
+ });
417
+
418
+ it("interpolates the monitor name into every incident title", () => {
419
+ for (const template of ALL_TEMPLATES) {
420
+ for (const instance of getCriteriaInstances(buildStep(template))) {
421
+ for (const incident of instance.data?.incidents || []) {
422
+ expect(incident.title).toContain(ARGS.monitorName);
423
+ expect((incident.description || "").trim().length).toBeGreaterThan(
424
+ 0,
425
+ );
426
+ expect(incident.incidentSeverityId?.toString()).toBe(
427
+ ARGS.defaultIncidentSeverityId.toString(),
428
+ );
429
+ expect(incident.autoResolveIncident).toBe(true);
430
+ // Filled in by the create flow from what the user picks.
431
+ expect(incident.onCallPolicyIds).toEqual([]);
432
+ }
433
+
434
+ for (const alert of instance.data?.alerts || []) {
435
+ expect(alert.title).toContain(ARGS.monitorName);
436
+ expect(alert.alertSeverityId?.toString()).toBe(
437
+ ARGS.defaultAlertSeverityId.toString(),
438
+ );
439
+ expect(alert.autoResolveAlert).toBe(true);
440
+ expect(alert.onCallPolicyIds).toEqual([]);
441
+ }
442
+ }
443
+ }
444
+ });
445
+ });
446
+
447
+ describe("criteria the evaluator can actually evaluate", () => {
448
+ /*
449
+ * The highest-value test in the file. There is exactly one implemented
450
+ * CheckOn per telemetry monitor type; every other value type-checks, saves
451
+ * and is then ignored, producing a monitor that is green forever.
452
+ */
453
+ it("uses the one checkOn its monitor type implements", () => {
454
+ for (const template of ALL_TEMPLATES) {
455
+ const expected: CheckOn | undefined =
456
+ CHECK_ON_BY_MONITOR_TYPE[template.monitorType];
457
+
458
+ expect(expected).toBeDefined();
459
+
460
+ const filters: Array<CriteriaFilter> = getFilters(buildStep(template));
461
+
462
+ expect(filters.length).toBeGreaterThan(0);
463
+
464
+ for (const filter of filters) {
465
+ expect(filter.checkOn).toBe(expected);
466
+ }
467
+ }
468
+ });
469
+
470
+ it("uses only comparators the numeric evaluator implements", () => {
471
+ for (const template of ALL_TEMPLATES) {
472
+ for (const filter of getFilters(buildStep(template))) {
473
+ expect(NUMERIC_FILTER_TYPES).toContain(filter.filterType);
474
+ expect(typeof filter.value).toBe("number");
475
+ }
476
+ }
477
+ });
478
+
479
+ /*
480
+ * `evaluateOverTime` belongs to the probe-monitor path and is not read on
481
+ * the telemetry path. Setting it would look like it configured something.
482
+ */
483
+ it("does not set evaluateOverTime on any filter", () => {
484
+ for (const template of ALL_TEMPLATES) {
485
+ for (const filter of getFilters(buildStep(template))) {
486
+ expect(filter.evaluateOverTime).toBeFalsy();
487
+ }
488
+ }
489
+ });
490
+
491
+ it("partitions the range at the threshold, with no gap and no overlap", () => {
492
+ /*
493
+ * The unhealthy and healthy filters must be exact complements. A gap
494
+ * leaves the monitor stuck in whichever state it was last in; an
495
+ * overlap makes the outcome depend on evaluation order.
496
+ */
497
+ const COMPLEMENTS: Record<string, FilterType> = {
498
+ [FilterType.GreaterThan]: FilterType.LessThanOrEqualTo,
499
+ [FilterType.GreaterThanOrEqualTo]: FilterType.LessThan,
500
+ [FilterType.LessThan]: FilterType.GreaterThanOrEqualTo,
501
+ [FilterType.LessThanOrEqualTo]: FilterType.GreaterThan,
502
+ };
503
+
504
+ for (const template of ALL_TEMPLATES) {
505
+ const instances: Array<MonitorCriteriaInstance> = getCriteriaInstances(
506
+ buildStep(template),
507
+ );
508
+
509
+ const unhealthy: CriteriaFilter = instances[0]!.data!.filters[0]!;
510
+ const healthy: CriteriaFilter = instances[1]!.data!.filters[0]!;
511
+
512
+ expect(healthy.filterType).toBe(
513
+ COMPLEMENTS[unhealthy.filterType as FilterType],
514
+ );
515
+ expect(healthy.value).toBe(unhealthy.value);
516
+ }
517
+ });
518
+
519
+ /*
520
+ * A metricAlias that matches no query and no formula does not throw — the
521
+ * evaluator falls back to result slot 0 and thresholds whatever happens to
522
+ * be there. On the two ratio templates that means comparing raw bytes
523
+ * against 90, which fires immediately and never clears.
524
+ */
525
+ it("names an alias that exists, for every metric template", () => {
526
+ for (const template of ALL_TEMPLATES) {
527
+ if (template.monitorType !== MonitorType.Metrics) {
528
+ continue;
529
+ }
530
+
531
+ const step: MonitorStep = buildStep(template);
532
+ const metricMonitor: MonitorStepMetricMonitor | undefined =
533
+ step.data?.metricMonitor;
534
+
535
+ const knownAliases: Array<string> = [
536
+ ...(metricMonitor?.metricViewConfig?.queryConfigs || []).map(
537
+ (queryConfig: MetricQueryConfigData) => {
538
+ return queryConfig.metricAliasData?.metricVariable || "";
539
+ },
540
+ ),
541
+ ...(metricMonitor?.metricViewConfig?.formulaConfigs || []).map(
542
+ (formulaConfig: MetricFormulaConfigData) => {
543
+ return formulaConfig.metricAliasData?.metricVariable || "";
544
+ },
545
+ ),
546
+ ];
547
+
548
+ for (const filter of getFilters(step)) {
549
+ const alias: string | undefined =
550
+ filter.metricMonitorOptions?.metricAlias;
551
+
552
+ expect(alias).toBeTruthy();
553
+ expect(knownAliases).toContain(alias);
554
+ }
555
+ }
556
+ });
557
+
558
+ it("gives every metric query a metric name and a distinct alias", () => {
559
+ for (const template of ALL_TEMPLATES) {
560
+ if (template.monitorType !== MonitorType.Metrics) {
561
+ continue;
562
+ }
563
+
564
+ const metricMonitor: MonitorStepMetricMonitor | undefined =
565
+ buildStep(template).data?.metricMonitor;
566
+
567
+ const queryConfigs: Array<MetricQueryConfigData> =
568
+ metricMonitor?.metricViewConfig?.queryConfigs || [];
569
+
570
+ expect(queryConfigs.length).toBeGreaterThan(0);
571
+
572
+ const aliases: Array<string> = [];
573
+
574
+ for (const queryConfig of queryConfigs) {
575
+ const metricName: unknown =
576
+ queryConfig.metricQueryData?.filterData?.["metricName"];
577
+
578
+ expect(typeof metricName).toBe("string");
579
+ expect((metricName as string).length).toBeGreaterThan(0);
580
+
581
+ const alias: string =
582
+ queryConfig.metricAliasData?.metricVariable || "";
583
+
584
+ expect(alias.length).toBeGreaterThan(0);
585
+ aliases.push(alias);
586
+ }
587
+
588
+ for (const formulaConfig of metricMonitor?.metricViewConfig
589
+ ?.formulaConfigs || []) {
590
+ aliases.push(formulaConfig.metricAliasData?.metricVariable || "");
591
+ }
592
+
593
+ expect(new Set<string>(aliases).size).toBe(aliases.length);
594
+ }
595
+ });
596
+ });
597
+
598
+ describe("the two ratio templates", () => {
599
+ const RATIO_TEMPLATE_IDS: Array<string> = [
600
+ "service-java-heap-utilization",
601
+ "service-nodejs-heap-pressure",
602
+ ];
603
+
604
+ it.each(RATIO_TEMPLATE_IDS)(
605
+ "%s thresholds the formula, not either of its two queries",
606
+ (templateId: string) => {
607
+ const template: ServiceAlertTemplate | undefined =
608
+ getServiceAlertTemplateById(templateId);
609
+
610
+ expect(template).toBeDefined();
611
+
612
+ const metricMonitor: MonitorStepMetricMonitor | undefined = buildStep(
613
+ template!,
614
+ ).data?.metricMonitor;
615
+
616
+ const queryConfigs: Array<MetricQueryConfigData> =
617
+ metricMonitor?.metricViewConfig?.queryConfigs || [];
618
+ const formulaConfigs: Array<MetricFormulaConfigData> =
619
+ metricMonitor?.metricViewConfig?.formulaConfigs || [];
620
+
621
+ expect(queryConfigs.length).toBe(2);
622
+ expect(formulaConfigs.length).toBe(1);
623
+
624
+ const formulaAlias: string =
625
+ formulaConfigs[0]!.metricAliasData?.metricVariable || "";
626
+ const numeratorAlias: string =
627
+ queryConfigs[0]!.metricAliasData?.metricVariable || "";
628
+ const denominatorAlias: string =
629
+ queryConfigs[1]!.metricAliasData?.metricVariable || "";
630
+
631
+ expect(formulaConfigs[0]!.metricFormulaData.metricFormula).toBe(
632
+ `(${numeratorAlias} / ${denominatorAlias}) * 100`,
633
+ );
634
+
635
+ for (const filter of getFilters(buildStep(template!))) {
636
+ expect(filter.metricMonitorOptions?.metricAlias).toBe(formulaAlias);
637
+ }
638
+ },
639
+ );
640
+
641
+ it.each(RATIO_TEMPLATE_IDS)(
642
+ "%s thresholds a percentage, so it needs no per-deployment tuning",
643
+ (templateId: string) => {
644
+ const template: ServiceAlertTemplate =
645
+ getServiceAlertTemplateById(templateId)!;
646
+
647
+ for (const filter of getFilters(buildStep(template))) {
648
+ expect(filter.value).toBeGreaterThan(0);
649
+ expect(filter.value).toBeLessThanOrEqual(100);
650
+ expect(filter.metricMonitorOptions?.thresholdUnit).toBe("%");
651
+ }
652
+ },
653
+ );
654
+ });
655
+
656
+ describe("the metric names each template reads", () => {
657
+ /*
658
+ * Restated here so a rename has to be made twice, on purpose. A template
659
+ * pointed at a metric name nobody emits is registered, renders, creates a
660
+ * monitor, and is green forever — there is no failing state to notice.
661
+ */
662
+ const EXPECTED_METRIC_NAMES: Record<string, Array<string>> = {
663
+ "service-latency-p95": ["http.server.request.duration"],
664
+ "service-latency-p99": ["http.server.request.duration"],
665
+ "service-process-cpu-saturation": ["process.cpu.utilization"],
666
+ "service-process-memory-high": ["process.memory.usage"],
667
+ "service-java-heap-utilization": ["jvm.memory.used", "jvm.memory.limit"],
668
+ "service-java-live-heap-after-gc": ["jvm.memory.used_after_last_gc"],
669
+ "service-java-gc-pause-p99": ["jvm.gc.duration"],
670
+ "service-java-cpu-saturation": ["jvm.cpu.recent_utilization"],
671
+ "service-java-thread-explosion": ["jvm.thread.count"],
672
+ "service-java-non-heap-growth": ["jvm.memory.used"],
673
+ "service-dotnet-threadpool-starvation": [
674
+ "dotnet.thread_pool.queue.length",
675
+ ],
676
+ "service-dotnet-threadpool-growth": ["dotnet.thread_pool.thread.count"],
677
+ "service-dotnet-gen2-heap-growth": [
678
+ "dotnet.gc.last_collection.heap.size",
679
+ ],
680
+ "service-dotnet-working-set": ["dotnet.process.memory.working_set"],
681
+ "service-nodejs-event-loop-saturated": ["nodejs.eventloop.utilization"],
682
+ "service-nodejs-event-loop-lag": ["nodejs.eventloop.delay.p99"],
683
+ "service-nodejs-heap-pressure": [
684
+ "v8js.memory.heap.used",
685
+ "v8js.memory.heap.limit",
686
+ ],
687
+ "service-nodejs-gc-pause-p99": ["v8js.gc.duration"],
688
+ "service-python-request-concurrency": ["http.server.active_requests"],
689
+ "service-python-rss-memory": ["process.runtime.cpython.memory"],
690
+ "service-python-thread-growth": ["process.runtime.cpython.thread_count"],
691
+ "service-go-goroutine-leak": ["go.goroutine.count"],
692
+ "service-go-heap-memory": ["go.memory.used"],
693
+ "service-go-stack-memory": ["go.memory.used"],
694
+ "service-go-scheduler-latency-p99": ["go.schedule.duration"],
695
+ };
696
+
697
+ it("covers every metric template", () => {
698
+ const metricTemplateIds: Array<string> = ALL_TEMPLATES.filter(
699
+ (template: ServiceAlertTemplate) => {
700
+ return template.monitorType === MonitorType.Metrics;
701
+ },
702
+ ).map((template: ServiceAlertTemplate) => {
703
+ return template.id;
704
+ });
705
+
706
+ expect(metricTemplateIds.sort()).toEqual(
707
+ Object.keys(EXPECTED_METRIC_NAMES).sort(),
708
+ );
709
+ });
710
+
711
+ it.each(Object.entries(EXPECTED_METRIC_NAMES))(
712
+ "%s reads %s",
713
+ (templateId: string, metricNames: Array<string>) => {
714
+ const template: ServiceAlertTemplate =
715
+ getServiceAlertTemplateById(templateId)!;
716
+
717
+ const queryConfigs: Array<MetricQueryConfigData> =
718
+ buildStep(template).data?.metricMonitor?.metricViewConfig
719
+ ?.queryConfigs || [];
720
+
721
+ expect(
722
+ queryConfigs.map((queryConfig: MetricQueryConfigData) => {
723
+ return queryConfig.metricQueryData?.filterData?.["metricName"];
724
+ }),
725
+ ).toEqual(metricNames);
726
+ },
727
+ );
728
+
729
+ /*
730
+ * Two Go templates read the same metric and are told apart only by an
731
+ * attribute filter: `go.memory.used` splits into `stack` and `other`
732
+ * series, and an unfiltered query averages the two — reporting about half
733
+ * the heap, which makes the threshold wrong by that factor.
734
+ */
735
+ it("separates the two go.memory.used templates by attribute", () => {
736
+ const heap: ServiceAlertTemplate = getServiceAlertTemplateById(
737
+ "service-go-heap-memory",
738
+ )!;
739
+ const stack: ServiceAlertTemplate = getServiceAlertTemplateById(
740
+ "service-go-stack-memory",
741
+ )!;
742
+
743
+ const attributesOf: (template: ServiceAlertTemplate) => unknown = (
744
+ template: ServiceAlertTemplate,
745
+ ): unknown => {
746
+ return buildStep(template).data?.metricMonitor?.metricViewConfig
747
+ ?.queryConfigs?.[0]?.metricQueryData?.filterData?.["attributes"];
748
+ };
749
+
750
+ expect(attributesOf(heap)).toEqual({ "go.memory.type": "other" });
751
+ expect(attributesOf(stack)).toEqual({ "go.memory.type": "stack" });
752
+ });
753
+
754
+ it("separates the two jvm.memory.used templates by attribute", () => {
755
+ const nonHeap: ServiceAlertTemplate = getServiceAlertTemplateById(
756
+ "service-java-non-heap-growth",
757
+ )!;
758
+
759
+ expect(
760
+ buildStep(nonHeap).data?.metricMonitor?.metricViewConfig
761
+ ?.queryConfigs?.[0]?.metricQueryData?.filterData?.["attributes"],
762
+ ).toEqual({ "jvm.memory.type": "non_heap" });
763
+ });
764
+ });
765
+
766
+ describe("the trace and exception templates", () => {
767
+ it("counts only error-status spans on the two error templates", () => {
768
+ for (const templateId of [
769
+ "service-failed-operations",
770
+ "service-error-burst",
771
+ ]) {
772
+ const traceMonitor: MonitorStepTraceMonitor | undefined = buildStep(
773
+ getServiceAlertTemplateById(templateId)!,
774
+ ).data?.traceMonitor;
775
+
776
+ expect(traceMonitor?.spanStatuses?.length).toBe(1);
777
+ }
778
+ });
779
+
780
+ /*
781
+ * The liveness template counts ALL spans, not error spans — filtering to
782
+ * errors would make "no errors" indistinguishable from "no traffic", and
783
+ * the whole point of it is to catch the second.
784
+ */
785
+ it("counts all spans on the liveness template, and inverts the comparison", () => {
786
+ const template: ServiceAlertTemplate = getServiceAlertTemplateById(
787
+ "service-traffic-stopped",
788
+ )!;
789
+
790
+ const step: MonitorStep = buildStep(template);
791
+
792
+ expect(step.data?.traceMonitor?.spanStatuses).toEqual([]);
793
+
794
+ const filters: Array<CriteriaFilter> = getFilters(step);
795
+
796
+ expect(filters[0]!.filterType).toBe(FilterType.LessThan);
797
+ expect(filters[1]!.filterType).toBe(FilterType.GreaterThanOrEqualTo);
798
+ });
799
+
800
+ /*
801
+ * Excluding resolved and archived exception groups is what lets
802
+ * acknowledging an exception close the incident it opened. Including them
803
+ * would make the alert unclearable short of the occurrences ageing out.
804
+ */
805
+ it("ignores exceptions the team has already resolved or archived", () => {
806
+ const exceptionMonitor: MonitorStepExceptionMonitor | undefined =
807
+ buildStep(getServiceAlertTemplateById("service-unhandled-exceptions")!)
808
+ .data?.exceptionMonitor;
809
+
810
+ expect(exceptionMonitor?.includeResolved).toBe(false);
811
+ expect(exceptionMonitor?.includeArchived).toBe(false);
812
+ expect(exceptionMonitor?.exceptionTypes).toEqual([]);
813
+ expect(exceptionMonitor?.message).toBe("");
814
+ });
815
+
816
+ /*
817
+ * These two windows are load-bearing beyond their own semantics: they are
818
+ * what keeps these templates' coverage fingerprints distinct from the RUM
819
+ * catalog's structurally identical pair. See the comment on
820
+ * failedOperationsTemplate.
821
+ */
822
+ it("evaluates the two agnostic error templates over ten minutes", () => {
823
+ expect(
824
+ buildStep(getServiceAlertTemplateById("service-failed-operations")!)
825
+ .data?.traceMonitor?.lastXSecondsOfSpans,
826
+ ).toBe(600);
827
+
828
+ expect(
829
+ buildStep(getServiceAlertTemplateById("service-unhandled-exceptions")!)
830
+ .data?.exceptionMonitor?.lastXSecondsOfExceptions,
831
+ ).toBe(600);
832
+ });
833
+ });
834
+
835
+ describe("thresholds", () => {
836
+ it("expresses ratio thresholds on the 0-1 scale the SDKs report", () => {
837
+ /*
838
+ * `process.cpu.utilization`, `jvm.cpu.recent_utilization` and
839
+ * `nodejs.eventloop.utilization` are all dimensionless [0, 1] ratios, so
840
+ * "85%" is the literal 0.85. A slip to 85 makes the template
841
+ * unreachable — the value can never exceed 1 — and nothing fails.
842
+ */
843
+ for (const templateId of [
844
+ "service-process-cpu-saturation",
845
+ "service-java-cpu-saturation",
846
+ "service-nodejs-event-loop-saturated",
847
+ ]) {
848
+ for (const filter of getFilters(
849
+ buildStep(getServiceAlertTemplateById(templateId)!),
850
+ )) {
851
+ expect(filter.value).toBeGreaterThan(0);
852
+ expect(filter.value).toBeLessThan(1);
853
+ }
854
+ }
855
+ });
856
+
857
+ it("states duration thresholds in milliseconds, and says so", () => {
858
+ /*
859
+ * Every duration metric here is reported in seconds by the semantic
860
+ * conventions, so the threshold only means what it reads as if the alias
861
+ * declares milliseconds — the worker converts each sample into the
862
+ * alias's unit before comparing.
863
+ */
864
+ for (const templateId of [
865
+ "service-latency-p95",
866
+ "service-latency-p99",
867
+ "service-java-gc-pause-p99",
868
+ "service-nodejs-event-loop-lag",
869
+ "service-nodejs-gc-pause-p99",
870
+ "service-go-scheduler-latency-p99",
871
+ ]) {
872
+ const template: ServiceAlertTemplate =
873
+ getServiceAlertTemplateById(templateId)!;
874
+
875
+ const queryConfigs: Array<MetricQueryConfigData> =
876
+ buildStep(template).data?.metricMonitor?.metricViewConfig
877
+ ?.queryConfigs || [];
878
+
879
+ expect(queryConfigs[0]!.metricAliasData?.legendUnit).toBe("ms");
880
+
881
+ for (const filter of getFilters(buildStep(template))) {
882
+ expect(filter.metricMonitorOptions?.thresholdUnit).toBe("ms");
883
+ expect(filter.value).toBeGreaterThanOrEqual(50);
884
+ }
885
+ }
886
+ });
887
+
888
+ it("gives the error-burst template a much higher bar than failed-operations", () => {
889
+ const anyFailure: CriteriaFilter = getFilters(
890
+ buildStep(getServiceAlertTemplateById("service-failed-operations")!),
891
+ )[0]!;
892
+ const burst: CriteriaFilter = getFilters(
893
+ buildStep(getServiceAlertTemplateById("service-error-burst")!),
894
+ )[0]!;
895
+
896
+ expect(Number(burst.value)).toBeGreaterThan(Number(anyFailure.value));
897
+ expect(
898
+ getServiceAlertTemplateById("service-failed-operations")!.severity,
899
+ ).toBe("Warning");
900
+ expect(getServiceAlertTemplateById("service-error-burst")!.severity).toBe(
901
+ "Critical",
902
+ );
903
+ });
904
+ });
905
+ });