@oneuptime/common 12.0.4 → 12.0.5

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 (374) hide show
  1. package/Models/DatabaseModels/AIRun.ts +38 -1
  2. package/Models/DatabaseModels/AlertFeed.ts +30 -0
  3. package/Models/DatabaseModels/Dashboard.ts +28 -0
  4. package/Models/DatabaseModels/Incident.ts +2 -0
  5. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  6. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  7. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  8. package/Models/DatabaseModels/IncidentFeed.ts +30 -0
  9. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  10. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  11. package/Models/DatabaseModels/LogSavedView.ts +33 -0
  12. package/Models/DatabaseModels/Project.ts +231 -9
  13. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  14. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  15. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  16. package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
  17. package/Models/DatabaseModels/StatusPage.ts +28 -0
  18. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  19. package/Server/API/AIAgentDataAPI.ts +44 -55
  20. package/Server/API/AIInvestigationAPI.ts +175 -17
  21. package/Server/API/DashboardAPI.ts +10 -10
  22. package/Server/API/StatusPageAPI.ts +38 -19
  23. package/Server/EnvironmentConfig.ts +16 -0
  24. package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
  25. package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  32. package/Server/Services/AIRunService.ts +78 -10
  33. package/Server/Services/AIService.ts +94 -22
  34. package/Server/Services/AlertFeedService.ts +5 -0
  35. package/Server/Services/AnalyticsDatabaseService.ts +29 -0
  36. package/Server/Services/DatabaseService.ts +43 -2
  37. package/Server/Services/IncidentFeedService.ts +5 -0
  38. package/Server/Services/LlmLogService.ts +46 -2
  39. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
  40. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
  41. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
  42. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
  43. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
  44. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
  45. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
  46. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
  47. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
  48. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
  49. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
  50. package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
  51. package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
  52. package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
  53. package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
  54. package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
  55. package/Server/Utils/AI/SRE/README.md +1 -1
  56. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
  57. package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
  58. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
  59. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
  60. package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
  61. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
  62. package/Server/Utils/PasswordHash.ts +28 -4
  63. package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
  64. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
  65. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
  66. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
  67. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
  68. package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
  69. package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
  70. package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
  71. package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
  72. package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
  73. package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
  74. package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
  75. package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
  76. package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
  77. package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
  78. package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
  79. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
  80. package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
  81. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
  82. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +45 -35
  83. package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
  84. package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
  85. package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
  86. package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
  87. package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
  88. package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
  89. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
  90. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
  91. package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
  92. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
  93. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
  94. package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
  95. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
  96. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
  97. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
  98. package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
  99. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
  100. package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
  101. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
  102. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
  103. package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
  104. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
  105. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
  106. package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
  107. package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
  108. package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
  109. package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
  110. package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
  111. package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
  112. package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
  113. package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
  114. package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
  115. package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
  116. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
  117. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
  118. package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
  119. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
  120. package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
  121. package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
  122. package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
  123. package/Tests/Types/Permission.test.ts +137 -0
  124. package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
  125. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
  126. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
  127. package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
  128. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
  129. package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
  130. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
  131. package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
  132. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
  133. package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
  134. package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
  135. package/Tests/UI/Utils/Project.test.ts +31 -0
  136. package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
  137. package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
  138. package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
  139. package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
  140. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
  141. package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
  142. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
  143. package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
  144. package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
  145. package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
  146. package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
  147. package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
  148. package/Types/AI/CodeFixTaskContext.ts +63 -3
  149. package/Types/AI/CodeFixTaskType.ts +3 -3
  150. package/Types/HashedString.ts +4 -3
  151. package/Types/Monitor/CriteriaFilter.ts +4 -0
  152. package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
  153. package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
  154. package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
  155. package/Types/Monitor/MonitorMetricType.ts +8 -0
  156. package/Types/Monitor/MonitorStep.ts +10 -1
  157. package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
  158. package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
  159. package/Types/Permission.ts +23 -175
  160. package/Types/Probe/ProbeMonitorResponse.ts +6 -0
  161. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
  162. package/UI/Components/ErrorBoundary.tsx +138 -11
  163. package/UI/Components/Feed/FeedItem.tsx +9 -2
  164. package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
  165. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  166. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
  167. package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
  168. package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
  169. package/UI/Utils/DownloadFile.ts +16 -8
  170. package/UI/Utils/ErrorSupportBundle.ts +805 -0
  171. package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
  172. package/UI/Utils/Project.ts +4 -0
  173. package/UI/Utils/StatusPage.ts +8 -2
  174. package/UI/Utils/UseDashboardGridDnd.ts +917 -0
  175. package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
  176. package/Utils/Dashboard/GridLayout.ts +523 -0
  177. package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
  178. package/Utils/Monitor/MonitorMetricType.ts +44 -2
  179. package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
  180. package/Utils/StatusPage/GroupTree.ts +157 -66
  181. package/Utils/StatusPage/ResourceUptime.ts +37 -2
  182. package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
  183. package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
  184. package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
  185. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
  187. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
  189. package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  191. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  193. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  195. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  197. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
  199. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  201. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  203. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
  205. package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/Project.js +241 -11
  207. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  209. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  211. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  212. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  213. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  214. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
  215. package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
  216. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  218. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +28 -46
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
  222. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +8 -5
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/StatusPageAPI.js +29 -11
  226. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  227. package/build/dist/Server/EnvironmentConfig.js +14 -0
  228. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  229. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
  230. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
  232. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  245. package/build/dist/Server/Services/AIRunService.js +58 -12
  246. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  247. package/build/dist/Server/Services/AIService.js +74 -21
  248. package/build/dist/Server/Services/AIService.js.map +1 -1
  249. package/build/dist/Server/Services/AlertFeedService.js +3 -0
  250. package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +29 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/DatabaseService.js +21 -2
  254. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  255. package/build/dist/Server/Services/IncidentFeedService.js +3 -0
  256. package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
  257. package/build/dist/Server/Services/LlmLogService.js +22 -1
  258. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  259. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
  260. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  261. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
  262. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  263. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
  264. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  265. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
  266. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  267. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
  268. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  269. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
  270. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
  272. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
  274. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
  276. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
  278. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
  279. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
  280. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  281. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
  282. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
  283. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
  284. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
  285. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
  286. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  287. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
  288. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
  290. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
  292. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  293. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
  294. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
  295. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
  296. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  297. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
  298. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  299. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
  300. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  301. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
  302. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  303. package/build/dist/Server/Utils/PasswordHash.js +25 -4
  304. package/build/dist/Server/Utils/PasswordHash.js.map +1 -1
  305. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
  306. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
  307. package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
  308. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
  309. package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
  310. package/build/dist/Types/HashedString.js +4 -3
  311. package/build/dist/Types/HashedString.js.map +1 -1
  312. package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
  313. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  314. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
  315. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
  316. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
  317. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  318. package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
  319. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  320. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  321. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  322. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
  323. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
  324. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
  325. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
  326. package/build/dist/Types/Permission.js +23 -155
  327. package/build/dist/Types/Permission.js.map +1 -1
  328. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
  329. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  330. package/build/dist/UI/Components/ErrorBoundary.js +67 -8
  331. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  332. package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
  333. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  334. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
  335. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  336. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  337. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  338. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
  339. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  340. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
  341. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  342. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
  343. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  344. package/build/dist/UI/Utils/DownloadFile.js +15 -8
  345. package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
  346. package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
  347. package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
  348. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
  349. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  350. package/build/dist/UI/Utils/Project.js +3 -0
  351. package/build/dist/UI/Utils/Project.js.map +1 -1
  352. package/build/dist/UI/Utils/StatusPage.js +4 -4
  353. package/build/dist/UI/Utils/StatusPage.js.map +1 -1
  354. package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
  355. package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
  356. package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
  357. package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
  358. package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
  359. package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
  360. package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
  361. package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
  362. package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
  363. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  364. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
  365. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
  366. package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
  367. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
  368. package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
  369. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  370. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
  371. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
  372. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
  373. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
  374. package/package.json +1 -1
@@ -0,0 +1,756 @@
1
+ import MoreMenu, {
2
+ ComponentProps as MoreMenuProps,
3
+ } from "../../../UI/Components/MoreMenu/MoreMenu";
4
+ import MoreMenuItem from "../../../UI/Components/MoreMenu/MoreMenuItem";
5
+ import MoreMenuSection from "../../../UI/Components/MoreMenu/MoreMenuSection";
6
+ import "@testing-library/jest-dom";
7
+ import {
8
+ cleanup,
9
+ fireEvent,
10
+ render,
11
+ screen,
12
+ waitFor,
13
+ } from "@testing-library/react";
14
+ import userEvent from "@testing-library/user-event";
15
+ import { afterEach, beforeEach, describe, jest, test } from "@jest/globals";
16
+ import React, { act, ReactElement } from "react";
17
+
18
+ jest.setTimeout(30000);
19
+
20
+ jest.mock("react-i18next", () => {
21
+ return {
22
+ useTranslation: () => {
23
+ return {
24
+ t: (key: string, options?: { defaultValue?: string }): string => {
25
+ return options?.defaultValue ?? key;
26
+ },
27
+ };
28
+ },
29
+ };
30
+ });
31
+
32
+ type TriggerVariant =
33
+ | "default"
34
+ | "custom"
35
+ | "custom-native"
36
+ | "custom-unstyled";
37
+ type OpenMethod = "click" | "Enter" | "Space";
38
+
39
+ interface MenuHarness {
40
+ trigger: HTMLElement;
41
+ outsideButton: HTMLButtonElement;
42
+ onSelect: Array<SelectionMock>;
43
+ }
44
+
45
+ type MoreMenuTestProps = Omit<MoreMenuProps, "children">;
46
+ type SelectionMock = ReturnType<typeof jest.fn>;
47
+ type UserEventController = ReturnType<typeof userEvent.setup>;
48
+
49
+ async function userClick(
50
+ user: UserEventController,
51
+ element: Element,
52
+ ): Promise<void> {
53
+ await act(async () => {
54
+ await user.click(element);
55
+ });
56
+ }
57
+
58
+ async function userKeyboard(
59
+ user: UserEventController,
60
+ keys: string,
61
+ ): Promise<void> {
62
+ await act(async () => {
63
+ await user.keyboard(keys);
64
+ });
65
+ }
66
+
67
+ async function userTab(
68
+ user: UserEventController,
69
+ shift: boolean,
70
+ ): Promise<void> {
71
+ await act(async () => {
72
+ await user.tab({ shift: shift });
73
+ });
74
+ }
75
+
76
+ const ACTION_LABELS: ReadonlyArray<string> = [
77
+ "First action",
78
+ "Second action",
79
+ "Third action",
80
+ ];
81
+
82
+ let originalRequestAnimationFrame:
83
+ | typeof window.requestAnimationFrame
84
+ | undefined;
85
+
86
+ beforeEach(() => {
87
+ originalRequestAnimationFrame = window.requestAnimationFrame;
88
+
89
+ /*
90
+ * MoreMenu restores focus on the next animation frame. JSDOM does not
91
+ * consistently expose requestAnimationFrame, so model that browser turn
92
+ * with a zero-delay timer and let waitFor observe the resulting focus.
93
+ */
94
+ Object.defineProperty(window, "requestAnimationFrame", {
95
+ configurable: true,
96
+ writable: true,
97
+ value: (callback: FrameRequestCallback): number => {
98
+ return window.setTimeout(() => {
99
+ callback(0);
100
+ }, 0);
101
+ },
102
+ });
103
+ });
104
+
105
+ afterEach(() => {
106
+ cleanup();
107
+
108
+ if (originalRequestAnimationFrame) {
109
+ Object.defineProperty(window, "requestAnimationFrame", {
110
+ configurable: true,
111
+ writable: true,
112
+ value: originalRequestAnimationFrame,
113
+ });
114
+ } else {
115
+ Reflect.deleteProperty(window, "requestAnimationFrame");
116
+ }
117
+
118
+ jest.clearAllMocks();
119
+ });
120
+
121
+ function renderMenu(
122
+ triggerVariant: TriggerVariant = "default",
123
+ props: MoreMenuTestProps = {},
124
+ useSection: boolean = false,
125
+ ): MenuHarness {
126
+ const onSelect: Array<SelectionMock> = ACTION_LABELS.map(() => {
127
+ return jest.fn();
128
+ });
129
+
130
+ const menuItems: Array<ReactElement> = ACTION_LABELS.map(
131
+ (label: string, index: number): ReactElement => {
132
+ return (
133
+ <MoreMenuItem
134
+ key={label}
135
+ text={label}
136
+ onClick={() => {
137
+ onSelect[index]!();
138
+ }}
139
+ />
140
+ );
141
+ },
142
+ );
143
+
144
+ const children: Array<ReactElement> = useSection
145
+ ? [
146
+ <MoreMenuSection key="state-changes" title="State changes">
147
+ {menuItems}
148
+ </MoreMenuSection>,
149
+ ]
150
+ : menuItems;
151
+
152
+ const menuProps: MoreMenuTestProps = {
153
+ text: "Event actions",
154
+ ...props,
155
+ };
156
+
157
+ if (triggerVariant === "custom") {
158
+ menuProps.elementToBeShownInsteadOfButton = (
159
+ <span aria-hidden="true">•••</span>
160
+ );
161
+ menuProps.triggerClassName = props.triggerClassName || "custom-trigger";
162
+ } else if (triggerVariant === "custom-native") {
163
+ menuProps.elementToBeShownInsteadOfButton = (
164
+ <button type="button" aria-label="Event actions">
165
+ •••
166
+ </button>
167
+ );
168
+ } else if (triggerVariant === "custom-unstyled") {
169
+ menuProps.elementToBeShownInsteadOfButton = (
170
+ <div aria-hidden="true">•••</div>
171
+ );
172
+ menuProps.triggerClassName = undefined;
173
+ }
174
+
175
+ render(
176
+ <div>
177
+ <MoreMenu {...menuProps}>{children}</MoreMenu>
178
+ <button type="button">Outside target</button>
179
+ </div>,
180
+ );
181
+
182
+ return {
183
+ trigger: screen.getByRole("button", {
184
+ name: menuProps.text || "More options",
185
+ }),
186
+ outsideButton: screen.getByRole("button", {
187
+ name: "Outside target",
188
+ }) as HTMLButtonElement,
189
+ onSelect,
190
+ };
191
+ }
192
+
193
+ async function openMenu(
194
+ trigger: HTMLElement,
195
+ method: OpenMethod = "click",
196
+ ): Promise<void> {
197
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
198
+
199
+ if (method === "click") {
200
+ await userClick(user, trigger);
201
+ } else {
202
+ trigger.focus();
203
+ await userKeyboard(user, method === "Enter" ? "{Enter}" : "[Space]");
204
+ }
205
+
206
+ expect(screen.getByRole("menu")).toBeInTheDocument();
207
+ }
208
+
209
+ describe("MoreMenu trigger accessibility", () => {
210
+ test("the default trigger uses the supplied accessible name", () => {
211
+ const { trigger } = renderMenu("default");
212
+
213
+ expect(trigger).toHaveAccessibleName("Event actions");
214
+ expect(trigger.tagName).toBe("BUTTON");
215
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
216
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
217
+ });
218
+
219
+ test("the default trigger falls back to 'More options' when text is omitted", () => {
220
+ const { trigger } = renderMenu("default", { text: undefined });
221
+
222
+ expect(trigger).toHaveAccessibleName("More options");
223
+ });
224
+
225
+ test("a custom trigger exposes the supplied accessible name and menu semantics", () => {
226
+ const { trigger } = renderMenu("custom");
227
+
228
+ expect(trigger).toHaveAccessibleName("Event actions");
229
+ expect(trigger.tagName).toBe("BUTTON");
230
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
231
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
232
+ });
233
+
234
+ test("a custom trigger falls back to 'More options' when text is omitted", () => {
235
+ renderMenu("custom", { text: undefined });
236
+
237
+ expect(
238
+ screen.getByRole("button", { name: "More options" }),
239
+ ).toBeInTheDocument();
240
+ });
241
+
242
+ test("triggerClassName is applied to the focusable custom trigger", () => {
243
+ const { trigger } = renderMenu("custom", {
244
+ triggerClassName: "event-actions-trigger h-9",
245
+ });
246
+
247
+ expect(trigger).toHaveClass("event-actions-trigger", "h-9");
248
+ });
249
+
250
+ test("an unstyled non-interactive custom element keeps a keyboard-operable wrapper", () => {
251
+ const { trigger } = renderMenu("custom-unstyled");
252
+
253
+ expect(trigger.tagName).toBe("DIV");
254
+ expect(trigger).toHaveAttribute("role", "button");
255
+ expect(trigger).toHaveAttribute("tabindex", "0");
256
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
257
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
258
+ });
259
+
260
+ test("an existing native custom button is enhanced without a nested trigger", () => {
261
+ const { trigger } = renderMenu("custom-native");
262
+
263
+ expect(trigger.tagName).toBe("BUTTON");
264
+ expect(trigger.parentElement).not.toHaveAttribute("role", "button");
265
+ expect(
266
+ screen.getAllByRole("button", { name: "Event actions" }),
267
+ ).toHaveLength(1);
268
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
269
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
270
+ });
271
+ });
272
+
273
+ describe.each<{
274
+ label: string;
275
+ variant: TriggerVariant;
276
+ }>([
277
+ { label: "default trigger", variant: "default" },
278
+ { label: "custom trigger", variant: "custom" },
279
+ { label: "native custom trigger", variant: "custom-native" },
280
+ { label: "unstyled custom trigger", variant: "custom-unstyled" },
281
+ ])(
282
+ "MoreMenu opening with the $label",
283
+ ({ variant }: { variant: TriggerVariant }) => {
284
+ test.each(["click", "Enter", "Space"])(
285
+ "%s opens the menu",
286
+ async (method: string) => {
287
+ const { trigger } = renderMenu(variant);
288
+
289
+ await openMenu(trigger, method as OpenMethod);
290
+
291
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
292
+ },
293
+ );
294
+
295
+ test("aria-expanded and aria-controls track the open menu", async () => {
296
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
297
+ const { trigger } = renderMenu(variant);
298
+
299
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
300
+ expect(trigger).not.toHaveAttribute("aria-controls");
301
+
302
+ await userClick(user, trigger);
303
+
304
+ const menu: HTMLElement = screen.getByRole("menu");
305
+
306
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
307
+ expect(trigger).toHaveAttribute("aria-controls", menu.id);
308
+ expect(menu).toHaveAttribute("aria-labelledby", trigger.id);
309
+
310
+ await userClick(user, trigger);
311
+
312
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
313
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
314
+ expect(trigger).not.toHaveAttribute("aria-controls");
315
+ });
316
+ },
317
+ );
318
+
319
+ describe("MoreMenu keyboard navigation", () => {
320
+ test("opening focuses the first item and ArrowDown/ArrowUp move and wrap focus", async () => {
321
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
322
+ const { trigger } = renderMenu();
323
+
324
+ await openMenu(trigger);
325
+
326
+ const items: Array<HTMLElement> = screen.getAllByRole("menuitem");
327
+
328
+ await waitFor(() => {
329
+ expect(items[0]).toHaveFocus();
330
+ });
331
+
332
+ await userKeyboard(user, "{ArrowDown}");
333
+ expect(items[1]).toHaveFocus();
334
+
335
+ await userKeyboard(user, "{ArrowDown}");
336
+ expect(items[2]).toHaveFocus();
337
+
338
+ await userKeyboard(user, "{ArrowDown}");
339
+ expect(items[0]).toHaveFocus();
340
+
341
+ await userKeyboard(user, "{ArrowUp}");
342
+ expect(items[2]).toHaveFocus();
343
+ });
344
+
345
+ test("Home and End jump to the first and last items", async () => {
346
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
347
+ const { trigger } = renderMenu();
348
+
349
+ await openMenu(trigger);
350
+
351
+ const items: Array<HTMLElement> = screen.getAllByRole("menuitem");
352
+
353
+ await waitFor(() => {
354
+ expect(items[0]).toHaveFocus();
355
+ });
356
+
357
+ await userKeyboard(user, "{End}");
358
+ expect(items[2]).toHaveFocus();
359
+
360
+ await userKeyboard(user, "{Home}");
361
+ expect(items[0]).toHaveFocus();
362
+ });
363
+
364
+ test("section labels and dividers are skipped while nested actions receive roving focus", async () => {
365
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
366
+ const { trigger } = renderMenu("default", {}, true);
367
+
368
+ await openMenu(trigger);
369
+
370
+ const items: Array<HTMLElement> = screen.getAllByRole("menuitem");
371
+
372
+ expect(items).toHaveLength(ACTION_LABELS.length);
373
+ expect(
374
+ items.map((item: HTMLElement) => {
375
+ return item.textContent;
376
+ }),
377
+ ).toEqual(ACTION_LABELS);
378
+ items.forEach((item: HTMLElement) => {
379
+ expect(item.querySelector('[role="menuitem"]')).toBeNull();
380
+ });
381
+ expect(screen.getByText("STATE CHANGES")).not.toHaveAttribute(
382
+ "role",
383
+ "menuitem",
384
+ );
385
+
386
+ await waitFor(() => {
387
+ expect(items[0]).toHaveFocus();
388
+ });
389
+
390
+ await userKeyboard(user, "{ArrowDown}");
391
+ expect(items[1]).toHaveFocus();
392
+
393
+ await userKeyboard(user, "{End}");
394
+ expect(items[2]).toHaveFocus();
395
+
396
+ await userKeyboard(user, "{ArrowDown}");
397
+ expect(items[0]).toHaveFocus();
398
+ });
399
+
400
+ test("an established custom child button remains keyboard reachable and dismisses the menu", async () => {
401
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
402
+ const customAction: SelectionMock = jest.fn();
403
+
404
+ render(
405
+ <MoreMenu text="Custom children">
406
+ {[
407
+ <div key="custom-wrapper" className="px-2 py-1">
408
+ <button type="button" onClick={customAction}>
409
+ Custom child action
410
+ </button>
411
+ </div>,
412
+ ]}
413
+ </MoreMenu>,
414
+ );
415
+
416
+ const trigger: HTMLElement = screen.getByRole("button", {
417
+ name: "Custom children",
418
+ });
419
+ await openMenu(trigger);
420
+
421
+ const customItem: HTMLElement = await screen.findByRole("menuitem", {
422
+ name: "Custom child action",
423
+ });
424
+ await waitFor(() => {
425
+ expect(customItem).toHaveFocus();
426
+ });
427
+
428
+ await userKeyboard(user, "{Enter}");
429
+
430
+ expect(customAction).toHaveBeenCalledTimes(1);
431
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
432
+ });
433
+
434
+ test("navigation keys prevent their native scrolling behavior", async () => {
435
+ const { trigger } = renderMenu();
436
+
437
+ await openMenu(trigger);
438
+
439
+ const items: Array<HTMLElement> = screen.getAllByRole("menuitem");
440
+
441
+ await waitFor(() => {
442
+ expect(items[0]).toHaveFocus();
443
+ });
444
+
445
+ expect(fireEvent.keyDown(items[0]!, { key: "ArrowDown" })).toBe(false);
446
+ expect(items[1]).toHaveFocus();
447
+
448
+ expect(fireEvent.keyDown(items[1]!, { key: "End" })).toBe(false);
449
+ expect(items[2]).toHaveFocus();
450
+
451
+ expect(fireEvent.keyDown(items[2]!, { key: "Home" })).toBe(false);
452
+ expect(items[0]).toHaveFocus();
453
+ });
454
+
455
+ test("Escape closes the menu and restores focus to the trigger", async () => {
456
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
457
+ const { trigger } = renderMenu();
458
+
459
+ await openMenu(trigger);
460
+ await waitFor(() => {
461
+ expect(screen.getAllByRole("menuitem")[0]).toHaveFocus();
462
+ });
463
+
464
+ await userKeyboard(user, "{Escape}");
465
+
466
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
467
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
468
+ await waitFor(() => {
469
+ expect(trigger).toHaveFocus();
470
+ });
471
+ });
472
+
473
+ test("Escape closes an empty loading menu and restores focus", async () => {
474
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
475
+
476
+ render(
477
+ <MoreMenu text="Loading actions">
478
+ {[<div key="loader">Loading…</div>]}
479
+ </MoreMenu>,
480
+ );
481
+
482
+ const trigger: HTMLElement = screen.getByRole("button", {
483
+ name: "Loading actions",
484
+ });
485
+
486
+ await userClick(user, trigger);
487
+ expect(screen.getByRole("menu")).toBeInTheDocument();
488
+ expect(screen.queryByRole("menuitem")).not.toBeInTheDocument();
489
+
490
+ await userKeyboard(user, "{Escape}");
491
+
492
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
493
+ await waitFor(() => {
494
+ expect(trigger).toHaveFocus();
495
+ });
496
+ });
497
+
498
+ test("items that replace an open loading state join roving focus immediately", async () => {
499
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
500
+ const firstAction: SelectionMock = jest.fn();
501
+ const secondAction: SelectionMock = jest.fn();
502
+ const { rerender } = render(
503
+ <MoreMenu text="Async actions">
504
+ {[<div key="loader">Loading…</div>]}
505
+ </MoreMenu>,
506
+ );
507
+
508
+ const trigger: HTMLElement = screen.getByRole("button", {
509
+ name: "Async actions",
510
+ });
511
+ await userClick(user, trigger);
512
+
513
+ rerender(
514
+ <MoreMenu text="Async actions">
515
+ {[
516
+ <MoreMenuItem
517
+ key="first"
518
+ text="First loaded action"
519
+ onClick={firstAction}
520
+ />,
521
+ <MoreMenuItem
522
+ key="second"
523
+ text="Second loaded action"
524
+ onClick={secondAction}
525
+ />,
526
+ ]}
527
+ </MoreMenu>,
528
+ );
529
+
530
+ const loadedItems: Array<HTMLElement> =
531
+ await screen.findAllByRole("menuitem");
532
+ await waitFor(() => {
533
+ expect(loadedItems[0]).toHaveFocus();
534
+ });
535
+
536
+ await userKeyboard(user, "{ArrowDown}{Enter}");
537
+
538
+ expect(firstAction).not.toHaveBeenCalled();
539
+ expect(secondAction).toHaveBeenCalledTimes(1);
540
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
541
+ });
542
+
543
+ test.each([
544
+ { direction: "forward", shift: false },
545
+ { direction: "backward", shift: true },
546
+ ])(
547
+ "Tab $direction dismisses the menu and follows the native focus order",
548
+ async ({ shift }: { shift: boolean }) => {
549
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
550
+ const { trigger, outsideButton } = renderMenu();
551
+
552
+ await openMenu(trigger);
553
+ await waitFor(() => {
554
+ expect(screen.getAllByRole("menuitem")[0]).toHaveFocus();
555
+ });
556
+
557
+ await userTab(user, shift);
558
+
559
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
560
+ expect(shift ? trigger : outsideButton).toHaveFocus();
561
+ },
562
+ );
563
+
564
+ test("disabled items are skipped by roving focus and cannot be activated", async () => {
565
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
566
+ const disabledAction: SelectionMock = jest.fn();
567
+ const enabledAction: SelectionMock = jest.fn();
568
+
569
+ render(
570
+ <MoreMenu text="Mixed actions">
571
+ {[
572
+ <MoreMenuItem
573
+ key="disabled"
574
+ text="Disabled action"
575
+ isDisabled={true}
576
+ onClick={disabledAction}
577
+ />,
578
+ <MoreMenuItem
579
+ key="enabled"
580
+ text="Enabled action"
581
+ onClick={enabledAction}
582
+ />,
583
+ ]}
584
+ </MoreMenu>,
585
+ );
586
+
587
+ const trigger: HTMLElement = screen.getByRole("button", {
588
+ name: "Mixed actions",
589
+ });
590
+ await openMenu(trigger);
591
+
592
+ const disabledItem: HTMLElement = screen.getByRole("menuitem", {
593
+ name: "Disabled action",
594
+ });
595
+ const enabledItem: HTMLElement = screen.getByRole("menuitem", {
596
+ name: "Enabled action",
597
+ });
598
+
599
+ expect(disabledItem).toBeDisabled();
600
+ expect(disabledItem).toHaveAttribute("tabindex", "-1");
601
+ await waitFor(() => {
602
+ expect(enabledItem).toHaveFocus();
603
+ });
604
+
605
+ fireEvent.click(disabledItem);
606
+ expect(disabledAction).not.toHaveBeenCalled();
607
+ expect(screen.getByRole("menu")).toBeInTheDocument();
608
+ expect(enabledItem).toHaveFocus();
609
+
610
+ await userKeyboard(user, "{Enter}");
611
+ expect(enabledAction).toHaveBeenCalledTimes(1);
612
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
613
+ });
614
+ });
615
+
616
+ describe("MoreMenu selection and dismissal", () => {
617
+ test("clicking an item runs its action exactly once and closes the menu", async () => {
618
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
619
+ const { trigger, onSelect } = renderMenu();
620
+
621
+ await openMenu(trigger);
622
+ await userClick(user, screen.getByText("Second action"));
623
+
624
+ expect(onSelect[0]).not.toHaveBeenCalled();
625
+ expect(onSelect[1]).toHaveBeenCalledTimes(1);
626
+ expect(onSelect[2]).not.toHaveBeenCalled();
627
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
628
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
629
+ });
630
+
631
+ test.each([
632
+ { key: "Enter", keySequence: "{Enter}", useSection: false },
633
+ { key: "Space", keySequence: "[Space]", useSection: true },
634
+ ])(
635
+ "$key invokes the exact focused action once and closes the menu",
636
+ async ({
637
+ keySequence,
638
+ useSection,
639
+ }: {
640
+ keySequence: string;
641
+ useSection: boolean;
642
+ }) => {
643
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
644
+ const { trigger, onSelect } = renderMenu("default", {}, useSection);
645
+
646
+ await openMenu(trigger);
647
+ await userKeyboard(user, "{ArrowDown}");
648
+ expect(screen.getAllByRole("menuitem")[1]).toHaveFocus();
649
+
650
+ await userKeyboard(user, keySequence);
651
+
652
+ expect(onSelect[0]).not.toHaveBeenCalled();
653
+ expect(onSelect[1]).toHaveBeenCalledTimes(1);
654
+ expect(onSelect[2]).not.toHaveBeenCalled();
655
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
656
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
657
+ },
658
+ );
659
+
660
+ test("an outside click closes the menu without selecting or stealing focus", async () => {
661
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
662
+ const { trigger, outsideButton, onSelect } = renderMenu();
663
+
664
+ await openMenu(trigger);
665
+ await userClick(user, outsideButton);
666
+
667
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
668
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
669
+ expect(outsideButton).toHaveFocus();
670
+ onSelect.forEach((callback: SelectionMock) => {
671
+ expect(callback).not.toHaveBeenCalled();
672
+ });
673
+ });
674
+ });
675
+
676
+ describe("MoreMenu disabled contract", () => {
677
+ test("disabling an open menu closes it immediately", async () => {
678
+ const action: SelectionMock = jest.fn();
679
+ const children: Array<ReactElement> = [
680
+ <MoreMenuItem key="action" text="Action" onClick={action} />,
681
+ ];
682
+ const { rerender } = render(
683
+ <MoreMenu text="Dynamic actions">{children}</MoreMenu>,
684
+ );
685
+
686
+ const trigger: HTMLElement = screen.getByRole("button", {
687
+ name: "Dynamic actions",
688
+ });
689
+ await openMenu(trigger);
690
+
691
+ rerender(
692
+ <MoreMenu text="Dynamic actions" isDisabled={true}>
693
+ {children}
694
+ </MoreMenu>,
695
+ );
696
+
697
+ await waitFor(() => {
698
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
699
+ });
700
+ expect(trigger).toBeDisabled();
701
+ expect(action).not.toHaveBeenCalled();
702
+ });
703
+
704
+ test("the disabled default trigger is a disabled native button and cannot open", async () => {
705
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
706
+ const { trigger } = renderMenu("default", { isDisabled: true });
707
+
708
+ expect(trigger).toBeDisabled();
709
+ expect(trigger).toHaveAttribute("aria-disabled", "true");
710
+
711
+ await userClick(user, trigger);
712
+
713
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
714
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
715
+ });
716
+
717
+ test.each([
718
+ { label: "styled custom", variant: "custom" as TriggerVariant },
719
+ {
720
+ label: "existing native custom",
721
+ variant: "custom-native" as TriggerVariant,
722
+ },
723
+ ])(
724
+ "the disabled $label trigger ignores click, Enter, and Space",
725
+ async ({ variant }: { variant: TriggerVariant }) => {
726
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
727
+ const { trigger } = renderMenu(variant, { isDisabled: true });
728
+
729
+ expect(trigger).toBeDisabled();
730
+
731
+ await userClick(user, trigger);
732
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
733
+
734
+ trigger.focus();
735
+ await userKeyboard(user, "{Enter}[Space]");
736
+
737
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
738
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
739
+ },
740
+ );
741
+
742
+ test("a disabled unstyled custom trigger is aria-disabled, leaves the tab order, and cannot open", async () => {
743
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
744
+ const { trigger } = renderMenu("custom-unstyled", { isDisabled: true });
745
+
746
+ expect(trigger).toHaveAttribute("aria-disabled", "true");
747
+ expect(trigger).toHaveAttribute("tabindex", "-1");
748
+
749
+ await userClick(user, trigger);
750
+ fireEvent.keyDown(trigger, { key: "Enter" });
751
+ fireEvent.keyDown(trigger, { key: " " });
752
+
753
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
754
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
755
+ });
756
+ });