@oneuptime/common 14.0.7 → 14.0.9

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 (1064) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/Index.ts +2 -0
  6. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  7. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  8. package/Models/DatabaseModels/Project.ts +55 -5
  9. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  10. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  11. package/Models/DatabaseModels/StatusPage.ts +55 -0
  12. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  13. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  14. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  15. package/Server/API/AIChatAPI.ts +6 -3
  16. package/Server/API/AIInvestigationAPI.ts +17 -10
  17. package/Server/API/AutoRemediationAPI.ts +207 -25
  18. package/Server/API/IncidentAPI.ts +162 -0
  19. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  20. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  21. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  22. package/Server/API/StatusPageAPI.ts +487 -171
  23. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  24. package/Server/Infrastructure/GlobalCache.ts +32 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  36. package/Server/Infrastructure/Semaphore.ts +10 -0
  37. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  38. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  39. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  40. package/Server/Middleware/TelemetryIngest.ts +41 -18
  41. package/Server/Services/AIService.ts +70 -1
  42. package/Server/Services/AlertService.ts +352 -11
  43. package/Server/Services/AlertSeverityService.ts +65 -15
  44. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  45. package/Server/Services/DatabaseService.ts +93 -10
  46. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  47. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  48. package/Server/Services/IncidentEpisodeService.ts +15 -0
  49. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  50. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  51. package/Server/Services/IncidentService.ts +1788 -142
  52. package/Server/Services/IncidentSeverityService.ts +66 -15
  53. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  54. package/Server/Services/IncidentTemplateService.ts +109 -0
  55. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  56. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  57. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  58. package/Server/Services/KubernetesClusterService.ts +129 -21
  59. package/Server/Services/ProjectService.ts +32 -0
  60. package/Server/Services/ProxmoxResourceService.ts +322 -5
  61. package/Server/Services/RunnerJobService.ts +241 -104
  62. package/Server/Services/RunnerService.ts +1 -1
  63. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  64. package/Server/Services/ScheduledMaintenanceService.ts +805 -19
  65. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  66. package/Server/Services/SmsService.ts +10 -0
  67. package/Server/Services/StatusPageService.ts +65 -8
  68. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  69. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  70. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  71. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  72. package/Server/Types/Markdown.ts +153 -6
  73. package/Server/Types/Workflow/Components/Email.ts +61 -2
  74. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  75. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  76. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  77. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  78. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  79. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  80. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  81. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  82. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  83. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  84. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  85. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  86. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  87. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  88. package/Server/Utils/SSRFProtection.ts +76 -16
  89. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  90. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  91. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  92. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  93. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  94. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  95. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  96. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  97. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  98. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  99. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  100. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  101. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  102. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  103. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  104. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  105. package/Server/Utils/StatusPageResource.ts +51 -20
  106. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  107. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  108. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  109. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  110. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  111. package/Server/Utils/VM/VMAPI.ts +1 -0
  112. package/Server/Utils/VM/VMRunner.ts +10 -0
  113. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  114. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  115. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  116. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  117. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  118. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  119. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  120. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  121. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  122. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  123. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  124. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  125. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  126. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  127. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  128. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  129. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  130. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  131. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  132. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  133. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  134. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  135. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  136. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  137. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  138. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  139. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  140. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  141. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  142. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  143. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  144. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  145. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  146. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  147. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  148. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  149. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  150. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  151. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  152. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  153. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  154. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  155. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  156. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  157. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  158. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  159. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  160. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  161. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  162. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  163. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  164. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  165. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  166. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  167. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  168. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  169. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  170. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  171. package/Tests/App/Dashboard/EventNotes.test.tsx +778 -2
  172. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  173. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  174. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +828 -17
  175. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  176. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  177. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  178. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  179. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  180. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  181. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  182. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  183. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  184. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  185. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  186. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  187. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  188. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  189. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  190. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  191. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  192. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  193. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  194. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  195. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  196. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  197. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  198. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  199. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  200. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  201. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  202. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  203. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  204. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  205. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  206. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  207. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  208. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  209. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  210. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  211. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  212. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  213. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  214. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  215. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  216. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  217. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  218. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  219. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  220. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  221. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  222. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  223. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  224. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  225. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  226. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  227. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  228. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  229. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  230. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  231. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  232. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  233. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  234. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  235. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  236. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  237. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  238. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  239. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  240. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  241. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  242. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  243. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  244. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  245. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  246. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  247. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  248. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  249. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  250. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  251. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  252. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  253. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  254. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  255. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  256. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  257. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  258. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  259. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  260. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  261. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  262. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  263. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  264. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  265. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  266. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  267. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  268. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  269. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  270. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  271. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  272. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  273. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  274. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  275. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  276. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  277. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  278. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  279. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  280. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  281. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  282. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  283. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  284. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  285. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  286. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  287. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  288. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  289. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  290. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  291. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  292. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  293. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  294. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  295. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  296. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  297. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  298. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  299. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  300. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  301. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  302. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  303. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  304. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  305. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  306. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  307. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  308. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  309. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  310. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  311. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  312. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  313. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  314. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  315. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  316. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  317. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  318. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  319. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  320. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  321. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  322. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  323. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  324. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  325. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  326. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  327. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  328. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  329. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  330. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  331. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  332. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  333. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  334. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  335. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  336. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  337. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  338. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  339. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  340. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  341. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  342. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  343. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  344. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  345. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  346. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  347. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  348. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  349. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  350. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  351. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  352. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  353. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  354. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  355. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  356. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  357. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  358. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  359. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  360. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  361. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  362. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  363. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  364. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  365. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  366. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  367. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  368. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  369. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  370. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  371. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  372. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  373. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  374. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  375. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  376. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  377. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  378. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  379. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  380. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  381. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  382. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  383. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  384. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  385. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  386. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  387. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  388. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  389. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  390. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  391. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  392. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  393. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  394. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  395. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  396. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  397. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  398. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  399. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  400. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  401. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  402. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  403. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  404. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  405. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  406. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  407. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  408. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  409. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  410. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  411. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  412. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  413. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  414. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  415. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  416. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  417. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  418. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  419. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  420. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  421. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  422. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  423. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  424. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  425. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  426. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  427. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  428. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  429. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  430. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  431. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  432. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  433. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  434. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  435. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  436. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  437. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  438. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  439. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  440. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  441. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  442. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  443. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  444. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  445. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  446. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  447. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  448. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  449. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  450. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  451. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  452. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  453. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  454. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  455. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  456. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  457. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  458. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  459. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  460. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  461. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  462. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  463. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  464. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  465. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  466. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  467. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  468. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  469. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  470. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  471. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  472. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  473. package/Tests/Types/SafeHtml.test.ts +102 -0
  474. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  475. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  476. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  477. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  478. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  479. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  480. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  481. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  482. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  483. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  484. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  485. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  486. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  487. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  488. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  489. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  490. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  491. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  492. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  493. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  494. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  495. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  496. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  497. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  498. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  499. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  500. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  501. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  502. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  503. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  504. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  505. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  506. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  507. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  508. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  509. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  510. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  511. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  512. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  513. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  514. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  515. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  516. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  517. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  518. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  519. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  520. package/Tests/UI/Components/Input.test.tsx +80 -1
  521. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  522. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  523. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  524. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  525. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  526. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  527. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  528. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  529. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  530. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  531. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  532. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  533. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  534. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  535. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  536. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  537. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  538. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  539. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  540. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  541. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  542. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  543. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  544. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  545. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  546. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  547. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  548. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  549. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  550. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  551. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  552. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  553. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  554. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  555. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  556. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  557. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  558. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  559. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  560. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  561. package/Types/AI/AIRunType.ts +9 -4
  562. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  563. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  564. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  565. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  566. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  567. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  568. package/Types/CustomField/CustomFieldType.ts +14 -0
  569. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  570. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  571. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  572. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  573. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  574. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  575. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  576. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  577. package/Types/SafeHtml.ts +93 -0
  578. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  579. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  580. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  581. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  582. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  583. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  584. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  585. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  586. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  587. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  588. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  589. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  590. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  591. package/Types/Team/ComplianceRule.ts +434 -0
  592. package/Types/Team/ComplianceRuleType.ts +21 -0
  593. package/Types/Team/TeamComplianceStatus.ts +81 -0
  594. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  595. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  596. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  597. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  598. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  599. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  600. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  601. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  602. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  603. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  604. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  605. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  606. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  607. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  608. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  609. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  610. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  611. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  612. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  613. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  614. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  615. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  616. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  617. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  618. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  619. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  620. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  621. package/UI/Components/Forms/ModelForm.tsx +70 -8
  622. package/UI/Components/Forms/Validation.ts +1 -1
  623. package/UI/Components/Input/Input.tsx +90 -57
  624. package/UI/Components/List/ListRow.tsx +8 -74
  625. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  626. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  627. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  628. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  629. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  630. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  631. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  632. package/UI/Components/ModelTable/BaseModelTable.tsx +21 -9
  633. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  634. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  635. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  636. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  637. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  638. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  639. package/UI/Components/Table/TableBody.tsx +8 -2
  640. package/UI/Components/Table/TableRow.tsx +20 -140
  641. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  642. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  643. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  644. package/UI/Components/TelemetryViewer/types.ts +2 -0
  645. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  646. package/UI/Components/Toggle/Toggle.tsx +26 -0
  647. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  648. package/UI/Styles/Theme.css +14 -2
  649. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  650. package/Utils/API.ts +247 -2
  651. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  652. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  653. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  654. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  655. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  656. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  657. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  658. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  659. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  660. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  661. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  662. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  663. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  664. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  665. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  666. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  667. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  668. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  669. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  670. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  671. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  672. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  673. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  674. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  675. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  676. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  677. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  678. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  679. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  680. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  681. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  682. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  683. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  684. package/build/dist/Server/API/AIChatAPI.js +6 -1
  685. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  686. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  687. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  688. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  689. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  690. package/build/dist/Server/API/IncidentAPI.js +100 -1
  691. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  692. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  693. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  694. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  695. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  696. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  697. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  698. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  699. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  700. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  701. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  702. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  703. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  704. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  705. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  706. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  707. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  708. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  709. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  710. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  711. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  712. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  713. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  714. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  715. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  716. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  717. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  718. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  719. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  720. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  721. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  722. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  723. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  724. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  725. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  726. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  727. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  728. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  729. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  730. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  731. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  732. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  733. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  734. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  735. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  736. package/build/dist/Server/Services/AIService.js +59 -1
  737. package/build/dist/Server/Services/AIService.js.map +1 -1
  738. package/build/dist/Server/Services/AlertService.js +248 -13
  739. package/build/dist/Server/Services/AlertService.js.map +1 -1
  740. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  741. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  742. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  743. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  744. package/build/dist/Server/Services/DatabaseService.js +56 -7
  745. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  746. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  747. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  748. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  749. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  750. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  751. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  752. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  753. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  754. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  755. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  756. package/build/dist/Server/Services/IncidentService.js +1251 -72
  757. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  758. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  759. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  760. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  761. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  762. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  763. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  764. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  765. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  766. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  767. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  768. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  769. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  770. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  771. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  772. package/build/dist/Server/Services/ProjectService.js +22 -0
  773. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  774. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  775. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  776. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  777. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  778. package/build/dist/Server/Services/RunnerService.js +1 -1
  779. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  780. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  781. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  782. package/build/dist/Server/Services/ScheduledMaintenanceService.js +582 -20
  783. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  784. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  785. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  786. package/build/dist/Server/Services/SmsService.js +2 -22
  787. package/build/dist/Server/Services/SmsService.js.map +1 -1
  788. package/build/dist/Server/Services/StatusPageService.js +47 -9
  789. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  790. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  791. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  792. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  793. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  794. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  795. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  796. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  797. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  798. package/build/dist/Server/Types/Markdown.js +110 -6
  799. package/build/dist/Server/Types/Markdown.js.map +1 -1
  800. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  801. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  802. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  803. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  804. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  805. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  806. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  807. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  808. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  809. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  810. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  811. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  812. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  813. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  814. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  815. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  816. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  817. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  818. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  819. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  820. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  821. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  822. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  823. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  824. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  825. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  826. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  827. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  828. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  829. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  830. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  831. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  832. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  833. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  834. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  835. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  836. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  837. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  838. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  839. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  840. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  841. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  842. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  843. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  844. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  845. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  846. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  847. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  848. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  849. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  850. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  851. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  852. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  853. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  854. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  855. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  856. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  857. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  858. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  859. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  860. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  861. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  862. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  863. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  864. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  865. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  866. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  867. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  868. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  869. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  870. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  871. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  872. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  873. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  874. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  875. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  876. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  877. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  878. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  879. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  880. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  881. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  882. package/build/dist/Types/AI/AIRunType.js +9 -4
  883. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  884. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  885. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  886. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  887. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  888. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  889. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  890. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  891. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  892. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  893. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  894. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  895. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  896. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  897. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  898. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  899. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  900. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  901. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  902. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  903. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  904. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  905. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  906. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  907. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  908. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  909. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  910. package/build/dist/Types/SafeHtml.js +84 -0
  911. package/build/dist/Types/SafeHtml.js.map +1 -0
  912. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  913. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  914. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  915. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  916. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  917. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  918. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  919. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  920. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  921. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  922. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  923. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  924. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  925. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  926. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  927. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  928. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  929. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  930. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  931. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  932. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  933. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  934. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  935. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  936. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  937. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  938. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  939. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  940. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  941. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  942. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  943. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  944. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  945. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  946. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  947. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  948. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  949. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  950. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  951. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  952. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  953. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  954. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  955. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  956. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  957. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  958. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  959. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  960. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  961. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  962. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  963. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  964. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  965. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  966. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  967. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  968. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  969. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  970. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  971. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  972. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  973. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  974. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  975. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  976. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  977. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  978. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  979. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  980. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  981. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  982. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  983. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  984. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  985. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  986. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  987. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  988. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  989. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  990. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  991. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  992. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  993. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  994. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  995. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  996. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  997. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  998. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  999. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  1000. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  1001. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  1002. package/build/dist/UI/Components/Input/Input.js +69 -54
  1003. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  1004. package/build/dist/UI/Components/List/ListRow.js +4 -39
  1005. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  1006. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  1007. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  1008. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  1009. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  1010. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  1011. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  1012. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  1013. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  1014. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  1015. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  1016. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  1017. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  1018. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -2
  1019. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  1020. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  1021. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  1022. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  1023. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  1024. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  1025. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  1026. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  1027. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  1028. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  1029. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  1030. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  1031. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  1032. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  1033. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  1034. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  1035. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  1036. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  1037. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1038. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  1039. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  1040. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  1041. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  1042. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  1043. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  1044. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  1045. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  1046. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  1047. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  1048. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  1049. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  1050. package/build/dist/Utils/API.js +177 -1
  1051. package/build/dist/Utils/API.js.map +1 -1
  1052. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  1053. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  1054. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  1055. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  1056. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  1057. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  1058. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  1059. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  1060. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  1061. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  1062. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  1063. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  1064. package/package.json +1 -1
@@ -0,0 +1,1861 @@
1
+ /** @timezone UTC */
2
+
3
+ import "@testing-library/jest-dom";
4
+ import {
5
+ afterEach,
6
+ beforeEach,
7
+ describe,
8
+ expect,
9
+ jest,
10
+ test,
11
+ } from "@jest/globals";
12
+ import {
13
+ RenderResult,
14
+ act,
15
+ cleanup,
16
+ fireEvent,
17
+ render,
18
+ screen,
19
+ waitFor,
20
+ } from "@testing-library/react";
21
+ import * as React from "react";
22
+ import getJestMockFunction, { MockFunction } from "../../../MockType";
23
+
24
+ /*
25
+ * Issue #4116, the placeholders (commits 7f6df1c28c and b6ec899df4). Right
26
+ * after a zoom a chart often has no bars to double-click: the zoomed window
27
+ * is still loading, or it turned out to hold nothing. That is exactly when
28
+ * a reader double-clicks to undo the zoom, so the loader and the empty box
29
+ * standing where the bars were take the double-click as the bars do - and
30
+ * are select-none, so the double-click does not also select a word of them.
31
+ *
32
+ * The bars can also land in the middle of that double-click - the #4116
33
+ * race once more - and take the placeholder's place between its second
34
+ * press and that press's release. The browser then sends the chart no
35
+ * dblclick: the node the press went down on is gone, or React kept it for
36
+ * the chart without the placeholder's handlers, and a dblclick sent to it
37
+ * bubbles away from the chart's own. So a placeholder takes the second
38
+ * press itself (placeholderProps): its release - over the chart that took
39
+ * the placeholder's place, or anywhere on the page - zooms out in the next
40
+ * task, once, and a dblclick that does reach the chart still zooms out only
41
+ * once. Only a main-button press on a zoomed chart counts.
42
+ *
43
+ * - The explorers' volume histograms (TelemetryHistogram - traces,
44
+ * exceptions, security events - and LogsHistogram): a zoomed chart's
45
+ * loader zooms out; a zoomed chart whose window is empty keeps its card,
46
+ * and its empty box zooms out. A zoomed chart keeps its card - title,
47
+ * the way back - while its window loads or reloads too, the loader in
48
+ * the box the bars will take, and says nothing of zooming in ("Click or
49
+ * drag to zoom", "Drag to zoom") while it has no bars. Unzoomed nothing
50
+ * changes: the loader is a bare box that ignores the double-click, and an
51
+ * empty histogram with no header controls renders nothing. A click on a
52
+ * bar still waiting out the double-click window when the bars go away
53
+ * does not zoom in once a double-click on the placeholder zooms out.
54
+ * - The log and trace analytics views swap their chart for a loader on
55
+ * every refetch. The timeseries' loader zooms out, through the chart's
56
+ * own selection, so a click on a bucket still waiting to zoom in when the
57
+ * loader came up zooms nowhere; the top list's and the table's loaders do
58
+ * not zoom out - they are no time charts, even when a host hands the view
59
+ * zoom handlers of its own.
60
+ * - An exception's Occurrence Trend clears its bars for a zoom's refetch;
61
+ * the loader in their place goes back to the preset window.
62
+ *
63
+ * Before commit b6ec899df4, every case in which the chart takes a
64
+ * placeholder's place mid-double-click stayed zoomed, a zoomed histogram
65
+ * with no bars offered "Click or drag to zoom", and one loading its window
66
+ * - its first bars, or an empty window again - stood as the bare loader
67
+ * box, its card gone.
68
+ *
69
+ * Everything is real, recharts included - only ResponsiveContainer is
70
+ * stood in for, as in HistogramDoubleClickResetRealRecharts.test.tsx, since
71
+ * jsdom lays nothing out - down to the data boundary: API and ModelAPI are
72
+ * mocked as in LogsAnalyticsViewZoom.test.tsx, TracesAnalyticsViewZoom.test.tsx
73
+ * and ExceptionOccurrenceTrendZoom.test.tsx, and a request can be held
74
+ * open to keep a loader on screen.
75
+ */
76
+
77
+ const postMock: MockFunction = getJestMockFunction();
78
+
79
+ jest.mock("../../../../UI/Utils/API/API", () => {
80
+ return {
81
+ __esModule: true,
82
+ default: {
83
+ post: (...args: Array<any>) => {
84
+ return postMock(...args);
85
+ },
86
+ getFriendlyMessage: (error: Error) => {
87
+ return error?.message || "Failed";
88
+ },
89
+ },
90
+ };
91
+ });
92
+
93
+ jest.mock("../../../../UI/Utils/ModelAPI/ModelAPI", () => {
94
+ return {
95
+ __esModule: true,
96
+ default: {
97
+ getCommonHeaders: () => {
98
+ return {};
99
+ },
100
+ },
101
+ };
102
+ });
103
+
104
+ jest.mock("../../../../UI/Utils/Telemetry/UseTelemetryEntityNames", () => {
105
+ // One map for every render: the views memoise their series on it.
106
+ const noNames: Map<string, string> = new Map();
107
+ return {
108
+ __esModule: true,
109
+ default: () => {
110
+ return noNames;
111
+ },
112
+ };
113
+ });
114
+
115
+ jest.mock("recharts", () => {
116
+ const actual: Record<string, unknown> = jest.requireActual(
117
+ "recharts",
118
+ ) as Record<string, unknown>;
119
+ const react: typeof React = jest.requireActual("react") as typeof React;
120
+ return {
121
+ ...actual,
122
+ ResponsiveContainer: ({ children }: { children: React.ReactElement }) => {
123
+ return react.cloneElement(children, { width: 600, height: 300 });
124
+ },
125
+ };
126
+ });
127
+
128
+ // Imported after the mocks so the components pick them up.
129
+ import LogsHistogram from "../../../../UI/Components/LogsViewer/components/LogsHistogram";
130
+ import { HistogramBucket as LogsHistogramBucket } from "../../../../UI/Components/LogsViewer/types";
131
+ import TelemetryHistogram from "../../../../UI/Components/TelemetryViewer/components/TelemetryHistogram";
132
+ import {
133
+ HistogramBucket as TelemetryHistogramBucket,
134
+ HistogramSeriesOption,
135
+ } from "../../../../UI/Components/TelemetryViewer/types";
136
+ import LogsAnalyticsView, {
137
+ LOGS_ANALYTICS_TIMESERIES_TEST_ID,
138
+ } from "../../../../UI/Components/LogsViewer/components/LogsAnalyticsView";
139
+ import TracesAnalyticsView, {
140
+ TRACES_ANALYTICS_TIMESERIES_TEST_ID,
141
+ } from "../../../../../App/FeatureSet/Dashboard/src/Components/Traces/TracesAnalyticsView";
142
+ import ExceptionOccurrenceTrend from "../../../../../App/FeatureSet/Dashboard/src/Components/Exceptions/ExceptionOccurrenceTrend";
143
+ import { DOUBLE_CLICK_DISAMBIGUATION_MS } from "../../../../UI/Components/Charts/ChartLibrary/Utils/DoubleClick";
144
+ import { RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID } from "../../../../UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton";
145
+ import { TimeRangeZoomProvider } from "../../../../UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext";
146
+ import { TimeRangeZoom } from "../../../../UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom";
147
+ import InBetween from "../../../../Types/BaseDatabase/InBetween";
148
+ import { JSONObject } from "../../../../Types/JSON";
149
+ import ObjectID from "../../../../Types/ObjectID";
150
+ import RangeStartAndEndDateTime from "../../../../Types/Time/RangeStartAndEndDateTime";
151
+ import TimeRange from "../../../../Types/Time/TimeRange";
152
+
153
+ const NOW: Date = new Date("2026-09-28T12:00:00.000Z");
154
+ const MINUTE_MS: number = 60 * 1000;
155
+ const DAY_MS: number = 24 * 60 * MINUTE_MS;
156
+ // One animation frame, as fake timers run them.
157
+ const FRAME_MS: number = 16;
158
+ // Inside every plot below: under its top edge and above its x-axis.
159
+ const POINTER_Y: number = 120;
160
+
161
+ type SelectHandler = (startTime: Date, endTime: Date) => void;
162
+
163
+ /* Pointer and page */
164
+
165
+ interface PointerSpot {
166
+ clientX: number;
167
+ clientY: number;
168
+ }
169
+
170
+ // One animation frame passes: fake timers run the frame's callbacks.
171
+ function nextFrame(): void {
172
+ act(() => {
173
+ jest.advanceTimersByTime(FRAME_MS);
174
+ });
175
+ }
176
+
177
+ // Long past the double-click window: a click still waiting to zoom in has.
178
+ function waitOutTheDoubleClickWindow(): void {
179
+ act(() => {
180
+ jest.advanceTimersByTime(DOUBLE_CLICK_DISAMBIGUATION_MS * 4);
181
+ });
182
+ }
183
+
184
+ // The browser moves on to the next task: a timeout set for 0ms runs.
185
+ function nextTask(): void {
186
+ act(() => {
187
+ jest.advanceTimersByTime(0);
188
+ });
189
+ }
190
+
191
+ // Every timer still pending runs, however far off.
192
+ function runEveryPendingTimer(): void {
193
+ act(() => {
194
+ jest.runOnlyPendingTimers();
195
+ });
196
+ }
197
+
198
+ // The pointer comes to rest at `spot` over a chart root.
199
+ function restAt(chartRoot: Element, spot: PointerSpot): void {
200
+ fireEvent.mouseMove(chartRoot, { ...spot, buttons: 0 });
201
+ nextFrame();
202
+ }
203
+
204
+ /*
205
+ * The main button goes down at `spot`. `clickCount` is the browser's own
206
+ * count (MouseEvent.detail): 2 for the second press of a double-click.
207
+ */
208
+ function press(
209
+ chartRoot: Element,
210
+ spot: PointerSpot,
211
+ clickCount: number,
212
+ ): void {
213
+ fireEvent.mouseDown(chartRoot, {
214
+ ...spot,
215
+ button: 0,
216
+ buttons: 1,
217
+ detail: clickCount,
218
+ });
219
+ }
220
+
221
+ // The pointer moves to `spot` with the button held.
222
+ function moveHeld(chartRoot: Element, spot: PointerSpot): void {
223
+ fireEvent.mouseMove(chartRoot, { ...spot, buttons: 1 });
224
+ }
225
+
226
+ function release(
227
+ chartRoot: Element,
228
+ spot: PointerSpot,
229
+ clickCount: number,
230
+ ): void {
231
+ fireEvent.mouseUp(chartRoot, {
232
+ ...spot,
233
+ button: 0,
234
+ buttons: 0,
235
+ detail: clickCount,
236
+ });
237
+ }
238
+
239
+ /*
240
+ * What a browser delivers for a double-click on an element that stays on
241
+ * the page: two presses, counted, each with its release and its click,
242
+ * and then the dblclick.
243
+ */
244
+ function doubleClickOn(target: Element): void {
245
+ for (const clickCount of [1, 2]) {
246
+ fireEvent.mouseDown(target, { button: 0, buttons: 1, detail: clickCount });
247
+ fireEvent.mouseUp(target, { button: 0, buttons: 0, detail: clickCount });
248
+ fireEvent.click(target, { button: 0, detail: clickCount });
249
+ }
250
+ fireEvent.doubleClick(target, { button: 0, detail: 2 });
251
+ }
252
+
253
+ /*
254
+ * Runs `gesture` and returns whatever it threw. An error thrown by a
255
+ * handler does not come back out of the event's dispatch: the page reports
256
+ * it as uncaught, so that is where this listens too.
257
+ */
258
+ function errorsThrownBy(gesture: () => void): Array<unknown> {
259
+ const errors: Array<unknown> = [];
260
+ const onError: (event: ErrorEvent) => void = (event: ErrorEvent): void => {
261
+ errors.push(event.error);
262
+ };
263
+ window.addEventListener("error", onError);
264
+ try {
265
+ gesture();
266
+ } catch (error: unknown) {
267
+ errors.push(error);
268
+ } finally {
269
+ window.removeEventListener("error", onError);
270
+ }
271
+ return errors;
272
+ }
273
+
274
+ /*
275
+ * The loader's box: what stands where the chart will be while it loads,
276
+ * around the spinner.
277
+ */
278
+ function loaderBox(): HTMLElement {
279
+ const box: HTMLElement | null =
280
+ screen.getByTestId("component-loader").parentElement;
281
+ if (!box) {
282
+ throw new Error("the loader stands in no box");
283
+ }
284
+ return box;
285
+ }
286
+
287
+ // The chart root recharts listens on for the pointer, inside `scope`.
288
+ function chartRootIn(scope: ParentNode): Element {
289
+ const wrapper: Element | null = scope.querySelector(".recharts-wrapper");
290
+ if (!wrapper) {
291
+ throw new Error("recharts drew no chart");
292
+ }
293
+ return wrapper;
294
+ }
295
+
296
+ interface DrawnTick {
297
+ label: string;
298
+ x: number;
299
+ }
300
+
301
+ // The x-axis tick labels recharts drew inside `scope`, left to right.
302
+ function drawnTicks(scope: ParentNode): Array<DrawnTick> {
303
+ return Array.from(
304
+ scope.querySelectorAll(
305
+ ".recharts-xAxis-tick-labels text.recharts-cartesian-axis-tick-value",
306
+ ),
307
+ ).map((tick: Element): DrawnTick => {
308
+ return {
309
+ label: tick.textContent || "",
310
+ x: Number(tick.getAttribute("x")),
311
+ };
312
+ });
313
+ }
314
+
315
+ /*
316
+ * Where the pointer lands on the bucket whose tick reads `label` ("10:04";
317
+ * the labels are local times, "10:04 AM" on a 12-hour clock).
318
+ */
319
+ function spotOnTick(scope: ParentNode, label: string): PointerSpot {
320
+ const tick: DrawnTick | undefined = drawnTicks(scope).find(
321
+ (drawn: DrawnTick): boolean => {
322
+ return drawn.label.startsWith(label);
323
+ },
324
+ );
325
+ if (!tick) {
326
+ throw new Error(`recharts drew no tick label "${label}"`);
327
+ }
328
+ return { clientX: tick.x, clientY: POINTER_Y };
329
+ }
330
+
331
+ // A single click on a chart: its press, its release, the browser's click.
332
+ function clickChartAt(chartRoot: Element, spot: PointerSpot): void {
333
+ restAt(chartRoot, spot);
334
+ press(chartRoot, spot, 1);
335
+ release(chartRoot, spot, 1);
336
+ fireEvent.click(chartRoot, { ...spot, button: 0, detail: 1 });
337
+ }
338
+
339
+ /* The data boundary */
340
+
341
+ interface PostRequest {
342
+ url: { toString: () => string };
343
+ data: Record<string, unknown>;
344
+ }
345
+
346
+ interface HeldRequest {
347
+ request: PostRequest;
348
+ answer: () => void;
349
+ }
350
+
351
+ const LOGS_ANALYTICS_ROUTE: string = "/telemetry/logs/analytics";
352
+ const TRACES_ANALYTICS_ROUTE: string = "/telemetry/traces/analytics";
353
+ const EXCEPTIONS_HISTOGRAM_ROUTE: string = "/telemetry/exceptions/histogram";
354
+
355
+ // ClickHouse's bucket labels for the analytics views: UTC, no zone marker.
356
+ const ANALYTICS_BUCKETS: Array<string> = [
357
+ "2026-09-28 11:10:00",
358
+ "2026-09-28 11:11:00",
359
+ "2026-09-28 11:12:00",
360
+ ];
361
+
362
+ // While set, a request waits for answerHeldRequests().
363
+ let isHoldingRequests: boolean = false;
364
+ const heldRequests: Array<HeldRequest> = [];
365
+
366
+ function logsAnalyticsAnswer(body: Record<string, unknown>): unknown {
367
+ if (body["chartType"] === "toplist") {
368
+ return { data: { data: [{ value: "Error", count: 4 }] } };
369
+ }
370
+ if (body["chartType"] === "table") {
371
+ return {
372
+ data: { data: [{ groupValues: { severityText: "Error" }, count: 4 }] },
373
+ };
374
+ }
375
+ return {
376
+ data: {
377
+ data: ANALYTICS_BUCKETS.map(
378
+ (time: string, index: number): Record<string, unknown> => {
379
+ return { time: time, count: index + 1, groupValues: {} };
380
+ },
381
+ ),
382
+ },
383
+ };
384
+ }
385
+
386
+ function tracesAnalyticsAnswer(body: Record<string, unknown>): unknown {
387
+ if (body["chartType"] === "toplist") {
388
+ return { data: { data: [{ value: "GET /", metricValue: 4, count: 4 }] } };
389
+ }
390
+ if (body["chartType"] === "table") {
391
+ return {
392
+ data: {
393
+ data: [
394
+ {
395
+ groupValues: { name: "GET /" },
396
+ count: 4,
397
+ errorCount: 0,
398
+ avgDurationMs: 1,
399
+ p50DurationMs: 1,
400
+ p90DurationMs: 1,
401
+ p95DurationMs: 1,
402
+ p99DurationMs: 1,
403
+ minDurationMs: 1,
404
+ maxDurationMs: 1,
405
+ },
406
+ ],
407
+ },
408
+ };
409
+ }
410
+ return {
411
+ data: {
412
+ data: ANALYTICS_BUCKETS.map((time: string): Record<string, unknown> => {
413
+ return { time: time, value: 3, groupValues: { name: "GET /" } };
414
+ }),
415
+ },
416
+ };
417
+ }
418
+
419
+ /*
420
+ * Occurrences in every bucket the exception histogram is asked for, so a
421
+ * zoomed window always has bars to draw.
422
+ */
423
+ function exceptionHistogramAnswer(body: Record<string, unknown>): unknown {
424
+ const startMs: number = new Date(String(body["startTime"])).getTime();
425
+ const endMs: number = new Date(String(body["endTime"])).getTime();
426
+ const bucketMs: number = Number(body["bucketSizeInMinutes"]) * MINUTE_MS;
427
+ const buckets: Array<Record<string, unknown>> = [];
428
+
429
+ for (
430
+ let timeMs: number = Math.floor(startMs / bucketMs) * bucketMs;
431
+ timeMs < endMs;
432
+ timeMs += bucketMs
433
+ ) {
434
+ buckets.push({
435
+ time: new Date(timeMs).toISOString(),
436
+ series: "unhandled",
437
+ count: 2,
438
+ });
439
+ }
440
+
441
+ return { data: { buckets: buckets } };
442
+ }
443
+
444
+ function answerFor(request: PostRequest): unknown {
445
+ const url: string = request.url.toString();
446
+
447
+ if (url.includes(LOGS_ANALYTICS_ROUTE)) {
448
+ return logsAnalyticsAnswer(request.data);
449
+ }
450
+ if (url.includes(TRACES_ANALYTICS_ROUTE)) {
451
+ return tracesAnalyticsAnswer(request.data);
452
+ }
453
+ if (url.includes(EXCEPTIONS_HISTOGRAM_ROUTE)) {
454
+ return exceptionHistogramAnswer(request.data);
455
+ }
456
+ throw new Error(`nothing answers ${url}`);
457
+ }
458
+
459
+ function holdRequests(): void {
460
+ isHoldingRequests = true;
461
+ }
462
+
463
+ // Every held request gets its answer, and requests are answered at once again.
464
+ async function answerHeldRequests(): Promise<void> {
465
+ isHoldingRequests = false;
466
+ await act(async () => {
467
+ for (const held of heldRequests.splice(0)) {
468
+ held.answer();
469
+ }
470
+ });
471
+ }
472
+
473
+ // The bodies of every request made to `route`, oldest first.
474
+ function requestsTo(route: string): Array<Record<string, unknown>> {
475
+ return postMock.mock.calls
476
+ .map((call: Array<unknown>): PostRequest => {
477
+ return call[0] as PostRequest;
478
+ })
479
+ .filter((request: PostRequest): boolean => {
480
+ return request.url.toString().includes(route);
481
+ })
482
+ .map((request: PostRequest): Record<string, unknown> => {
483
+ return request.data;
484
+ });
485
+ }
486
+
487
+ // The bodies of the requests to `route` still waiting for an answer.
488
+ function heldBodies(route: string): Array<Record<string, unknown>> {
489
+ return heldRequests
490
+ .filter((held: HeldRequest): boolean => {
491
+ return held.request.url.toString().includes(route);
492
+ })
493
+ .map((held: HeldRequest): Record<string, unknown> => {
494
+ return held.request.data;
495
+ });
496
+ }
497
+
498
+ let histogramOnScreen: HistogramOnScreen | null = null;
499
+
500
+ beforeEach(() => {
501
+ /*
502
+ * Installed AT the fixed time, not moved there with setSystemTime: fake
503
+ * timers count animation frames from the moment they are installed.
504
+ */
505
+ jest.useFakeTimers({ now: NOW });
506
+ isHoldingRequests = false;
507
+ heldRequests.length = 0;
508
+ postMock.mockReset();
509
+ postMock.mockImplementation((request: PostRequest): Promise<unknown> => {
510
+ if (!isHoldingRequests) {
511
+ return Promise.resolve(answerFor(request));
512
+ }
513
+ return new Promise((resolve: (value: unknown) => void): void => {
514
+ heldRequests.push({
515
+ request: request,
516
+ answer: (): void => {
517
+ resolve(answerFor(request));
518
+ },
519
+ });
520
+ });
521
+ });
522
+ });
523
+
524
+ afterEach(() => {
525
+ cleanup();
526
+ histogramOnScreen = null;
527
+ heldRequests.length = 0;
528
+ jest.useRealTimers();
529
+ });
530
+
531
+ /* The explorers' volume histograms */
532
+
533
+ interface Bar {
534
+ time: string;
535
+ count: number;
536
+ }
537
+
538
+ // What the zoomed chart shows: ten one-minute bars, 10:00 to 10:09.
539
+ const SHOWN_BARS: Array<Bar> = Array.from(
540
+ { length: 10 },
541
+ (_: unknown, index: number): Bar => {
542
+ return {
543
+ time: new Date(
544
+ Date.parse("2026-09-28T10:00:00.000Z") + index * MINUTE_MS,
545
+ ).toISOString(),
546
+ count: 5 + index,
547
+ };
548
+ },
549
+ );
550
+
551
+ const TELEMETRY_SERIES: Array<HistogramSeriesOption> = [
552
+ { key: "error", label: "Error", color: "#dc2626" },
553
+ ];
554
+
555
+ // The traces explorer's header control: which metric the histogram charts.
556
+ const CHART_METRIC_PICKER: React.ReactElement = (
557
+ <select title="Chart metric" defaultValue="count">
558
+ <option value="count">Count</option>
559
+ <option value="p95">P95 response time</option>
560
+ </select>
561
+ );
562
+
563
+ // Every message an empty histogram card can show.
564
+ const EMPTY_TEXTS: Array<string> = [
565
+ "No data in the selected range",
566
+ "No data for this metric in the selected range",
567
+ "No logs in the selected range",
568
+ ];
569
+
570
+ interface HistogramProps {
571
+ bars: Array<Bar>;
572
+ isLoading: boolean;
573
+ onTimeRangeSelect?: SelectHandler | undefined;
574
+ // Set only while the chart shows a window dragged out of it.
575
+ onZoomOut?: (() => void) | undefined;
576
+ /*
577
+ * Handed no bucket interval, the chart can only zoom by a drag ("Drag
578
+ * to zoom"); with one (as by default here), by a click too.
579
+ */
580
+ withoutBucketInterval?: boolean | undefined;
581
+ }
582
+
583
+ function bucketIntervalFor(props: HistogramProps): number | undefined {
584
+ return props.withoutBucketInterval ? undefined : MINUTE_MS;
585
+ }
586
+
587
+ interface HistogramUnderTest {
588
+ name: string;
589
+ title: string;
590
+ // What its empty box says.
591
+ emptyText: string;
592
+ /*
593
+ * Header controls (the traces explorer's metric picker) keep the card on
594
+ * screen through any empty window, zoomed or not.
595
+ */
596
+ hasHeaderActions: boolean;
597
+ render: (props: HistogramProps) => React.ReactElement;
598
+ }
599
+
600
+ function telemetryBuckets(bars: Array<Bar>): Array<TelemetryHistogramBucket> {
601
+ return bars.map((bar: Bar): TelemetryHistogramBucket => {
602
+ return { time: bar.time, series: "error", count: bar.count };
603
+ });
604
+ }
605
+
606
+ function logsBuckets(bars: Array<Bar>): Array<LogsHistogramBucket> {
607
+ return bars.map((bar: Bar): LogsHistogramBucket => {
608
+ return { time: bar.time, severity: "Error", count: bar.count };
609
+ });
610
+ }
611
+
612
+ const HISTOGRAMS: Array<HistogramUnderTest> = [
613
+ {
614
+ name: "telemetry histogram (the exceptions explorer's)",
615
+ title: "Exceptions over time",
616
+ emptyText: "No data in the selected range",
617
+ hasHeaderActions: false,
618
+ render: (props: HistogramProps): React.ReactElement => {
619
+ return (
620
+ <TelemetryHistogram
621
+ buckets={telemetryBuckets(props.bars)}
622
+ isLoading={props.isLoading}
623
+ series={TELEMETRY_SERIES}
624
+ title="Exceptions over time"
625
+ onTimeRangeSelect={props.onTimeRangeSelect}
626
+ onZoomOut={props.onZoomOut}
627
+ bucketIntervalMs={bucketIntervalFor(props)}
628
+ />
629
+ );
630
+ },
631
+ },
632
+ {
633
+ name: "telemetry histogram with a metric picker (the traces explorer's)",
634
+ title: "Traces over time",
635
+ emptyText: "No data for this metric in the selected range",
636
+ hasHeaderActions: true,
637
+ render: (props: HistogramProps): React.ReactElement => {
638
+ return (
639
+ <TelemetryHistogram
640
+ buckets={telemetryBuckets(props.bars)}
641
+ isLoading={props.isLoading}
642
+ series={TELEMETRY_SERIES}
643
+ title="Traces over time"
644
+ onTimeRangeSelect={props.onTimeRangeSelect}
645
+ onZoomOut={props.onZoomOut}
646
+ bucketIntervalMs={bucketIntervalFor(props)}
647
+ headerActions={CHART_METRIC_PICKER}
648
+ />
649
+ );
650
+ },
651
+ },
652
+ {
653
+ name: "logs histogram",
654
+ title: "Log Volume",
655
+ emptyText: "No logs in the selected range",
656
+ hasHeaderActions: false,
657
+ render: (props: HistogramProps): React.ReactElement => {
658
+ return (
659
+ <LogsHistogram
660
+ buckets={logsBuckets(props.bars)}
661
+ isLoading={props.isLoading}
662
+ onTimeRangeSelect={props.onTimeRangeSelect}
663
+ onZoomOut={props.onZoomOut}
664
+ bucketIntervalMs={bucketIntervalFor(props)}
665
+ />
666
+ );
667
+ },
668
+ },
669
+ ];
670
+
671
+ interface HistogramOnScreen {
672
+ histogram: HistogramUnderTest;
673
+ props: HistogramProps;
674
+ rendered: RenderResult;
675
+ }
676
+
677
+ function showHistogram(
678
+ histogram: HistogramUnderTest,
679
+ props: HistogramProps,
680
+ ): HTMLElement {
681
+ const rendered: RenderResult = render(histogram.render(props));
682
+ histogramOnScreen = {
683
+ histogram: histogram,
684
+ props: props,
685
+ rendered: rendered,
686
+ };
687
+ return rendered.container;
688
+ }
689
+
690
+ function shownHistogram(): HistogramOnScreen {
691
+ if (!histogramOnScreen) {
692
+ throw new Error("no histogram rendered");
693
+ }
694
+ return histogramOnScreen;
695
+ }
696
+
697
+ // The host re-renders the chart: new bars, or a new loading state.
698
+ function rerenderHistogram(changes: Partial<HistogramProps>): void {
699
+ const chart: HistogramOnScreen = shownHistogram();
700
+ chart.props = { ...chart.props, ...changes };
701
+ chart.rendered.rerender(chart.histogram.render(chart.props));
702
+ }
703
+
704
+ // The band a click on one bar paints while it waits to zoom in, if drawn.
705
+ function drawnBand(): Element | null {
706
+ return shownHistogram().rendered.container.querySelector(
707
+ ".recharts-reference-area-rect",
708
+ );
709
+ }
710
+
711
+ // A single click on the bar whose tick reads `label`.
712
+ function clickBar(label: string): void {
713
+ const container: HTMLElement = shownHistogram().rendered.container;
714
+ clickChartAt(chartRootIn(container), spotOnTick(container, label));
715
+ }
716
+
717
+ /*
718
+ * Where the pointer rests through a whole double-click on a placeholder:
719
+ * over the box, and over the plot of the chart that takes its place.
720
+ */
721
+ const STILL_POINTER: PointerSpot = { clientX: 300, clientY: 60 };
722
+
723
+ /*
724
+ * The first click of a double-click on `target` - press, release, click -
725
+ * and the second press: what the browser sends before the chart comes.
726
+ */
727
+ function clickThenPressAgain(target: Element): void {
728
+ press(target, STILL_POINTER, 1);
729
+ release(target, STILL_POINTER, 1);
730
+ fireEvent.click(target, { ...STILL_POINTER, button: 0, detail: 1 });
731
+ press(target, STILL_POINTER, 2);
732
+ }
733
+
734
+ /*
735
+ * The same with the right button, which the browser counts too, but which
736
+ * opens a context menu rather than clicking.
737
+ */
738
+ function rightClickThenPressAgain(target: Element): void {
739
+ const rightButton: PointerSpot & { button: number } = {
740
+ ...STILL_POINTER,
741
+ button: 2,
742
+ };
743
+ fireEvent.mouseDown(target, { ...rightButton, buttons: 2, detail: 1 });
744
+ fireEvent.mouseUp(target, { ...rightButton, buttons: 0, detail: 1 });
745
+ fireEvent.contextMenu(target, rightButton);
746
+ fireEvent.mouseDown(target, { ...rightButton, buttons: 2, detail: 2 });
747
+ }
748
+
749
+ /*
750
+ * A second press of `button` let go over the chart drawn inside
751
+ * `chartArea`: returns the node the release lands on.
752
+ */
753
+ function releaseOverTheChart(chartArea: ParentNode, button: number): Element {
754
+ const chartRoot: Element = chartRootIn(chartArea);
755
+ fireEvent.mouseUp(chartRoot, {
756
+ ...STILL_POINTER,
757
+ button: button,
758
+ buttons: 0,
759
+ detail: 2,
760
+ });
761
+ return chartRoot;
762
+ }
763
+
764
+ // The same, let go off the chart, where only the page hears it.
765
+ function releaseOffTheChart(button: number): Element {
766
+ fireEvent.mouseUp(document.body, {
767
+ ...STILL_POINTER,
768
+ button: button,
769
+ buttons: 0,
770
+ detail: 2,
771
+ });
772
+ return document.body;
773
+ }
774
+
775
+ /*
776
+ * What the browser sends after the release of a double-click's second
777
+ * press: its click and the dblclick, to the nearest node the press and the
778
+ * release share - or nothing at all once the node the press went down on
779
+ * has left the page.
780
+ */
781
+ function browserSendsTheClicks(pressed: Element, released: Element): void {
782
+ if (!pressed.isConnected) {
783
+ return;
784
+ }
785
+ let shared: Element | null = pressed;
786
+ while (shared && !shared.contains(released)) {
787
+ shared = shared.parentElement;
788
+ }
789
+ if (!shared) {
790
+ return;
791
+ }
792
+ fireEvent.click(shared, { ...STILL_POINTER, button: 0, detail: 2 });
793
+ fireEvent.doubleClick(shared, { ...STILL_POINTER, button: 0, detail: 2 });
794
+ }
795
+
796
+ /*
797
+ * Each way a double-click on a loader can end once the chart has taken its
798
+ * place: where its second press went down, and where it is let go. The
799
+ * spinner goes with the loader. The box around it goes too, or React keeps
800
+ * it for the chart - the analytics views' and the Occurrence Trend's charts
801
+ * render into the very div their loader did - without the loader's
802
+ * handlers; the click and the dblclick the browser then sends that box
803
+ * bubble away from the chart's own dblclick handler.
804
+ */
805
+ interface LoaderRaceEnding {
806
+ name: string;
807
+ // What the second press goes down on.
808
+ pressTarget: () => HTMLElement;
809
+ // Its release, of `button`: returns the node it lands on.
810
+ release: (chartArea: HTMLElement, button: number) => Element;
811
+ }
812
+
813
+ function spinner(): HTMLElement {
814
+ return screen.getByTestId("component-loader");
815
+ }
816
+
817
+ const LOADER_RACE_ENDINGS: Array<LoaderRaceEnding> = [
818
+ {
819
+ name: "pressed on its spinner and let go over the chart",
820
+ pressTarget: spinner,
821
+ release: releaseOverTheChart,
822
+ },
823
+ {
824
+ name: "pressed on its spinner and let go off the chart, where only the page hears it",
825
+ pressTarget: spinner,
826
+ release: (_chartArea: HTMLElement, button: number): Element => {
827
+ return releaseOffTheChart(button);
828
+ },
829
+ },
830
+ {
831
+ name: "pressed on the box around its spinner and let go over the chart",
832
+ pressTarget: loaderBox,
833
+ release: releaseOverTheChart,
834
+ },
835
+ ];
836
+
837
+ // The chart took the loader's place: the spinner is gone, the chart drawn.
838
+ function expectChartInTheLoadersPlace(chartArea: HTMLElement): void {
839
+ expect(screen.queryByTestId("component-loader")).toBeNull();
840
+ expect(chartRootIn(chartArea)).toBeInTheDocument();
841
+ }
842
+
843
+ // The zoom hints a histogram's header can show.
844
+ const ZOOM_IN_HINTS: Array<string> = ["Click or drag to zoom", "Drag to zoom"];
845
+
846
+ function zoomInHintsShown(): Array<string> {
847
+ return ZOOM_IN_HINTS.filter((hint: string): boolean => {
848
+ return screen.queryByText(hint) !== null;
849
+ });
850
+ }
851
+
852
+ interface Placeholder {
853
+ name: string;
854
+ // Whether the chart's next window is still loading when its bars go.
855
+ isLoading: boolean;
856
+ find: (histogram: HistogramUnderTest) => HTMLElement;
857
+ }
858
+
859
+ const PLACEHOLDERS: Array<Placeholder> = [
860
+ {
861
+ name: "empty box",
862
+ isLoading: false,
863
+ find: (histogram: HistogramUnderTest): HTMLElement => {
864
+ return screen.getByText(histogram.emptyText);
865
+ },
866
+ },
867
+ {
868
+ name: "loader",
869
+ isLoading: true,
870
+ find: (): HTMLElement => {
871
+ return loaderBox();
872
+ },
873
+ },
874
+ ];
875
+
876
+ for (const histogram of HISTOGRAMS) {
877
+ describe(`the ${histogram.name}`, () => {
878
+ describe("loading its first bars", () => {
879
+ test("zoomed: the loader is not selectable, and a double-click on it zooms out once", () => {
880
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
881
+ const onZoomOut: MockFunction = getJestMockFunction();
882
+ showHistogram(histogram, {
883
+ bars: [],
884
+ isLoading: true,
885
+ onTimeRangeSelect: onTimeRangeSelect,
886
+ onZoomOut: onZoomOut,
887
+ });
888
+
889
+ const box: HTMLElement = loaderBox();
890
+ // The double-click must not also select anything around the spinner.
891
+ expect(box).toHaveClass("select-none");
892
+ /*
893
+ * A zoomed chart keeps its card while its window loads - its title
894
+ * and the way back - with the loader in the box the bars will take,
895
+ * so the double-click stays where the reader's pointer already is.
896
+ */
897
+ expect(screen.getByText(histogram.title)).toBeInTheDocument();
898
+ expect(screen.getByText("Double-click to reset")).toBeInTheDocument();
899
+
900
+ doubleClickOn(box);
901
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
902
+
903
+ waitOutTheDoubleClickWindow();
904
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
905
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
906
+ });
907
+
908
+ test("not zoomed: a double-click on the loader does nothing, and throws nothing", () => {
909
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
910
+ showHistogram(histogram, {
911
+ bars: [],
912
+ isLoading: true,
913
+ onTimeRangeSelect: onTimeRangeSelect,
914
+ });
915
+
916
+ expect(
917
+ errorsThrownBy((): void => {
918
+ doubleClickOn(loaderBox());
919
+ }),
920
+ ).toEqual([]);
921
+
922
+ waitOutTheDoubleClickWindow();
923
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
924
+ expect(screen.getByTestId("component-loader")).toBeInTheDocument();
925
+ });
926
+
927
+ test("with no zoom offered at all: a double-click on the loader throws nothing", () => {
928
+ showHistogram(histogram, { bars: [], isLoading: true });
929
+
930
+ expect(
931
+ errorsThrownBy((): void => {
932
+ doubleClickOn(loaderBox());
933
+ waitOutTheDoubleClickWindow();
934
+ }),
935
+ ).toEqual([]);
936
+ expect(screen.getByTestId("component-loader")).toBeInTheDocument();
937
+ });
938
+ });
939
+
940
+ describe("with an empty window", () => {
941
+ test("zoomed: the card stays - its title and the reset hint - and its empty box takes the double-click", () => {
942
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
943
+ const onZoomOut: MockFunction = getJestMockFunction();
944
+ const container: HTMLElement = showHistogram(histogram, {
945
+ bars: [],
946
+ isLoading: false,
947
+ onTimeRangeSelect: onTimeRangeSelect,
948
+ onZoomOut: onZoomOut,
949
+ });
950
+
951
+ expect(screen.getByText(histogram.title)).toBeInTheDocument();
952
+ expect(screen.getByText("Double-click to reset")).toBeInTheDocument();
953
+ if (histogram.hasHeaderActions) {
954
+ expect(screen.getByTitle("Chart metric")).toBeInTheDocument();
955
+ }
956
+ // Its own message, and only that one; no chart over an empty window.
957
+ expect(
958
+ EMPTY_TEXTS.filter((text: string): boolean => {
959
+ return screen.queryByText(text) !== null;
960
+ }),
961
+ ).toEqual([histogram.emptyText]);
962
+ expect(container.querySelector(".recharts-wrapper")).toBeNull();
963
+
964
+ const emptyBox: HTMLElement = screen.getByText(histogram.emptyText);
965
+ // The double-click must not also select a word of the message.
966
+ expect(emptyBox).toHaveClass("select-none");
967
+
968
+ doubleClickOn(emptyBox);
969
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
970
+
971
+ waitOutTheDoubleClickWindow();
972
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
973
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
974
+ });
975
+
976
+ if (histogram.hasHeaderActions) {
977
+ test("not zoomed: the card stays for its metric picker, and a double-click on its empty box does nothing", () => {
978
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
979
+ showHistogram(histogram, {
980
+ bars: [],
981
+ isLoading: false,
982
+ onTimeRangeSelect: onTimeRangeSelect,
983
+ });
984
+
985
+ expect(screen.getByTitle("Chart metric")).toBeInTheDocument();
986
+ expect(screen.queryByText("Double-click to reset")).toBeNull();
987
+
988
+ expect(
989
+ errorsThrownBy((): void => {
990
+ doubleClickOn(screen.getByText(histogram.emptyText));
991
+ waitOutTheDoubleClickWindow();
992
+ }),
993
+ ).toEqual([]);
994
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
995
+ });
996
+ } else {
997
+ test("not zoomed: it still renders nothing at all", () => {
998
+ const container: HTMLElement = showHistogram(histogram, {
999
+ bars: [],
1000
+ isLoading: false,
1001
+ onTimeRangeSelect: getJestMockFunction(),
1002
+ });
1003
+
1004
+ expect(container).toBeEmptyDOMElement();
1005
+ });
1006
+ }
1007
+ });
1008
+
1009
+ describe("a click on a bar still waiting to zoom in when the bars go away", () => {
1010
+ for (const placeholder of PLACEHOLDERS) {
1011
+ test(`a double-click on the ${placeholder.name} that takes their place zooms out once, and the click never zooms in`, () => {
1012
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
1013
+ const onZoomOut: MockFunction = getJestMockFunction();
1014
+ showHistogram(histogram, {
1015
+ bars: SHOWN_BARS,
1016
+ isLoading: false,
1017
+ onTimeRangeSelect: onTimeRangeSelect,
1018
+ onZoomOut: onZoomOut,
1019
+ });
1020
+
1021
+ clickBar("10:04");
1022
+ // The click waits out the double-click window, its bar banded.
1023
+ expect(drawnBand()).not.toBeNull();
1024
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
1025
+
1026
+ // The chart's next window lands (or starts loading) with no bars.
1027
+ rerenderHistogram({ bars: [], isLoading: placeholder.isLoading });
1028
+ expect(
1029
+ shownHistogram().rendered.container.querySelector(
1030
+ ".recharts-wrapper",
1031
+ ),
1032
+ ).toBeNull();
1033
+
1034
+ const box: HTMLElement = placeholder.find(histogram);
1035
+ expect(box).toHaveClass("select-none");
1036
+ doubleClickOn(box);
1037
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
1038
+
1039
+ waitOutTheDoubleClickWindow();
1040
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
1041
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
1042
+ });
1043
+ }
1044
+ });
1045
+
1046
+ /*
1047
+ * The zoomed window lands in the middle of the double-click: its bars
1048
+ * take the loader's place between the second press and its release.
1049
+ * No dblclick reaches the chart - the node the press went down on is
1050
+ * gone, or kept without a dblclick handler - and before commit
1051
+ * b6ec899df4 the chart stayed zoomed.
1052
+ */
1053
+ describe("a double-click on the loader whose second press the bars land under", () => {
1054
+ for (const ending of LOADER_RACE_ENDINGS) {
1055
+ test(`zoomed: ${ending.name}, it zooms out once, in the next task`, () => {
1056
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
1057
+ const onZoomOut: MockFunction = getJestMockFunction();
1058
+ const container: HTMLElement = showHistogram(histogram, {
1059
+ bars: [],
1060
+ isLoading: true,
1061
+ onTimeRangeSelect: onTimeRangeSelect,
1062
+ onZoomOut: onZoomOut,
1063
+ });
1064
+ const pressed: HTMLElement = ending.pressTarget();
1065
+
1066
+ clickThenPressAgain(pressed);
1067
+ rerenderHistogram({ bars: SHOWN_BARS, isLoading: false });
1068
+ expectChartInTheLoadersPlace(container);
1069
+
1070
+ browserSendsTheClicks(pressed, ending.release(container, 0));
1071
+
1072
+ // No dblclick reached the chart: the zoom-out waits for the next task.
1073
+ expect(onZoomOut).not.toHaveBeenCalled();
1074
+
1075
+ nextTask();
1076
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
1077
+
1078
+ waitOutTheDoubleClickWindow();
1079
+ runEveryPendingTimer();
1080
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
1081
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
1082
+ });
1083
+ }
1084
+
1085
+ test("zoomed: when the browser does send the click and the dblclick, to the box around the new chart, it zooms out exactly once", () => {
1086
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
1087
+ const onZoomOut: MockFunction = getJestMockFunction();
1088
+ const container: HTMLElement = showHistogram(histogram, {
1089
+ bars: [],
1090
+ isLoading: true,
1091
+ onTimeRangeSelect: onTimeRangeSelect,
1092
+ onZoomOut: onZoomOut,
1093
+ });
1094
+
1095
+ clickThenPressAgain(loaderBox());
1096
+ rerenderHistogram({ bars: SHOWN_BARS, isLoading: false });
1097
+ const chartRoot: Element = chartRootIn(container);
1098
+ release(chartRoot, STILL_POINTER, 2);
1099
+ const chartBox: HTMLElement | null = chartRoot.parentElement;
1100
+ if (!chartBox) {
1101
+ throw new Error("the chart has no box around it");
1102
+ }
1103
+ fireEvent.click(chartBox, { ...STILL_POINTER, button: 0, detail: 2 });
1104
+ fireEvent.doubleClick(chartBox, {
1105
+ ...STILL_POINTER,
1106
+ button: 0,
1107
+ detail: 2,
1108
+ });
1109
+
1110
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
1111
+
1112
+ nextTask();
1113
+ waitOutTheDoubleClickWindow();
1114
+ runEveryPendingTimer();
1115
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
1116
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
1117
+ });
1118
+
1119
+ for (const ending of LOADER_RACE_ENDINGS) {
1120
+ test(`not zoomed: ${ending.name}, it zooms nothing in and throws nothing, and a click on a bar then zooms in as ever`, () => {
1121
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
1122
+ const container: HTMLElement = showHistogram(histogram, {
1123
+ bars: [],
1124
+ isLoading: true,
1125
+ onTimeRangeSelect: onTimeRangeSelect,
1126
+ });
1127
+ const pressed: HTMLElement = ending.pressTarget();
1128
+
1129
+ expect(
1130
+ errorsThrownBy((): void => {
1131
+ clickThenPressAgain(pressed);
1132
+ rerenderHistogram({ bars: SHOWN_BARS, isLoading: false });
1133
+ browserSendsTheClicks(pressed, ending.release(container, 0));
1134
+ nextTask();
1135
+ waitOutTheDoubleClickWindow();
1136
+ }),
1137
+ ).toEqual([]);
1138
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
1139
+
1140
+ clickBar("10:04");
1141
+ expect(onTimeRangeSelect).toHaveBeenCalledTimes(1);
1142
+ });
1143
+ }
1144
+
1145
+ for (const ending of LOADER_RACE_ENDINGS) {
1146
+ test(`zoomed: the same with the right button - ${ending.name} - zooms nothing out`, () => {
1147
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
1148
+ const onZoomOut: MockFunction = getJestMockFunction();
1149
+ const container: HTMLElement = showHistogram(histogram, {
1150
+ bars: [],
1151
+ isLoading: true,
1152
+ onTimeRangeSelect: onTimeRangeSelect,
1153
+ onZoomOut: onZoomOut,
1154
+ });
1155
+
1156
+ rightClickThenPressAgain(ending.pressTarget());
1157
+ rerenderHistogram({ bars: SHOWN_BARS, isLoading: false });
1158
+ // A right-button release: a context menu, no click.
1159
+ ending.release(container, 2);
1160
+ nextTask();
1161
+ waitOutTheDoubleClickWindow();
1162
+ runEveryPendingTimer();
1163
+
1164
+ expect(onZoomOut).not.toHaveBeenCalled();
1165
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
1166
+ });
1167
+ }
1168
+ });
1169
+
1170
+ /*
1171
+ * The header says how to zoom in only when there are bars to click or
1172
+ * drag; with none, "Click or drag to zoom" pointed at nothing, and only
1173
+ * the way back applies (commit b6ec899df4).
1174
+ */
1175
+ describe("its header's zoom hints", () => {
1176
+ const INTERVALS: Array<{
1177
+ name: string;
1178
+ withoutBucketInterval: boolean;
1179
+ zoomInHint: string;
1180
+ }> = [
1181
+ {
1182
+ name: "a bar can be clicked",
1183
+ withoutBucketInterval: false,
1184
+ zoomInHint: "Click or drag to zoom",
1185
+ },
1186
+ {
1187
+ name: "only a drag zooms (no bucket interval)",
1188
+ withoutBucketInterval: true,
1189
+ zoomInHint: "Drag to zoom",
1190
+ },
1191
+ ];
1192
+
1193
+ for (const interval of INTERVALS) {
1194
+ test(`where ${interval.name}: zoomed with no bars, loaded or loading, only "Double-click to reset"; the zoom-in hint comes back with the bars`, () => {
1195
+ showHistogram(histogram, {
1196
+ bars: [],
1197
+ isLoading: false,
1198
+ onTimeRangeSelect: getJestMockFunction(),
1199
+ onZoomOut: getJestMockFunction(),
1200
+ withoutBucketInterval: interval.withoutBucketInterval,
1201
+ });
1202
+
1203
+ expect(zoomInHintsShown()).toEqual([]);
1204
+ expect(screen.getByText("Double-click to reset")).toBeInTheDocument();
1205
+
1206
+ // The empty window loads again.
1207
+ rerenderHistogram({ isLoading: true });
1208
+
1209
+ expect(zoomInHintsShown()).toEqual([]);
1210
+ expect(screen.getByText("Double-click to reset")).toBeInTheDocument();
1211
+
1212
+ // Its bars land.
1213
+ rerenderHistogram({ bars: SHOWN_BARS, isLoading: false });
1214
+
1215
+ expect(zoomInHintsShown()).toEqual([interval.zoomInHint]);
1216
+ expect(screen.getByText("Double-click to reset")).toBeInTheDocument();
1217
+ });
1218
+
1219
+ test(`where ${interval.name}: not zoomed, with bars, the zoom-in hint alone`, () => {
1220
+ showHistogram(histogram, {
1221
+ bars: SHOWN_BARS,
1222
+ isLoading: false,
1223
+ onTimeRangeSelect: getJestMockFunction(),
1224
+ withoutBucketInterval: interval.withoutBucketInterval,
1225
+ });
1226
+
1227
+ expect(zoomInHintsShown()).toEqual([interval.zoomInHint]);
1228
+ expect(screen.queryByText("Double-click to reset")).toBeNull();
1229
+ });
1230
+ }
1231
+ });
1232
+
1233
+ describe("reloading an empty window", () => {
1234
+ /*
1235
+ * Before commit b6ec899df4 a zoomed chart with no bars swapped its
1236
+ * whole card for the bare loader box whenever it loaded: the title
1237
+ * and the way back went, and the box under the pointer changed
1238
+ * height (h-32 for h-[120px]) - just as the reader double-clicked.
1239
+ */
1240
+ test("zoomed: the card stays - its title and the way back - and the loader comes up in its empty box, which takes the double-click", () => {
1241
+ const onTimeRangeSelect: MockFunction = getJestMockFunction();
1242
+ const onZoomOut: MockFunction = getJestMockFunction();
1243
+ const container: HTMLElement = showHistogram(histogram, {
1244
+ bars: [],
1245
+ isLoading: false,
1246
+ onTimeRangeSelect: onTimeRangeSelect,
1247
+ onZoomOut: onZoomOut,
1248
+ });
1249
+ const card: HTMLElement | null =
1250
+ container.firstElementChild as HTMLElement | null;
1251
+ const title: HTMLElement = screen.getByText(histogram.title);
1252
+ const emptyBox: HTMLElement = screen.getByText(histogram.emptyText);
1253
+
1254
+ // A filter changes, say: the zoomed window loads again.
1255
+ rerenderHistogram({ isLoading: true });
1256
+
1257
+ // The very same card and box, not a bare loader in their place.
1258
+ expect(container.firstElementChild).toBe(card);
1259
+ expect(title.isConnected).toBe(true);
1260
+ expect(screen.getByText("Double-click to reset")).toBeInTheDocument();
1261
+ expect(screen.queryByText(histogram.emptyText)).toBeNull();
1262
+
1263
+ const box: HTMLElement = loaderBox();
1264
+ expect(box).toBe(emptyBox);
1265
+ expect(card).toContainElement(box);
1266
+ expect(box).toHaveClass("h-[120px]", "select-none");
1267
+ expect(box).not.toHaveClass("h-32");
1268
+
1269
+ doubleClickOn(box);
1270
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
1271
+
1272
+ waitOutTheDoubleClickWindow();
1273
+ expect(onZoomOut).toHaveBeenCalledTimes(1);
1274
+ expect(onTimeRangeSelect).not.toHaveBeenCalled();
1275
+ });
1276
+
1277
+ test("not zoomed: loading with no bars is still the bare loader box, with no card around it", () => {
1278
+ const container: HTMLElement = showHistogram(histogram, {
1279
+ bars: [],
1280
+ isLoading: true,
1281
+ onTimeRangeSelect: getJestMockFunction(),
1282
+ });
1283
+
1284
+ const box: HTMLElement = loaderBox();
1285
+ expect(container.firstElementChild).toBe(box);
1286
+ expect(box).toHaveClass("h-32");
1287
+ expect(screen.queryByText(histogram.title)).toBeNull();
1288
+ expect(screen.queryByText("Double-click to reset")).toBeNull();
1289
+ });
1290
+ });
1291
+ });
1292
+ }
1293
+
1294
+ /* The log and trace analytics views */
1295
+
1296
+ type WindowName = "wide" | "zoomed";
1297
+
1298
+ // The viewer's window before the zoom (six hours), and the zoomed hour.
1299
+ const WINDOWS: Record<WindowName, { startTime: string; endTime: string }> = {
1300
+ wide: {
1301
+ startTime: "2026-09-28T06:00:00.000Z",
1302
+ endTime: "2026-09-28T12:00:00.000Z",
1303
+ },
1304
+ zoomed: {
1305
+ startTime: "2026-09-28T11:00:00.000Z",
1306
+ endTime: "2026-09-28T12:00:00.000Z",
1307
+ },
1308
+ };
1309
+
1310
+ /*
1311
+ * Each window's props, built once: the views refetch whenever the object
1312
+ * they are handed changes, as a viewer's does when its window does.
1313
+ */
1314
+ const LOGS_TIME_RANGES: Record<WindowName, RangeStartAndEndDateTime> = {
1315
+ wide: {
1316
+ range: TimeRange.CUSTOM,
1317
+ startAndEndDate: new InBetween<Date>(
1318
+ new Date(WINDOWS.wide.startTime),
1319
+ new Date(WINDOWS.wide.endTime),
1320
+ ),
1321
+ },
1322
+ zoomed: {
1323
+ range: TimeRange.CUSTOM,
1324
+ startAndEndDate: new InBetween<Date>(
1325
+ new Date(WINDOWS.zoomed.startTime),
1326
+ new Date(WINDOWS.zoomed.endTime),
1327
+ ),
1328
+ },
1329
+ };
1330
+
1331
+ const TRACES_BASE_FILTERS: Record<WindowName, JSONObject> = {
1332
+ wide: { ...WINDOWS.wide },
1333
+ zoomed: { ...WINDOWS.zoomed },
1334
+ };
1335
+
1336
+ const NO_FACET_FILTERS: Map<string, Set<string>> = new Map();
1337
+ // The logs viewer's severity filter, narrowed to errors.
1338
+ const ERRORS_ONLY: Map<string, Set<string>> = new Map([
1339
+ ["severityText", new Set(["Error"])],
1340
+ ]);
1341
+ const NO_ATTRIBUTES: Array<string> = [];
1342
+ const NO_SERVICE_NAMES: Record<string, string> = {};
1343
+
1344
+ interface HostZoomHandlers {
1345
+ onTimeRangeSelect?: SelectHandler | undefined;
1346
+ onTimeRangeReset?: (() => void) | undefined;
1347
+ }
1348
+
1349
+ interface AnalyticsViewUnderTest {
1350
+ name: string;
1351
+ route: string;
1352
+ timeseriesTestId: string;
1353
+ // What the top list and the table show once they land.
1354
+ groupText: string;
1355
+ /*
1356
+ * The view on `window`. `refetched` asks it for its window again without
1357
+ * moving it: the logs viewer's filter changes, the traces explorer's
1358
+ * Refresh button bumps its tick.
1359
+ */
1360
+ render: (
1361
+ window: WindowName,
1362
+ handlers: HostZoomHandlers,
1363
+ refetched?: boolean,
1364
+ ) => React.ReactElement;
1365
+ }
1366
+
1367
+ const ANALYTICS_VIEWS: Array<AnalyticsViewUnderTest> = [
1368
+ {
1369
+ name: "LogsAnalyticsView",
1370
+ route: LOGS_ANALYTICS_ROUTE,
1371
+ timeseriesTestId: LOGS_ANALYTICS_TIMESERIES_TEST_ID,
1372
+ groupText: "Error",
1373
+ render: (
1374
+ window: WindowName,
1375
+ handlers: HostZoomHandlers,
1376
+ refetched?: boolean,
1377
+ ): React.ReactElement => {
1378
+ return (
1379
+ <LogsAnalyticsView
1380
+ timeRange={LOGS_TIME_RANGES[window]}
1381
+ appliedFacetFilters={refetched ? ERRORS_ONLY : NO_FACET_FILTERS}
1382
+ logAttributes={NO_ATTRIBUTES}
1383
+ onTimeRangeSelect={handlers.onTimeRangeSelect}
1384
+ onTimeRangeReset={handlers.onTimeRangeReset}
1385
+ />
1386
+ );
1387
+ },
1388
+ },
1389
+ {
1390
+ name: "TracesAnalyticsView",
1391
+ route: TRACES_ANALYTICS_ROUTE,
1392
+ timeseriesTestId: TRACES_ANALYTICS_TIMESERIES_TEST_ID,
1393
+ groupText: "GET /",
1394
+ render: (
1395
+ window: WindowName,
1396
+ handlers: HostZoomHandlers,
1397
+ refetched?: boolean,
1398
+ ): React.ReactElement => {
1399
+ return (
1400
+ <TracesAnalyticsView
1401
+ baseFilters={TRACES_BASE_FILTERS[window]}
1402
+ attributeKeys={NO_ATTRIBUTES}
1403
+ serviceNameMap={NO_SERVICE_NAMES}
1404
+ refreshTick={refetched ? 1 : 0}
1405
+ onTimeRangeSelect={handlers.onTimeRangeSelect}
1406
+ onTimeRangeReset={handlers.onTimeRangeReset}
1407
+ />
1408
+ );
1409
+ },
1410
+ },
1411
+ ];
1412
+
1413
+ interface SpyZoom {
1414
+ zoom: TimeRangeZoom;
1415
+ zoomToTimeRange: MockFunction;
1416
+ resetZoom: MockFunction;
1417
+ }
1418
+
1419
+ // The zoom a viewer offers the charts inside it, with its two gestures spied on.
1420
+ function spyZoom(isZoomed: boolean): SpyZoom {
1421
+ const zoomToTimeRange: MockFunction = getJestMockFunction();
1422
+ const resetZoom: MockFunction = getJestMockFunction();
1423
+ return {
1424
+ zoomToTimeRange: zoomToTimeRange,
1425
+ resetZoom: resetZoom,
1426
+ zoom: {
1427
+ isZoomed: isZoomed,
1428
+ rangeBeforeZoom: isZoomed ? LOGS_TIME_RANGES.wide : null,
1429
+ zoomToTimeRange: (startTime: Date, endTime: Date): void => {
1430
+ zoomToTimeRange(startTime, endTime);
1431
+ },
1432
+ resetZoom: (): void => {
1433
+ resetZoom();
1434
+ },
1435
+ },
1436
+ };
1437
+ }
1438
+
1439
+ function inViewer(
1440
+ zoom: TimeRangeZoom,
1441
+ view: React.ReactElement,
1442
+ ): React.ReactElement {
1443
+ return <TimeRangeZoomProvider zoom={zoom}>{view}</TimeRangeZoomProvider>;
1444
+ }
1445
+
1446
+ async function waitForTimeseries(view: AnalyticsViewUnderTest): Promise<void> {
1447
+ await waitFor(() => {
1448
+ expect(
1449
+ chartRootIn(screen.getByTestId(view.timeseriesTestId)),
1450
+ ).toBeInTheDocument();
1451
+ });
1452
+ }
1453
+
1454
+ // The reader picks another chart in the view's "Chart" control.
1455
+ function switchChartTo(chartType: string): void {
1456
+ fireEvent.change(screen.getByDisplayValue("Timeseries"), {
1457
+ target: { value: chartType },
1458
+ });
1459
+ }
1460
+
1461
+ const NOT_TIME_CHARTS: Array<[string, string]> = [
1462
+ ["top list", "toplist"],
1463
+ ["table", "table"],
1464
+ ];
1465
+
1466
+ for (const view of ANALYTICS_VIEWS) {
1467
+ describe(`${view.name}: the loader standing in for the chart while it refetches`, () => {
1468
+ test("timeseries, zoomed by the viewer around it: a double-click on the loader of the zoom's refetch zooms out once", async () => {
1469
+ const beforeTheZoom: SpyZoom = spyZoom(false);
1470
+ const zoomed: SpyZoom = spyZoom(true);
1471
+ const { rerender } = render(
1472
+ inViewer(beforeTheZoom.zoom, view.render("wide", {})),
1473
+ );
1474
+ await waitForTimeseries(view);
1475
+
1476
+ // The reader zoomed: the viewer is on the zoomed hour, and refetches.
1477
+ holdRequests();
1478
+ rerender(inViewer(zoomed.zoom, view.render("zoomed", {})));
1479
+ expect(heldBodies(view.route)).toEqual([
1480
+ expect.objectContaining({
1481
+ chartType: "timeseries",
1482
+ startTime: WINDOWS.zoomed.startTime,
1483
+ endTime: WINDOWS.zoomed.endTime,
1484
+ }),
1485
+ ]);
1486
+ expect(screen.queryByTestId(view.timeseriesTestId)).toBeNull();
1487
+
1488
+ const box: HTMLElement = loaderBox();
1489
+ expect(box).toHaveClass("select-none");
1490
+ doubleClickOn(box);
1491
+ expect(zoomed.resetZoom).toHaveBeenCalledTimes(1);
1492
+
1493
+ waitOutTheDoubleClickWindow();
1494
+ expect(zoomed.resetZoom).toHaveBeenCalledTimes(1);
1495
+ expect(zoomed.zoomToTimeRange).not.toHaveBeenCalled();
1496
+ expect(beforeTheZoom.resetZoom).not.toHaveBeenCalled();
1497
+ expect(beforeTheZoom.zoomToTimeRange).not.toHaveBeenCalled();
1498
+
1499
+ await answerHeldRequests();
1500
+ await waitForTimeseries(view);
1501
+ });
1502
+
1503
+ test("timeseries, zoomed by its host: a double-click on the loader zooms out through the host's reset, once", async () => {
1504
+ const hostSelect: MockFunction = getJestMockFunction();
1505
+ const hostReset: MockFunction = getJestMockFunction();
1506
+ const { rerender } = render(
1507
+ view.render("wide", { onTimeRangeSelect: hostSelect }),
1508
+ );
1509
+ await waitForTimeseries(view);
1510
+
1511
+ holdRequests();
1512
+ rerender(
1513
+ view.render("zoomed", {
1514
+ onTimeRangeSelect: hostSelect,
1515
+ onTimeRangeReset: hostReset,
1516
+ }),
1517
+ );
1518
+ expect(heldBodies(view.route)).toEqual([
1519
+ expect.objectContaining({
1520
+ chartType: "timeseries",
1521
+ startTime: WINDOWS.zoomed.startTime,
1522
+ }),
1523
+ ]);
1524
+
1525
+ doubleClickOn(loaderBox());
1526
+ expect(hostReset).toHaveBeenCalledTimes(1);
1527
+
1528
+ waitOutTheDoubleClickWindow();
1529
+ expect(hostReset).toHaveBeenCalledTimes(1);
1530
+ expect(hostSelect).not.toHaveBeenCalled();
1531
+
1532
+ await answerHeldRequests();
1533
+ await waitForTimeseries(view);
1534
+ });
1535
+
1536
+ /*
1537
+ * The loader takes the double-click as the chart does - through the
1538
+ * chart's own selection, which also drops the first click's zoom-in -
1539
+ * not by resetting the zoom behind the selection's back.
1540
+ */
1541
+ test("timeseries: a click on a bucket still waiting to zoom in when a refetch swaps the chart for the loader - a double-click on the loader drops it, and zooms out once", async () => {
1542
+ const zoomed: SpyZoom = spyZoom(true);
1543
+ const { rerender } = render(
1544
+ inViewer(zoomed.zoom, view.render("zoomed", {})),
1545
+ );
1546
+ await waitForTimeseries(view);
1547
+
1548
+ const plot: HTMLElement = screen.getByTestId(view.timeseriesTestId);
1549
+ expect(plot.querySelector(".recharts-reference-area")).toBeNull();
1550
+ clickChartAt(chartRootIn(plot), spotOnTick(plot, "11:11"));
1551
+ /*
1552
+ * The click waits out the double-click window, its bucket banded. (On
1553
+ * the logs view's area chart a one-bucket band has no width, so
1554
+ * recharts draws the band's layer and no rectangle in it.)
1555
+ */
1556
+ expect(plot.querySelector(".recharts-reference-area")).not.toBeNull();
1557
+ expect(zoomed.zoomToTimeRange).not.toHaveBeenCalled();
1558
+
1559
+ holdRequests();
1560
+ rerender(inViewer(zoomed.zoom, view.render("zoomed", {}, true)));
1561
+ expect(heldBodies(view.route)).toEqual([
1562
+ expect.objectContaining({
1563
+ chartType: "timeseries",
1564
+ startTime: WINDOWS.zoomed.startTime,
1565
+ }),
1566
+ ]);
1567
+
1568
+ doubleClickOn(loaderBox());
1569
+ expect(zoomed.resetZoom).toHaveBeenCalledTimes(1);
1570
+
1571
+ waitOutTheDoubleClickWindow();
1572
+ expect(zoomed.zoomToTimeRange).not.toHaveBeenCalled();
1573
+ expect(zoomed.resetZoom).toHaveBeenCalledTimes(1);
1574
+
1575
+ await answerHeldRequests();
1576
+ await waitForTimeseries(view);
1577
+ });
1578
+
1579
+ /*
1580
+ * The zoom's refetch lands in the middle of the double-click on its
1581
+ * loader: the chart takes the loader's place between the second press
1582
+ * and its release, and no dblclick reaches the chart - the view keeps
1583
+ * the loader's box for the chart, without its handlers. Before commit
1584
+ * b6ec899df4 the loader took only the dblclick, and the view stayed
1585
+ * zoomed.
1586
+ */
1587
+ for (const ending of LOADER_RACE_ENDINGS) {
1588
+ test(`timeseries, zoomed by the viewer: a double-click on the loader whose second press the chart lands under - ${ending.name} - zooms out once, in the next task`, async () => {
1589
+ const beforeTheZoom: SpyZoom = spyZoom(false);
1590
+ const zoomed: SpyZoom = spyZoom(true);
1591
+ const { rerender } = render(
1592
+ inViewer(beforeTheZoom.zoom, view.render("wide", {})),
1593
+ );
1594
+ await waitForTimeseries(view);
1595
+
1596
+ holdRequests();
1597
+ rerender(inViewer(zoomed.zoom, view.render("zoomed", {})));
1598
+ const pressed: HTMLElement = ending.pressTarget();
1599
+
1600
+ clickThenPressAgain(pressed);
1601
+ await answerHeldRequests();
1602
+ await waitForTimeseries(view);
1603
+
1604
+ const chartArea: HTMLElement = screen.getByTestId(
1605
+ view.timeseriesTestId,
1606
+ );
1607
+ expectChartInTheLoadersPlace(chartArea);
1608
+ expect(zoomed.resetZoom).not.toHaveBeenCalled();
1609
+
1610
+ browserSendsTheClicks(pressed, ending.release(chartArea, 0));
1611
+
1612
+ // No dblclick reached the chart: the reset waits for the next task.
1613
+ expect(zoomed.resetZoom).not.toHaveBeenCalled();
1614
+
1615
+ nextTask();
1616
+ expect(zoomed.resetZoom).toHaveBeenCalledTimes(1);
1617
+
1618
+ waitOutTheDoubleClickWindow();
1619
+ runEveryPendingTimer();
1620
+ expect(zoomed.resetZoom).toHaveBeenCalledTimes(1);
1621
+ expect(zoomed.zoomToTimeRange).not.toHaveBeenCalled();
1622
+ expect(beforeTheZoom.resetZoom).not.toHaveBeenCalled();
1623
+ });
1624
+ }
1625
+
1626
+ for (const [chartName, chartType] of NOT_TIME_CHARTS) {
1627
+ test(`the ${chartName} is no time chart: zoomed by the viewer, a double-click on its loader zooms nothing out`, async () => {
1628
+ const zoomed: SpyZoom = spyZoom(true);
1629
+ render(inViewer(zoomed.zoom, view.render("zoomed", {})));
1630
+ await waitForTimeseries(view);
1631
+
1632
+ holdRequests();
1633
+ switchChartTo(chartType);
1634
+ expect(heldBodies(view.route)).toEqual([
1635
+ expect.objectContaining({ chartType: chartType }),
1636
+ ]);
1637
+
1638
+ const box: HTMLElement = loaderBox();
1639
+ expect(
1640
+ errorsThrownBy((): void => {
1641
+ doubleClickOn(box);
1642
+ waitOutTheDoubleClickWindow();
1643
+ }),
1644
+ ).toEqual([]);
1645
+ expect(zoomed.resetZoom).not.toHaveBeenCalled();
1646
+ expect(zoomed.zoomToTimeRange).not.toHaveBeenCalled();
1647
+
1648
+ await answerHeldRequests();
1649
+ await waitFor(() => {
1650
+ expect(screen.getByText(view.groupText)).toBeInTheDocument();
1651
+ });
1652
+ });
1653
+
1654
+ /*
1655
+ * A host's handlers reach the view whatever chart it shows (see
1656
+ * resolveChartTimeRangeZoom), so here only the loader itself keeps the
1657
+ * double-click from the host's reset.
1658
+ */
1659
+ test(`the ${chartName} is no time chart: zoomed by its host, a double-click on its loader does not reach the host's reset`, async () => {
1660
+ const hostSelect: MockFunction = getJestMockFunction();
1661
+ const hostReset: MockFunction = getJestMockFunction();
1662
+ render(
1663
+ view.render("zoomed", {
1664
+ onTimeRangeSelect: hostSelect,
1665
+ onTimeRangeReset: hostReset,
1666
+ }),
1667
+ );
1668
+ await waitForTimeseries(view);
1669
+
1670
+ holdRequests();
1671
+ switchChartTo(chartType);
1672
+ expect(heldBodies(view.route)).toEqual([
1673
+ expect.objectContaining({ chartType: chartType }),
1674
+ ]);
1675
+
1676
+ doubleClickOn(loaderBox());
1677
+ waitOutTheDoubleClickWindow();
1678
+ expect(hostReset).not.toHaveBeenCalled();
1679
+ expect(hostSelect).not.toHaveBeenCalled();
1680
+
1681
+ await answerHeldRequests();
1682
+ await waitFor(() => {
1683
+ expect(screen.getByText(view.groupText)).toBeInTheDocument();
1684
+ });
1685
+ });
1686
+ }
1687
+ });
1688
+ }
1689
+
1690
+ /* An exception's Occurrence Trend */
1691
+
1692
+ const FINGERPRINT: string = "9f86d081884c7d659a2feaa0c55ad015";
1693
+ const SERVICE_ID: string = "60000000-0000-4000-8000-000000000001";
1694
+
1695
+ function trendRequests(): Array<Record<string, unknown>> {
1696
+ return requestsTo(EXCEPTIONS_HISTOGRAM_ROUTE);
1697
+ }
1698
+
1699
+ function spanOf(body: Record<string, unknown> | undefined): number {
1700
+ if (!body) {
1701
+ throw new Error("no such request");
1702
+ }
1703
+ return (
1704
+ new Date(String(body["endTime"])).getTime() -
1705
+ new Date(String(body["startTime"])).getTime()
1706
+ );
1707
+ }
1708
+
1709
+ function trendResetButton(): HTMLElement | null {
1710
+ return screen.queryByTestId(RESET_TIME_RANGE_ZOOM_BUTTON_TEST_ID);
1711
+ }
1712
+
1713
+ function renderTrend(): void {
1714
+ render(
1715
+ <ExceptionOccurrenceTrend
1716
+ fingerprint={FINGERPRINT}
1717
+ primaryEntityId={new ObjectID(SERVICE_ID)}
1718
+ />,
1719
+ );
1720
+ }
1721
+
1722
+ async function waitForTrendBars(): Promise<void> {
1723
+ await waitFor(() => {
1724
+ expect(
1725
+ chartRootIn(screen.getByTestId("exception-trend-plot")),
1726
+ ).toBeInTheDocument();
1727
+ });
1728
+ }
1729
+
1730
+ /*
1731
+ * A drag on the real chart, from the bar under one drawn tick label to the
1732
+ * bar under the next: the card zooms into those two bars.
1733
+ */
1734
+ function dragAcrossTwoBars(): void {
1735
+ const plot: HTMLElement = screen.getByTestId("exception-trend-plot");
1736
+ const chartRoot: Element = chartRootIn(plot);
1737
+ const ticks: Array<DrawnTick> = drawnTicks(plot);
1738
+ expect(ticks.length).toBeGreaterThanOrEqual(2);
1739
+ const from: PointerSpot = { clientX: ticks[0]!.x, clientY: POINTER_Y };
1740
+ const to: PointerSpot = { clientX: ticks[1]!.x, clientY: POINTER_Y };
1741
+
1742
+ restAt(chartRoot, from);
1743
+ press(chartRoot, from, 1);
1744
+ nextFrame();
1745
+ moveHeld(chartRoot, to);
1746
+ nextFrame();
1747
+ release(chartRoot, to, 1);
1748
+ }
1749
+
1750
+ describe("an exception's Occurrence Trend: the loader standing in for its bars", () => {
1751
+ test("after a zoom, a double-click on the loader of the zoomed window goes back to the preset, once", async () => {
1752
+ renderTrend();
1753
+ await waitForTrendBars();
1754
+ expect(trendRequests()).toHaveLength(1);
1755
+ expect(spanOf(trendRequests()[0])).toBe(DAY_MS);
1756
+
1757
+ // The zoom clears the bars and asks for the dragged window.
1758
+ holdRequests();
1759
+ dragAcrossTwoBars();
1760
+ expect(trendRequests()).toHaveLength(2);
1761
+ expect(spanOf(trendRequests()[1])).toBeLessThan(DAY_MS);
1762
+ expect(heldBodies(EXCEPTIONS_HISTOGRAM_ROUTE)).toEqual([
1763
+ trendRequests()[1],
1764
+ ]);
1765
+ expect(trendResetButton()).toBeInTheDocument();
1766
+ expect(
1767
+ screen.getByText("Loading occurrences for the selected window…"),
1768
+ ).toBeInTheDocument();
1769
+ expect(screen.queryByTestId("exception-trend-plot")).toBeNull();
1770
+
1771
+ const box: HTMLElement = loaderBox();
1772
+ expect(box).toHaveClass("select-none");
1773
+ doubleClickOn(box);
1774
+
1775
+ // Back on the whole preset: one request for the last 24 hours.
1776
+ expect(trendRequests()).toHaveLength(3);
1777
+ expect(spanOf(trendRequests()[2])).toBe(DAY_MS);
1778
+ expect(trendResetButton()).toBeNull();
1779
+ expect(
1780
+ screen.getByText("Loading occurrences for the last 24 hours…"),
1781
+ ).toBeInTheDocument();
1782
+
1783
+ waitOutTheDoubleClickWindow();
1784
+ expect(trendRequests()).toHaveLength(3);
1785
+
1786
+ await answerHeldRequests();
1787
+ await waitForTrendBars();
1788
+ expect(
1789
+ screen.getByText(/occurrences in the last 24 hours$/),
1790
+ ).toBeInTheDocument();
1791
+ expect(trendRequests()).toHaveLength(3);
1792
+ });
1793
+
1794
+ /*
1795
+ * The zoomed window's bars land in the middle of the double-click on
1796
+ * the loader: they take its place between the second press and its
1797
+ * release, and no dblclick reaches them - the card keeps the loader's
1798
+ * box for them, without its handlers. Before commit b6ec899df4 the
1799
+ * loader took only the dblclick, and the card stayed on the zoomed
1800
+ * window.
1801
+ */
1802
+ for (const ending of LOADER_RACE_ENDINGS) {
1803
+ test(`after a zoom, a double-click on the loader whose second press the bars land under - ${ending.name} - goes back to the preset once, in the next task`, async () => {
1804
+ renderTrend();
1805
+ await waitForTrendBars();
1806
+
1807
+ holdRequests();
1808
+ dragAcrossTwoBars();
1809
+ expect(trendRequests()).toHaveLength(2);
1810
+ const pressed: HTMLElement = ending.pressTarget();
1811
+
1812
+ clickThenPressAgain(pressed);
1813
+ await answerHeldRequests();
1814
+ await waitForTrendBars();
1815
+
1816
+ const chartArea: HTMLElement = screen.getByTestId("exception-trend-plot");
1817
+ expectChartInTheLoadersPlace(chartArea);
1818
+ expect(trendResetButton()).toBeInTheDocument();
1819
+
1820
+ browserSendsTheClicks(pressed, ending.release(chartArea, 0));
1821
+
1822
+ // No dblclick reached the bars: the reset waits for the next task.
1823
+ expect(trendRequests()).toHaveLength(2);
1824
+
1825
+ nextTask();
1826
+
1827
+ // Back on the whole preset: one request for the last 24 hours.
1828
+ expect(trendRequests()).toHaveLength(3);
1829
+ expect(spanOf(trendRequests()[2])).toBe(DAY_MS);
1830
+ expect(trendResetButton()).toBeNull();
1831
+
1832
+ waitOutTheDoubleClickWindow();
1833
+ runEveryPendingTimer();
1834
+ await waitForTrendBars();
1835
+ expect(trendRequests()).toHaveLength(3);
1836
+ expect(
1837
+ screen.getByText(/occurrences in the last 24 hours$/),
1838
+ ).toBeInTheDocument();
1839
+ });
1840
+ }
1841
+
1842
+ test("before any zoom, a double-click on the first load's loader asks for nothing and throws nothing", async () => {
1843
+ holdRequests();
1844
+ renderTrend();
1845
+ expect(trendRequests()).toHaveLength(1);
1846
+
1847
+ const box: HTMLElement = loaderBox();
1848
+ expect(
1849
+ errorsThrownBy((): void => {
1850
+ doubleClickOn(box);
1851
+ waitOutTheDoubleClickWindow();
1852
+ }),
1853
+ ).toEqual([]);
1854
+ expect(trendRequests()).toHaveLength(1);
1855
+ expect(trendResetButton()).toBeNull();
1856
+
1857
+ await answerHeldRequests();
1858
+ await waitForTrendBars();
1859
+ expect(trendRequests()).toHaveLength(1);
1860
+ });
1861
+ });