@oneuptime/common 13.0.8 → 14.0.1

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 (514) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Index.ts +4 -0
  4. package/Models/DatabaseModels/Monitor.ts +3 -2
  5. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  6. package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
  7. package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
  8. package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
  9. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
  10. package/Scripts/generate-service-worker.js +65 -4
  11. package/Server/API/BaseAPI.ts +26 -5
  12. package/Server/API/GlobalConfigAPI.ts +228 -462
  13. package/Server/API/ProjectOIDC.ts +25 -16
  14. package/Server/API/ProjectSSO.ts +24 -16
  15. package/Server/API/StatusPageAPI.ts +100 -42
  16. package/Server/API/TeamMemberAPI.ts +10 -1
  17. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  18. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  19. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  20. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  21. package/Server/EnvironmentConfig.ts +147 -2
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  26. package/Server/Middleware/UserAuthorization.ts +38 -16
  27. package/Server/Services/AuditLogService.ts +84 -1079
  28. package/Server/Services/DatabaseService.ts +10 -0
  29. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  30. package/Server/Services/GlobalConfigService.ts +80 -1
  31. package/Server/Services/Index.ts +4 -0
  32. package/Server/Services/MonitorProbeService.ts +116 -20
  33. package/Server/Services/MonitorService.ts +59 -6
  34. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  35. package/Server/Services/MonitorTemplateService.ts +26 -0
  36. package/Server/Services/ProjectService.ts +284 -0
  37. package/Server/Services/RumSessionPinService.ts +25 -6
  38. package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
  39. package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
  40. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
  41. package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
  42. package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
  43. package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
  44. package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
  45. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  46. package/Server/Services/StatusPageService.ts +78 -1
  47. package/Server/Services/TeamMemberService.ts +11 -0
  48. package/Server/Services/TeamService.ts +10 -0
  49. package/Server/Services/UserService.ts +10 -17
  50. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  51. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  52. package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
  53. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  54. package/Server/Types/Markdown.ts +41 -0
  55. package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
  56. package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
  57. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  58. package/Server/Utils/EditionEnforcement.ts +233 -0
  59. package/Server/Utils/FrontendEnvironment.ts +41 -2
  60. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  61. package/Server/Utils/Monitor/AffectedResourceList.ts +148 -0
  62. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
  63. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  64. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
  65. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +720 -465
  66. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  67. package/Server/Utils/Monitor/RootCauseList.ts +139 -0
  68. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
  69. package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
  70. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  71. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  72. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  73. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  74. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  75. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  76. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  77. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  78. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  79. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  80. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  81. package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
  82. package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
  83. package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
  84. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  85. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
  86. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
  87. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  88. package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
  89. package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
  90. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
  91. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
  92. package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
  93. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
  94. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  95. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  96. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
  97. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  98. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  99. package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
  100. package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
  101. package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
  102. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
  103. package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
  104. package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
  105. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  106. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  107. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  108. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  109. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
  110. package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
  111. package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
  112. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  113. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  114. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  115. package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
  116. package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
  117. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  118. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  119. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  120. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  121. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  122. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  123. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  124. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  125. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  126. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  127. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  128. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  129. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  130. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  131. package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
  132. package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
  133. package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
  134. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  135. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  136. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  137. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  138. package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
  139. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  140. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  141. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  142. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  143. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  144. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  145. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  146. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
  147. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
  148. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  149. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
  150. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
  151. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
  152. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
  153. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
  154. package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
  155. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
  156. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  157. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  158. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
  159. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  160. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  161. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  162. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
  163. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  164. package/Tests/Server/Types/Markdown.test.ts +183 -1
  165. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
  166. package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
  167. package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
  168. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  169. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  170. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  171. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  172. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  173. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  174. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
  175. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +1339 -96
  176. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
  177. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
  178. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  179. package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
  180. package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
  181. package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
  182. package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
  183. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
  184. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  185. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  186. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  187. package/Tests/Types/API/Route.test.ts +56 -0
  188. package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
  189. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  190. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  191. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  192. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
  193. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  194. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  195. package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
  196. package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
  197. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  198. package/Tests/UI/Components/Icon.test.tsx +26 -0
  199. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  200. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  201. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  202. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  203. package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
  204. package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
  205. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  206. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  207. package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
  208. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  209. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  210. package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
  211. package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
  212. package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
  213. package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
  214. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  215. package/Types/API/Hostname.ts +63 -16
  216. package/Types/API/Route.ts +19 -2
  217. package/Types/API/URL.ts +26 -2
  218. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  219. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  220. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  221. package/Types/Database/CanonicalModelInstance.ts +102 -0
  222. package/Types/Database/TableColumn.ts +20 -6
  223. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  224. package/Types/IP/IPv6.ts +6 -1
  225. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
  226. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
  227. package/Types/Monitor/MonitorStep.ts +6 -1
  228. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  229. package/Types/Permission.ts +90 -0
  230. package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
  231. package/Types/Rules/RuleRun.ts +4 -0
  232. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  233. package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
  234. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  235. package/UI/Components/Feed/FeedOptions.ts +427 -0
  236. package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
  237. package/UI/Components/Feed/useFeedItems.ts +28 -9
  238. package/UI/Components/Feed/useFeedOptions.ts +225 -0
  239. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  240. package/UI/Components/Icon/Icon.tsx +1 -1
  241. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  242. package/UI/Components/ModelList/ModelList.tsx +11 -1
  243. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  244. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  245. package/UI/esbuild-enterprise.js +263 -0
  246. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  247. package/Utils/HostAddressUtil.ts +94 -0
  248. package/Utils/LabelRuleImportExport.ts +27 -9
  249. package/Utils/Monitor/MetricValueFormatter.ts +2 -2
  250. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  251. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  252. package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
  253. package/Utils/Slo/SloDuration.ts +22 -6
  254. package/Utils/Slo/SloHealth.ts +8 -7
  255. package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
  256. package/Utils/Slo/SloOverviewText.ts +33 -0
  257. package/Utils/Slo/SloProjection.ts +4 -3
  258. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  259. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  260. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  261. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  262. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  263. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  264. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  265. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  266. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  267. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  268. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  269. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
  270. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
  271. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
  272. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
  273. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
  274. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
  275. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
  276. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
  277. package/build/dist/Server/API/BaseAPI.js +26 -5
  278. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  279. package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
  280. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  281. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  282. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  283. package/build/dist/Server/API/ProjectSSO.js +24 -16
  284. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  285. package/build/dist/Server/API/StatusPageAPI.js +93 -43
  286. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  287. package/build/dist/Server/API/TeamMemberAPI.js +8 -1
  288. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  289. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  290. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  291. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  292. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  293. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  294. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  295. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  296. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  297. package/build/dist/Server/EnvironmentConfig.js +84 -2
  298. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  299. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  300. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  301. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
  302. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
  303. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
  304. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
  305. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  306. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  307. package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
  308. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  309. package/build/dist/Server/Services/AuditLogService.js +71 -740
  310. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  311. package/build/dist/Server/Services/DatabaseService.js +7 -0
  312. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  313. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  314. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  315. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  316. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  317. package/build/dist/Server/Services/Index.js +4 -0
  318. package/build/dist/Server/Services/Index.js.map +1 -1
  319. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  320. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  321. package/build/dist/Server/Services/MonitorService.js +48 -6
  322. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  323. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  324. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  325. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  326. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  327. package/build/dist/Server/Services/ProjectService.js +214 -0
  328. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  329. package/build/dist/Server/Services/RumSessionPinService.js +24 -6
  330. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  331. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
  332. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
  333. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
  334. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
  335. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
  336. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
  337. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
  338. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
  339. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
  340. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
  341. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
  342. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
  343. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
  344. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  345. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  346. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  347. package/build/dist/Server/Services/StatusPageService.js +71 -0
  348. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  349. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  350. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  351. package/build/dist/Server/Services/TeamService.js +9 -0
  352. package/build/dist/Server/Services/TeamService.js.map +1 -1
  353. package/build/dist/Server/Services/UserService.js +10 -16
  354. package/build/dist/Server/Services/UserService.js.map +1 -1
  355. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  356. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  357. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  358. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  359. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
  360. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  361. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  362. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  363. package/build/dist/Server/Types/Markdown.js +31 -0
  364. package/build/dist/Server/Types/Markdown.js.map +1 -1
  365. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
  366. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
  367. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
  368. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  369. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  370. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  371. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  372. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  373. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  374. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  375. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  376. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  377. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +67 -0
  378. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  379. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
  380. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  381. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  382. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
  383. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +585 -367
  384. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  385. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  386. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  387. package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
  388. package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
  389. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
  390. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  391. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
  392. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
  393. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  394. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  395. package/build/dist/Types/API/Hostname.js +59 -11
  396. package/build/dist/Types/API/Hostname.js.map +1 -1
  397. package/build/dist/Types/API/Route.js +18 -2
  398. package/build/dist/Types/API/Route.js.map +1 -1
  399. package/build/dist/Types/API/URL.js +23 -2
  400. package/build/dist/Types/API/URL.js.map +1 -1
  401. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  402. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  403. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  404. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  405. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  406. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  407. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  408. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  409. package/build/dist/Types/Database/TableColumn.js +19 -6
  410. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  411. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  412. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  413. package/build/dist/Types/IP/IPv6.js +6 -1
  414. package/build/dist/Types/IP/IPv6.js.map +1 -1
  415. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  416. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  417. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  418. package/build/dist/Types/Permission.js +84 -0
  419. package/build/dist/Types/Permission.js.map +1 -1
  420. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
  421. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  422. package/build/dist/Types/Rules/RuleRun.js +4 -0
  423. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  424. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  425. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  426. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
  427. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  428. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  429. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  430. package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
  431. package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
  432. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
  433. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
  434. package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
  435. package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
  436. package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
  437. package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
  438. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  439. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  440. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  441. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  442. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  443. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  444. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  445. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  446. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  447. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  448. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  449. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  450. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  451. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  452. package/build/dist/Utils/HostAddressUtil.js +80 -0
  453. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  454. package/build/dist/Utils/LabelRuleImportExport.js +22 -6
  455. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  456. package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
  457. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  458. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  459. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  460. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  461. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
  462. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
  463. package/build/dist/Utils/Slo/SloDuration.js +13 -5
  464. package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
  465. package/build/dist/Utils/Slo/SloHealth.js +8 -7
  466. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  467. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
  468. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
  469. package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
  470. package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
  471. package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
  472. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  473. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  474. package/jest.config.json +3 -1
  475. package/package.json +1 -1
  476. package/test-setup.sh +103 -34
  477. package/tsconfig.json +13 -0
  478. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  479. package/Server/API/TeamComplianceAPI.ts +0 -158
  480. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  481. package/Server/Services/TeamComplianceService.ts +0 -733
  482. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  483. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  484. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  485. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  486. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
  487. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  488. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  489. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
  490. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  491. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  492. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  493. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  494. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  495. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  496. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  497. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  498. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  499. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  500. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  501. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  502. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  503. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  504. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  505. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  506. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  507. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  508. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  509. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  510. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  511. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  512. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  513. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  514. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -1,4 +1,9 @@
1
1
  import { describe, expect, it } from "@jest/globals";
2
+ import DOMPurify from "dompurify";
3
+ import { marked, Token, Tokens } from "marked";
4
+ import AffectedResourceList, {
5
+ AffectedResourceListEntry,
6
+ } from "../../../Server/Utils/Monitor/AffectedResourceList";
2
7
  import {
3
8
  htmlToMarkdown,
4
9
  markdownToHtml,
@@ -273,6 +278,167 @@ describe("markdownToHtml", () => {
273
278
  "<ul><li>a <strong>b</strong></li></ul>",
274
279
  );
275
280
  });
281
+
282
+ /*
283
+ * An ordered list that does not start at one keeps its first number, so
284
+ * the serializer can write the same numbers back.
285
+ */
286
+ it("records a first marker other than one as the list's start", () => {
287
+ expect(markdownToHtml("3. a\n4. b")).toBe(
288
+ '<ol start="3"><li>a</li><li>b</li></ol>',
289
+ );
290
+ });
291
+
292
+ it("does not add a start attribute to a list that starts at one", () => {
293
+ expect(markdownToHtml("1. a")).not.toContain("start=");
294
+ });
295
+
296
+ it("keeps an indented continuation line inside its item", () => {
297
+ expect(markdownToHtml("- a\n b")).toBe("<ul><li>a<br>b</li></ul>");
298
+ });
299
+
300
+ /*
301
+ * The fixed two-space indent this converter used to write under every
302
+ * marker. Two spaces is short of the content column of "1.", but a plain
303
+ * line indented past the marker still continues the item (CommonMark's
304
+ * lazy continuation), so documents saved that way read back whole.
305
+ */
306
+ it("keeps a continuation line indented short of the content column", () => {
307
+ expect(markdownToHtml("1. a\n b")).toBe("<ol><li>a<br>b</li></ol>");
308
+ });
309
+
310
+ it("ends the list at a blank line followed by unindented text", () => {
311
+ expect(markdownToHtml("- a\n\nb")).toBe("<ul><li>a</li></ul><p>b</p>");
312
+ });
313
+ });
314
+
315
+ /*
316
+ * Nesting follows CommonMark, as marked (the email renderer) does: a line
317
+ * belongs to an item when it is indented to the item's content column --
318
+ * the marker's indent, plus the marker, plus one space.
319
+ */
320
+ describe("nested lists", () => {
321
+ it("nests a bullet list indented to an ordered item's content column", () => {
322
+ expect(markdownToHtml("1. a\n - b\n - c\n2. d")).toBe(
323
+ "<ol><li>a<ul><li>b</li><li>c</li></ul></li><li>d</li></ol>",
324
+ );
325
+ });
326
+
327
+ it("nests a bullet list in a bullet list", () => {
328
+ expect(markdownToHtml("- a\n - b\n- c")).toBe(
329
+ "<ul><li>a<ul><li>b</li></ul></li><li>c</li></ul>",
330
+ );
331
+ });
332
+
333
+ it("nests an ordered list in a bullet list", () => {
334
+ expect(markdownToHtml("- a\n 1. b\n 2. c")).toBe(
335
+ "<ul><li>a<ol><li>b</li><li>c</li></ol></li></ul>",
336
+ );
337
+ });
338
+
339
+ it("nests three levels deep", () => {
340
+ expect(markdownToHtml("- a\n - b\n - c\n- d")).toBe(
341
+ "<ul><li>a<ul><li>b<ul><li>c</li></ul></li></ul></li><li>d</li></ul>",
342
+ );
343
+ });
344
+
345
+ /* Four columns under "10.", not three. */
346
+ it("uses the wider content column of a two-digit marker", () => {
347
+ expect(markdownToHtml("10. a\n - b")).toBe(
348
+ '<ol start="10"><li>a<ul><li>b</li></ul></li></ol>',
349
+ );
350
+ });
351
+
352
+ /*
353
+ * Three spaces is short of the content column of "10.", so the bullet is
354
+ * a sibling of the item, of the other kind, and starts a new list. That
355
+ * is how marked reads it too, and why AffectedResourceList indents item
356
+ * 10's details by four.
357
+ */
358
+ it("does not nest a list indented short of the content column", () => {
359
+ expect(markdownToHtml("10. a\n - b")).toBe(
360
+ '<ol start="10"><li>a</li></ol><ul><li>b</li></ul>',
361
+ );
362
+ });
363
+
364
+ /*
365
+ * The regression. Changing marker kind used to end the list at any
366
+ * depth, so each item of a ranked list with bullet details became a
367
+ * one-item <ol> with its details stranded in a <ul> after it.
368
+ */
369
+ it("keeps one ordered list when its items carry bullet lists", () => {
370
+ const html: string = markdownToHtml(
371
+ "1. a\n - x\n2. b\n - y\n3. c\n - z",
372
+ );
373
+
374
+ expect(html).toBe(
375
+ "<ol>" +
376
+ "<li>a<ul><li>x</li></ul></li>" +
377
+ "<li>b<ul><li>y</li></ul></li>" +
378
+ "<li>c<ul><li>z</li></ul></li>" +
379
+ "</ol>",
380
+ );
381
+ });
382
+
383
+ it("still ends a nested list on a marker change at its own indentation", () => {
384
+ expect(markdownToHtml("- a\n - b\n 1. c")).toBe(
385
+ "<ul><li>a<ul><li>b</li></ul><ol><li>c</li></ol></li></ul>",
386
+ );
387
+ });
388
+
389
+ it("nests a tab-indented list", () => {
390
+ expect(markdownToHtml("1. a\n\t- b")).toBe(
391
+ "<ol><li>a<ul><li>b</li></ul></li></ol>",
392
+ );
393
+ });
394
+
395
+ it("keeps an indented list after a blank line inside the item", () => {
396
+ expect(markdownToHtml("- a\n\n - b")).toBe(
397
+ "<ul><li>a<ul><li>b</li></ul></li></ul>",
398
+ );
399
+ });
400
+
401
+ it("keeps text after a nested list inside the item", () => {
402
+ expect(markdownToHtml("- a\n - b\n\n c")).toBe(
403
+ "<ul><li>a<ul><li>b</li></ul><p>c</p></li></ul>",
404
+ );
405
+ });
406
+
407
+ /*
408
+ * What the serializer writes for an item whose own text was deleted and
409
+ * only its nested list is left.
410
+ */
411
+ it("reads an item that opens straight onto a marker as a nested list", () => {
412
+ expect(markdownToHtml("1. - a\n - b")).toBe(
413
+ "<ol><li><ul><li>a</li><li>b</li></ul></li></ol>",
414
+ );
415
+ });
416
+
417
+ it("nests a list under a task item", () => {
418
+ const html: string = markdownToHtml("- [ ] a\n - b");
419
+
420
+ expect(html).toContain('<ul class="task-list">');
421
+ expect(html).toContain("> a<ul><li>b</li></ul></li></ul>");
422
+ });
423
+
424
+ it("renders inline markup inside a nested item", () => {
425
+ expect(markdownToHtml("1. a\n - Node: `n-1`")).toBe(
426
+ "<ol><li>a<ul><li>Node: <code>n-1</code></li></ul></li></ol>",
427
+ );
428
+ });
429
+
430
+ it("escapes HTML inside a nested item", () => {
431
+ const html: string = markdownToHtml("- a\n - <script>alert(1)</script>");
432
+
433
+ expect(html).not.toContain("<script>");
434
+ expect(html).toContain("&lt;script&gt;");
435
+ });
436
+
437
+ it("renders a fenced block nested in an item", () => {
438
+ expect(markdownToHtml("- a\n ```\n x\n\n y\n ```")).toBe(
439
+ "<ul><li>a<pre><code>x\n\ny</code></pre></li></ul>",
440
+ );
441
+ });
276
442
  });
277
443
 
278
444
  describe("blockquotes", () => {
@@ -537,6 +703,78 @@ describe("htmlToMarkdown", () => {
537
703
  it("indents a continuation line so it stays inside its item", () => {
538
704
  expect(htmlToMarkdown("<ul><li>a<br>b</li></ul>")).toBe("- a\n b");
539
705
  });
706
+
707
+ it("indents a continuation line by the width of an ordered marker", () => {
708
+ expect(htmlToMarkdown("<ol><li>a<br>b</li></ol>")).toBe("1. a\n b");
709
+ });
710
+
711
+ it("starts numbering at the list's start attribute", () => {
712
+ expect(htmlToMarkdown('<ol start="3"><li>a</li><li>b</li></ol>')).toBe(
713
+ "3. a\n4. b",
714
+ );
715
+ });
716
+
717
+ it("keeps a start of zero", () => {
718
+ expect(htmlToMarkdown('<ol start="0"><li>a</li></ol>')).toBe("0. a");
719
+ });
720
+
721
+ it("numbers from one when the start attribute is not a number", () => {
722
+ expect(htmlToMarkdown('<ol start="x"><li>a</li></ol>')).toBe("1. a");
723
+ expect(htmlToMarkdown('<ol start="-2"><li>a</li></ol>')).toBe("1. a");
724
+ });
725
+
726
+ /*
727
+ * A nested list is indented to its item's content column -- three
728
+ * spaces under "1. ". The old fixed two spaces put it outside the item.
729
+ */
730
+ it("indents a nested list by the width of its item's marker", () => {
731
+ expect(htmlToMarkdown("<ol><li>a<ul><li>b</li></ul></li></ol>")).toBe(
732
+ "1. a\n - b",
733
+ );
734
+ });
735
+
736
+ it("indents a nested list by four under a two-digit marker", () => {
737
+ expect(
738
+ htmlToMarkdown(
739
+ '<ol start="9"><li>a<ul><li>x</li></ul></li><li>b<ul><li>y</li></ul></li></ol>',
740
+ ),
741
+ ).toBe("9. a\n - x\n10. b\n - y");
742
+ });
743
+
744
+ /*
745
+ * No blank line either side of a nested list: that would make the list
746
+ * loose and, between items, read as the end of the outer list.
747
+ */
748
+ it("keeps a nested list tight inside its item", () => {
749
+ expect(
750
+ htmlToMarkdown("<ul><li>a<ul><li>b</li></ul></li><li>c</li></ul>"),
751
+ ).toBe("- a\n - b\n- c");
752
+ });
753
+
754
+ it("indents every level of a deep nest", () => {
755
+ expect(
756
+ htmlToMarkdown(
757
+ "<ul><li>a<ol><li>b<ul><li>c</li></ul></li></ol></li></ul>",
758
+ ),
759
+ ).toBe("- a\n 1. b\n - c");
760
+ });
761
+
762
+ it("indents a list nested under a task item by the bullet's width", () => {
763
+ expect(
764
+ htmlToMarkdown(
765
+ '<ul class="task-list"><li class="task-list-item">' +
766
+ '<input type="checkbox" disabled> a<ul><li>b</li></ul></li></ul>',
767
+ ),
768
+ ).toBe("- [ ] a\n - b");
769
+ });
770
+
771
+ it("does not pad a blank line inside an item with spaces", () => {
772
+ const md: string = htmlToMarkdown(
773
+ "<ul><li>a<ul><li>b</li></ul><p>c</p></li></ul>",
774
+ );
775
+
776
+ expect(md).toBe("- a\n - b\n\n c");
777
+ });
540
778
  });
541
779
 
542
780
  describe("tables", () => {
@@ -684,3 +922,449 @@ describe("markdown round trip", () => {
684
922
  expect(html).not.toContain("<script>");
685
923
  });
686
924
  });
925
+
926
+ /*
927
+ * The same property for nested lists, which the loop used to take apart:
928
+ * a change of marker kind ended the list at any depth, and the serializer
929
+ * numbered every <ol> from one and indented by a fixed two spaces.
930
+ */
931
+ describe("nested list round trip", () => {
932
+ const cases: Array<{ name: string; markdown: string }> = [
933
+ { name: "a bullet list in a bullet list", markdown: "- a\n - b\n- c" },
934
+ {
935
+ name: "an ordered list in a bullet list",
936
+ markdown: "- a\n 1. b\n 2. c\n- d",
937
+ },
938
+ {
939
+ name: "a bullet list in an ordered list",
940
+ markdown: "1. a\n - b\n2. c\n - d",
941
+ },
942
+ { name: "three levels", markdown: "- a\n - b\n - c\n- d" },
943
+ {
944
+ name: "three levels of mixed kinds",
945
+ markdown: "1. a\n - b\n 1. c\n2. d",
946
+ },
947
+ { name: "a list that starts at three", markdown: "3. a\n4. b" },
948
+ { name: "an ordered list split by a blank line", markdown: "1. a\n\n2. b" },
949
+ {
950
+ name: "a nested list under a two-digit marker",
951
+ markdown: "9. a\n - x\n10. b\n - y",
952
+ },
953
+ { name: "a continuation line in an ordered item", markdown: "1. a\n b" },
954
+ {
955
+ name: "a task list with a nested list",
956
+ markdown: "- [ ] a\n - b\n- [x] c",
957
+ },
958
+ {
959
+ name: "a nested list after a paragraph",
960
+ markdown: "Intro\n\n1. a\n - b",
961
+ },
962
+ {
963
+ name: "a paragraph after a nested list",
964
+ markdown: "1. a\n - b\n\nOutro",
965
+ },
966
+ { name: "an item that is only a nested list", markdown: "1. - a\n - b" },
967
+ ];
968
+
969
+ for (const testCase of cases) {
970
+ it(`preserves ${testCase.name}`, () => {
971
+ const once: string = htmlToMarkdown(markdownToHtml(testCase.markdown));
972
+
973
+ expect(once).toBe(testCase.markdown);
974
+ });
975
+ }
976
+
977
+ it("preserves an ol start attribute through markdown and back", () => {
978
+ const html: string = '<ol start="3"><li>a</li><li>b</li></ol>';
979
+ const markdown: string = htmlToMarkdown(html);
980
+
981
+ expect(markdown).toBe("3. a\n4. b");
982
+ expect(markdownToHtml(markdown)).toBe(html);
983
+
984
+ // And marked, which renders the same markdown into emails, agrees.
985
+ const list: Tokens.List = marked.lexer(markdown)[0] as Tokens.List;
986
+
987
+ expect(list.type).toBe("list");
988
+ expect(list.start).toBe(3);
989
+ });
990
+
991
+ /*
992
+ * Markdown the serializer would not have written itself -- a blank line
993
+ * inside the item, a deeper indent, another bullet character -- is
994
+ * normalised on the first pass and left alone after that.
995
+ */
996
+ it("reaches a fixed point for loosely written nested lists", () => {
997
+ for (const source of ["- a\n\n - b", "* a\n * b", "1. a\n - b"]) {
998
+ const first: string = htmlToMarkdown(markdownToHtml(source));
999
+ const second: string = htmlToMarkdown(markdownToHtml(first));
1000
+
1001
+ expect(markdownToHtml(first)).toContain("<li>a<ul><li>b</li></ul></li>");
1002
+ expect(second).toBe(first);
1003
+ }
1004
+ });
1005
+ });
1006
+
1007
+ /*
1008
+ * The document that exposed all of this. Platform monitors (Kubernetes,
1009
+ * Proxmox, VMware, ...) write an "Affected Resources" block into the root
1010
+ * cause of the incidents and alerts they open: a ranked ordered list whose
1011
+ * items each own a bullet list of details. "Edit Root Cause" opens it in this
1012
+ * editor, and before nested lists were supported the first keystroke saved
1013
+ * it back with every resource numbered "1." and its details detached.
1014
+ */
1015
+ describe("Affected Resources block in the editor", () => {
1016
+ const code: (value: string) => string = (value: string): string => {
1017
+ return AffectedResourceList.code(value);
1018
+ };
1019
+
1020
+ const podEntries: (count: number) => Array<AffectedResourceListEntry> = (
1021
+ count: number,
1022
+ ): Array<AffectedResourceListEntry> => {
1023
+ const result: Array<AffectedResourceListEntry> = [
1024
+ {
1025
+ kind: "Pod",
1026
+ name: code("oneuptime-migrate-267-k64qm"),
1027
+ value: "**3**",
1028
+ details: [
1029
+ { label: "Namespace", value: code("default") },
1030
+ { label: "Job", value: code("oneuptime-migrate-267") },
1031
+ {
1032
+ label: "Node",
1033
+ value: code("gke-gke-test-cluster-default-pool-662f6819-c6w3"),
1034
+ },
1035
+ ],
1036
+ },
1037
+ ];
1038
+
1039
+ for (let i: number = 2; i <= count; i++) {
1040
+ result.push({
1041
+ kind: "Pod",
1042
+ name: code(`kube-dns-autoscaler-859854db85-gg5x${i}`),
1043
+ value: `**${i % 3 === 0 ? 1 : 2}**`,
1044
+ details: [
1045
+ { label: "Namespace", value: code("kube-system") },
1046
+ // An empty detail is dropped by the renderer.
1047
+ { label: "Deployment", value: i % 2 === 0 ? code("kube-dns") : "" },
1048
+ {
1049
+ label: "Node",
1050
+ value: code(`gke-gke-test-cluster-db-pool-3e2bfa3b-du6${i}`),
1051
+ },
1052
+ ],
1053
+ });
1054
+ }
1055
+
1056
+ return result;
1057
+ };
1058
+
1059
+ const renderBlock: (count: number, totalCount: number) => string = (
1060
+ count: number,
1061
+ totalCount: number,
1062
+ ): string => {
1063
+ return AffectedResourceList.render({
1064
+ heading: "Affected Resources",
1065
+ overflowNoun: "affected resources",
1066
+ totalCount,
1067
+ entries: podEntries(count),
1068
+ });
1069
+ };
1070
+
1071
+ // A root cause as a monitor saves it: a sentence, then the block.
1072
+ const rootCause: (count: number, totalCount: number) => string = (
1073
+ count: number,
1074
+ totalCount: number,
1075
+ ): string => {
1076
+ return `Pods are restarting in cluster \`gke-test\`.${renderBlock(count, totalCount)}`;
1077
+ };
1078
+
1079
+ // The sanitizer config of MarkdownEditor.tsx; its URI rule is not relevant here.
1080
+ const sanitize: (html: string) => string = (html: string): string => {
1081
+ return DOMPurify.sanitize(html, { ADD_ATTR: ["target"] });
1082
+ };
1083
+
1084
+ // Top-level block tokens as marked (the email renderer) sees them.
1085
+ const blockTokens: (markdown: string) => Array<Token> = (
1086
+ markdown: string,
1087
+ ): Array<Token> => {
1088
+ return marked.lexer(markdown).filter((token: Token): boolean => {
1089
+ return token.type !== "space";
1090
+ });
1091
+ };
1092
+
1093
+ /*
1094
+ * marked must read the re-serialized markdown as ONE ordered list, with
1095
+ * every item owning a nested bullet list. A string that merely looks right
1096
+ * can still parse as a run of one-item lists.
1097
+ */
1098
+ const expectOneRankedList: (
1099
+ markdown: string,
1100
+ entries: Array<AffectedResourceListEntry>,
1101
+ ) => void = (
1102
+ markdown: string,
1103
+ entries: Array<AffectedResourceListEntry>,
1104
+ ): void => {
1105
+ const lists: Array<Tokens.List> = blockTokens(markdown).filter(
1106
+ (token: Token): boolean => {
1107
+ return token.type === "list";
1108
+ },
1109
+ ) as Array<Tokens.List>;
1110
+
1111
+ expect(lists).toHaveLength(1);
1112
+
1113
+ const list: Tokens.List = lists[0] as Tokens.List;
1114
+
1115
+ expect(list.ordered).toBe(true);
1116
+ expect(list.start).toBe(1);
1117
+ expect(list.items).toHaveLength(entries.length);
1118
+
1119
+ list.items.forEach((item: Tokens.ListItem, index: number): void => {
1120
+ const nested: Array<Tokens.List> = item.tokens.filter(
1121
+ (token: Token): boolean => {
1122
+ return token.type === "list";
1123
+ },
1124
+ ) as Array<Tokens.List>;
1125
+ const expectedDetails: number = (entries[index]?.details || []).filter(
1126
+ (detail: { value: string }): boolean => {
1127
+ return detail.value.length > 0;
1128
+ },
1129
+ ).length;
1130
+
1131
+ expect(nested).toHaveLength(1);
1132
+ expect(nested[0]?.ordered).toBe(false);
1133
+ expect(nested[0]?.items).toHaveLength(expectedDetails);
1134
+ });
1135
+ };
1136
+
1137
+ it("round trips a three-entry block unchanged", () => {
1138
+ const block: string = renderBlock(3, 3);
1139
+ const once: string = htmlToMarkdown(markdownToHtml(block));
1140
+
1141
+ // A document's leading blank lines are dropped, as for any document.
1142
+ expect(once).toBe(block.replace(/^\n+/, ""));
1143
+ expectOneRankedList(once, podEntries(3));
1144
+ });
1145
+
1146
+ it("round trips a ten-entry block with its overflow line unchanged", () => {
1147
+ const block: string = renderBlock(10, 83);
1148
+ const once: string = htmlToMarkdown(markdownToHtml(block));
1149
+
1150
+ expect(block).toContain("\n10. **Pod**");
1151
+ expect(block).toContain("\n - Namespace:");
1152
+ expect(block).toContain("*... and 73 more affected resources*");
1153
+ expect(once).toBe(block.replace(/^\n+/, ""));
1154
+ expectOneRankedList(once, podEntries(10));
1155
+ });
1156
+
1157
+ it("round trips a whole root cause unchanged", () => {
1158
+ const source: string = rootCause(10, 83);
1159
+
1160
+ expect(htmlToMarkdown(markdownToHtml(source))).toBe(source);
1161
+ });
1162
+
1163
+ it("renders the block as one ordered list whose items own their details", () => {
1164
+ const container: HTMLDivElement = document.createElement("div");
1165
+ container.innerHTML = markdownToHtml(renderBlock(10, 83));
1166
+
1167
+ const topLevelLists: NodeListOf<Element> = container.querySelectorAll(
1168
+ ":scope > ol, :scope > ul",
1169
+ );
1170
+
1171
+ expect(topLevelLists).toHaveLength(1);
1172
+
1173
+ const list: Element = topLevelLists[0] as Element;
1174
+
1175
+ expect(list.tagName).toBe("OL");
1176
+ expect(list.hasAttribute("start")).toBe(false);
1177
+
1178
+ const items: NodeListOf<Element> = list.querySelectorAll(":scope > li");
1179
+
1180
+ expect(items).toHaveLength(10);
1181
+
1182
+ items.forEach((item: Element): void => {
1183
+ const details: NodeListOf<Element> = item.querySelectorAll(":scope > ul");
1184
+
1185
+ expect(details).toHaveLength(1);
1186
+ expect(details[0]?.querySelector(":scope > li")?.textContent).toMatch(
1187
+ /^Namespace: /,
1188
+ );
1189
+ });
1190
+
1191
+ // The heading and the overflow line stay paragraphs around the list.
1192
+ expect(container.firstElementChild?.tagName).toBe("P");
1193
+ expect(container.lastElementChild?.tagName).toBe("P");
1194
+ expect(container.lastElementChild?.textContent).toBe(
1195
+ "... and 73 more affected resources",
1196
+ );
1197
+ });
1198
+
1199
+ /*
1200
+ * The editor sanitizes before injecting. DOMPurify's default allowlist
1201
+ * keeps nested lists and the start attribute; if it ever stopped doing so
1202
+ * the round trip would lose them there instead of here.
1203
+ */
1204
+ it("survives the editor's sanitizer unchanged", () => {
1205
+ const html: string = markdownToHtml(rootCause(10, 83));
1206
+
1207
+ expect(sanitize(html)).toBe(html);
1208
+ expect(sanitize(markdownToHtml("3. a\n4. b"))).toContain('<ol start="3">');
1209
+ });
1210
+
1211
+ /*
1212
+ * What "Edit Root Cause" actually does: render into the contenteditable,
1213
+ * let the user type, serialize. Adding a note after the list must not
1214
+ * renumber the list or detach any item's details.
1215
+ */
1216
+ it("keeps numbering and nesting when the user adds a paragraph", () => {
1217
+ const source: string = rootCause(10, 83);
1218
+ const editable: HTMLDivElement = document.createElement("div");
1219
+ editable.innerHTML = sanitize(markdownToHtml(source));
1220
+
1221
+ const note: HTMLParagraphElement = document.createElement("p");
1222
+ note.textContent = "Rolled back the migration job.";
1223
+ editable.appendChild(note);
1224
+
1225
+ const saved: string = htmlToMarkdown(editable.innerHTML);
1226
+
1227
+ expect(saved).toBe(`${source}\n\nRolled back the migration job.`);
1228
+ expectOneRankedList(saved, podEntries(10));
1229
+ });
1230
+
1231
+ it("keeps numbering and nesting when the user edits a nested detail", () => {
1232
+ const source: string = rootCause(10, 83);
1233
+ const editable: HTMLDivElement = document.createElement("div");
1234
+ editable.innerHTML = sanitize(markdownToHtml(source));
1235
+
1236
+ const lastDetail: Element | null = editable.querySelector(
1237
+ ":scope > ol > li:nth-child(10) > ul > li:last-child",
1238
+ );
1239
+
1240
+ expect(lastDetail?.textContent).toMatch(/^Node: /);
1241
+ lastDetail?.appendChild(document.createTextNode(" (cordoned)"));
1242
+
1243
+ const saved: string = htmlToMarkdown(editable.innerHTML);
1244
+ const nodeTen: string = "`gke-gke-test-cluster-db-pool-3e2bfa3b-du610`";
1245
+
1246
+ expect(saved).toBe(
1247
+ source.replace(
1248
+ ` - Node: ${nodeTen}`,
1249
+ ` - Node: ${nodeTen} (cordoned)`,
1250
+ ),
1251
+ );
1252
+ expectOneRankedList(saved, podEntries(10));
1253
+ });
1254
+
1255
+ it("is a fixed point: a second save changes nothing", () => {
1256
+ const first: string = htmlToMarkdown(markdownToHtml(rootCause(10, 83)));
1257
+
1258
+ expect(htmlToMarkdown(markdownToHtml(first))).toBe(first);
1259
+ });
1260
+ });
1261
+
1262
+ /*
1263
+ * Regressions found while verifying nested-list support.
1264
+ */
1265
+ describe("nested list edge cases", () => {
1266
+ /*
1267
+ * Each level of nesting parses its item's lines as a document of its own.
1268
+ * Uncapped, a single "- - - … x" line a thousand levels deep overflowed
1269
+ * the stack — in the editor's mount effect, so one such document would
1270
+ * have crashed the editor for everyone who opened it — and a staircase of
1271
+ * indented bullets cost cubic time.
1272
+ */
1273
+ it("does not overflow the stack on a pathologically deep single line", () => {
1274
+ const deep: string = `${"- ".repeat(1000)}x`;
1275
+
1276
+ let html: string = "";
1277
+ expect(() => {
1278
+ html = markdownToHtml(deep);
1279
+ }).not.toThrow();
1280
+ expect(html).toContain("x");
1281
+ // Nesting stops at the cap; the rest of the item is kept as text.
1282
+ expect((html.match(/<ul>/g) || []).length).toBeLessThanOrEqual(21);
1283
+ });
1284
+
1285
+ it("parses a deep staircase of indented bullets quickly", () => {
1286
+ const lines: Array<string> = [];
1287
+ for (let i: number = 0; i < 1000; i++) {
1288
+ lines.push(`${" ".repeat(i * 2)}- item ${i}`);
1289
+ }
1290
+
1291
+ const startedAt: number = performance.now();
1292
+ const html: string = markdownToHtml(lines.join("\n"));
1293
+ const elapsed: number = performance.now() - startedAt;
1294
+
1295
+ expect(html).toContain("item 0");
1296
+ expect(html).toContain("item 999");
1297
+ // The uncapped parser took about 8 seconds here.
1298
+ expect(elapsed).toBeLessThan(2000);
1299
+ });
1300
+
1301
+ it("still nests normally well inside the cap", () => {
1302
+ const lines: Array<string> = [];
1303
+ for (let i: number = 0; i < 10; i++) {
1304
+ lines.push(`${" ".repeat(i * 2)}- level ${i}`);
1305
+ }
1306
+ const markdown: string = lines.join("\n");
1307
+
1308
+ const html: string = markdownToHtml(markdown);
1309
+
1310
+ expect((html.match(/<ul>/g) || []).length).toBe(10);
1311
+ expect(htmlToMarkdown(html)).toBe(markdown);
1312
+ });
1313
+
1314
+ /*
1315
+ * A loose item's second paragraph is a <p> after the item's text. It used
1316
+ * to come back one newline short, so the saved markdown joined it to the
1317
+ * first paragraph and every save lost another break.
1318
+ */
1319
+ it("keeps the blank line before a later paragraph of an ordered item", () => {
1320
+ const markdown: string = "1. a\n\n b\n2. c";
1321
+
1322
+ const html: string = markdownToHtml(markdown);
1323
+
1324
+ expect(html).toBe("<ol><li>a<p>b</p></li><li>c</li></ol>");
1325
+ expect(htmlToMarkdown(html)).toBe(markdown);
1326
+ });
1327
+
1328
+ it("keeps every paragraph break of a multi-paragraph bullet, save after save", () => {
1329
+ const markdown: string = "- a\n\n b\n\n c\n- d";
1330
+
1331
+ const once: string = htmlToMarkdown(markdownToHtml(markdown));
1332
+ const twice: string = htmlToMarkdown(markdownToHtml(once));
1333
+
1334
+ expect(once).toBe(markdown);
1335
+ expect(twice).toBe(markdown);
1336
+ });
1337
+
1338
+ it("separates a later paragraph from the item's text when serializing editor HTML", () => {
1339
+ expect(htmlToMarkdown("<ul><li>a<p>b</p></li></ul>")).toBe("- a\n\n b");
1340
+ });
1341
+
1342
+ /*
1343
+ * Enter at the end of a nested bullet leaves an empty <li>. Trimming the
1344
+ * item's content writes it as a bare "-", which used to reopen as a
1345
+ * literal "-" paragraph while marked (the email) read it as an empty item.
1346
+ */
1347
+ it("reads a bare marker after a list item as an empty item", () => {
1348
+ const saved: string = htmlToMarkdown(
1349
+ "<ol><li>a<ul><li>x</li><li><br></li></ul></li><li>b</li></ol>",
1350
+ );
1351
+
1352
+ expect(saved).toBe("1. a\n - x\n -\n2. b");
1353
+
1354
+ const html: string = markdownToHtml(saved);
1355
+ expect(html).toBe(
1356
+ "<ol><li>a<ul><li>x</li><li></li></ul></li><li>b</li></ol>",
1357
+ );
1358
+ expect(html).not.toContain("<p>-</p>");
1359
+ expect(htmlToMarkdown(html)).toBe(saved);
1360
+ });
1361
+
1362
+ it("an empty ordered item after another is read as an item too", () => {
1363
+ expect(markdownToHtml("1. a\n2.")).toBe("<ol><li>a</li><li></li></ol>");
1364
+ });
1365
+
1366
+ it("a bare marker cannot START a list, so a lone number stays text", () => {
1367
+ expect(markdownToHtml("2021.")).toBe("<p>2021.</p>");
1368
+ expect(markdownToHtml("-")).not.toContain("<li>");
1369
+ });
1370
+ });