@oneuptime/common 12.0.18 → 12.0.20

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 (262) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/EnterpriseLicenseAPI.ts +94 -19
  11. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  12. package/Server/API/UserSlackAPI.ts +125 -0
  13. package/Server/EnvironmentConfig.ts +29 -107
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  19. package/Server/Infrastructure/Queue.ts +7 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +171 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Response.ts +2 -2
  40. package/Server/Utils/StartServer.ts +2 -2
  41. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  42. package/Server/Views/Partials/AnalyticsConsent.ejs +533 -0
  43. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  44. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  45. package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +465 -0
  46. package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +302 -0
  47. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +408 -0
  48. package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +474 -0
  49. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +368 -0
  50. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  51. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  52. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  53. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  54. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +763 -0
  55. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  56. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  57. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  58. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  59. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  60. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  61. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  62. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  63. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  64. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  65. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  66. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +138 -0
  67. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +516 -0
  68. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  69. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  70. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  71. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  72. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  73. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  74. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  75. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  76. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  77. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  78. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  79. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  80. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  81. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  82. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  83. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  84. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  85. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  86. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  87. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  88. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  89. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +53 -0
  90. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  91. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  92. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  93. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  94. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  95. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  96. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +241 -0
  97. package/Tests/UI/Components/Charts/ChartTooltipSorted.test.tsx +129 -0
  98. package/Tests/UI/Components/Charts/TooltipEntries.test.ts +155 -0
  99. package/Tests/UI/Components/Detail/DetailFieldErrors.test.tsx +347 -0
  100. package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +392 -0
  101. package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +549 -0
  102. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  103. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  104. package/Tests/Utils/EnterpriseLicenseSync.test.ts +562 -0
  105. package/Types/Analytics/RevenueEvent.ts +1 -0
  106. package/Types/Marketing/MarketingEvent.ts +140 -0
  107. package/UI/Components/Charts/Area/AreaChart.tsx +8 -1
  108. package/UI/Components/Charts/Bar/BarChart.tsx +8 -1
  109. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +19 -4
  110. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +20 -2
  111. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +19 -4
  112. package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +105 -0
  113. package/UI/Components/Charts/Line/LineChart.tsx +8 -1
  114. package/UI/Components/Detail/Detail.tsx +18 -1
  115. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  116. package/UI/Utils/PermissionGate.ts +56 -0
  117. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +244 -0
  118. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  119. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  120. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  121. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  123. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  125. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  126. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  127. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  129. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  131. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  133. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  134. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  135. package/build/dist/Server/API/EnterpriseLicenseAPI.js +66 -12
  136. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  137. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  138. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  139. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  140. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  141. package/build/dist/Server/EnvironmentConfig.js +25 -70
  142. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  151. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  152. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  153. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  154. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  155. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  156. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  157. package/build/dist/Server/Services/Index.js +6 -2
  158. package/build/dist/Server/Services/Index.js.map +1 -1
  159. package/build/dist/Server/Services/LlmLogService.js +25 -9
  160. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  161. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  162. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  163. package/build/dist/Server/Services/ProjectService.js +144 -0
  164. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  165. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  166. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  167. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  168. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  169. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  170. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  171. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  172. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  173. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  174. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  175. package/build/dist/Server/Services/UserService.js +26 -1
  176. package/build/dist/Server/Services/UserService.js.map +1 -1
  177. package/build/dist/Server/Services/UserSlackService.js +195 -0
  178. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  179. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  180. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  181. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  182. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  183. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  184. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  185. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  186. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  187. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  188. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  189. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  190. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  192. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  193. package/build/dist/Server/Utils/Response.js +2 -2
  194. package/build/dist/Server/Utils/Response.js.map +1 -1
  195. package/build/dist/Server/Utils/StartServer.js +2 -2
  196. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  197. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  198. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  199. package/build/dist/Types/Analytics/RevenueEvent.js +1 -0
  200. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -1
  201. package/build/dist/Types/Marketing/MarketingEvent.js +82 -0
  202. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  203. package/build/dist/UI/Components/Charts/Area/AreaChart.js +9 -1
  204. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  205. package/build/dist/UI/Components/Charts/Bar/BarChart.js +9 -1
  206. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  207. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +22 -13
  208. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  209. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +27 -15
  210. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  211. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +27 -18
  212. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  213. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +71 -0
  214. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -0
  215. package/build/dist/UI/Components/Charts/Line/LineChart.js +9 -1
  216. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  217. package/build/dist/UI/Components/Detail/Detail.js +17 -1
  218. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  219. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  220. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  221. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  222. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  223. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +174 -0
  224. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +1 -0
  225. package/package.json +1 -1
  226. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  227. package/Server/Services/MarketingConversionService.ts +0 -10
  228. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  229. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  230. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  231. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  232. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  233. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  234. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  235. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  236. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  237. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  238. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  239. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  240. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  241. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  242. package/Types/Marketing/MarketingConversion.ts +0 -53
  243. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  244. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  245. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  246. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  247. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  248. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  249. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  250. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  251. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  252. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  253. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  254. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  255. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  256. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  257. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  258. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  259. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  260. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  261. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  262. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -33,7 +33,7 @@ import getJestMockFunction, { MockFunction } from "../../MockType";
33
33
  * decision satisfied by the new answer.
34
34
  *
35
35
  * The old surface showed a masked, read-only list and a prefilled "please add
36
- * one yourself" email, because the seven notification method models are scoped
36
+ * one yourself" email, because the nine notification method models are scoped
37
37
  * to the person who owns the device: the columns behind them are the raw
38
38
  * address, the phone number, the webhook bearer url, the push device token and
39
39
  * the verification code, and an attempt to widen that scope so an admin could
@@ -44,7 +44,7 @@ import getJestMockFunction, { MockFunction } from "../../MockType";
44
44
  * So the models are STILL owner-scoped, and this page talks to a narrow
45
45
  * server-side capability instead. What that buys, and what it must not:
46
46
  *
47
- * - NO METHOD MODEL IS READ. Not by ModelAPI over UserEmail and its six
47
+ * - NO METHOD MODEL IS READ. Not by ModelAPI over UserEmail and its eight
48
48
  * siblings, and not through a nested relation select on a rule, which
49
49
  * reaches the same columns through a table an administrator IS allowed to
50
50
  * read. The assertion is about the REQUEST, because a component that asks
@@ -192,7 +192,7 @@ jest.mock("react-i18next", () => {
192
192
  * A stand-in ModelTable that records the model it was mounted over.
193
193
  *
194
194
  * It renders nothing on purpose. What this file needs from it is the single
195
- * fact that a table over UserEmail (or any of its six siblings) would be
195
+ * fact that a table over UserEmail (or any of its eight siblings) would be
196
196
  * visible here at all — the self-serve method components mount exactly those,
197
197
  * so "did the admin branch accidentally render the self-serve one?" is answered
198
198
  * by this list.
@@ -217,7 +217,9 @@ import TeamMember from "../../../Models/DatabaseModels/TeamMember";
217
217
  import User from "../../../Models/DatabaseModels/User";
218
218
  import UserCall from "../../../Models/DatabaseModels/UserCall";
219
219
  import UserEmail from "../../../Models/DatabaseModels/UserEmail";
220
+ import UserMicrosoftTeams from "../../../Models/DatabaseModels/UserMicrosoftTeams";
220
221
  import UserPush from "../../../Models/DatabaseModels/UserPush";
222
+ import UserSlack from "../../../Models/DatabaseModels/UserSlack";
221
223
  import UserSMS from "../../../Models/DatabaseModels/UserSMS";
222
224
  import UserTelegram from "../../../Models/DatabaseModels/UserTelegram";
223
225
  import UserWebhook from "../../../Models/DatabaseModels/UserWebhook";
@@ -238,7 +240,7 @@ const PROJECT_ID: ObjectID = new ObjectID(PROJECT_ID_STRING);
238
240
  const SIGNED_IN_USER_ID: ObjectID = new ObjectID(SIGNED_IN_USER_ID_STRING);
239
241
 
240
242
  /*
241
- * The seven models only their owner may read. Asserted as a SET because which
243
+ * The nine models only their owner may read. Asserted as a SET because which
242
244
  * one an admin surface reaches for hardly matters — every one carries a raw
243
245
  * identifier and most carry a credential.
244
246
  */
@@ -249,6 +251,8 @@ const NOTIFICATION_METHOD_MODELS: Array<unknown> = [
249
251
  UserPush,
250
252
  UserWhatsApp,
251
253
  UserTelegram,
254
+ UserSlack,
255
+ UserMicrosoftTeams,
252
256
  UserWebhook,
253
257
  ];
254
258
 
@@ -486,7 +490,7 @@ beforeEach((): void => {
486
490
  }
487
491
 
488
492
  /*
489
- * Anything else, INCLUDING the seven method models this page must never ask
493
+ * Anything else, INCLUDING the nine method models this page must never ask
490
494
  * for. A non-empty answer would make a leak look like a feature working, so
491
495
  * the fallback stays empty and the assertion that matters is about the
492
496
  * request rather than the response.
@@ -570,7 +574,7 @@ describe("the list", () => {
570
574
  );
571
575
 
572
576
  /*
573
- * The assertion is about the REQUEST, not the render. The seven models are
577
+ * The assertion is about the REQUEST, not the render. The nine models are
574
578
  * scoped to their owner, so a read here would be refused — but a component
575
579
  * that asks is already wrong, and the refusal is not something this page
576
580
  * may rely on.
@@ -657,10 +661,11 @@ describe("adding a method on somebody's behalf", () => {
657
661
 
658
662
  /*
659
663
  * Push is a device token minted at registration — there is nothing to type.
660
- * Telegram needs the account holder to message the bot first. Webhook has
661
- * no verification at all, so an admin-created one would be live the instant
662
- * it was written, which is exactly the silent redirect this design rules
663
- * out.
664
+ * Telegram needs the account holder to message the bot first. Slack and
665
+ * Microsoft Teams are born from the account holder's own OAuth workspace
666
+ * link, which nobody can establish for them. Webhook has no verification
667
+ * at all, so an admin-created one would be live the instant it was
668
+ * written, which is exactly the silent redirect this design rules out.
664
669
  */
665
670
  const modalText: string = document.body.textContent || "";
666
671
 
@@ -674,7 +679,13 @@ describe("adding a method on somebody's behalf", () => {
674
679
  return option.textContent || "";
675
680
  });
676
681
 
677
- for (const forbidden of ["Push", "Telegram", "Webhook"]) {
682
+ for (const forbidden of [
683
+ "Push",
684
+ "Telegram",
685
+ "Slack",
686
+ "Microsoft Teams",
687
+ "Webhook",
688
+ ]) {
678
689
  expect(optionLabels).not.toContain(forbidden);
679
690
  }
680
691
  });
@@ -0,0 +1,465 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from "@jest/globals";
10
+ import { cleanup, render, screen, waitFor } from "@testing-library/react";
11
+ import React from "react";
12
+ import { Location } from "react-router-dom";
13
+ import getJestMockFunction, { MockFunction } from "../../MockType";
14
+
15
+ /*
16
+ * Issue #3374's twin. The Alert Episode detail page carried a byte for byte
17
+ * copy of the crashing Incident Episode severity renderer, including the
18
+ * throw - and alertSeverityId is nullable in exactly the same way, so an
19
+ * Alert Episode created without a severity could not be opened either. The severity field's
20
+ * getElement threw BadDataException("Episode Severity not found") from inside
21
+ * Detail's own render, so the whole route was replaced by the app level
22
+ * "Something went wrong" boundary. The only recovery was deleting the episode,
23
+ * which detached every member incident.
24
+ *
25
+ * These tests drive the real page against a fake server and assert what a
26
+ * person actually sees: the Episode Severity row, holding a placeholder.
27
+ *
28
+ * The current state field had the identical shape. Its FK is NOT NULL, but the
29
+ * relation is nullable with orphanedRowAction "nullify" and nothing stops an
30
+ * in-use AlertState from being soft deleted - so it is covered here too.
31
+ *
32
+ * Detail now also swallows a throwing field renderer as a last resort. That
33
+ * guard must NOT be what makes these pass: every case asserts the page never
34
+ * logged "threw and was ignored", which is how a regression at the page level
35
+ * stays visible instead of being quietly absorbed by the framework.
36
+ */
37
+
38
+ const EPISODE_ID: string = "11111111-1111-4111-8111-111111111111";
39
+ const PROJECT_ID: string = "22222222-2222-4222-8222-222222222222";
40
+
41
+ const getItemMock: MockFunction = getJestMockFunction();
42
+ const getListMock: MockFunction = getJestMockFunction();
43
+ const getCommonHeadersMock: MockFunction = getJestMockFunction();
44
+ const loggerErrorMock: MockFunction = getJestMockFunction();
45
+
46
+ /*
47
+ * The arrow wrappers are load bearing: jest.mock is hoisted above the compiled
48
+ * requires, so the mock consts above are still unassigned when the factory
49
+ * runs. Dereferencing them lazily, at call time, is what makes this work.
50
+ */
51
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
52
+ return {
53
+ __esModule: true,
54
+ default: {
55
+ getItem: (...args: Array<any>) => {
56
+ return getItemMock(...args);
57
+ },
58
+ getList: (...args: Array<any>) => {
59
+ return getListMock(...args);
60
+ },
61
+ getCommonHeaders: (...args: Array<any>) => {
62
+ return getCommonHeadersMock(...args);
63
+ },
64
+ },
65
+ };
66
+ });
67
+
68
+ jest.mock("../../../UI/Utils/Logger", () => {
69
+ return {
70
+ __esModule: true,
71
+ Logger: {
72
+ error: (...args: Array<any>) => {
73
+ return loggerErrorMock(...args);
74
+ },
75
+ warn: () => {
76
+ return undefined;
77
+ },
78
+ log: () => {
79
+ return undefined;
80
+ },
81
+ info: () => {
82
+ return undefined;
83
+ },
84
+ },
85
+ };
86
+ });
87
+
88
+ /*
89
+ * The page's two heavy children each drive their own API traffic and neither
90
+ * renders a detail field. Stubbing them keeps this suite pointed at the
91
+ * Episode Details card, which is where #3374 lives.
92
+ */
93
+ jest.mock(
94
+ "../../../../App/FeatureSet/Dashboard/src/Components/AlertEpisode/ChangeState",
95
+ () => {
96
+ return {
97
+ __esModule: true,
98
+ default: () => {
99
+ return null;
100
+ },
101
+ };
102
+ },
103
+ );
104
+
105
+ jest.mock(
106
+ "../../../../App/FeatureSet/Dashboard/src/Components/AlertEpisode/AlertEpisodeFeed",
107
+ () => {
108
+ return {
109
+ __esModule: true,
110
+ default: () => {
111
+ return null;
112
+ },
113
+ };
114
+ },
115
+ );
116
+
117
+ import AlertEpisodeView from "../../../../App/FeatureSet/Dashboard/src/Pages/Alerts/EpisodeView/Index";
118
+ import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
119
+ import AlertEpisode from "../../../Models/DatabaseModels/AlertEpisode";
120
+ import AlertSeverity from "../../../Models/DatabaseModels/AlertSeverity";
121
+ import AlertState from "../../../Models/DatabaseModels/AlertState";
122
+ import Route from "../../../Types/API/Route";
123
+ import Color from "../../../Types/Color";
124
+ import ObjectID from "../../../Types/ObjectID";
125
+ import Permission from "../../../Types/Permission";
126
+ import Navigation from "../../../UI/Utils/Navigation";
127
+ import PermissionUtil from "../../../UI/Utils/Permission";
128
+ import UserUtil from "../../../UI/Utils/User";
129
+
130
+ const pageProps: PageComponentProps = {
131
+ pageRoute: new Route("/alerts"),
132
+ currentProject: null,
133
+ hasPaymentMethod: false,
134
+ };
135
+
136
+ interface EpisodeSpec {
137
+ severity?: AlertSeverity | null | undefined;
138
+ state?: AlertState | null | undefined;
139
+ }
140
+
141
+ type BuildSeverityFunction = (
142
+ name: string | null,
143
+ color: Color | null,
144
+ ) => AlertSeverity;
145
+
146
+ const buildSeverity: BuildSeverityFunction = (
147
+ name: string | null,
148
+ color: Color | null,
149
+ ): AlertSeverity => {
150
+ const severity: AlertSeverity = new AlertSeverity();
151
+ severity.id = new ObjectID("33333333-3333-4333-8333-333333333333");
152
+
153
+ if (name) {
154
+ severity.name = name;
155
+ }
156
+
157
+ if (color) {
158
+ severity.color = color;
159
+ }
160
+
161
+ return severity;
162
+ };
163
+
164
+ type BuildStateFunction = (
165
+ name: string | null,
166
+ color: Color | null,
167
+ ) => AlertState;
168
+
169
+ const buildState: BuildStateFunction = (
170
+ name: string | null,
171
+ color: Color | null,
172
+ ): AlertState => {
173
+ const state: AlertState = new AlertState();
174
+ state.id = new ObjectID("44444444-4444-4444-8444-444444444444");
175
+
176
+ if (name) {
177
+ state.name = name;
178
+ }
179
+
180
+ if (color) {
181
+ state.color = color;
182
+ }
183
+
184
+ return state;
185
+ };
186
+
187
+ /*
188
+ * A row shaped like what the API actually returns for an episode created with
189
+ * `{"title": "..."}` and nothing else: a title, a number, a state assigned by
190
+ * AlertEpisodeService.onBeforeCreate, and no severity at all.
191
+ */
192
+ type BuildEpisodeFunction = (spec: EpisodeSpec) => AlertEpisode;
193
+
194
+ const buildEpisode: BuildEpisodeFunction = (
195
+ spec: EpisodeSpec,
196
+ ): AlertEpisode => {
197
+ const episode: AlertEpisode = new AlertEpisode();
198
+ episode.id = new ObjectID(EPISODE_ID);
199
+ episode.projectId = new ObjectID(PROJECT_ID);
200
+ episode.title = "episode without severity";
201
+ episode.episodeNumber = 7;
202
+ episode.episodeNumberWithPrefix = "#7";
203
+ episode.alertCount = 0;
204
+ episode.createdAt = new Date("2026-01-01T00:00:00.000Z");
205
+
206
+ if (spec.severity) {
207
+ episode.alertSeverity = spec.severity;
208
+ }
209
+
210
+ if (spec.state) {
211
+ episode.currentAlertState = spec.state;
212
+ }
213
+
214
+ return episode;
215
+ };
216
+
217
+ type RenderPageFunction = (spec: EpisodeSpec) => Promise<void>;
218
+
219
+ const renderPage: RenderPageFunction = async (
220
+ spec: EpisodeSpec,
221
+ ): Promise<void> => {
222
+ getItemMock.mockResolvedValue(buildEpisode(spec) as never);
223
+
224
+ render(<AlertEpisodeView {...pageProps} />);
225
+
226
+ await waitFor((): void => {
227
+ expect(screen.getByText("Episode Severity")).toBeInTheDocument();
228
+ });
229
+ };
230
+
231
+ /*
232
+ * Detail renders each field as <container><FieldLabel/><value/></container>,
233
+ * and FieldLabel's root is the only div.space-y-1 in that subtree. Walking up
234
+ * from the label and taking the sibling is what a person reading the row does.
235
+ */
236
+ type FieldValueFunction = (title: string) => string;
237
+
238
+ const fieldValue: FieldValueFunction = (title: string): string => {
239
+ const label: HTMLElement = screen.getByText(title);
240
+ const labelRoot: Element | null = label.closest("div.space-y-1");
241
+ const container: HTMLElement | null | undefined = labelRoot?.parentElement;
242
+
243
+ if (!container) {
244
+ throw new Error(`Could not find the field container for "${title}"`);
245
+ }
246
+
247
+ const value: Element | undefined = container.children[1];
248
+
249
+ if (!value) {
250
+ throw new Error(`The field "${title}" rendered no value element`);
251
+ }
252
+
253
+ return (value.textContent || "").trim();
254
+ };
255
+
256
+ type PillForFunction = (title: string) => HTMLElement;
257
+
258
+ const pillFor: PillForFunction = (title: string): HTMLElement => {
259
+ const label: HTMLElement = screen.getByText(title);
260
+ const labelRoot: Element | null = label.closest("div.space-y-1");
261
+ const pill: HTMLElement | null | undefined =
262
+ labelRoot?.parentElement?.querySelector<HTMLElement>(
263
+ '[data-testid="pill"]',
264
+ );
265
+
266
+ if (!pill) {
267
+ throw new Error(`The field "${title}" rendered no pill`);
268
+ }
269
+
270
+ return pill;
271
+ };
272
+
273
+ type ExpectNoCrashFunction = () => void;
274
+
275
+ /*
276
+ * Two separate claims, and both matter:
277
+ * - the app level ErrorBoundary copy is nowhere on the page, and
278
+ * - Detail never had to swallow a throw, so the page itself is null safe
279
+ * rather than being rescued by the framework guard.
280
+ */
281
+ const expectNoCrash: ExpectNoCrashFunction = (): void => {
282
+ expect(screen.queryByText("Something went wrong")).toBeNull();
283
+ expect(screen.queryByText(/An unexpected error has occurred/)).toBeNull();
284
+
285
+ const swallowed: Array<unknown> = loggerErrorMock.mock.calls.filter(
286
+ (call: Array<unknown>): boolean => {
287
+ return String(call[0]).includes("threw and was ignored");
288
+ },
289
+ );
290
+
291
+ expect(swallowed).toEqual([]);
292
+ };
293
+
294
+ beforeEach((): void => {
295
+ getItemMock.mockReset();
296
+ getListMock.mockReset();
297
+ getCommonHeadersMock.mockReset();
298
+ loggerErrorMock.mockReset();
299
+
300
+ getListMock.mockResolvedValue({
301
+ data: [],
302
+ count: 0,
303
+ skip: 0,
304
+ limit: 0,
305
+ } as never);
306
+ getCommonHeadersMock.mockReturnValue({} as never);
307
+
308
+ const path: string = `/dashboard/${PROJECT_ID}/alerts/episodes/${EPISODE_ID}`;
309
+
310
+ window.history.pushState({}, "", path);
311
+ Navigation.setLocation({
312
+ pathname: path,
313
+ search: "",
314
+ hash: "",
315
+ state: null,
316
+ key: "test",
317
+ } as Location);
318
+
319
+ jest.spyOn(UserUtil, "isMasterAdmin").mockReturnValue(false);
320
+ jest
321
+ .spyOn(PermissionUtil, "getAllPermissions")
322
+ .mockReturnValue([Permission.ProjectAdmin]);
323
+ });
324
+
325
+ afterEach((): void => {
326
+ cleanup();
327
+ jest.restoreAllMocks();
328
+ });
329
+
330
+ describe("Alert Episode detail page: severity (issue #3374)", () => {
331
+ test("renders the page when the episode has no severity at all", async () => {
332
+ await renderPage({ state: buildState("Created", new Color("#4b5563")) });
333
+
334
+ expect(screen.getByText("Episode Severity")).toBeInTheDocument();
335
+ expect(fieldValue("Episode Severity")).toEqual("-");
336
+ expectNoCrash();
337
+ });
338
+
339
+ test("renders the page when the severity relation is explicitly null", async () => {
340
+ getItemMock.mockResolvedValue(
341
+ Object.assign(
342
+ buildEpisode({ state: buildState("Created", new Color("#4b5563")) }),
343
+ { alertSeverity: null },
344
+ ) as never,
345
+ );
346
+
347
+ render(<AlertEpisodeView {...pageProps} />);
348
+
349
+ await waitFor((): void => {
350
+ expect(screen.getByText("Episode Severity")).toBeInTheDocument();
351
+ });
352
+
353
+ expect(fieldValue("Episode Severity")).toEqual("-");
354
+ expectNoCrash();
355
+ });
356
+
357
+ test("still renders the rest of the episode when severity is missing", async () => {
358
+ await renderPage({ state: buildState("Created", new Color("#4b5563")) });
359
+
360
+ expect(screen.getByText("Episode Title")).toBeInTheDocument();
361
+ expect(fieldValue("Episode Title")).toEqual("episode without severity");
362
+ expect(fieldValue("Episode Number")).toContain("#7");
363
+ expect(fieldValue("Alert Count")).toEqual("0");
364
+ // An episode with no grouping rule is a manual one, not a crash.
365
+ expect(fieldValue("Grouping Rule")).toEqual("Manual Episode");
366
+ // No createdByUser on an API-created episode.
367
+ expect(fieldValue("Created By")).toEqual("System");
368
+ expectNoCrash();
369
+ });
370
+
371
+ test("renders the severity pill when the episode has one", async () => {
372
+ await renderPage({
373
+ severity: buildSeverity("Critical", new Color("#ef4444")),
374
+ state: buildState("Created", new Color("#4b5563")),
375
+ });
376
+
377
+ const pill: HTMLElement = pillFor("Episode Severity");
378
+
379
+ expect(pill.textContent).toContain("Critical");
380
+ expect(pill.style.backgroundColor).toEqual("rgb(239, 68, 68)");
381
+ expectNoCrash();
382
+ });
383
+
384
+ test("falls back to Unknown when the severity has no name", async () => {
385
+ await renderPage({
386
+ severity: buildSeverity(null, new Color("#ef4444")),
387
+ state: buildState("Created", new Color("#4b5563")),
388
+ });
389
+
390
+ expect(pillFor("Episode Severity").textContent).toContain("Unknown");
391
+ expectNoCrash();
392
+ });
393
+
394
+ test("renders a severity with no color instead of crashing", async () => {
395
+ await renderPage({
396
+ severity: buildSeverity("Critical", null),
397
+ state: buildState("Created", new Color("#4b5563")),
398
+ });
399
+
400
+ const pill: HTMLElement = pillFor("Episode Severity");
401
+
402
+ expect(pill.textContent).toContain("Critical");
403
+ // Black is the declared fallback in the page.
404
+ expect(pill.style.backgroundColor).toEqual("rgb(0, 0, 0)");
405
+ expectNoCrash();
406
+ });
407
+ });
408
+
409
+ describe("Alert Episode detail page: current state", () => {
410
+ test("renders a placeholder when the state relation is absent", async () => {
411
+ await renderPage({
412
+ severity: buildSeverity("Critical", new Color("#ef4444")),
413
+ });
414
+
415
+ expect(screen.getByText("Current State")).toBeInTheDocument();
416
+ expect(fieldValue("Current State")).toEqual("-");
417
+ expectNoCrash();
418
+ });
419
+
420
+ test("renders the state pill when the episode has one", async () => {
421
+ await renderPage({
422
+ severity: buildSeverity("Critical", new Color("#ef4444")),
423
+ state: buildState("Acknowledged", new Color("#f59e0b")),
424
+ });
425
+
426
+ const pill: HTMLElement = pillFor("Current State");
427
+
428
+ expect(pill.textContent).toContain("Acknowledged");
429
+ expect(pill.style.backgroundColor).toEqual("rgb(245, 158, 11)");
430
+ expectNoCrash();
431
+ });
432
+
433
+ test("falls back to Unknown when the state has no name", async () => {
434
+ await renderPage({
435
+ severity: buildSeverity("Critical", new Color("#ef4444")),
436
+ state: buildState(null, new Color("#f59e0b")),
437
+ });
438
+
439
+ expect(pillFor("Current State").textContent).toContain("Unknown");
440
+ expectNoCrash();
441
+ });
442
+
443
+ test("renders a state with no color instead of crashing", async () => {
444
+ await renderPage({
445
+ severity: buildSeverity("Critical", new Color("#ef4444")),
446
+ state: buildState("Created", null),
447
+ });
448
+
449
+ expect(pillFor("Current State").style.backgroundColor).toEqual(
450
+ "rgb(0, 0, 0)",
451
+ );
452
+ expectNoCrash();
453
+ });
454
+ });
455
+
456
+ describe("Alert Episode detail page: both relations missing", () => {
457
+ test("renders both placeholders when state and severity are absent", async () => {
458
+ await renderPage({});
459
+
460
+ expect(fieldValue("Episode Severity")).toEqual("-");
461
+ expect(fieldValue("Current State")).toEqual("-");
462
+ expect(fieldValue("Episode Title")).toEqual("episode without severity");
463
+ expectNoCrash();
464
+ });
465
+ });