@oneuptime/common 13.0.8 → 14.0.0

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 (333) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Monitor.ts +3 -2
  4. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  5. package/Scripts/generate-service-worker.js +65 -4
  6. package/Server/API/BaseAPI.ts +26 -5
  7. package/Server/API/GlobalConfigAPI.ts +228 -462
  8. package/Server/API/ProjectOIDC.ts +25 -16
  9. package/Server/API/ProjectSSO.ts +24 -16
  10. package/Server/API/StatusPageAPI.ts +100 -42
  11. package/Server/API/TeamMemberAPI.ts +10 -1
  12. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  13. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  14. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  15. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  16. package/Server/EnvironmentConfig.ts +147 -2
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  19. package/Server/Middleware/UserAuthorization.ts +38 -16
  20. package/Server/Services/AuditLogService.ts +84 -1079
  21. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  22. package/Server/Services/GlobalConfigService.ts +80 -1
  23. package/Server/Services/MonitorProbeService.ts +116 -20
  24. package/Server/Services/MonitorService.ts +56 -4
  25. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  26. package/Server/Services/MonitorTemplateService.ts +26 -0
  27. package/Server/Services/ProjectService.ts +284 -0
  28. package/Server/Services/RumSessionPinService.ts +25 -6
  29. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  30. package/Server/Services/StatusPageService.ts +78 -1
  31. package/Server/Services/TeamMemberService.ts +11 -0
  32. package/Server/Services/TeamService.ts +10 -0
  33. package/Server/Services/UserService.ts +10 -17
  34. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  35. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  36. package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
  37. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  38. package/Server/Types/Markdown.ts +41 -0
  39. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  40. package/Server/Utils/EditionEnforcement.ts +233 -0
  41. package/Server/Utils/FrontendEnvironment.ts +41 -2
  42. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  43. package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
  44. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  45. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
  46. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  47. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  48. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  49. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  50. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  51. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  52. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  53. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  54. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  55. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  56. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  57. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  58. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  59. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  60. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
  61. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  62. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  63. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
  64. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  65. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  66. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  67. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  68. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  69. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  70. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  71. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  72. package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
  73. package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
  74. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  75. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  76. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  77. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  78. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  79. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  80. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  81. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  82. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  83. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  84. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  85. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  86. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  87. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  88. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  89. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  90. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  91. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  92. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  93. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  95. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  96. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  97. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  98. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  99. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  100. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  101. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  102. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  103. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  104. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  105. package/Tests/Server/Types/Markdown.test.ts +181 -0
  106. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  107. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  108. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  109. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  110. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  111. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  112. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
  113. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  114. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  115. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  116. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  117. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  118. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  119. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  120. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
  121. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  122. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  123. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  124. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  125. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  126. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  127. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  128. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  129. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  130. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  131. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  132. package/Types/API/Hostname.ts +63 -16
  133. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  134. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  135. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  136. package/Types/Database/CanonicalModelInstance.ts +102 -0
  137. package/Types/Database/TableColumn.ts +20 -6
  138. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  139. package/Types/IP/IPv6.ts +6 -1
  140. package/Types/Monitor/MonitorStep.ts +6 -1
  141. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  142. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  143. package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
  144. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  145. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  146. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  147. package/UI/Components/ModelList/ModelList.tsx +11 -1
  148. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  149. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  150. package/UI/esbuild-enterprise.js +263 -0
  151. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  152. package/Utils/HostAddressUtil.ts +94 -0
  153. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  154. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  155. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  156. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  157. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  159. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  161. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  162. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  163. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  164. package/build/dist/Server/API/BaseAPI.js +26 -5
  165. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  166. package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
  167. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  168. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  169. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  170. package/build/dist/Server/API/ProjectSSO.js +24 -16
  171. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  172. package/build/dist/Server/API/StatusPageAPI.js +93 -43
  173. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  174. package/build/dist/Server/API/TeamMemberAPI.js +8 -1
  175. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  176. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  177. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  178. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  179. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  180. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  181. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  182. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  183. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  184. package/build/dist/Server/EnvironmentConfig.js +84 -2
  185. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  190. package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
  191. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  192. package/build/dist/Server/Services/AuditLogService.js +71 -740
  193. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  194. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  195. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  196. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  197. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  198. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  199. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  200. package/build/dist/Server/Services/MonitorService.js +45 -4
  201. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  202. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  203. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  204. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  205. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  206. package/build/dist/Server/Services/ProjectService.js +214 -0
  207. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  208. package/build/dist/Server/Services/RumSessionPinService.js +24 -6
  209. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  210. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  211. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  212. package/build/dist/Server/Services/StatusPageService.js +71 -0
  213. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  214. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  215. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  216. package/build/dist/Server/Services/TeamService.js +9 -0
  217. package/build/dist/Server/Services/TeamService.js.map +1 -1
  218. package/build/dist/Server/Services/UserService.js +10 -16
  219. package/build/dist/Server/Services/UserService.js.map +1 -1
  220. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  221. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  222. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  223. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  224. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
  225. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  226. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  227. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  228. package/build/dist/Server/Types/Markdown.js +31 -0
  229. package/build/dist/Server/Types/Markdown.js.map +1 -1
  230. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  231. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  232. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  233. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  234. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  235. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  236. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  237. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  238. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
  239. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  240. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  241. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  242. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
  243. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  244. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  245. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  246. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  247. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  248. package/build/dist/Types/API/Hostname.js +59 -11
  249. package/build/dist/Types/API/Hostname.js.map +1 -1
  250. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  251. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  252. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  253. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  254. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  255. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  256. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  257. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  258. package/build/dist/Types/Database/TableColumn.js +19 -6
  259. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  260. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  261. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  262. package/build/dist/Types/IP/IPv6.js +6 -1
  263. package/build/dist/Types/IP/IPv6.js.map +1 -1
  264. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  265. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  266. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  267. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  268. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  269. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
  270. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  271. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  272. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  273. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  274. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  275. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  276. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  277. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  278. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  279. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  280. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  281. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  282. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  283. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  284. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  285. package/build/dist/Utils/HostAddressUtil.js +80 -0
  286. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  287. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  288. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  289. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  290. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  291. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  292. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  293. package/jest.config.json +3 -1
  294. package/package.json +1 -1
  295. package/test-setup.sh +103 -34
  296. package/tsconfig.json +13 -0
  297. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  298. package/Server/API/TeamComplianceAPI.ts +0 -158
  299. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  300. package/Server/Services/TeamComplianceService.ts +0 -733
  301. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  302. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  303. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  304. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  305. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
  306. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  307. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  308. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
  309. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  310. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  311. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  312. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  313. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  314. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  315. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  316. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  317. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  318. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  319. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  320. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  321. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  322. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  323. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  324. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  325. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  326. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  327. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  328. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  329. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  330. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  331. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  332. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  333. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -0,0 +1,399 @@
1
+ /** @timezone UTC */
2
+
3
+ import {
4
+ afterEach,
5
+ beforeEach,
6
+ describe,
7
+ expect,
8
+ jest,
9
+ test,
10
+ } from "@jest/globals";
11
+ import "@testing-library/jest-dom";
12
+ import {
13
+ cleanup,
14
+ fireEvent,
15
+ render,
16
+ screen,
17
+ within,
18
+ } from "@testing-library/react";
19
+ import * as React from "react";
20
+ import { MemoryRouter } from "react-router-dom";
21
+ import SecurityEvent from "../../../Models/AnalyticsModels/SecurityEvent";
22
+ import OcsfSeverity from "../../../Types/SecurityEvent/OcsfSeverity";
23
+ import Navigation from "../../../UI/Utils/Navigation";
24
+ import getJestMockFunction, { MockFunction } from "../../MockType";
25
+ import SecurityEventDetailPanel, {
26
+ SECURITY_EVENT_DETAIL_ATTRIBUTES_TAB_ID,
27
+ SECURITY_EVENT_DETAIL_JSON_TAB_ID,
28
+ SECURITY_EVENT_DETAIL_OVERVIEW_TAB_ID,
29
+ SECURITY_EVENT_DETAIL_PANEL_TEST_ID,
30
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/SecurityEventDetailPanel";
31
+ import { SECURITY_EVENT_ATTRIBUTE_FACET_PREFIX } from "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/SecurityEventsFacets";
32
+
33
+ /*
34
+ * One security event, opened from the list.
35
+ *
36
+ * Built on the shared TelemetryDetailPanel — the drawer the traces and
37
+ * exceptions explorers open — rather than a SideOver, so the list stays
38
+ * usable behind it. What is pinned here is what the drawer is FOR: reading
39
+ * the whole event (typed fields, every source attribute, the raw record) and
40
+ * turning anything in it into a filter on the list behind it without
41
+ * re-typing it into the search bar.
42
+ */
43
+
44
+ const PROJECT_ID: string = "11111111-1111-4111-8111-111111111111";
45
+
46
+ function event(fields: Partial<SecurityEvent> = {}): SecurityEvent {
47
+ return Object.assign(new SecurityEvent(), fields);
48
+ }
49
+
50
+ const FULL_EVENT: SecurityEvent = event({
51
+ time: new Date("2026-09-17T10:00:00.000Z"),
52
+ eventUid: "abc123",
53
+ severityName: OcsfSeverity.Critical,
54
+ className: "Authentication",
55
+ categoryName: "IAM",
56
+ activityName: "Logon",
57
+ statusName: "Failure",
58
+ message: "Failed logon for alice",
59
+ vendorName: "Acme",
60
+ productName: "Defender",
61
+ ruleName: "BruteForce",
62
+ principalUser: "alice",
63
+ principalHost: "web-01",
64
+ observables: ["alice", "10.0.0.4"],
65
+ attributes: {
66
+ "threat.matched": "true",
67
+ "device.hostname": "web-01",
68
+ },
69
+ });
70
+
71
+ function renderPanel(
72
+ securityEvent: SecurityEvent = FULL_EVENT,
73
+ props: {
74
+ onClose?: () => void;
75
+ onFilterBy?: (facetKey: string, value: string) => void;
76
+ onCorrelateObservable?: (observable: string) => void;
77
+ } = {},
78
+ ): void {
79
+ render(
80
+ <MemoryRouter>
81
+ <SecurityEventDetailPanel
82
+ securityEvent={securityEvent}
83
+ onClose={props.onClose || ((): void => {})}
84
+ {...(props.onFilterBy ? { onFilterBy: props.onFilterBy } : {})}
85
+ {...(props.onCorrelateObservable
86
+ ? { onCorrelateObservable: props.onCorrelateObservable }
87
+ : {})}
88
+ />
89
+ </MemoryRouter>,
90
+ );
91
+ }
92
+
93
+ function panel(): HTMLElement {
94
+ return screen.getByTestId(SECURITY_EVENT_DETAIL_PANEL_TEST_ID);
95
+ }
96
+
97
+ function tab(label: string): HTMLElement {
98
+ return screen.getByRole("tab", { name: new RegExp(`^${label}`) });
99
+ }
100
+
101
+ function filterButton(label: string): HTMLElement {
102
+ return screen.getByRole("button", { name: `Filter by ${label}` });
103
+ }
104
+
105
+ beforeEach(() => {
106
+ window.history.replaceState(
107
+ null,
108
+ "",
109
+ `/dashboard/${PROJECT_ID}/security-events`,
110
+ );
111
+ });
112
+
113
+ afterEach(() => {
114
+ cleanup();
115
+ jest.restoreAllMocks();
116
+ });
117
+
118
+ describe("the drawer", () => {
119
+ test("names the event and when it happened", () => {
120
+ renderPanel();
121
+
122
+ expect(panel()).toHaveTextContent("Failed logon for alice");
123
+ expect(panel().textContent).toContain("2026");
124
+ });
125
+
126
+ test("falls back to the event class when there is no message", () => {
127
+ renderPanel(event({ className: "Authentication" }));
128
+
129
+ expect(panel()).toHaveTextContent("Authentication");
130
+ });
131
+
132
+ test("offers Overview, Attributes and JSON, opening on Overview", () => {
133
+ renderPanel();
134
+
135
+ expect(
136
+ screen.getAllByRole("tab").map((node: HTMLElement): string => {
137
+ return node.getAttribute("id") || "";
138
+ }),
139
+ ).toEqual(
140
+ expect.arrayContaining([
141
+ expect.stringContaining(SECURITY_EVENT_DETAIL_OVERVIEW_TAB_ID),
142
+ expect.stringContaining(SECURITY_EVENT_DETAIL_ATTRIBUTES_TAB_ID),
143
+ expect.stringContaining(SECURITY_EVENT_DETAIL_JSON_TAB_ID),
144
+ ]),
145
+ );
146
+ expect(tab("Overview")).toHaveAttribute("aria-selected", "true");
147
+ });
148
+
149
+ test("the Attributes tab counts what it holds, so a reader knows before opening it", () => {
150
+ renderPanel();
151
+
152
+ expect(tab("Attributes")).toHaveTextContent("2");
153
+ });
154
+
155
+ test("closing it tells the page", () => {
156
+ const onClose: MockFunction = getJestMockFunction();
157
+
158
+ renderPanel(FULL_EVENT, { onClose: onClose as unknown as () => void });
159
+
160
+ fireEvent.click(screen.getByRole("button", { name: /close/i }));
161
+
162
+ expect(onClose).toHaveBeenCalledTimes(1);
163
+ });
164
+ });
165
+
166
+ describe("Overview", () => {
167
+ test("shows the typed OCSF fields, severity as a pill", () => {
168
+ renderPanel();
169
+
170
+ expect(panel()).toHaveTextContent("Event Class");
171
+ expect(panel()).toHaveTextContent("Authentication");
172
+ expect(panel()).toHaveTextContent("Status");
173
+ expect(panel()).toHaveTextContent("Failure");
174
+ expect(panel()).toHaveTextContent(OcsfSeverity.Critical);
175
+ });
176
+
177
+ test("omits what the source did not say", () => {
178
+ renderPanel(event({ className: "Authentication" }));
179
+
180
+ expect(panel()).not.toHaveTextContent("Target Host");
181
+ expect(panel()).not.toHaveTextContent("MITRE Tactics");
182
+ });
183
+
184
+ test("lists observables as chips, and says so when there are none", () => {
185
+ renderPanel();
186
+
187
+ expect(
188
+ screen.getByTestId("security-event-panel-observable-chip-0"),
189
+ ).toHaveTextContent("alice");
190
+ expect(
191
+ screen.getByTestId("security-event-panel-observable-chip-1"),
192
+ ).toHaveTextContent("10.0.0.4");
193
+
194
+ cleanup();
195
+ renderPanel(event({ message: "no observables" }));
196
+
197
+ expect(panel()).toHaveTextContent(
198
+ "No observables extracted from this event.",
199
+ );
200
+ });
201
+ });
202
+
203
+ describe("filtering the list from the drawer", () => {
204
+ test("severity, and every typed field the sidebar also facets, offers a filter", () => {
205
+ const onFilterBy: MockFunction = getJestMockFunction();
206
+
207
+ renderPanel(FULL_EVENT, {
208
+ onFilterBy: onFilterBy as unknown as (
209
+ facetKey: string,
210
+ value: string,
211
+ ) => void,
212
+ });
213
+
214
+ fireEvent.click(filterButton("Severity"));
215
+ expect(onFilterBy).toHaveBeenLastCalledWith(
216
+ "severityName",
217
+ OcsfSeverity.Critical,
218
+ );
219
+
220
+ fireEvent.click(filterButton("Event Class"));
221
+ expect(onFilterBy).toHaveBeenLastCalledWith("className", "Authentication");
222
+
223
+ fireEvent.click(filterButton("Principal User"));
224
+ expect(onFilterBy).toHaveBeenLastCalledWith("principalUser", "alice");
225
+
226
+ fireEvent.click(filterButton("Detection Rule"));
227
+ expect(onFilterBy).toHaveBeenLastCalledWith("ruleName", "BruteForce");
228
+ });
229
+
230
+ /*
231
+ * Filtering on a value that is unique per row would return the one event
232
+ * already on screen, so those fields deliberately offer no action.
233
+ */
234
+ test("free-text and unique fields offer none", () => {
235
+ renderPanel(FULL_EVENT, {
236
+ onFilterBy: getJestMockFunction() as unknown as (
237
+ facetKey: string,
238
+ value: string,
239
+ ) => void,
240
+ });
241
+
242
+ expect(
243
+ screen.queryByRole("button", { name: "Filter by Message" }),
244
+ ).toBeNull();
245
+ expect(
246
+ screen.queryByRole("button", { name: "Filter by Event UID" }),
247
+ ).toBeNull();
248
+ });
249
+
250
+ test("an attribute filters under its own attributes.<key> chip", () => {
251
+ const onFilterBy: MockFunction = getJestMockFunction();
252
+
253
+ renderPanel(FULL_EVENT, {
254
+ onFilterBy: onFilterBy as unknown as (
255
+ facetKey: string,
256
+ value: string,
257
+ ) => void,
258
+ });
259
+
260
+ fireEvent.click(tab("Attributes"));
261
+ fireEvent.click(filterButton("threat.matched"));
262
+
263
+ expect(onFilterBy).toHaveBeenCalledWith(
264
+ `${SECURITY_EVENT_ATTRIBUTE_FACET_PREFIX}threat.matched`,
265
+ "true",
266
+ );
267
+ });
268
+
269
+ test("a page that cannot filter gets no filter actions at all", () => {
270
+ renderPanel();
271
+
272
+ expect(screen.queryByRole("button", { name: /^Filter by/ })).toBeNull();
273
+ });
274
+ });
275
+
276
+ describe("Attributes", () => {
277
+ test("lists every source attribute, sorted, with its value", () => {
278
+ renderPanel();
279
+
280
+ fireEvent.click(tab("Attributes"));
281
+
282
+ const terms: Array<string> = Array.from(panel().querySelectorAll("dt")).map(
283
+ (node: Element): string => {
284
+ return node.textContent || "";
285
+ },
286
+ );
287
+
288
+ expect(terms).toEqual(["device.hostname", "threat.matched"]);
289
+ expect(panel()).toHaveTextContent("web-01");
290
+ expect(panel()).toHaveTextContent("true");
291
+ });
292
+
293
+ test("an event with none says so", () => {
294
+ renderPanel(event({ message: "bare" }));
295
+
296
+ fireEvent.click(tab("Attributes"));
297
+
298
+ expect(panel()).toHaveTextContent("No attributes recorded for this event.");
299
+ });
300
+ });
301
+
302
+ describe("JSON", () => {
303
+ test("prints the record, including what the source left empty", () => {
304
+ renderPanel();
305
+
306
+ fireEvent.click(tab("JSON"));
307
+
308
+ const json: Record<string, unknown> = JSON.parse(
309
+ screen.getByTestId("security-event-detail-json").textContent || "{}",
310
+ );
311
+
312
+ expect(json["message"]).toBe("Failed logon for alice");
313
+ expect(json["severityName"]).toBe(OcsfSeverity.Critical);
314
+ expect(json["targetHost"]).toBe("");
315
+ expect(json["attributes"]).toEqual({
316
+ "threat.matched": "true",
317
+ "device.hostname": "web-01",
318
+ });
319
+ });
320
+
321
+ /*
322
+ * An analytics model carries its whole table definition on the object;
323
+ * serializing that would bury twenty facts about the event under a few
324
+ * hundred about the table.
325
+ */
326
+ test("describes the event, not the table", () => {
327
+ renderPanel();
328
+
329
+ fireEvent.click(tab("JSON"));
330
+
331
+ const text: string =
332
+ screen.getByTestId("security-event-detail-json").textContent || "";
333
+
334
+ expect(text).not.toContain("tableColumns");
335
+ expect(text).not.toContain("accessControl");
336
+ });
337
+ });
338
+
339
+ describe("correlating an observable", () => {
340
+ test("deep-links to the Correlate tab seeded with the observable", () => {
341
+ const navigate: MockFunction = getJestMockFunction();
342
+ jest
343
+ .spyOn(Navigation, "navigate")
344
+ .mockImplementation(navigate as unknown as typeof Navigation.navigate);
345
+
346
+ renderPanel();
347
+
348
+ fireEvent.click(
349
+ screen.getByTestId("security-event-panel-observable-chip-1"),
350
+ );
351
+
352
+ expect(navigate).toHaveBeenCalledTimes(1);
353
+ const route: string = String((navigate.mock.calls[0] as Array<unknown>)[0]);
354
+
355
+ expect(route).toContain("/security-events/correlate");
356
+ expect(route).toContain(`observable=${encodeURIComponent("10.0.0.4")}`);
357
+ });
358
+
359
+ test("a host that already shows a graph pivots in place instead", () => {
360
+ const onCorrelateObservable: MockFunction = getJestMockFunction();
361
+ const navigate: MockFunction = getJestMockFunction();
362
+ jest
363
+ .spyOn(Navigation, "navigate")
364
+ .mockImplementation(navigate as unknown as typeof Navigation.navigate);
365
+
366
+ renderPanel(FULL_EVENT, {
367
+ onCorrelateObservable: onCorrelateObservable as unknown as (
368
+ observable: string,
369
+ ) => void,
370
+ });
371
+
372
+ fireEvent.click(
373
+ screen.getByTestId("security-event-panel-observable-chip-0"),
374
+ );
375
+
376
+ expect(onCorrelateObservable).toHaveBeenCalledWith("alice");
377
+ expect(navigate).not.toHaveBeenCalled();
378
+ });
379
+ });
380
+
381
+ describe("the drawer is a modal", () => {
382
+ test("Escape closes it", () => {
383
+ const onClose: MockFunction = getJestMockFunction();
384
+
385
+ renderPanel(FULL_EVENT, { onClose: onClose as unknown as () => void });
386
+
387
+ fireEvent.keyDown(document, { key: "Escape" });
388
+
389
+ expect(onClose).toHaveBeenCalledTimes(1);
390
+ });
391
+
392
+ test("its tabs are a real tablist", () => {
393
+ renderPanel();
394
+
395
+ const tablist: HTMLElement = screen.getByRole("tablist");
396
+
397
+ expect(within(tablist).getAllByRole("tab")).toHaveLength(3);
398
+ });
399
+ });
@@ -0,0 +1,214 @@
1
+ /** @timezone UTC */
2
+
3
+ import { afterEach, describe, expect, test } from "@jest/globals";
4
+ import "@testing-library/jest-dom";
5
+ import { cleanup, fireEvent, render, screen } from "@testing-library/react";
6
+ import * as React from "react";
7
+ import SecurityEvent from "../../../Models/AnalyticsModels/SecurityEvent";
8
+ import OcsfSeverity from "../../../Types/SecurityEvent/OcsfSeverity";
9
+ import getJestMockFunction, { MockFunction } from "../../MockType";
10
+ import SecurityEventListRow, {
11
+ SECURITY_EVENT_ROW_TEST_ID,
12
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/SecurityEventListRow";
13
+ import { SECURITY_EVENT_VOLUME_COLORS } from "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/SecurityEventVolume";
14
+
15
+ /*
16
+ * One security event as a dense list row — the shape the logs and traces
17
+ * explorers use, rather than a grid of table cells.
18
+ *
19
+ * Line one is the read that has to work while scrolling: when, how bad, what
20
+ * class, what happened. Line two is the context a responder scans for, and
21
+ * every chip on it is labelled because `svc-deploy` and `web-01` are
22
+ * indistinguishable without one — a SIEM row that shows the wrong side of an
23
+ * interaction is worse than one that shows nothing.
24
+ */
25
+
26
+ function event(fields: Partial<SecurityEvent> = {}): SecurityEvent {
27
+ return Object.assign(new SecurityEvent(), fields);
28
+ }
29
+
30
+ function renderRow(
31
+ securityEvent: SecurityEvent,
32
+ props: { isSelected?: boolean; onClick?: () => void } = {},
33
+ ): void {
34
+ render(<SecurityEventListRow securityEvent={securityEvent} {...props} />);
35
+ }
36
+
37
+ function row(): HTMLElement {
38
+ return screen.getByTestId(SECURITY_EVENT_ROW_TEST_ID);
39
+ }
40
+
41
+ function severityDot(): HTMLElement {
42
+ const dot: Element | null = row().querySelector("span[aria-hidden='true']");
43
+
44
+ if (!dot) {
45
+ throw new Error("The row has no severity dot.");
46
+ }
47
+
48
+ return dot as HTMLElement;
49
+ }
50
+
51
+ afterEach(() => {
52
+ cleanup();
53
+ });
54
+
55
+ describe("what the row says", () => {
56
+ test("reads severity, class and message on one line", () => {
57
+ renderRow(
58
+ event({
59
+ time: new Date("2026-09-17T10:00:00.000Z"),
60
+ severityName: OcsfSeverity.Critical,
61
+ className: "Authentication",
62
+ message: "Failed logon for alice",
63
+ }),
64
+ );
65
+
66
+ expect(row()).toHaveTextContent("Critical");
67
+ expect(row()).toHaveTextContent("Authentication");
68
+ expect(row()).toHaveTextContent("Failed logon for alice");
69
+ });
70
+
71
+ test("only the first line of a multi-line message is shown", () => {
72
+ renderRow(
73
+ event({ message: "Failed logon\n at Authenticator.verify\n at ..." }),
74
+ );
75
+
76
+ expect(row()).toHaveTextContent("Failed logon");
77
+ expect(row()).not.toHaveTextContent("Authenticator.verify");
78
+ });
79
+
80
+ test("an event with no message says so rather than showing a blank line", () => {
81
+ renderRow(event({ className: "Authentication" }));
82
+
83
+ expect(row()).toHaveTextContent("No message recorded");
84
+ });
85
+
86
+ test("labels every identity chip, so the two sides of an interaction stay apart", () => {
87
+ renderRow(
88
+ event({
89
+ principalUser: "alice",
90
+ principalHost: "web-01",
91
+ principalIp: "10.0.0.4",
92
+ targetUser: "root",
93
+ targetHost: "db-01",
94
+ targetIp: "10.0.0.9",
95
+ statusName: "Failure",
96
+ ruleName: "BruteForce",
97
+ vendorName: "Acme",
98
+ }),
99
+ );
100
+
101
+ for (const [label, value] of [
102
+ ["user", "alice"],
103
+ ["host", "web-01"],
104
+ ["ip", "10.0.0.4"],
105
+ ["target user", "root"],
106
+ ["target host", "db-01"],
107
+ ["target ip", "10.0.0.9"],
108
+ ["status", "Failure"],
109
+ ["rule", "BruteForce"],
110
+ ["vendor", "Acme"],
111
+ ]) {
112
+ const labelNode: HTMLElement = screen.getByText(label as string);
113
+ expect(labelNode.parentElement).toHaveTextContent(value as string);
114
+ }
115
+ });
116
+
117
+ test("a field the source did not send gets no chip at all", () => {
118
+ renderRow(event({ principalUser: "alice" }));
119
+
120
+ expect(screen.getByText("user")).toBeInTheDocument();
121
+ expect(screen.queryByText("host")).toBeNull();
122
+ expect(screen.queryByText("target user")).toBeNull();
123
+ expect(screen.queryByText("rule")).toBeNull();
124
+ });
125
+
126
+ test("the time is shown, and an event with none renders without one", () => {
127
+ renderRow(event({ time: new Date("2026-09-17T10:00:00.000Z") }));
128
+
129
+ expect(row().textContent).toContain("2026");
130
+
131
+ cleanup();
132
+ renderRow(event({ message: "no time" }));
133
+
134
+ expect(row()).toHaveTextContent("no time");
135
+ });
136
+ });
137
+
138
+ describe("severity colour", () => {
139
+ test("uses the volume chart's palette, so a colour means one thing on the page", () => {
140
+ renderRow(event({ severityName: OcsfSeverity.Critical }));
141
+
142
+ expect(severityDot()).toHaveStyle({
143
+ backgroundColor: SECURITY_EVENT_VOLUME_COLORS[OcsfSeverity.Critical],
144
+ });
145
+ });
146
+
147
+ /*
148
+ * The column defaults to '' and pre-normalization rows exist. Anything that
149
+ * is not an OCSF name is read as Unknown — the same fallback the volume
150
+ * chart uses, so the chart's total always equals the number of rows listed.
151
+ */
152
+ test("an unrecognised or missing severity reads as Unknown", () => {
153
+ renderRow(event({ severityName: "" as OcsfSeverity }));
154
+
155
+ expect(row()).toHaveTextContent(OcsfSeverity.Unknown);
156
+ expect(severityDot()).toHaveStyle({
157
+ backgroundColor: SECURITY_EVENT_VOLUME_COLORS[OcsfSeverity.Unknown],
158
+ });
159
+
160
+ cleanup();
161
+ renderRow(event({ severityName: "SUPER-BAD" as OcsfSeverity }));
162
+
163
+ expect(row()).toHaveTextContent(OcsfSeverity.Unknown);
164
+ });
165
+
166
+ test.each([
167
+ OcsfSeverity.Fatal,
168
+ OcsfSeverity.Critical,
169
+ OcsfSeverity.High,
170
+ OcsfSeverity.Medium,
171
+ OcsfSeverity.Low,
172
+ OcsfSeverity.Informational,
173
+ OcsfSeverity.Other,
174
+ OcsfSeverity.Unknown,
175
+ ])("%s has its own colour", (severity: OcsfSeverity): void => {
176
+ renderRow(event({ severityName: severity }));
177
+
178
+ expect(severityDot()).toHaveStyle({
179
+ backgroundColor: SECURITY_EVENT_VOLUME_COLORS[severity],
180
+ });
181
+ });
182
+ });
183
+
184
+ describe("opening an event", () => {
185
+ test("the whole row is one button", () => {
186
+ renderRow(event({ message: "Failed logon" }));
187
+
188
+ expect(row().tagName).toBe("BUTTON");
189
+ expect(row()).toHaveAttribute("type", "button");
190
+ });
191
+
192
+ test("clicking it asks the page to open the event", () => {
193
+ const onClick: MockFunction = getJestMockFunction();
194
+
195
+ renderRow(event({ message: "Failed logon" }), {
196
+ onClick: onClick as unknown as () => void,
197
+ });
198
+
199
+ fireEvent.click(row());
200
+
201
+ expect(onClick).toHaveBeenCalledTimes(1);
202
+ });
203
+
204
+ test("the open event is marked, so the list says which one the drawer shows", () => {
205
+ renderRow(event({ message: "Failed logon" }), { isSelected: true });
206
+
207
+ expect(row()).toHaveAttribute("aria-pressed", "true");
208
+
209
+ cleanup();
210
+ renderRow(event({ message: "Failed logon" }));
211
+
212
+ expect(row()).not.toHaveAttribute("aria-pressed");
213
+ });
214
+ });