@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
@@ -14,6 +14,7 @@ import {
14
14
  render,
15
15
  screen,
16
16
  waitFor,
17
+ within,
17
18
  } from "@testing-library/react";
18
19
  import * as React from "react";
19
20
 
@@ -65,6 +66,27 @@ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
65
66
  };
66
67
  });
67
68
 
69
+ /*
70
+ * The real Icon draws an SVG that says nothing about which icon it is. This
71
+ * one names the icon, so the checklist's icons and the trigger's glyph can be
72
+ * read back off the page. Everything else the module exports (SizeProp,
73
+ * ThickProp, IconType) stays real - components across the feed read those at
74
+ * render time.
75
+ */
76
+ jest.mock("../../../UI/Components/Icon/Icon", () => {
77
+ const actualIconModule: Record<string, unknown> = jest.requireActual(
78
+ "../../../UI/Components/Icon/Icon",
79
+ ) as Record<string, unknown>;
80
+
81
+ return {
82
+ ...actualIconModule,
83
+ __esModule: true,
84
+ default: (props: { icon: string }): React.ReactElement => {
85
+ return <span data-icon={props.icon} aria-hidden="true" />;
86
+ },
87
+ };
88
+ });
89
+
68
90
  import ResourceFeed, {
69
91
  GetResourceFeedIconFunction,
70
92
  getIconForEventType,
@@ -77,7 +99,16 @@ import IconProp from "../../../Types/Icon/IconProp";
77
99
  import ObjectID from "../../../Types/ObjectID";
78
100
  import { Green500 } from "../../../Types/BrandColors";
79
101
  import SortOrder from "../../../Types/BaseDatabase/SortOrder";
102
+ import Includes from "../../../Types/BaseDatabase/Includes";
80
103
  import { DEFAULT_LIMIT } from "../../../Types/Database/LimitMax";
104
+ import {
105
+ DEFAULT_FEED_OPTIONS,
106
+ FEED_OPTIONS_TEXT,
107
+ FILTERED_FEED_NO_ITEMS_MESSAGE,
108
+ FeedOptions,
109
+ getFeedOptionsSummary,
110
+ } from "../../../UI/Components/Feed/FeedOptions";
111
+ import { getSortOrderStorageKey } from "../../../UI/Components/Feed/useFeedOptions";
81
112
 
82
113
  /*
83
114
  * One component serves all ten resource feeds, parameterised by the two
@@ -90,6 +121,13 @@ import { DEFAULT_LIMIT } from "../../../Types/Database/LimitMax";
90
121
 
91
122
  const CLUSTER_ID: ObjectID = ObjectID.generate();
92
123
 
124
+ const ALL_EVENT_TYPES: Array<string> = Object.values(
125
+ KubernetesClusterFeedEventType,
126
+ );
127
+
128
+ const NO_ITEMS_MESSAGE: string =
129
+ "No activity has been recorded for this Kubernetes cluster yet.";
130
+
93
131
  function feedItem(
94
132
  eventType: KubernetesClusterFeedEventType,
95
133
  markdown: string,
@@ -112,9 +150,10 @@ function getFeedElement(resourceId: ObjectID = CLUSTER_ID): React.ReactElement {
112
150
  resourceIdColumn="kubernetesClusterId"
113
151
  resourceId={resourceId}
114
152
  eventTypeColumn="kubernetesClusterFeedEventType"
153
+ eventTypes={ALL_EVENT_TYPES}
115
154
  title="Kubernetes Cluster Feed"
116
155
  description="Everything that has happened to this Kubernetes cluster."
117
- noItemsMessage="No activity has been recorded for this Kubernetes cluster yet."
156
+ noItemsMessage={NO_ITEMS_MESSAGE}
118
157
  />
119
158
  );
120
159
  }
@@ -125,11 +164,130 @@ function renderFeed(
125
164
  return render(getFeedElement(resourceId));
126
165
  }
127
166
 
167
+ const RESOURCE_ID_COLUMN: string = "kubernetesClusterId";
168
+
169
+ const EVENT_TYPE_COLUMN: string = "kubernetesClusterFeedEventType";
170
+
171
+ type GetQuery = (request: GetListRequest) => Record<string, unknown>;
172
+
173
+ const getQuery: GetQuery = (
174
+ request: GetListRequest,
175
+ ): Record<string, unknown> => {
176
+ return request["query"] as Record<string, unknown>;
177
+ };
178
+
179
+ type GetRequestsFor = (resourceId: ObjectID) => Array<GetListRequest>;
180
+
181
+ // Every request made so far for this cluster's feed, oldest first.
182
+ const getRequestsFor: GetRequestsFor = (
183
+ resourceId: ObjectID,
184
+ ): Array<GetListRequest> => {
185
+ return mockGetListCalls.filter((request: GetListRequest) => {
186
+ return (
187
+ String(getQuery(request)[RESOURCE_ID_COLUMN]) === resourceId.toString()
188
+ );
189
+ });
190
+ };
191
+
192
+ type WaitForFeed = (requestCount: number) => Promise<void>;
193
+
194
+ // The feed has asked the API `requestCount` times and drawn the last answer.
195
+ const waitForFeed: WaitForFeed = async (
196
+ requestCount: number,
197
+ ): Promise<void> => {
198
+ await waitFor(() => {
199
+ expect(mockGetListCalls).toHaveLength(requestCount);
200
+ expect(screen.queryByTestId("component-loader")).not.toBeInTheDocument();
201
+ });
202
+ };
203
+
204
+ type GetFilterAndSortButton = () => HTMLElement;
205
+
206
+ /*
207
+ * Found the way a screen reader finds it: by its label. What the feed is
208
+ * showing is the button's description, not part of its name, so the name
209
+ * stays "Filter & Sort" whatever is chosen - and the count badge, which is
210
+ * aria-hidden, never joins it either.
211
+ */
212
+ const getFilterAndSortButton: GetFilterAndSortButton = (): HTMLElement => {
213
+ return screen.getByRole("button", { name: "Filter & Sort" });
214
+ };
215
+
216
+ type OpenFilterAndSort = () => HTMLElement;
217
+
218
+ // Presses Filter & Sort and returns the panel it opens.
219
+ const openFilterAndSort: OpenFilterAndSort = (): HTMLElement => {
220
+ fireEvent.click(getFilterAndSortButton());
221
+
222
+ return screen.getByRole("dialog", { name: FEED_OPTIONS_TEXT.panelLabel });
223
+ };
224
+
225
+ type CloseFilterAndSort = () => void;
226
+
227
+ // A press anywhere outside the panel - on the way to another page, say.
228
+ const closeFilterAndSort: CloseFilterAndSort = (): void => {
229
+ fireEvent.mouseDown(document.body);
230
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
231
+ };
232
+
233
+ type GetExpectedSummary = (options: FeedOptions) => string;
234
+
235
+ /*
236
+ * The sentence the trigger is described by, for this feed's event types.
237
+ * Built by the same function the button uses, so the wording can change
238
+ * without this suite noticing - what is pinned is that the description
239
+ * follows the feed's live order and filter, counted over this feed's own
240
+ * event types.
241
+ */
242
+ const getExpectedSummary: GetExpectedSummary = (
243
+ options: FeedOptions,
244
+ ): string => {
245
+ return getFeedOptionsSummary({
246
+ options: options,
247
+ eventTypeCount: ALL_EVENT_TYPES.length,
248
+ });
249
+ };
250
+
251
+ type GetTriggerIcon = () => string | null;
252
+
253
+ // The glyph in front of the trigger's label: a funnel, or the sort arrows.
254
+ const getTriggerIcon: GetTriggerIcon = (): string | null => {
255
+ return (
256
+ getFilterAndSortButton()
257
+ .querySelector("[data-icon]")
258
+ ?.getAttribute("data-icon") || null
259
+ );
260
+ };
261
+
262
+ type GetStoredSortOrderKeys = () => Array<string>;
263
+
264
+ /*
265
+ * Every remembered feed sort order in this browser, whichever feed wrote it.
266
+ * The prefix is whatever getSortOrderStorageKey puts in front of a feed's
267
+ * name, so it is read from there rather than copied.
268
+ */
269
+ const getStoredSortOrderKeys: GetStoredSortOrderKeys = (): Array<string> => {
270
+ const prefix: string = getSortOrderStorageKey("");
271
+ const keys: Array<string> = [];
272
+
273
+ for (let index: number = 0; index < window.localStorage.length; index++) {
274
+ const key: string | null = window.localStorage.key(index);
275
+
276
+ if (key && key.startsWith(prefix)) {
277
+ keys.push(key);
278
+ }
279
+ }
280
+
281
+ return keys;
282
+ };
283
+
128
284
  beforeEach(() => {
129
285
  mockGetListCalls.length = 0;
130
286
  mockGetListResponse = (): Promise<unknown> => {
131
287
  return Promise.resolve({ data: [], count: 0 });
132
288
  };
289
+ // The chosen sort order is remembered, so one test's choice must not leak.
290
+ window.localStorage.clear();
133
291
  });
134
292
 
135
293
  afterEach(() => {
@@ -725,9 +883,10 @@ describe("ResourceFeed - a feed's own icons", () => {
725
883
  resourceIdColumn="kubernetesClusterId"
726
884
  resourceId={CLUSTER_ID}
727
885
  eventTypeColumn="kubernetesClusterFeedEventType"
886
+ eventTypes={ALL_EVENT_TYPES}
728
887
  title="Kubernetes Cluster Feed"
729
888
  description="Everything that has happened to this Kubernetes cluster."
730
- noItemsMessage="No activity has been recorded for this Kubernetes cluster yet."
889
+ noItemsMessage={NO_ITEMS_MESSAGE}
731
890
  getIcon={getIcon}
732
891
  />,
733
892
  );
@@ -744,4 +903,455 @@ describe("ResourceFeed - a feed's own icons", () => {
744
903
  KubernetesClusterFeedEventType.OwnerUserAdded,
745
904
  );
746
905
  });
906
+
907
+ test("gives the Filter & Sort checklist the same icons, one entry per event type", async () => {
908
+ const askedEventTypes: Array<string> = [];
909
+
910
+ /*
911
+ * One event type gets an icon of the feed's own that the shared rules
912
+ * would never pick for it, so its row can only show it if the checklist
913
+ * really goes through getIcon.
914
+ */
915
+ const ownIconEventType: string =
916
+ KubernetesClusterFeedEventType.OwnerUserAdded;
917
+ const ownIcon: IconProp = IconProp.Fire;
918
+
919
+ expect(getIconForEventType(ownIconEventType)).not.toBe(ownIcon);
920
+
921
+ const getIcon: GetResourceFeedIconFunction = (
922
+ eventType: string,
923
+ ): IconProp | undefined => {
924
+ askedEventTypes.push(eventType);
925
+ return eventType === ownIconEventType ? ownIcon : undefined;
926
+ };
927
+
928
+ render(
929
+ <ResourceFeed<KubernetesClusterFeed>
930
+ modelType={KubernetesClusterFeed}
931
+ resourceIdColumn="kubernetesClusterId"
932
+ resourceId={CLUSTER_ID}
933
+ eventTypeColumn="kubernetesClusterFeedEventType"
934
+ eventTypes={ALL_EVENT_TYPES}
935
+ title="Kubernetes Cluster Feed"
936
+ description="Everything that has happened to this Kubernetes cluster."
937
+ noItemsMessage={NO_ITEMS_MESSAGE}
938
+ getIcon={getIcon}
939
+ />,
940
+ );
941
+
942
+ await waitFor(() => {
943
+ expect(screen.getByText(NO_ITEMS_MESSAGE)).toBeInTheDocument();
944
+ });
945
+
946
+ // The feed is empty, so only the checklist can have asked.
947
+ for (const eventType of ALL_EVENT_TYPES) {
948
+ expect(askedEventTypes).toContain(eventType);
949
+ }
950
+
951
+ const panel: HTMLElement = openFilterAndSort();
952
+
953
+ /*
954
+ * Exactly one box per event type of this feed's own enum: none missing,
955
+ * none repeated, and nothing from another feed.
956
+ */
957
+ const checkboxTestIds: Array<string> = within(panel)
958
+ .getAllByRole("checkbox")
959
+ .map((checkbox: HTMLElement): string => {
960
+ return checkbox.getAttribute("data-testid") || "";
961
+ });
962
+
963
+ expect([...checkboxTestIds].sort()).toEqual(
964
+ ALL_EVENT_TYPES.map((eventType: string): string => {
965
+ return `feed-options-event-type-${eventType}`;
966
+ }).sort(),
967
+ );
968
+
969
+ type GetRowIcon = (eventType: string) => string | null;
970
+
971
+ const getRowIcon: GetRowIcon = (eventType: string): string | null => {
972
+ return (
973
+ within(panel)
974
+ .getByTestId(`feed-options-event-type-${eventType}`)
975
+ .closest("label")
976
+ ?.querySelector("[data-icon]")
977
+ ?.getAttribute("data-icon") || null
978
+ );
979
+ };
980
+
981
+ // The feed's own answer wins...
982
+ expect(getRowIcon(ownIconEventType)).toBe(ownIcon);
983
+
984
+ // ...and "no answer" falls back to the shared rules, row by row.
985
+ for (const eventType of ALL_EVENT_TYPES) {
986
+ if (eventType === ownIconEventType) {
987
+ continue;
988
+ }
989
+
990
+ expect(getRowIcon(eventType)).toBe(getIconForEventType(eventType));
991
+ }
992
+ });
993
+ });
994
+
995
+ /*
996
+ * Filter & Sort is applied by the API, not to the rows already loaded, and
997
+ * the event type filter is keyed by the same string column as the icon - so
998
+ * a typo there would filter on a column that does not exist.
999
+ */
1000
+ describe("ResourceFeed - Filter & Sort", () => {
1001
+ test("puts Filter & Sort first in the header, and an untouched feed sends exactly the query it always sent", async () => {
1002
+ renderFeed();
1003
+
1004
+ await waitFor(() => {
1005
+ expect(mockGetListCalls.length).toBe(1);
1006
+ });
1007
+
1008
+ expect(
1009
+ Object.keys(mockGetListCalls[0]!["query"] as Record<string, unknown>),
1010
+ ).toEqual(["kubernetesClusterId"]);
1011
+
1012
+ const optionsButton: HTMLElement = getFilterAndSortButton();
1013
+ const refreshButton: HTMLElement = screen.getByRole("button", {
1014
+ name: "Refresh",
1015
+ });
1016
+
1017
+ expect(
1018
+ optionsButton.compareDocumentPosition(refreshButton) &
1019
+ Node.DOCUMENT_POSITION_FOLLOWING,
1020
+ ).toBeTruthy();
1021
+
1022
+ /*
1023
+ * The state behind the button is read out as its description - and shown
1024
+ * as its tooltip - so the reader need not open it to know what the feed
1025
+ * is showing.
1026
+ */
1027
+ const summary: string = getExpectedSummary(DEFAULT_FEED_OPTIONS);
1028
+
1029
+ expect(summary.length).toBeGreaterThan(0);
1030
+ expect(optionsButton).toHaveAccessibleDescription(summary);
1031
+ expect(optionsButton).toHaveAttribute("title", summary);
1032
+
1033
+ // An untouched feed: the funnel, no count, and nothing remembered.
1034
+ expect(getTriggerIcon()).toBe(IconProp.Filter);
1035
+ expect(screen.queryByTestId("feed-options-count")).not.toBeInTheDocument();
1036
+ expect(getStoredSortOrderKeys()).toEqual([]);
1037
+ });
1038
+
1039
+ test("filters on the feed's own event type column and says so when nothing matches", async () => {
1040
+ renderFeed();
1041
+
1042
+ await waitFor(() => {
1043
+ expect(screen.getByText(NO_ITEMS_MESSAGE)).toBeInTheDocument();
1044
+ });
1045
+
1046
+ const panel: HTMLElement = openFilterAndSort();
1047
+
1048
+ fireEvent.click(
1049
+ within(panel).getByTestId(
1050
+ `feed-options-event-type-${KubernetesClusterFeedEventType.OwnerUserAdded}`,
1051
+ ),
1052
+ );
1053
+
1054
+ await waitFor(() => {
1055
+ expect(mockGetListCalls).toHaveLength(2);
1056
+ });
1057
+
1058
+ const request: GetListRequest = mockGetListCalls[1]!;
1059
+ const query: Record<string, unknown> = request["query"] as Record<
1060
+ string,
1061
+ unknown
1062
+ >;
1063
+ const eventTypeFilter: unknown = query["kubernetesClusterFeedEventType"];
1064
+
1065
+ expect(query["kubernetesClusterId"]).toBe(CLUSTER_ID);
1066
+ expect(eventTypeFilter).toBeInstanceOf(Includes);
1067
+ expect((eventTypeFilter as Includes).values).toEqual([
1068
+ KubernetesClusterFeedEventType.OwnerUserAdded,
1069
+ ]);
1070
+ expect(request["skip"]).toBe(0);
1071
+ expect(request["limit"]).toBe(DEFAULT_LIMIT);
1072
+
1073
+ /*
1074
+ * An empty filtered feed must not claim the cluster has no history at
1075
+ * all.
1076
+ */
1077
+ expect(
1078
+ await screen.findByText(FILTERED_FEED_NO_ITEMS_MESSAGE),
1079
+ ).toBeInTheDocument();
1080
+ expect(screen.queryByText(NO_ITEMS_MESSAGE)).not.toBeInTheDocument();
1081
+
1082
+ /*
1083
+ * The trigger keeps its name with a count on it, and its description now
1084
+ * says the feed is narrowed - to one of this feed's event types.
1085
+ */
1086
+ const optionsButton: HTMLElement = getFilterAndSortButton();
1087
+
1088
+ expect(screen.getByTestId("feed-options-count")).toHaveTextContent(/^1$/);
1089
+ expect(optionsButton).toHaveAccessibleDescription(
1090
+ getExpectedSummary({
1091
+ sortOrder: SortOrder.Descending,
1092
+ eventTypes: [KubernetesClusterFeedEventType.OwnerUserAdded],
1093
+ }),
1094
+ );
1095
+ expect(optionsButton).not.toHaveAccessibleDescription(
1096
+ getExpectedSummary(DEFAULT_FEED_OPTIONS),
1097
+ );
1098
+ });
1099
+
1100
+ test("Oldest first re-reads the first window in ascending order, draws it exactly as the API returned it, and remembers it under this feed's key only", async () => {
1101
+ // sort-entry-0 is the newest event, sort-entry-11 the oldest.
1102
+ const newestFirst: Array<KubernetesClusterFeed> = Array.from(
1103
+ { length: 12 },
1104
+ (_value: unknown, index: number): KubernetesClusterFeed => {
1105
+ return feedItem(
1106
+ KubernetesClusterFeedEventType.KubernetesClusterUpdated,
1107
+ `sort-entry-${index}`,
1108
+ new Date(Date.UTC(2024, 0, 12 - index)),
1109
+ );
1110
+ },
1111
+ );
1112
+ const oldestFirst: Array<KubernetesClusterFeed> = [
1113
+ ...newestFirst,
1114
+ ].reverse();
1115
+
1116
+ /*
1117
+ * The stub answers each order with its own rows, as the API would. The
1118
+ * ascending window (entry-11 down to entry-2) is neither the newest window
1119
+ * reversed nor anything a client-side sort by time would draw, so the
1120
+ * page can only show it by showing the API's rows as they came.
1121
+ */
1122
+ mockGetListResponse = (request: GetListRequest): Promise<unknown> => {
1123
+ const sort: Record<string, unknown> = request["sort"] as Record<
1124
+ string,
1125
+ unknown
1126
+ >;
1127
+ const rows: Array<KubernetesClusterFeed> =
1128
+ sort["postedAt"] === SortOrder.Ascending ? oldestFirst : newestFirst;
1129
+
1130
+ return Promise.resolve({
1131
+ data: rows.slice(0, request["limit"] as number),
1132
+ count: rows.length,
1133
+ });
1134
+ };
1135
+
1136
+ type GetDrawnEntries = () => Array<string>;
1137
+
1138
+ const getDrawnEntries: GetDrawnEntries = (): Array<string> => {
1139
+ return screen
1140
+ .getAllByText(/sort-entry-/)
1141
+ .map((element: HTMLElement): string => {
1142
+ return element.textContent || "";
1143
+ });
1144
+ };
1145
+
1146
+ type GetMarkdown = (items: Array<KubernetesClusterFeed>) => Array<string>;
1147
+
1148
+ const getMarkdown: GetMarkdown = (
1149
+ items: Array<KubernetesClusterFeed>,
1150
+ ): Array<string> => {
1151
+ return items.map((item: KubernetesClusterFeed): string => {
1152
+ return item.feedInfoInMarkdown || "";
1153
+ });
1154
+ };
1155
+
1156
+ renderFeed();
1157
+ await waitFor(() => {
1158
+ expect(screen.getAllByText(/sort-entry-/)).toHaveLength(DEFAULT_LIMIT);
1159
+ });
1160
+
1161
+ fireEvent.click(screen.getByRole("button", { name: "More" }));
1162
+ await waitFor(() => {
1163
+ expect(screen.getAllByText(/sort-entry-/)).toHaveLength(12);
1164
+ });
1165
+
1166
+ // Nothing is remembered until the reader changes something.
1167
+ expect(getStoredSortOrderKeys()).toEqual([]);
1168
+
1169
+ const panel: HTMLElement = openFilterAndSort();
1170
+
1171
+ fireEvent.click(
1172
+ within(panel).getByTestId(`feed-options-sort-${SortOrder.Ascending}`),
1173
+ );
1174
+
1175
+ await waitForFeed(3);
1176
+
1177
+ // Reversing the loaded rows would show the newest twelve upside down.
1178
+ const request: GetListRequest = mockGetListCalls[2]!;
1179
+
1180
+ expect(request["sort"]).toEqual({
1181
+ postedAt: SortOrder.Ascending,
1182
+ });
1183
+ expect(request["skip"]).toBe(0);
1184
+ expect(request["limit"]).toBe(DEFAULT_LIMIT);
1185
+ expect(Object.keys(getQuery(request))).toEqual([RESOURCE_ID_COLUMN]);
1186
+
1187
+ await waitFor(() => {
1188
+ expect(getDrawnEntries()).toEqual(
1189
+ getMarkdown(oldestFirst.slice(0, DEFAULT_LIMIT)),
1190
+ );
1191
+ });
1192
+
1193
+ /*
1194
+ * The trigger says so without being opened: the sort glyph instead of the
1195
+ * funnel, and a description that leads with the new order.
1196
+ */
1197
+ expect(getTriggerIcon()).toBe(IconProp.BarsArrowUp);
1198
+ expect(getFilterAndSortButton()).toHaveAccessibleDescription(
1199
+ getExpectedSummary({ sortOrder: SortOrder.Ascending, eventTypes: [] }),
1200
+ );
1201
+
1202
+ /*
1203
+ * Remembered under this feed's own key, and only there: another
1204
+ * product's feed must not open oldest first because of this one.
1205
+ */
1206
+ const storageKey: string = getSortOrderStorageKey(EVENT_TYPE_COLUMN);
1207
+
1208
+ expect(window.localStorage.getItem(storageKey)).toBe(SortOrder.Ascending);
1209
+ expect(
1210
+ window.localStorage.getItem(
1211
+ getSortOrderStorageKey("dockerHostFeedEventType"),
1212
+ ),
1213
+ ).toBeNull();
1214
+ expect(getStoredSortOrderKeys()).toEqual([storageKey]);
1215
+ });
1216
+
1217
+ test("drops the event type filter when the feed moves to another cluster, and keeps the sort order", async () => {
1218
+ /*
1219
+ * The dashboard moves between clusters without remounting the feed. The
1220
+ * filter was chosen for one cluster's investigation, so the next cluster
1221
+ * must start unfiltered - from its very first request, or the API is
1222
+ * asked for the old cluster's filter and the reader briefly sees a feed
1223
+ * with events missing. The sort order is a preference and stays.
1224
+ */
1225
+ const clusterA: ObjectID = CLUSTER_ID;
1226
+ const clusterB: ObjectID = ObjectID.generate();
1227
+ const tickedEventType: string =
1228
+ KubernetesClusterFeedEventType.OwnerUserAdded;
1229
+
1230
+ type ExpectUnfilteredOldestFirst = (
1231
+ request: GetListRequest,
1232
+ resourceId: ObjectID,
1233
+ ) => void;
1234
+
1235
+ const expectUnfilteredOldestFirst: ExpectUnfilteredOldestFirst = (
1236
+ request: GetListRequest,
1237
+ resourceId: ObjectID,
1238
+ ): void => {
1239
+ const query: Record<string, unknown> = getQuery(request);
1240
+
1241
+ expect(Object.keys(query)).toEqual([RESOURCE_ID_COLUMN]);
1242
+ expect(String(query[RESOURCE_ID_COLUMN])).toBe(resourceId.toString());
1243
+ expect(request["sort"]).toEqual({ postedAt: SortOrder.Ascending });
1244
+ expect(request["skip"]).toBe(0);
1245
+ expect(request["limit"]).toBe(DEFAULT_LIMIT);
1246
+ };
1247
+
1248
+ type ExpectNothingTicked = () => void;
1249
+
1250
+ // Reopens the panel on the feed as it is now, and closes it again.
1251
+ const expectNothingTicked: ExpectNothingTicked = (): void => {
1252
+ const reopenedPanel: HTMLElement = openFilterAndSort();
1253
+ const checkboxes: Array<HTMLElement> =
1254
+ within(reopenedPanel).getAllByRole("checkbox");
1255
+
1256
+ expect(checkboxes).toHaveLength(ALL_EVENT_TYPES.length);
1257
+
1258
+ for (const checkbox of checkboxes) {
1259
+ expect(checkbox).not.toBeChecked();
1260
+ }
1261
+
1262
+ expect(
1263
+ within(reopenedPanel).getByTestId(
1264
+ `feed-options-sort-${SortOrder.Ascending}`,
1265
+ ),
1266
+ ).toHaveAttribute("aria-checked", "true");
1267
+
1268
+ closeFilterAndSort();
1269
+ };
1270
+
1271
+ const view: ReturnType<typeof render> = renderFeed(clusterA);
1272
+ await waitForFeed(1);
1273
+
1274
+ // On cluster A: oldest first, then only owners being added.
1275
+ const panel: HTMLElement = openFilterAndSort();
1276
+
1277
+ fireEvent.click(
1278
+ within(panel).getByTestId(`feed-options-sort-${SortOrder.Ascending}`),
1279
+ );
1280
+ await waitForFeed(2);
1281
+
1282
+ fireEvent.click(
1283
+ within(
1284
+ screen.getByRole("dialog", { name: FEED_OPTIONS_TEXT.panelLabel }),
1285
+ ).getByTestId(`feed-options-event-type-${tickedEventType}`),
1286
+ );
1287
+ await waitForFeed(3);
1288
+
1289
+ const filteredRequest: GetListRequest = mockGetListCalls[2]!;
1290
+ const eventTypeFilter: unknown =
1291
+ getQuery(filteredRequest)[EVENT_TYPE_COLUMN];
1292
+
1293
+ expect(eventTypeFilter).toBeInstanceOf(Includes);
1294
+ expect((eventTypeFilter as Includes).values).toEqual([tickedEventType]);
1295
+ expect(filteredRequest["sort"]).toEqual({ postedAt: SortOrder.Ascending });
1296
+ expect(screen.getByTestId("feed-options-count")).toHaveTextContent(/^1$/);
1297
+
1298
+ closeFilterAndSort();
1299
+
1300
+ // To cluster B.
1301
+ view.rerender(getFeedElement(clusterB));
1302
+ await waitForFeed(4);
1303
+
1304
+ const requestsForB: Array<GetListRequest> = getRequestsFor(clusterB);
1305
+
1306
+ /*
1307
+ * One request for B, and it was already unfiltered - not a filtered one
1308
+ * that a later reset then replaced.
1309
+ */
1310
+ expect(requestsForB).toHaveLength(1);
1311
+ expectUnfilteredOldestFirst(requestsForB[0]!, clusterB);
1312
+
1313
+ // An empty unfiltered feed says the cluster has no history, not "no match".
1314
+ expect(await screen.findByText(NO_ITEMS_MESSAGE)).toBeInTheDocument();
1315
+ expect(
1316
+ screen.queryByText(FILTERED_FEED_NO_ITEMS_MESSAGE),
1317
+ ).not.toBeInTheDocument();
1318
+
1319
+ expect(screen.queryByTestId("feed-options-count")).not.toBeInTheDocument();
1320
+ expect(getTriggerIcon()).toBe(IconProp.BarsArrowUp);
1321
+ expect(getFilterAndSortButton()).toHaveAccessibleDescription(
1322
+ getExpectedSummary({ sortOrder: SortOrder.Ascending, eventTypes: [] }),
1323
+ );
1324
+ expectNothingTicked();
1325
+
1326
+ // Nothing asked B for the old filter afterwards either.
1327
+ expect(getRequestsFor(clusterB)).toHaveLength(1);
1328
+
1329
+ // The sort order is still remembered, for this feed only.
1330
+ expect(getStoredSortOrderKeys()).toEqual([
1331
+ getSortOrderStorageKey(EVENT_TYPE_COLUMN),
1332
+ ]);
1333
+ expect(
1334
+ window.localStorage.getItem(getSortOrderStorageKey(EVENT_TYPE_COLUMN)),
1335
+ ).toBe(SortOrder.Ascending);
1336
+
1337
+ // Back to cluster A: its old filter does not come back with it.
1338
+ const requestCountBeforeReturn: number = mockGetListCalls.length;
1339
+
1340
+ view.rerender(getFeedElement(clusterA));
1341
+ await waitForFeed(requestCountBeforeReturn + 1);
1342
+
1343
+ const requestsAfterReturn: Array<GetListRequest> = mockGetListCalls.slice(
1344
+ requestCountBeforeReturn,
1345
+ );
1346
+
1347
+ expect(requestsAfterReturn).toHaveLength(1);
1348
+ expectUnfilteredOldestFirst(requestsAfterReturn[0]!, clusterA);
1349
+
1350
+ expect(await screen.findByText(NO_ITEMS_MESSAGE)).toBeInTheDocument();
1351
+ expect(screen.queryByTestId("feed-options-count")).not.toBeInTheDocument();
1352
+ expect(getFilterAndSortButton()).toHaveAccessibleDescription(
1353
+ getExpectedSummary({ sortOrder: SortOrder.Ascending, eventTypes: [] }),
1354
+ );
1355
+ expectNothingTicked();
1356
+ });
747
1357
  });
@@ -227,4 +227,34 @@ describe("Resource feed pages", () => {
227
227
  expect(page).toContain("ResourceFeed");
228
228
  },
229
229
  );
230
+
231
+ test.each(FEED_PAGES)(
232
+ "$product's feed page offers its own model's event types to Filter & Sort",
233
+ (spec: FeedPageSpec) => {
234
+ const page: string = read(
235
+ "Pages",
236
+ spec.pagesDirectory,
237
+ "View",
238
+ "Feed.tsx",
239
+ );
240
+
241
+ /*
242
+ * The pages are near copies, so the realistic slip is pasting another
243
+ * product's enum: it compiles, and the checklist then offers event
244
+ * types this feed never records. The enum must be the one named after
245
+ * the page's own feed model. The SLO page takes its props from
246
+ * getSloResourceFeedProps, so its list lives there.
247
+ */
248
+ const modelMatch: RegExpMatchArray | null =
249
+ page.match(/<ResourceFeed<(\w+)>/);
250
+
251
+ expect(modelMatch).toBeTruthy();
252
+
253
+ const source: string = page.includes("getSloResourceFeedProps")
254
+ ? read("Components", "Slo", "SloFeed.tsx")
255
+ : page;
256
+
257
+ expect(source).toContain(`Object.values(${modelMatch![1]}EventType)`);
258
+ },
259
+ );
230
260
  });