@oneuptime/common 14.0.8 → 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 (639) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  3. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  4. package/Models/DatabaseModels/Project.ts +55 -5
  5. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  6. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  7. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  8. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  9. package/Server/API/AIChatAPI.ts +6 -3
  10. package/Server/API/AIInvestigationAPI.ts +17 -10
  11. package/Server/API/AutoRemediationAPI.ts +207 -25
  12. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  13. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  14. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  15. package/Server/Infrastructure/GlobalCache.ts +32 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/TelemetryIngest.ts +41 -18
  21. package/Server/Services/AIService.ts +70 -1
  22. package/Server/Services/AlertService.ts +352 -11
  23. package/Server/Services/AlertSeverityService.ts +65 -15
  24. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  25. package/Server/Services/IncidentService.ts +280 -104
  26. package/Server/Services/IncidentSeverityService.ts +66 -15
  27. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  28. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  29. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  30. package/Server/Services/KubernetesClusterService.ts +129 -21
  31. package/Server/Services/ProjectService.ts +32 -0
  32. package/Server/Services/ProxmoxResourceService.ts +322 -5
  33. package/Server/Services/RunnerJobService.ts +241 -104
  34. package/Server/Services/RunnerService.ts +1 -1
  35. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  36. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  37. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  38. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  39. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  40. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  41. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  42. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  43. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  44. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  45. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  46. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  47. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  48. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  49. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  50. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  51. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  52. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  53. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  54. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  55. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  56. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  57. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  58. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  59. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  60. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  61. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  62. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  63. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  64. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  65. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  66. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  67. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  68. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  69. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  70. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  71. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  72. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  73. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  74. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  75. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  76. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  77. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  78. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  79. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  80. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  81. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  82. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  83. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  84. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  85. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  86. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  87. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  88. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  89. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  90. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  91. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  92. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  93. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  94. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  95. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  96. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  97. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  98. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  99. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  100. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  101. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  102. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  103. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  104. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  105. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  106. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  107. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  108. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  109. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  110. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  111. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  112. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  113. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  114. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  115. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  116. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  117. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  118. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  119. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  120. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  121. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  122. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  123. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  124. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  125. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  126. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  127. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  128. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  129. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  130. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  131. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  132. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  133. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  134. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  135. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  136. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  137. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  138. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  139. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  140. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  141. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  142. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  143. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  144. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  145. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  146. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  147. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  148. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  149. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  150. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  151. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  152. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  153. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  154. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  155. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  156. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  157. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  158. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  159. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  160. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  161. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  162. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  163. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  164. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  165. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  166. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  167. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  168. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  169. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  170. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  171. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  172. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  173. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  174. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  175. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  176. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  177. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  178. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  179. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  180. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  181. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  182. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  183. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  184. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  185. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  186. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  187. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  188. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  189. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  190. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  191. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  192. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  193. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  194. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  195. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  196. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  197. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  198. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  199. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  200. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  201. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  202. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  203. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  204. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  205. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  206. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  207. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  208. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  209. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  210. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  211. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  212. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  213. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  214. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  215. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  216. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  217. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  218. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  219. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  220. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  221. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  222. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  223. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  224. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  225. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  226. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  227. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  228. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  229. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  230. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  231. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  232. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  233. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  234. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  235. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  236. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  237. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  238. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  239. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  240. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  241. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  242. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  243. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  244. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  245. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  246. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  247. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  248. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  249. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  250. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  251. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  252. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  253. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  254. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  255. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  256. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  257. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  258. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  259. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  260. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  261. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  262. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  263. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  264. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  265. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  266. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  267. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  268. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  269. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  270. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  271. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  272. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  273. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  274. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  275. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  276. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  277. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  278. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  279. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  280. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  281. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  282. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  283. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  284. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  285. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  286. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  287. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  288. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  289. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  290. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  291. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  292. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  293. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  294. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  295. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  296. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  297. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  298. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  299. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  300. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  301. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  302. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  303. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  304. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  305. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  306. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  307. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  308. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  309. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  310. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  311. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  312. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  313. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  314. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  315. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  316. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  317. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  318. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  319. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  320. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  321. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  322. package/Tests/UI/Components/Input.test.tsx +80 -1
  323. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  324. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  325. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  326. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  327. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  328. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  329. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  330. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  331. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  332. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  333. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  334. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  335. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  336. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  337. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  338. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  339. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  340. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  341. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  342. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  343. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  344. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  345. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  346. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  347. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  348. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  349. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  350. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  351. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  352. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  353. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  354. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  355. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  356. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  357. package/Types/AI/AIRunType.ts +9 -4
  358. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  359. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  360. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  361. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  362. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  363. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  364. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  365. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  366. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  367. package/Types/Team/ComplianceRule.ts +434 -0
  368. package/Types/Team/ComplianceRuleType.ts +21 -0
  369. package/Types/Team/TeamComplianceStatus.ts +81 -0
  370. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  371. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  372. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  373. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  374. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  375. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  376. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  377. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  378. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  379. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  380. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  381. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  382. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  383. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  384. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  385. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  386. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  387. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  388. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  389. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  390. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  391. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  392. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  393. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  394. package/UI/Components/Input/Input.tsx +90 -57
  395. package/UI/Components/List/ListRow.tsx +8 -74
  396. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  397. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  398. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  399. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  400. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  401. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  402. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  403. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  404. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  405. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  406. package/UI/Components/Table/TableBody.tsx +8 -2
  407. package/UI/Components/Table/TableRow.tsx +20 -140
  408. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  409. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  410. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  411. package/UI/Components/TelemetryViewer/types.ts +2 -0
  412. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  413. package/UI/Components/Toggle/Toggle.tsx +26 -0
  414. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  415. package/UI/Styles/Theme.css +14 -2
  416. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  417. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  418. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  419. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  420. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  421. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  422. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  423. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  424. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  425. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  426. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  427. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  428. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  429. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  430. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  431. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  432. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  433. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  434. package/build/dist/Server/API/AIChatAPI.js +6 -1
  435. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  436. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  437. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  438. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  439. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  440. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  441. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  442. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  443. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  444. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  445. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  446. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  447. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  448. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  449. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  450. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  451. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  452. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  453. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  454. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  455. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  456. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  457. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  458. package/build/dist/Server/Services/AIService.js +59 -1
  459. package/build/dist/Server/Services/AIService.js.map +1 -1
  460. package/build/dist/Server/Services/AlertService.js +248 -13
  461. package/build/dist/Server/Services/AlertService.js.map +1 -1
  462. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  463. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  464. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  465. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  466. package/build/dist/Server/Services/IncidentService.js +199 -72
  467. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  468. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  469. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  470. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  471. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  472. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  473. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  474. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  475. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  476. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  477. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  478. package/build/dist/Server/Services/ProjectService.js +22 -0
  479. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  480. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  481. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  482. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  483. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  484. package/build/dist/Server/Services/RunnerService.js +1 -1
  485. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  486. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  487. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  488. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  489. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  490. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  491. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  492. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  493. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  494. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  495. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  496. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  497. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  498. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  499. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  500. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  501. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  502. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  503. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  504. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  505. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  506. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  507. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  508. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  509. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  510. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  511. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  512. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  513. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  514. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  515. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  516. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  517. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  518. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  519. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  520. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  521. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  522. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  523. package/build/dist/Types/AI/AIRunType.js +9 -4
  524. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  525. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  526. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  527. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  528. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  529. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  530. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  531. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  532. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  533. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  534. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  535. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  536. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  537. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  538. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  539. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  540. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  541. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  542. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  543. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  544. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  545. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  546. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  547. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  548. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  549. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  550. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  551. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  552. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  553. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  554. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  555. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  556. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  557. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  558. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  559. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  560. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  561. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  562. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  563. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  564. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  565. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  566. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  567. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  568. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  569. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  570. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  571. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  572. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  573. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  574. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  575. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  576. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  577. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  578. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  579. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  580. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  581. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  582. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  583. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  584. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  585. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  586. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  587. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  588. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  589. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  590. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  591. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  592. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  593. package/build/dist/UI/Components/Input/Input.js +69 -54
  594. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  595. package/build/dist/UI/Components/List/ListRow.js +4 -39
  596. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  597. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  598. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  599. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  600. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  601. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  602. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  603. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  604. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  605. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  606. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  607. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  608. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  609. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  610. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  611. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  612. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  613. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  614. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  615. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  616. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  617. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  618. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  619. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  620. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  621. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  622. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  623. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  624. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  625. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  626. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  627. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  628. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  629. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  630. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  631. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  632. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  633. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  634. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  635. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  636. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  637. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  638. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  639. package/package.json +1 -1
@@ -0,0 +1,1292 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeAll,
5
+ beforeEach,
6
+ describe,
7
+ expect,
8
+ jest,
9
+ test,
10
+ } from "@jest/globals";
11
+ import {
12
+ RenderResult,
13
+ act,
14
+ cleanup,
15
+ fireEvent,
16
+ render,
17
+ screen,
18
+ waitFor,
19
+ within,
20
+ } from "@testing-library/react";
21
+ import * as React from "react";
22
+ import getJestMockFunction, { MockFunction } from "../../MockType";
23
+
24
+ /*
25
+ * Issue #4105, review finding dash-1: a drag across a Value widget's
26
+ * sparkline crashed the whole dashboard when the zoomed line came back
27
+ * with fewer points.
28
+ *
29
+ * The line remembered the point under the pointer as an index into the
30
+ * points it was drawing. A drag always ends with the pointer resting on the
31
+ * line, the board reloads the widget for the dragged window, and a narrower
32
+ * window has fewer points: "Past 1 Hour" draws 60, ten minutes of it 11. The
33
+ * render that drew the new line read the remembered index past its end and
34
+ * threw, and the error boundary above replaced the whole page - on the
35
+ * Component Settings preview, taking unsaved edits with it. A refresh that
36
+ * lands one point fewer in the middle of a drag did the same through the
37
+ * drag's own ends.
38
+ *
39
+ * The first half drives the view directly and hands it the next line the
40
+ * way a host would; the second goes through the real widgets and the real
41
+ * zoom hooks both dashboard shells and the settings preview use, over a
42
+ * data layer that answers with one point per minute of whatever window it
43
+ * is asked for - so a zoom really does shrink the line.
44
+ */
45
+
46
+ const MINUTE_MS: number = 60 * 1000;
47
+ const BOARD_START_MS: number = Date.UTC(2026, 8, 28, 10, 0, 0);
48
+
49
+ function at(minute: number): Date {
50
+ return new Date(BOARD_START_MS + minute * MINUTE_MS);
51
+ }
52
+
53
+ const fetchResultsMock: MockFunction = getJestMockFunction();
54
+ const fetchTimeSeriesMock: MockFunction = getJestMockFunction();
55
+
56
+ /*
57
+ * The arrow wrappers are load bearing: jest.mock is hoisted above the
58
+ * compiled requires, so the mock variables above are still unassigned when
59
+ * the factory runs. Dereferencing them lazily, at call time, is what makes
60
+ * this work.
61
+ */
62
+ jest.mock(
63
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/Utils/Metrics",
64
+ () => {
65
+ return {
66
+ __esModule: true,
67
+ default: {
68
+ fetchResults: (...args: Array<any>) => {
69
+ return fetchResultsMock(...args);
70
+ },
71
+ clearQueryTopNOverridesForScope: () => {
72
+ return undefined;
73
+ },
74
+ },
75
+ };
76
+ },
77
+ );
78
+
79
+ jest.mock(
80
+ "../../../../App/FeatureSet/Dashboard/src/Utils/DataSourceQuery",
81
+ () => {
82
+ return {
83
+ __esModule: true,
84
+ default: {
85
+ fetchTimeSeries: (...args: Array<any>) => {
86
+ return fetchTimeSeriesMock(...args);
87
+ },
88
+ },
89
+ };
90
+ },
91
+ );
92
+
93
+ // The settings form is not under test; the preview above it is.
94
+ jest.mock(
95
+ "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/ArgumentsForm",
96
+ () => {
97
+ const react: typeof React = jest.requireActual("react") as typeof React;
98
+ return {
99
+ __esModule: true,
100
+ default: () => {
101
+ return react.createElement("div", { "data-testid": "arguments-form" });
102
+ },
103
+ };
104
+ },
105
+ );
106
+
107
+ import ValueWidgetView, {
108
+ SPARKLINE_SELECTION_TEST_ID,
109
+ SPARKLINE_TEST_ID,
110
+ Sparkline,
111
+ SparklinePoint,
112
+ ValueWidgetViewProps,
113
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/ValueWidgetView";
114
+ import DashboardValueComponentElement from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardValueComponent";
115
+ import DashboardDataSourceValueComponentElement from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardDataSourceValueComponent";
116
+ import { DashboardBaseComponentProps } from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardBaseComponent";
117
+ import DashboardCanvas from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/Index";
118
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
119
+ import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
120
+ import DashboardDataSourceValueComponent, {
121
+ DataSourceValueReduce,
122
+ } from "../../../Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent";
123
+ import DashboardValueComponent from "../../../Types/Dashboard/DashboardComponents/DashboardValueComponent";
124
+ import DashboardViewConfig from "../../../Types/Dashboard/DashboardViewConfig";
125
+ import { ObjectType } from "../../../Types/JSON";
126
+ import MetricsAggregationType from "../../../Types/Metrics/MetricsAggregationType";
127
+ import MetricViewData from "../../../Types/Metrics/MetricViewData";
128
+ import ObjectID from "../../../Types/ObjectID";
129
+ import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
130
+ import TimeRange from "../../../Types/Time/TimeRange";
131
+ import useDashboardTimeRangeZoom, {
132
+ DashboardTimeRangeZoom,
133
+ } from "../../../UI/Utils/UseDashboardTimeRangeZoom";
134
+
135
+ // What an error boundary above the widget shows once a render has thrown.
136
+ const CRASHED_TEST_ID: string = "widget-crashed";
137
+
138
+ interface CrashBoundaryProps {
139
+ children: React.ReactNode;
140
+ }
141
+
142
+ interface CrashBoundaryState {
143
+ error: Error | null;
144
+ }
145
+
146
+ /*
147
+ * Stands for the application's error boundary, which replaces the whole
148
+ * dashboard page with its fallback: a throw anywhere under it shows here.
149
+ */
150
+ class CrashBoundary extends React.Component<
151
+ CrashBoundaryProps,
152
+ CrashBoundaryState
153
+ > {
154
+ public constructor(props: CrashBoundaryProps) {
155
+ super(props);
156
+ this.state = { error: null };
157
+ }
158
+
159
+ public static getDerivedStateFromError(error: Error): CrashBoundaryState {
160
+ return { error: error };
161
+ }
162
+
163
+ public override render(): React.ReactNode {
164
+ if (this.state.error) {
165
+ return (
166
+ <div data-testid={CRASHED_TEST_ID}>
167
+ {`${this.state.error.name}: ${this.state.error.message}`}
168
+ </div>
169
+ );
170
+ }
171
+
172
+ return this.props.children;
173
+ }
174
+ }
175
+
176
+ function crashed(): HTMLElement | null {
177
+ return screen.queryByTestId(CRASHED_TEST_ID);
178
+ }
179
+
180
+ /*
181
+ * `count` points a minute apart from `firstMinute`, each worth 100 plus its
182
+ * minute: every point reads differently, and differently from any
183
+ * aggregate the tests hand the view.
184
+ */
185
+ function minutePoints(
186
+ firstMinute: number,
187
+ count: number,
188
+ ): Array<SparklinePoint> {
189
+ const points: Array<SparklinePoint> = [];
190
+
191
+ for (
192
+ let minute: number = firstMinute;
193
+ minute < firstMinute + count;
194
+ minute++
195
+ ) {
196
+ points.push({ timestamp: at(minute), value: 100 + minute });
197
+ }
198
+
199
+ return points;
200
+ }
201
+
202
+ /*
203
+ * jsdom lays nothing out, so the sparkline would read every pointer as
204
+ * being over its first point. Give it a real box, BOX_LEFT from the left
205
+ * and as wide as it draws, and hand back the pointer x over any point of
206
+ * the line as it is drawn right now.
207
+ *
208
+ * That x is rounded to a whole pixel: jsdom truncates a mouse event's
209
+ * clientX, and an hour of points on a 120px line sits under 2px apart, so
210
+ * a truncated x can land nearer the point before.
211
+ */
212
+ const BOX_LEFT: number = 100;
213
+ const PADDING: number = 4;
214
+
215
+ interface LaidOutSparkline {
216
+ svg: SVGSVGElement;
217
+ xOf: (index: number) => number;
218
+ }
219
+
220
+ function sparklineIn(container: HTMLElement | null): SVGSVGElement {
221
+ return (container ? within(container) : screen).getByTestId(
222
+ SPARKLINE_TEST_ID,
223
+ ) as unknown as SVGSVGElement;
224
+ }
225
+
226
+ function pointCountOf(svg: SVGSVGElement): number {
227
+ return (svg.querySelector("polyline")!.getAttribute("points") || "")
228
+ .trim()
229
+ .split(" ").length;
230
+ }
231
+
232
+ function layOut(container: HTMLElement | null = null): LaidOutSparkline {
233
+ const svg: SVGSVGElement = sparklineIn(container);
234
+ const width: number = Number(svg.getAttribute("width"));
235
+
236
+ svg.getBoundingClientRect = (): DOMRect => {
237
+ return {
238
+ left: BOX_LEFT,
239
+ top: 0,
240
+ right: BOX_LEFT + width,
241
+ bottom: 20,
242
+ width: width,
243
+ height: 20,
244
+ x: BOX_LEFT,
245
+ y: 0,
246
+ toJSON: (): string => {
247
+ return "";
248
+ },
249
+ } as DOMRect;
250
+ };
251
+
252
+ return {
253
+ svg: svg,
254
+ xOf: (index: number): number => {
255
+ const lastIndex: number = pointCountOf(svg) - 1;
256
+ return Math.round(
257
+ BOX_LEFT + PADDING + (index / lastIndex) * (width - PADDING * 2),
258
+ );
259
+ },
260
+ };
261
+ }
262
+
263
+ // The hover marker: the ring the line draws on the point it names.
264
+ function hoverMarkerIn(svg: SVGSVGElement): Element | null {
265
+ return svg.querySelector("circle");
266
+ }
267
+
268
+ function hoverOver(sparkline: LaidOutSparkline, index: number): void {
269
+ fireEvent.mouseMove(sparkline.svg, {
270
+ clientX: sparkline.xOf(index),
271
+ buttons: 0,
272
+ });
273
+ }
274
+
275
+ function pressOn(sparkline: LaidOutSparkline, index: number): void {
276
+ fireEvent.mouseDown(sparkline.svg, {
277
+ clientX: sparkline.xOf(index),
278
+ button: 0,
279
+ });
280
+ }
281
+
282
+ function dragOnTo(sparkline: LaidOutSparkline, index: number): void {
283
+ fireEvent.mouseMove(sparkline.svg, {
284
+ clientX: sparkline.xOf(index),
285
+ buttons: 1,
286
+ });
287
+ }
288
+
289
+ function releaseOn(sparkline: LaidOutSparkline, index: number): void {
290
+ fireEvent.mouseUp(sparkline.svg, { clientX: sparkline.xOf(index) });
291
+ }
292
+
293
+ /*
294
+ * A reader's drag: onto the line, press, across, let go - and the pointer
295
+ * stays where it was let go, on the line.
296
+ */
297
+ function dragAcross(
298
+ sparkline: LaidOutSparkline,
299
+ fromIndex: number,
300
+ toIndex: number,
301
+ ): void {
302
+ hoverOver(sparkline, fromIndex);
303
+ pressOn(sparkline, fromIndex);
304
+ dragOnTo(sparkline, toIndex);
305
+ releaseOn(sparkline, toIndex);
306
+ }
307
+
308
+ function windowsOf(mock: MockFunction): Array<[number, number]> {
309
+ return mock.mock.calls.map((call: Array<unknown>): [number, number] => {
310
+ return [(call[0] as Date).getTime(), (call[1] as Date).getTime()];
311
+ });
312
+ }
313
+
314
+ afterEach(() => {
315
+ cleanup();
316
+ });
317
+
318
+ /*
319
+ * What the line tells whoever draws the read-out: the index of the point
320
+ * under the pointer, and null as soon as that index names no point on the
321
+ * line as it is now. The view holds only that index, so these are the
322
+ * moments it must hear about.
323
+ */
324
+ describe("the Sparkline's word to its host about the point under the pointer", () => {
325
+ let onHoverIndex: MockFunction;
326
+ let onSelect: MockFunction;
327
+
328
+ function line(points: Array<SparklinePoint>): React.ReactElement {
329
+ return (
330
+ <Sparkline
331
+ data={points}
332
+ width={120}
333
+ height={24}
334
+ color="#6366f1"
335
+ fillColor="rgba(99, 102, 241, 0.08)"
336
+ onHoverIndex={onHoverIndex as unknown as (index: number | null) => void}
337
+ onTimeRangeSelect={
338
+ onSelect as unknown as (startTime: Date, endTime: Date) => void
339
+ }
340
+ />
341
+ );
342
+ }
343
+
344
+ // Everything the host has been told, in order.
345
+ function told(): Array<number | null> {
346
+ return onHoverIndex.mock.calls.map(
347
+ (call: Array<unknown>): number | null => {
348
+ return call[0] as number | null;
349
+ },
350
+ );
351
+ }
352
+
353
+ beforeEach(() => {
354
+ onHoverIndex = getJestMockFunction();
355
+ onSelect = getJestMockFunction();
356
+ });
357
+
358
+ test("a move names the point under the pointer by its index in the data", () => {
359
+ render(line(minutePoints(0, 6)));
360
+
361
+ hoverOver(layOut(), 4);
362
+
363
+ expect(told()).toEqual([4]);
364
+ });
365
+
366
+ test("a drag that zooms lets go of the hover before it hands the window up", () => {
367
+ render(line(minutePoints(0, 6)));
368
+
369
+ dragAcross(layOut(), 1, 3);
370
+
371
+ expect(windowsOf(onSelect)).toEqual([[at(1).getTime(), at(4).getTime()]]);
372
+ expect(told()[told().length - 1]).toBeNull();
373
+ /*
374
+ * Told first, so nothing the zoom renders on its way in can read the
375
+ * point the pointer was over in the window being left.
376
+ */
377
+ const hoverCalls: Array<number> = onHoverIndex.mock.invocationCallOrder;
378
+ expect(hoverCalls[hoverCalls.length - 1]!).toBeLessThan(
379
+ onSelect.mock.invocationCallOrder[0]!,
380
+ );
381
+ });
382
+
383
+ test("a press and release on one point zooms nothing, so the hover stands", () => {
384
+ render(line(minutePoints(0, 6)));
385
+ const sparkline: LaidOutSparkline = layOut();
386
+
387
+ hoverOver(sparkline, 2);
388
+ pressOn(sparkline, 2);
389
+ releaseOn(sparkline, 2);
390
+
391
+ expect(onSelect).not.toHaveBeenCalled();
392
+ // Never told to let go: the read-out still shows the point.
393
+ expect(told()).toEqual([2]);
394
+ expect(hoverMarkerIn(sparkline.svg)).not.toBeNull();
395
+ });
396
+
397
+ test("redrawn with as many points the hover stands; with a different number it is let go of, once", () => {
398
+ const rendered: RenderResult = render(line(minutePoints(0, 6)));
399
+ hoverOver(layOut(), 2);
400
+
401
+ // A rolling window moved on a minute: six points again.
402
+ rendered.rerender(line(minutePoints(1, 6)));
403
+ expect(told()).toEqual([2]);
404
+ expect(hoverMarkerIn(sparklineIn(null))).not.toBeNull();
405
+
406
+ rendered.rerender(line(minutePoints(0, 4)));
407
+ expect(told()).toEqual([2, null]);
408
+ expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
409
+
410
+ // Nothing more to say while the count holds.
411
+ rendered.rerender(line(minutePoints(2, 4)));
412
+ expect(told()).toEqual([2, null]);
413
+ });
414
+
415
+ test("a line cut to one point draws nothing, and lets go of the hover", () => {
416
+ const rendered: RenderResult = render(line(minutePoints(0, 6)));
417
+ hoverOver(layOut(), 5);
418
+
419
+ rendered.rerender(line(minutePoints(0, 1)));
420
+
421
+ expect(told()).toEqual([5, null]);
422
+ expect(screen.queryByTestId(SPARKLINE_TEST_ID)).toBeNull();
423
+ });
424
+
425
+ test("taken away while hovered, it lets go of the hover", () => {
426
+ const rendered: RenderResult = render(line(minutePoints(0, 6)));
427
+ hoverOver(layOut(), 3);
428
+
429
+ rendered.unmount();
430
+
431
+ expect(told()).toEqual([3, null]);
432
+ });
433
+ });
434
+
435
+ describe("the sparkline under a line that changes beneath the pointer", () => {
436
+ /*
437
+ * Tall enough that the layout admits the sparkline: it only does once the
438
+ * number is already at its largest size.
439
+ */
440
+ const baseViewProps: ValueWidgetViewProps = {
441
+ widthInPx: 400,
442
+ heightInPx: 320,
443
+ isEditMode: false,
444
+ isLoading: false,
445
+ hasEverLoaded: true,
446
+ error: null,
447
+ value: 42,
448
+ points: minutePoints(0, 60),
449
+ noDataMessage: "No data for the selected time range",
450
+ isConfigured: true,
451
+ setupTitle: "Value",
452
+ setupMessage: "Click to configure",
453
+ title: "Requests",
454
+ rawUnit: "",
455
+ metricName: "",
456
+ hideUnit: false,
457
+ warningThreshold: undefined,
458
+ criticalThreshold: undefined,
459
+ trendDirection: undefined,
460
+ };
461
+
462
+ let onSelect: MockFunction;
463
+ let rendered: RenderResult;
464
+
465
+ function view(overrides: Partial<ValueWidgetViewProps>): React.ReactElement {
466
+ return (
467
+ <CrashBoundary>
468
+ <ValueWidgetView
469
+ {...baseViewProps}
470
+ onTimeRangeSelect={
471
+ onSelect as unknown as (startTime: Date, endTime: Date) => void
472
+ }
473
+ {...overrides}
474
+ />
475
+ </CrashBoundary>
476
+ );
477
+ }
478
+
479
+ function renderView(overrides: Partial<ValueWidgetViewProps> = {}): void {
480
+ rendered = render(view(overrides));
481
+ }
482
+
483
+ // What the host hands down next: a zoom or a refresh landed.
484
+ function handDown(overrides: Partial<ValueWidgetViewProps>): void {
485
+ rendered.rerender(view(overrides));
486
+ }
487
+
488
+ beforeEach(() => {
489
+ onSelect = getJestMockFunction();
490
+ });
491
+
492
+ describe("a zoom that lands fewer points", () => {
493
+ test("the drag lets go of the hover as it zooms, and the zoomed line draws without throwing", () => {
494
+ renderView();
495
+ const sparkline: LaidOutSparkline = layOut();
496
+
497
+ hoverOver(sparkline, 20);
498
+ pressOn(sparkline, 20);
499
+ dragOnTo(sparkline, 30);
500
+ // While dragging, the read-out names the point the window ends on.
501
+ expect(screen.getByText("130")).toBeInTheDocument();
502
+
503
+ releaseOn(sparkline, 30);
504
+
505
+ expect(windowsOf(onSelect)).toEqual([
506
+ [at(20).getTime(), at(31).getTime()],
507
+ ]);
508
+ /*
509
+ * The pointer has not moved, but the point it was naming belongs to
510
+ * the window being left: the aggregate is back and nothing is marked.
511
+ */
512
+ expect(screen.getByText("42")).toBeInTheDocument();
513
+ expect(screen.queryByText("130")).toBeNull();
514
+ expect(hoverMarkerIn(sparkline.svg)).toBeNull();
515
+
516
+ // The board hands down the zoomed window: 10:20 to 10:30.
517
+ handDown({ points: minutePoints(20, 11), value: 7 });
518
+
519
+ expect(crashed()).toBeNull();
520
+ expect(pointCountOf(sparklineIn(null))).toBe(11);
521
+ expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
522
+ expect(screen.getByText("7")).toBeInTheDocument();
523
+ });
524
+
525
+ test("a pointer moved while the zoom loads names a point of the old line, and the new line lets go of it", () => {
526
+ renderView();
527
+ const sparkline: LaidOutSparkline = layOut();
528
+ dragAcross(sparkline, 20, 30);
529
+
530
+ // The zoomed window is still loading; the old line is still drawn.
531
+ hoverOver(sparkline, 33);
532
+ expect(screen.getByText("133")).toBeInTheDocument();
533
+ expect(hoverMarkerIn(sparkline.svg)).not.toBeNull();
534
+
535
+ handDown({ points: minutePoints(20, 11), value: 7 });
536
+
537
+ expect(crashed()).toBeNull();
538
+ expect(pointCountOf(sparklineIn(null))).toBe(11);
539
+ expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
540
+ expect(screen.getByText("7")).toBeInTheDocument();
541
+ expect(screen.queryByText("133")).toBeNull();
542
+ });
543
+
544
+ test("the hover it let go of stays gone when a reset brings the longer line back", () => {
545
+ renderView();
546
+ const sparkline: LaidOutSparkline = layOut();
547
+ dragAcross(sparkline, 20, 30);
548
+ hoverOver(sparkline, 33);
549
+ handDown({ points: minutePoints(20, 11), value: 7 });
550
+
551
+ /*
552
+ * The pointer never moved. The place it was over is a place on the
553
+ * old line again, but nothing has told the line the pointer is there.
554
+ */
555
+ handDown({ points: minutePoints(0, 60), value: 42 });
556
+
557
+ expect(crashed()).toBeNull();
558
+ expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
559
+ expect(screen.getByText("42")).toBeInTheDocument();
560
+ expect(screen.queryByText("133")).toBeNull();
561
+ });
562
+
563
+ test("a right-to-left drag, released past the end of the zoomed line, draws it too", () => {
564
+ renderView();
565
+ const sparkline: LaidOutSparkline = layOut();
566
+
567
+ dragAcross(sparkline, 50, 30);
568
+ hoverOver(sparkline, 45);
569
+
570
+ expect(windowsOf(onSelect)).toEqual([
571
+ [at(30).getTime(), at(51).getTime()],
572
+ ]);
573
+
574
+ handDown({ points: minutePoints(30, 21), value: 9 });
575
+
576
+ expect(crashed()).toBeNull();
577
+ expect(pointCountOf(sparklineIn(null))).toBe(21);
578
+ expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
579
+ expect(screen.getByText("9")).toBeInTheDocument();
580
+ });
581
+
582
+ test("a plain click is no zoom, so the hover stays on the point", () => {
583
+ renderView();
584
+ const sparkline: LaidOutSparkline = layOut();
585
+
586
+ hoverOver(sparkline, 12);
587
+ pressOn(sparkline, 12);
588
+ releaseOn(sparkline, 12);
589
+
590
+ expect(onSelect).not.toHaveBeenCalled();
591
+ expect(screen.getByText("112")).toBeInTheDocument();
592
+ expect(hoverMarkerIn(sparkline.svg)).not.toBeNull();
593
+ });
594
+ });
595
+
596
+ describe("a refresh that lands under a resting pointer", () => {
597
+ test("with as many points as before, the hover stays and reads the point now at that place", () => {
598
+ renderView({ points: minutePoints(0, 6) });
599
+ const sparkline: LaidOutSparkline = layOut();
600
+
601
+ hoverOver(sparkline, 3);
602
+ expect(screen.getByText("103")).toBeInTheDocument();
603
+
604
+ // The same six minutes, re-measured: every value moved.
605
+ handDown({
606
+ points: minutePoints(0, 6).map(
607
+ (point: SparklinePoint): SparklinePoint => {
608
+ return { timestamp: point.timestamp, value: point.value + 50 };
609
+ },
610
+ ),
611
+ });
612
+
613
+ expect(crashed()).toBeNull();
614
+ expect(hoverMarkerIn(sparklineIn(null))).not.toBeNull();
615
+ // The point under the pointer as it reads now, not as it read before.
616
+ expect(screen.getByText("153")).toBeInTheDocument();
617
+ expect(screen.queryByText("103")).toBeNull();
618
+ });
619
+
620
+ test("with a different number of points, the pointer is over another place, so the hover is let go of", () => {
621
+ renderView({ points: minutePoints(0, 6) });
622
+ const sparkline: LaidOutSparkline = layOut();
623
+
624
+ hoverOver(sparkline, 2);
625
+ expect(screen.getByText("102")).toBeInTheDocument();
626
+
627
+ // A seventh minute came in: every point sits somewhere else now.
628
+ handDown({ points: minutePoints(0, 7) });
629
+
630
+ expect(crashed()).toBeNull();
631
+ expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
632
+ expect(screen.getByText("42")).toBeInTheDocument();
633
+ expect(screen.queryByText("102")).toBeNull();
634
+ });
635
+
636
+ test("a line that goes away takes the read-out with it, so it cannot come back stale", () => {
637
+ renderView({ points: minutePoints(0, 6) });
638
+ const sparkline: LaidOutSparkline = layOut();
639
+ hoverOver(sparkline, 2);
640
+ expect(screen.getByText("102")).toBeInTheDocument();
641
+
642
+ // A zoom into a quiet stretch: nothing to draw, nothing to hover.
643
+ handDown({ points: [], value: null });
644
+ expect(
645
+ screen.getByText("No data for the selected time range"),
646
+ ).toBeInTheDocument();
647
+
648
+ // And back out: the same line, with no pointer on it.
649
+ handDown({ points: minutePoints(0, 6), value: 42 });
650
+
651
+ expect(crashed()).toBeNull();
652
+ expect(screen.getByText("42")).toBeInTheDocument();
653
+ expect(screen.queryByText("102")).toBeNull();
654
+ expect(hoverMarkerIn(sparklineIn(null))).toBeNull();
655
+ });
656
+ });
657
+
658
+ describe("the empty state a zoom into a quiet stretch lands on", () => {
659
+ const NO_DATA: string = "No data for the selected time range";
660
+
661
+ // The box that takes the double-click: the message sits directly in it.
662
+ function emptyState(): HTMLElement {
663
+ return screen.getByText(NO_DATA).parentElement as HTMLElement;
664
+ }
665
+
666
+ test("while it takes the double-click that resets, the double-click selects none of its words", () => {
667
+ const onReset: MockFunction = getJestMockFunction();
668
+ renderView({
669
+ value: null,
670
+ points: [],
671
+ onTimeRangeReset: onReset as unknown as () => void,
672
+ });
673
+
674
+ expect(emptyState()).toHaveClass("select-none");
675
+
676
+ fireEvent.doubleClick(screen.getByText(NO_DATA));
677
+ expect(onReset).toHaveBeenCalledTimes(1);
678
+ });
679
+
680
+ test("with no zoom to undo, its words stay selectable", () => {
681
+ renderView({ value: null, points: [] });
682
+
683
+ expect(emptyState()).not.toHaveClass("select-none");
684
+ });
685
+ });
686
+
687
+ describe("a refresh that lands one point fewer in the middle of a drag", () => {
688
+ // Seven five-minute points, 10:00 to 10:30.
689
+ function fiveMinutePoints(
690
+ fromIndex: number,
691
+ toIndex: number,
692
+ ): Array<SparklinePoint> {
693
+ const points: Array<SparklinePoint> = [];
694
+
695
+ for (let index: number = fromIndex; index <= toIndex; index++) {
696
+ points.push({ timestamp: at(index * 5), value: 10 + index });
697
+ }
698
+
699
+ return points;
700
+ }
701
+
702
+ // Where the line draws point `index` of `count`, as the band reads it.
703
+ function bandXOf(svg: SVGSVGElement, index: number, count: number): number {
704
+ const width: number = Number(svg.getAttribute("width"));
705
+ return PADDING + (index / (count - 1)) * (width - PADDING * 2);
706
+ }
707
+
708
+ function band(): Element | null {
709
+ return screen.queryByTestId(SPARKLINE_SELECTION_TEST_ID);
710
+ }
711
+
712
+ test("a drag onto the newest point keeps its band on the line and zooms what it still covers", () => {
713
+ renderView({ points: fiveMinutePoints(0, 6), value: 42 });
714
+ const sparkline: LaidOutSparkline = layOut();
715
+
716
+ pressOn(sparkline, 3);
717
+ dragOnTo(sparkline, 6);
718
+ expect(band()).not.toBeNull();
719
+
720
+ /*
721
+ * The rolling window moved on under the drag: the oldest bucket
722
+ * dropped out and the newest has no data yet.
723
+ */
724
+ handDown({ points: fiveMinutePoints(1, 6), value: 42 });
725
+
726
+ expect(crashed()).toBeNull();
727
+ const svg: SVGSVGElement = sparklineIn(null);
728
+ expect(pointCountOf(svg)).toBe(6);
729
+ // The band runs from the drag's start to the newest point left.
730
+ expect(Number(band()!.getAttribute("x"))).toBeCloseTo(bandXOf(svg, 3, 6));
731
+ expect(Number(band()!.getAttribute("width"))).toBeCloseTo(
732
+ bandXOf(svg, 5, 6) - bandXOf(svg, 3, 6),
733
+ );
734
+
735
+ releaseOn(layOut(), 5);
736
+
737
+ // Places 3 to 5 of the line as it is now: 10:20 to the end of 10:30.
738
+ expect(windowsOf(onSelect)).toEqual([
739
+ [at(20).getTime(), at(35).getTime()],
740
+ ]);
741
+ expect(band()).toBeNull();
742
+ });
743
+
744
+ test("a press on the newest point, before the pointer moves, survives the refresh", () => {
745
+ renderView({ points: fiveMinutePoints(0, 6), value: 42 });
746
+ const sparkline: LaidOutSparkline = layOut();
747
+
748
+ // No move before the press: the hover names nothing, only the drag does.
749
+ pressOn(sparkline, 6);
750
+
751
+ handDown({ points: fiveMinutePoints(1, 6), value: 42 });
752
+
753
+ expect(crashed()).toBeNull();
754
+ expect(band()).toBeNull();
755
+
756
+ const shrunk: LaidOutSparkline = layOut();
757
+ dragOnTo(shrunk, 2);
758
+ expect(band()).not.toBeNull();
759
+ releaseOn(shrunk, 2);
760
+
761
+ // From place 2 to the newest place the drag still reaches, 5.
762
+ expect(windowsOf(onSelect)).toEqual([
763
+ [at(15).getTime(), at(35).getTime()],
764
+ ]);
765
+ });
766
+
767
+ test("a right-to-left drag that began on the newest point survives it too", () => {
768
+ renderView({ points: fiveMinutePoints(0, 6), value: 42 });
769
+ const sparkline: LaidOutSparkline = layOut();
770
+
771
+ pressOn(sparkline, 6);
772
+ dragOnTo(sparkline, 2);
773
+
774
+ handDown({ points: fiveMinutePoints(1, 6), value: 42 });
775
+
776
+ expect(crashed()).toBeNull();
777
+ const svg: SVGSVGElement = sparklineIn(null);
778
+ expect(Number(band()!.getAttribute("x"))).toBeCloseTo(bandXOf(svg, 2, 6));
779
+ expect(Number(band()!.getAttribute("width"))).toBeCloseTo(
780
+ bandXOf(svg, 5, 6) - bandXOf(svg, 2, 6),
781
+ );
782
+
783
+ releaseOn(layOut(), 2);
784
+
785
+ expect(windowsOf(onSelect)).toEqual([
786
+ [at(15).getTime(), at(35).getTime()],
787
+ ]);
788
+ });
789
+
790
+ test("a drag whose points all dropped out zooms nothing", () => {
791
+ renderView({ points: fiveMinutePoints(0, 6), value: 42 });
792
+ const sparkline: LaidOutSparkline = layOut();
793
+
794
+ pressOn(sparkline, 5);
795
+ dragOnTo(sparkline, 6);
796
+
797
+ // A refresh that lost the whole end of the line.
798
+ handDown({ points: fiveMinutePoints(0, 3), value: 42 });
799
+
800
+ expect(crashed()).toBeNull();
801
+ expect(band()).toBeNull();
802
+
803
+ fireEvent.mouseUp(window);
804
+
805
+ expect(onSelect).not.toHaveBeenCalled();
806
+ });
807
+ });
808
+ });
809
+
810
+ /*
811
+ * Through the real widgets, on the real zoom hooks. The data layer answers
812
+ * with one point, worth 1, per minute of the window it is asked for: an
813
+ * hour is 60 points, and both widgets sum them, so the big number is the
814
+ * number of points on the line.
815
+ */
816
+
817
+ const COMPONENT_ID: ObjectID = new ObjectID(
818
+ "5a5a5a5a-1111-4111-8111-5a5a5a5a5a5a",
819
+ );
820
+
821
+ const BOARD_RANGE: RangeStartAndEndDateTime = {
822
+ range: TimeRange.CUSTOM,
823
+ startAndEndDate: new InBetween<Date>(at(0), at(60)),
824
+ };
825
+
826
+ const DASHBOARD_VIEW_CONFIG: DashboardViewConfig = {
827
+ _type: ObjectType.DashboardViewConfig,
828
+ components: [],
829
+ heightInDashboardUnits: 60,
830
+ };
831
+
832
+ interface Point {
833
+ timestamp: Date;
834
+ value: number;
835
+ }
836
+
837
+ function onePerMinute(start: Date, end: Date): Array<Point> {
838
+ const points: Array<Point> = [];
839
+
840
+ for (let ms: number = start.getTime(); ms < end.getTime(); ms += MINUTE_MS) {
841
+ points.push({ timestamp: new Date(ms), value: 1 });
842
+ }
843
+
844
+ return points;
845
+ }
846
+
847
+ /*
848
+ * Holds every fetch made after it is called until the returned release
849
+ * runs: the zoomed window "still loading".
850
+ */
851
+ let heldFetches: Promise<void> | null = null;
852
+
853
+ function holdFetches(): () => void {
854
+ let release: () => void = (): void => {
855
+ // Replaced below, synchronously, by the promise executor.
856
+ };
857
+ heldFetches = new Promise<void>((resolve: () => void) => {
858
+ release = resolve;
859
+ });
860
+ return (): void => {
861
+ heldFetches = null;
862
+ release();
863
+ };
864
+ }
865
+
866
+ // Drops the newest point of every answer from here on.
867
+ let dropNewestPoint: boolean = false;
868
+
869
+ async function answer(start: Date, end: Date): Promise<Array<Point>> {
870
+ const hold: Promise<void> | null = heldFetches;
871
+ const points: Array<Point> = onePerMinute(start, end);
872
+
873
+ if (hold) {
874
+ await hold;
875
+ }
876
+
877
+ return dropNewestPoint ? points.slice(0, -1) : points;
878
+ }
879
+
880
+ interface WidgetZoomProps {
881
+ dashboardStartAndEndDate: RangeStartAndEndDateTime;
882
+ refreshTick: number;
883
+ onDashboardTimeRangeSelect: (startTime: Date, endTime: Date) => void;
884
+ onDashboardTimeRangeReset: () => void;
885
+ isDashboardTimeRangeZoomed: boolean;
886
+ }
887
+
888
+ function hostProps(zoom: WidgetZoomProps): DashboardBaseComponentProps {
889
+ return {
890
+ componentId: COMPONENT_ID,
891
+ isEditMode: false,
892
+ isSelected: false,
893
+ key: "value-widget",
894
+ onComponentUpdate: (): void => {
895
+ // The widget never writes back through this.
896
+ },
897
+ totalCurrentDashboardWidthInPx: 1200,
898
+ dashboardCanvasTopInPx: 0,
899
+ dashboardCanvasLeftInPx: 0,
900
+ dashboardCanvasWidthInPx: 1200,
901
+ dashboardCanvasHeightInPx: 800,
902
+ dashboardComponentHeightInPx: 320,
903
+ dashboardComponentWidthInPx: 400,
904
+ dashboardViewConfig: DASHBOARD_VIEW_CONFIG,
905
+ metricTypes: [],
906
+ variables: undefined,
907
+ ...zoom,
908
+ };
909
+ }
910
+
911
+ function buildValueComponent(): DashboardValueComponent {
912
+ return {
913
+ _type: ObjectType.DashboardComponent,
914
+ componentId: COMPONENT_ID,
915
+ componentType: DashboardComponentType.Value,
916
+ topInDashboardUnits: 0,
917
+ leftInDashboardUnits: 0,
918
+ widthInDashboardUnits: 3,
919
+ heightInDashboardUnits: 2,
920
+ minWidthInDashboardUnits: 1,
921
+ minHeightInDashboardUnits: 1,
922
+ arguments: {
923
+ title: "Requests",
924
+ metricQueryConfig: {
925
+ metricAliasData: { metricVariable: "a" },
926
+ metricQueryData: {
927
+ filterData: {
928
+ metricName: "http.requests",
929
+ aggegationType: MetricsAggregationType.Sum,
930
+ },
931
+ },
932
+ },
933
+ },
934
+ } as unknown as DashboardValueComponent;
935
+ }
936
+
937
+ function buildDataSourceValueComponent(): DashboardDataSourceValueComponent {
938
+ return {
939
+ _type: ObjectType.DashboardComponent,
940
+ componentId: COMPONENT_ID,
941
+ componentType: DashboardComponentType.DataSourceValue,
942
+ topInDashboardUnits: 0,
943
+ leftInDashboardUnits: 0,
944
+ widthInDashboardUnits: 3,
945
+ heightInDashboardUnits: 2,
946
+ minWidthInDashboardUnits: 1,
947
+ minHeightInDashboardUnits: 1,
948
+ arguments: {
949
+ title: "Queue depth",
950
+ reduce: DataSourceValueReduce.Sum,
951
+ query: {
952
+ id: "q1",
953
+ dataSourceId: "6b6b6b6b-1111-4111-8111-6b6b6b6b6b6b",
954
+ query: "sum(queue_depth)",
955
+ },
956
+ },
957
+ } as unknown as DashboardDataSourceValueComponent;
958
+ }
959
+
960
+ interface HostUnderTest {
961
+ name: string;
962
+ widget: (zoom: WidgetZoomProps) => React.ReactElement;
963
+ // Every window the widget has asked the data layer for, in order.
964
+ fetchedWindows: () => Array<[number, number]>;
965
+ }
966
+
967
+ const HOSTS: Array<HostUnderTest> = [
968
+ {
969
+ name: "metric Value widget",
970
+ widget: (zoom: WidgetZoomProps): React.ReactElement => {
971
+ return (
972
+ <DashboardValueComponentElement
973
+ {...hostProps(zoom)}
974
+ component={buildValueComponent()}
975
+ />
976
+ );
977
+ },
978
+ fetchedWindows: (): Array<[number, number]> => {
979
+ return fetchResultsMock.mock.calls.map(
980
+ (call: Array<unknown>): [number, number] => {
981
+ const data: MetricViewData = (
982
+ call[0] as { metricViewData: MetricViewData }
983
+ ).metricViewData;
984
+ return [
985
+ data.startAndEndDate!.startValue.getTime(),
986
+ data.startAndEndDate!.endValue.getTime(),
987
+ ];
988
+ },
989
+ );
990
+ },
991
+ },
992
+ {
993
+ name: "Data Source Value widget",
994
+ widget: (zoom: WidgetZoomProps): React.ReactElement => {
995
+ return (
996
+ <DashboardDataSourceValueComponentElement
997
+ {...hostProps(zoom)}
998
+ component={buildDataSourceValueComponent()}
999
+ />
1000
+ );
1001
+ },
1002
+ fetchedWindows: (): Array<[number, number]> => {
1003
+ return fetchTimeSeriesMock.mock.calls.map(
1004
+ (call: Array<unknown>): [number, number] => {
1005
+ const args: { startDate: Date; endDate: Date } = call[0] as {
1006
+ startDate: Date;
1007
+ endDate: Date;
1008
+ };
1009
+ return [args.startDate.getTime(), args.endDate.getTime()];
1010
+ },
1011
+ );
1012
+ },
1013
+ },
1014
+ ];
1015
+
1016
+ interface BoardShellProps {
1017
+ host: HostUnderTest;
1018
+ refreshTick: number;
1019
+ }
1020
+
1021
+ /*
1022
+ * The board's range as both dashboard shells hold it (DashboardView and the
1023
+ * public DashboardViewPage): useDashboardTimeRangeZoom, its handlers handed
1024
+ * to the widget, the zoomed range handed back down.
1025
+ */
1026
+ function BoardShell(props: BoardShellProps): React.ReactElement {
1027
+ const zoom: DashboardTimeRangeZoom = useDashboardTimeRangeZoom(BOARD_RANGE);
1028
+
1029
+ return props.host.widget({
1030
+ dashboardStartAndEndDate: zoom.startAndEndDate,
1031
+ refreshTick: props.refreshTick,
1032
+ onDashboardTimeRangeSelect: zoom.zoomToTimeRange,
1033
+ onDashboardTimeRangeReset: zoom.resetZoom,
1034
+ isDashboardTimeRangeZoomed: zoom.isZoomed,
1035
+ });
1036
+ }
1037
+
1038
+ function board(
1039
+ host: HostUnderTest,
1040
+ refreshTick: number = 0,
1041
+ ): React.ReactElement {
1042
+ return (
1043
+ <CrashBoundary>
1044
+ <BoardShell host={host} refreshTick={refreshTick} />
1045
+ </CrashBoundary>
1046
+ );
1047
+ }
1048
+
1049
+ function bigNumber(text: string): HTMLElement | null {
1050
+ return screen.queryByText(text);
1051
+ }
1052
+
1053
+ // Waits for the line to be drawn with `count` points, and hands it back.
1054
+ async function lineOf(count: number): Promise<SVGSVGElement> {
1055
+ await waitFor(() => {
1056
+ expect(crashed()).toBeNull();
1057
+ expect(pointCountOf(sparklineIn(null))).toBe(count);
1058
+ });
1059
+
1060
+ return sparklineIn(null);
1061
+ }
1062
+
1063
+ beforeEach(() => {
1064
+ heldFetches = null;
1065
+ dropNewestPoint = false;
1066
+ fetchResultsMock.mockReset();
1067
+ fetchTimeSeriesMock.mockReset();
1068
+ fetchResultsMock.mockImplementation(async (...args: Array<unknown>) => {
1069
+ const data: MetricViewData = (args[0] as { metricViewData: MetricViewData })
1070
+ .metricViewData;
1071
+ return [
1072
+ {
1073
+ data: await answer(
1074
+ data.startAndEndDate!.startValue,
1075
+ data.startAndEndDate!.endValue,
1076
+ ),
1077
+ },
1078
+ ];
1079
+ });
1080
+ fetchTimeSeriesMock.mockImplementation(async (...args: Array<unknown>) => {
1081
+ const request: { startDate: Date; endDate: Date } = args[0] as {
1082
+ startDate: Date;
1083
+ endDate: Date;
1084
+ };
1085
+ return { data: await answer(request.startDate, request.endDate) };
1086
+ });
1087
+ });
1088
+
1089
+ describe.each(HOSTS)(
1090
+ "the $name on a board, zoomed from its own sparkline",
1091
+ (host: HostUnderTest) => {
1092
+ test("a drag, with the pointer left resting on the line, survives the zoomed line landing", async () => {
1093
+ render(board(host));
1094
+ await lineOf(60);
1095
+ expect(bigNumber("60")).not.toBeNull();
1096
+
1097
+ dragAcross(layOut(), 20, 30);
1098
+
1099
+ await waitFor(() => {
1100
+ const windows: Array<[number, number]> = host.fetchedWindows();
1101
+ expect(windows[windows.length - 1]).toEqual([
1102
+ at(20).getTime(),
1103
+ at(31).getTime(),
1104
+ ]);
1105
+ });
1106
+ const zoomed: SVGSVGElement = await lineOf(11);
1107
+
1108
+ expect(crashed()).toBeNull();
1109
+ expect(hoverMarkerIn(zoomed)).toBeNull();
1110
+ // The zoomed window's sum, not a point left over from the hour.
1111
+ expect(bigNumber("11")).not.toBeNull();
1112
+ });
1113
+
1114
+ test("a pointer moved while the zoomed window loads is let go of when it lands", async () => {
1115
+ render(board(host));
1116
+ await lineOf(60);
1117
+ const sparkline: LaidOutSparkline = layOut();
1118
+
1119
+ const release: () => void = holdFetches();
1120
+ dragAcross(sparkline, 20, 30);
1121
+ await waitFor(() => {
1122
+ expect(host.fetchedWindows().length).toBe(2);
1123
+ });
1124
+
1125
+ // Still the hour on screen, dimmed: the reader nudges the pointer.
1126
+ hoverOver(sparkline, 33);
1127
+ expect(hoverMarkerIn(sparkline.svg)).not.toBeNull();
1128
+ expect(bigNumber("1")).not.toBeNull();
1129
+
1130
+ await act(async () => {
1131
+ release();
1132
+ });
1133
+ const zoomed: SVGSVGElement = await lineOf(11);
1134
+
1135
+ expect(crashed()).toBeNull();
1136
+ expect(hoverMarkerIn(zoomed)).toBeNull();
1137
+ expect(bigNumber("11")).not.toBeNull();
1138
+ });
1139
+
1140
+ test("a right-to-left drag survives the zoomed line landing", async () => {
1141
+ render(board(host));
1142
+ await lineOf(60);
1143
+
1144
+ dragAcross(layOut(), 50, 30);
1145
+
1146
+ const zoomed: SVGSVGElement = await lineOf(21);
1147
+
1148
+ expect(crashed()).toBeNull();
1149
+ expect(hoverMarkerIn(zoomed)).toBeNull();
1150
+ expect(bigNumber("21")).not.toBeNull();
1151
+ });
1152
+
1153
+ test("a zoom inside a zoom, and the reset out of both, never throw", async () => {
1154
+ render(board(host));
1155
+ await lineOf(60);
1156
+
1157
+ dragAcross(layOut(), 20, 40);
1158
+ await lineOf(21);
1159
+
1160
+ dragAcross(layOut(), 5, 15);
1161
+ await lineOf(11);
1162
+ expect(host.fetchedWindows()[host.fetchedWindows().length - 1]).toEqual([
1163
+ at(25).getTime(),
1164
+ at(36).getTime(),
1165
+ ]);
1166
+
1167
+ // Back to the hour in one step, the pointer still on the line.
1168
+ fireEvent.doubleClick(sparklineIn(null));
1169
+ const hour: SVGSVGElement = await lineOf(60);
1170
+
1171
+ expect(crashed()).toBeNull();
1172
+ expect(hoverMarkerIn(hour)).toBeNull();
1173
+ expect(bigNumber("60")).not.toBeNull();
1174
+ });
1175
+
1176
+ test("an auto-refresh that lands one point fewer in the middle of a drag keeps the board up", async () => {
1177
+ const rendered: RenderResult = render(board(host, 0));
1178
+ await lineOf(60);
1179
+
1180
+ // Pressed on the newest point; the pointer has not moved since.
1181
+ pressOn(layOut(), 59);
1182
+
1183
+ dropNewestPoint = true;
1184
+ rendered.rerender(board(host, 1));
1185
+ await lineOf(59);
1186
+
1187
+ expect(crashed()).toBeNull();
1188
+
1189
+ // The drag goes on over the line as it is now.
1190
+ const refreshed: LaidOutSparkline = layOut();
1191
+ dragOnTo(refreshed, 40);
1192
+ releaseOn(refreshed, 40);
1193
+
1194
+ await waitFor(() => {
1195
+ const windows: Array<[number, number]> = host.fetchedWindows();
1196
+ expect(windows[windows.length - 1]).toEqual([
1197
+ at(40).getTime(),
1198
+ at(59).getTime(),
1199
+ ]);
1200
+ });
1201
+ await lineOf(18);
1202
+ expect(crashed()).toBeNull();
1203
+ });
1204
+ },
1205
+ );
1206
+
1207
+ /*
1208
+ * The Component Settings preview zooms itself (useTimeRangeZoom over its
1209
+ * own copy of the board's range), so the same drag there landed on the
1210
+ * same stale index - and threw away the edits the dialog was holding.
1211
+ */
1212
+ class FakeResizeObserver {
1213
+ private callback: ResizeObserverCallback;
1214
+
1215
+ public constructor(callback: ResizeObserverCallback) {
1216
+ this.callback = callback;
1217
+ }
1218
+
1219
+ public observe(): void {
1220
+ this.callback(
1221
+ [{ contentRect: { width: 800 } } as unknown as ResizeObserverEntry],
1222
+ this as unknown as ResizeObserver,
1223
+ );
1224
+ }
1225
+
1226
+ public unobserve(): void {
1227
+ // Nothing to stop observing.
1228
+ }
1229
+
1230
+ public disconnect(): void {
1231
+ // Nothing to disconnect.
1232
+ }
1233
+ }
1234
+
1235
+ describe("the Value widget in the Component Settings preview", () => {
1236
+ beforeAll(() => {
1237
+ (globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
1238
+ FakeResizeObserver;
1239
+ });
1240
+
1241
+ function editor(): React.ReactElement {
1242
+ const viewConfig: DashboardViewConfig = {
1243
+ _type: ObjectType.DashboardViewConfig,
1244
+ components: [buildValueComponent()],
1245
+ heightInDashboardUnits: 12,
1246
+ };
1247
+
1248
+ return (
1249
+ <CrashBoundary>
1250
+ <DashboardCanvas
1251
+ dashboardViewConfig={viewConfig}
1252
+ onDashboardViewConfigChange={() => {}}
1253
+ isEditMode={true}
1254
+ currentTotalDashboardWidthInPx={1200}
1255
+ onComponentSelected={() => {}}
1256
+ onComponentUnselected={() => {}}
1257
+ selectedComponentId={COMPONENT_ID}
1258
+ metrics={{ metricTypes: [], telemetryAttributes: [] }}
1259
+ dashboardStartAndEndDate={BOARD_RANGE}
1260
+ refreshTick={0}
1261
+ onDashboardTimeRangeSelect={() => {}}
1262
+ onDashboardTimeRangeReset={() => {}}
1263
+ isDashboardTimeRangeZoomed={false}
1264
+ />
1265
+ </CrashBoundary>
1266
+ );
1267
+ }
1268
+
1269
+ function previewLine(): SVGSVGElement {
1270
+ return sparklineIn(screen.getByTestId("modal"));
1271
+ }
1272
+
1273
+ test("a drag on the preview's sparkline zooms the preview without taking the editor down", async () => {
1274
+ render(editor());
1275
+
1276
+ await waitFor(() => {
1277
+ expect(pointCountOf(previewLine())).toBe(60);
1278
+ });
1279
+
1280
+ dragAcross(layOut(screen.getByTestId("modal")), 20, 30);
1281
+
1282
+ await waitFor(() => {
1283
+ expect(crashed()).toBeNull();
1284
+ expect(pointCountOf(previewLine())).toBe(11);
1285
+ });
1286
+
1287
+ // Still editing: the dialog, and the settings it holds, are there.
1288
+ expect(screen.getByTestId("modal")).toBeInTheDocument();
1289
+ expect(screen.getByTestId("arguments-form")).toBeInTheDocument();
1290
+ expect(hoverMarkerIn(previewLine())).toBeNull();
1291
+ });
1292
+ });