@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,1023 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ act,
4
+ fireEvent,
5
+ render,
6
+ screen,
7
+ waitFor,
8
+ within,
9
+ } from "@testing-library/react";
10
+ import { beforeEach, describe, expect, test } from "@jest/globals";
11
+ import * as React from "react";
12
+ import { JSONObject } from "../../../Types/JSON";
13
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
14
+
15
+ /*
16
+ * Issue #4106: group chats were listed by their members' names instead of
17
+ * their Teams names.
18
+ *
19
+ * On the card, that means: opening the page reads what is stored (GET), and
20
+ * Refresh Chats asks the server to re-read every group chat's name from
21
+ * Microsoft (POST /microsoft-teams/chats/refresh) and shows the result. The
22
+ * server also says which chats' names it could not read, split by why:
23
+ *
24
+ * - chatNamePermissionDeniedChatIds: Microsoft refused, because the app in
25
+ * that chat has not been granted the permission to share its name. That
26
+ * takes action from an admin, so those rows are marked and an amber notice
27
+ * says what to do - which differs between self-hosted and SaaS.
28
+ * - chatNameFailedChatIds: the read failed for some other reason. Nothing to
29
+ * fix, so those rows are marked and a gray notice says to try again.
30
+ *
31
+ * Both lists only mean anything right after a refresh, so the first load
32
+ * never shows them. And because the list (with its notices) is swapped for a
33
+ * loader during every refresh, the outcome is announced to screen readers
34
+ * through a live region that stays mounted the whole time.
35
+ *
36
+ * Only the network edge is mocked: API.get and API.post answer from per-test
37
+ * responders, which may resolve an HTTPErrorResponse exactly as the real
38
+ * client does for a 4xx/5xx. Card, Button, the loader and the Send Test
39
+ * button are the production components.
40
+ */
41
+
42
+ let billingEnabled: boolean = false;
43
+
44
+ jest.mock("../../../UI/Config", () => {
45
+ const actualConfig: Record<string, unknown> = jest.requireActual(
46
+ "../../../UI/Config",
47
+ ) as Record<string, unknown>;
48
+
49
+ const mockedConfig: Record<string, unknown> = { ...actualConfig };
50
+
51
+ /*
52
+ * Object.defineProperty, not a getter in an object literal: this file is
53
+ * down-levelled, so `{ ...actual, get X() {} }` becomes Object.assign, which
54
+ * would read the getter once and freeze the value at module-load time.
55
+ */
56
+ Object.defineProperty(mockedConfig, "BILLING_ENABLED", {
57
+ get: (): boolean => {
58
+ return billingEnabled;
59
+ },
60
+ });
61
+
62
+ Object.defineProperty(mockedConfig, "MicrosoftTeamsAppClientId", {
63
+ get: (): string => {
64
+ return "9f6a2c31-self-hosted-registration";
65
+ },
66
+ });
67
+
68
+ return mockedConfig;
69
+ });
70
+
71
+ interface ApiRequestOptions {
72
+ url: { toString: () => string };
73
+ }
74
+
75
+ interface RecordedCall {
76
+ method: "get" | "post";
77
+ route: string;
78
+ }
79
+
80
+ /*
81
+ * The real client resolves (it does not reject) with an HTTPErrorResponse
82
+ * when the server answers with an error status, so a responder may too.
83
+ */
84
+ type ApiResult = { data: JSONObject } | HTTPErrorResponse;
85
+ type Responder = () => Promise<ApiResult>;
86
+
87
+ let calls: Array<RecordedCall> = [];
88
+ let getResponder: Responder;
89
+ let postResponder: Responder;
90
+
91
+ function routeOf(options: ApiRequestOptions): string {
92
+ // Compared without the API base path, which differs between environments.
93
+ return new window.URL(options.url.toString()).pathname.replace(/^\/api/, "");
94
+ }
95
+
96
+ jest.mock("../../../UI/Utils/API/API", () => {
97
+ return {
98
+ __esModule: true,
99
+ default: {
100
+ get: (options: ApiRequestOptions): Promise<ApiResult> => {
101
+ calls.push({ method: "get", route: routeOf(options) });
102
+ return getResponder();
103
+ },
104
+ post: (options: ApiRequestOptions): Promise<ApiResult> => {
105
+ calls.push({ method: "post", route: routeOf(options) });
106
+ return postResponder();
107
+ },
108
+ getFriendlyErrorMessage: (err: unknown): string => {
109
+ /*
110
+ * Error and HTTPErrorResponse both expose `message`; surface it so a
111
+ * test can see its own error text reach the card.
112
+ */
113
+ if (
114
+ err &&
115
+ typeof err === "object" &&
116
+ typeof (err as { message?: unknown }).message === "string" &&
117
+ (err as { message: string }).message
118
+ ) {
119
+ return (err as { message: string }).message;
120
+ }
121
+
122
+ return "Something went wrong.";
123
+ },
124
+ },
125
+ };
126
+ });
127
+
128
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
129
+ return {
130
+ __esModule: true,
131
+ default: {
132
+ getCommonHeaders: (): Record<string, string> => {
133
+ return {};
134
+ },
135
+ },
136
+ };
137
+ });
138
+
139
+ import MicrosoftTeamsChatsCard from "../../../../App/FeatureSet/Dashboard/src/Components/MicrosoftTeams/MicrosoftTeamsChatsCard";
140
+
141
+ const GROUP_CHAT_ID: string = "19:group@thread.v2";
142
+ const PERSONAL_CHAT_ID: string = "a:personal";
143
+
144
+ // What the issue's screenshot showed in place of the chat's Teams name.
145
+ const MEMBER_NAME: string =
146
+ "Bibishek G S Steephensen [Contractor], Karthik Kallam [Contractor], Robin Examp…";
147
+ const TEAMS_NAME: string = "Platform On-Call";
148
+
149
+ const REFRESH_ROUTE: string = "/microsoft-teams/chats/refresh";
150
+
151
+ const DENIED_MARKER: string =
152
+ "Microsoft Teams did not let OneUptime read this chat's name";
153
+ const FAILED_MARKER: string = "This chat's name could not be read just now";
154
+
155
+ /*
156
+ * Anchored on words only the notices carry. The live region repeats the
157
+ * start of each sentence ("Microsoft Teams did not let OneUptime read ...",
158
+ * "OneUptime could not read ..."), so matching on that would find two
159
+ * elements.
160
+ */
161
+ const PERMISSION_NOTICE_TEXT: RegExp = /may be out of date or built from/;
162
+ const FAILED_NOTICE_TEXT: RegExp = /from Microsoft Teams just now/;
163
+
164
+ const REFRESH_ERROR_PREFIX: string =
165
+ "Chats could not be refreshed, so the list below may be out of date.";
166
+
167
+ function groupChat(id: string, name: string): JSONObject {
168
+ return { id: id, name: name, chatType: "groupChat", addedAt: null };
169
+ }
170
+
171
+ function personalChat(id: string, name: string): JSONObject {
172
+ return { id: id, name: name, chatType: "personal", addedAt: null };
173
+ }
174
+
175
+ const STORED_CHATS: Array<JSONObject> = [
176
+ groupChat(GROUP_CHAT_ID, MEMBER_NAME),
177
+ personalChat(PERSONAL_CHAT_ID, "Jane Doe"),
178
+ ];
179
+
180
+ // Sorted by name, as the server sends them.
181
+ const REFRESHED_CHATS: Array<JSONObject> = [
182
+ personalChat(PERSONAL_CHAT_ID, "Jane Doe"),
183
+ groupChat(GROUP_CHAT_ID, TEAMS_NAME),
184
+ ];
185
+
186
+ /*
187
+ * One row per outcome of a name read: refused, failed, read, and a 1:1 chat
188
+ * (whose name is never read from Microsoft at all).
189
+ */
190
+ const DENIED_CHAT_ID: string = "19:denied@thread.v2";
191
+ const FAILED_CHAT_ID: string = "19:failed@thread.v2";
192
+ const DENIED_CHAT_NAME: string =
193
+ "Alice Example, Bob Example, Carol Example + 2 more";
194
+ const FAILED_CHAT_NAME: string = "Release Train";
195
+
196
+ const MIXED_CHATS: Array<JSONObject> = [
197
+ groupChat(DENIED_CHAT_ID, DENIED_CHAT_NAME),
198
+ personalChat(PERSONAL_CHAT_ID, "Jane Doe"),
199
+ groupChat(GROUP_CHAT_ID, TEAMS_NAME),
200
+ groupChat(FAILED_CHAT_ID, FAILED_CHAT_NAME),
201
+ ];
202
+
203
+ // Three more group chats, for the plural wording.
204
+ const EXTRA_GROUP_CHATS: Array<JSONObject> = [
205
+ groupChat("19:second@thread.v2", "Dave Example, Erin Example"),
206
+ groupChat("19:third@thread.v2", "Frank Example, Grace Example"),
207
+ groupChat("19:fourth@thread.v2", "Heidi Example, Ivan Example"),
208
+ ];
209
+
210
+ function respond(data: JSONObject): Responder {
211
+ return async (): Promise<ApiResult> => {
212
+ return { data: data };
213
+ };
214
+ }
215
+
216
+ function refreshResponse(options: {
217
+ chats?: Array<JSONObject>;
218
+ permissionDeniedChatIds?: Array<string>;
219
+ failedChatIds?: Array<string>;
220
+ }): Responder {
221
+ const permissionDeniedChatIds: Array<string> =
222
+ options.permissionDeniedChatIds || [];
223
+ const failedChatIds: Array<string> = options.failedChatIds || [];
224
+
225
+ // The shape POST /microsoft-teams/chats/refresh answers with.
226
+ return respond({
227
+ chats: options.chats || REFRESHED_CHATS,
228
+ chatNamePermissionDeniedChatIds: permissionDeniedChatIds,
229
+ chatNamePermissionDeniedCount: permissionDeniedChatIds.length,
230
+ chatNameFailedChatIds: failedChatIds,
231
+ chatNameFailedCount: failedChatIds.length,
232
+ });
233
+ }
234
+
235
+ interface Deferred {
236
+ promise: Promise<ApiResult>;
237
+ resolve: (value: ApiResult) => void;
238
+ }
239
+
240
+ function createDeferred(): Deferred {
241
+ const deferred: Partial<Deferred> = {};
242
+
243
+ deferred.promise = new Promise<ApiResult>(
244
+ (resolve: (value: ApiResult) => void): void => {
245
+ deferred.resolve = resolve;
246
+ },
247
+ );
248
+
249
+ return deferred as Deferred;
250
+ }
251
+
252
+ async function waitForLoaderToGo(): Promise<void> {
253
+ await waitFor(() => {
254
+ expect(screen.queryByTestId("component-loader")).not.toBeInTheDocument();
255
+ });
256
+ }
257
+
258
+ async function renderCard(): Promise<void> {
259
+ // The card fetches /microsoft-teams/chats on mount; settle that inside act.
260
+ await act(async (): Promise<void> => {
261
+ render(<MicrosoftTeamsChatsCard />);
262
+ });
263
+
264
+ await waitForLoaderToGo();
265
+ }
266
+
267
+ async function clickRefresh(): Promise<void> {
268
+ const postsBefore: number = calls.filter((call: RecordedCall) => {
269
+ return call.method === "post";
270
+ }).length;
271
+
272
+ await act(async (): Promise<void> => {
273
+ fireEvent.click(screen.getByRole("button", { name: "Refresh Chats" }));
274
+ });
275
+
276
+ await waitForLoaderToGo();
277
+
278
+ /*
279
+ * Without this a click that did nothing (say, on a disabled button) would
280
+ * leave the previous state on screen for the assertions that follow.
281
+ */
282
+ expect(
283
+ calls.filter((call: RecordedCall) => {
284
+ return call.method === "post";
285
+ }).length,
286
+ ).toBe(postsBefore + 1);
287
+ }
288
+
289
+ function getRows(): Array<HTMLElement> {
290
+ return within(screen.getByRole("list")).getAllByRole("listitem");
291
+ }
292
+
293
+ function getRow(name: string): HTMLElement {
294
+ const row: HTMLElement | null = screen.getByText(name).closest("li");
295
+
296
+ if (!row) {
297
+ throw new Error(`"${name}" is not inside a chat row.`);
298
+ }
299
+
300
+ return row;
301
+ }
302
+
303
+ function permissionNotice(): HTMLElement | null {
304
+ return screen.queryByText(PERMISSION_NOTICE_TEXT);
305
+ }
306
+
307
+ function failedNotice(): HTMLElement | null {
308
+ return screen.queryByText(FAILED_NOTICE_TEXT);
309
+ }
310
+
311
+ function getPermissionNotice(): HTMLElement {
312
+ const element: HTMLElement | null = permissionNotice();
313
+ expect(element).toBeInTheDocument();
314
+ return element!;
315
+ }
316
+
317
+ function getFailedNotice(): HTMLElement {
318
+ const element: HTMLElement | null = failedNotice();
319
+ expect(element).toBeInTheDocument();
320
+ return element!;
321
+ }
322
+
323
+ /*
324
+ * The card's own live regions. Every Send Test button carries a
325
+ * role="status" of its own (inside its row), and so does the loader (labelled
326
+ * "Loading"); neither is the card's refresh summary.
327
+ */
328
+ function cardLiveRegions(): Array<HTMLElement> {
329
+ return screen
330
+ .getAllByRole("status")
331
+ .filter((element: HTMLElement): boolean => {
332
+ return (
333
+ element.closest("li") === null &&
334
+ element.getAttribute("aria-label") !== "Loading"
335
+ );
336
+ });
337
+ }
338
+
339
+ function getLiveRegion(): HTMLElement {
340
+ const regions: Array<HTMLElement> = cardLiveRegions();
341
+ expect(regions).toHaveLength(1);
342
+ return regions[0]!;
343
+ }
344
+
345
+ beforeEach(() => {
346
+ billingEnabled = false;
347
+ calls = [];
348
+ getResponder = respond({ chats: STORED_CHATS });
349
+ postResponder = refreshResponse({});
350
+ });
351
+
352
+ describe("MicrosoftTeamsChatsCard — Refresh Chats re-reads group chat names", () => {
353
+ test("opening the page only reads stored chats (GET), it does not refresh names", async () => {
354
+ await renderCard();
355
+
356
+ expect(calls).toEqual([{ method: "get", route: "/microsoft-teams/chats" }]);
357
+ });
358
+
359
+ test("Refresh Chats posts to /microsoft-teams/chats/refresh", async () => {
360
+ await renderCard();
361
+ calls = [];
362
+
363
+ await clickRefresh();
364
+
365
+ expect(calls).toEqual([{ method: "post", route: REFRESH_ROUTE }]);
366
+ });
367
+
368
+ test("the issue: after Refresh Chats the group chat shows its Teams name, still tagged 'Group chat'", async () => {
369
+ await renderCard();
370
+ expect(screen.getByText(MEMBER_NAME)).toBeInTheDocument();
371
+
372
+ await clickRefresh();
373
+
374
+ expect(screen.queryByText(MEMBER_NAME)).not.toBeInTheDocument();
375
+ const row: HTMLElement = getRow(TEAMS_NAME);
376
+ expect(within(row).getByText("Group chat")).toBeInTheDocument();
377
+ expect(within(row).queryByText("1:1 chat")).not.toBeInTheDocument();
378
+ });
379
+
380
+ test("1:1 chats keep the other person's name and the '1:1 chat' tag", async () => {
381
+ await renderCard();
382
+ await clickRefresh();
383
+
384
+ const row: HTMLElement = getRow("Jane Doe");
385
+ expect(within(row).getByText("1:1 chat")).toBeInTheDocument();
386
+ expect(within(row).queryByText("Group chat")).not.toBeInTheDocument();
387
+ });
388
+
389
+ test("rows keep the order the server sends, without re-sorting them", async () => {
390
+ /*
391
+ * Deliberately neither alphabetical nor grouped by chat type, so a card
392
+ * that sorted by name or put group chats first would show a different
393
+ * order than the one sent.
394
+ */
395
+ postResponder = refreshResponse({
396
+ chats: [
397
+ groupChat("19:zeta@thread.v2", "Zeta War Room"),
398
+ personalChat(PERSONAL_CHAT_ID, "Jane Doe"),
399
+ groupChat("19:alpha@thread.v2", "Alpha Ops"),
400
+ ],
401
+ });
402
+
403
+ await renderCard();
404
+ await clickRefresh();
405
+
406
+ const rows: Array<HTMLElement> = getRows();
407
+ const namesSent: Array<string> = ["Zeta War Room", "Jane Doe", "Alpha Ops"];
408
+ expect(rows).toHaveLength(namesSent.length);
409
+ namesSent.forEach((name: string, index: number) => {
410
+ expect(within(rows[index]!).getByText(name)).toBeInTheDocument();
411
+ });
412
+ });
413
+
414
+ test("Send Test is labelled with the refreshed name", async () => {
415
+ await renderCard();
416
+ await clickRefresh();
417
+
418
+ const row: HTMLElement = getRow(TEAMS_NAME);
419
+ expect(
420
+ within(row).getByRole("button", {
421
+ name: `Send test notification to ${TEAMS_NAME}`,
422
+ }),
423
+ ).toBeInTheDocument();
424
+ // The member-name label went with the member name.
425
+ expect(
426
+ screen.queryByRole("button", {
427
+ name: `Send test notification to ${MEMBER_NAME}`,
428
+ }),
429
+ ).not.toBeInTheDocument();
430
+ });
431
+
432
+ test("the chat name carries its full text as a tooltip, since a long name is truncated", async () => {
433
+ const longName: string =
434
+ "Payments Platform — Production Incident Bridge (EMEA and APAC follow-the-sun)";
435
+ postResponder = refreshResponse({
436
+ chats: [groupChat(GROUP_CHAT_ID, longName)],
437
+ });
438
+
439
+ await renderCard();
440
+ await clickRefresh();
441
+
442
+ const nameElement: HTMLElement = screen.getByText(longName);
443
+ expect(nameElement).toHaveAttribute("title", longName);
444
+ expect(nameElement).toHaveClass("truncate");
445
+ });
446
+
447
+ test("Refresh can be clicked again and each click refreshes again", async () => {
448
+ await renderCard();
449
+ calls = [];
450
+
451
+ await clickRefresh();
452
+ await clickRefresh();
453
+
454
+ expect(calls).toEqual([
455
+ { method: "post", route: REFRESH_ROUTE },
456
+ { method: "post", route: REFRESH_ROUTE },
457
+ ]);
458
+ });
459
+ });
460
+
461
+ describe("MicrosoftTeamsChatsCard — a refresh that fails", () => {
462
+ const MESSAGE: string =
463
+ "Microsoft Teams is not connected for this project. Connect it again.";
464
+
465
+ test.each([
466
+ [
467
+ "rejects",
468
+ async (): Promise<ApiResult> => {
469
+ throw new Error(MESSAGE);
470
+ },
471
+ ],
472
+ [
473
+ "resolves an HTTPErrorResponse, as the client does for a 400",
474
+ async (): Promise<ApiResult> => {
475
+ return new HTTPErrorResponse(400, { message: MESSAGE }, {});
476
+ },
477
+ ],
478
+ ])(
479
+ "keeps the loaded list, with its Send Test buttons, and shows the error above it when the request %s",
480
+ async (_label: string, failingResponder: Responder) => {
481
+ await renderCard();
482
+ postResponder = failingResponder;
483
+
484
+ await clickRefresh();
485
+
486
+ expect(
487
+ screen.getByText(`${REFRESH_ERROR_PREFIX} ${MESSAGE}`),
488
+ ).toBeInTheDocument();
489
+ // The list that was loaded stays up, still usable.
490
+ expect(screen.getByRole("list")).toBeInTheDocument();
491
+ expect(
492
+ within(getRow(MEMBER_NAME)).getByRole("button", {
493
+ name: `Send test notification to ${MEMBER_NAME}`,
494
+ }),
495
+ ).toBeInTheDocument();
496
+ expect(getLiveRegion().textContent).toBe(
497
+ `Chats could not be refreshed. ${MESSAGE}`,
498
+ );
499
+ },
500
+ );
501
+
502
+ test("when the first load failed too, the error stands alone: no empty-state guidance", async () => {
503
+ /*
504
+ * With nothing loaded there is no list to keep, and "No chats connected
505
+ * yet" would be a false statement about a list that could not be read.
506
+ */
507
+ getResponder = async (): Promise<ApiResult> => {
508
+ return new HTTPErrorResponse(500, { message: MESSAGE }, {});
509
+ };
510
+ await act(async (): Promise<void> => {
511
+ render(<MicrosoftTeamsChatsCard />);
512
+ });
513
+ await waitForLoaderToGo();
514
+ expect(screen.getByText(MESSAGE)).toBeInTheDocument();
515
+
516
+ postResponder = async (): Promise<ApiResult> => {
517
+ return new HTTPErrorResponse(500, { message: MESSAGE }, {});
518
+ };
519
+ await clickRefresh();
520
+
521
+ expect(screen.getByText(MESSAGE)).toBeInTheDocument();
522
+ expect(
523
+ screen.queryByText(`${REFRESH_ERROR_PREFIX} ${MESSAGE}`),
524
+ ).not.toBeInTheDocument();
525
+ expect(
526
+ screen.queryByText("No chats connected yet"),
527
+ ).not.toBeInTheDocument();
528
+ });
529
+
530
+ test("the next Refresh Chats clears the error", async () => {
531
+ await renderCard();
532
+ postResponder = async (): Promise<ApiResult> => {
533
+ return new HTTPErrorResponse(503, { message: MESSAGE }, {});
534
+ };
535
+ await clickRefresh();
536
+ expect(
537
+ screen.getByText(`${REFRESH_ERROR_PREFIX} ${MESSAGE}`),
538
+ ).toBeInTheDocument();
539
+
540
+ postResponder = refreshResponse({});
541
+ await clickRefresh();
542
+
543
+ expect(
544
+ screen.queryByText(`${REFRESH_ERROR_PREFIX} ${MESSAGE}`),
545
+ ).not.toBeInTheDocument();
546
+ expect(getRow(TEAMS_NAME)).toBeInTheDocument();
547
+ expect(getLiveRegion().textContent).toBe("Chat names refreshed.");
548
+ });
549
+ });
550
+
551
+ describe("MicrosoftTeamsChatsCard — rows whose name could not be read are marked", () => {
552
+ test("each marker sits on its own row only", async () => {
553
+ postResponder = refreshResponse({
554
+ chats: MIXED_CHATS,
555
+ permissionDeniedChatIds: [DENIED_CHAT_ID],
556
+ failedChatIds: [FAILED_CHAT_ID],
557
+ });
558
+
559
+ await renderCard();
560
+ await clickRefresh();
561
+
562
+ const deniedRow: HTMLElement = getRow(DENIED_CHAT_NAME);
563
+ expect(within(deniedRow).getByText(DENIED_MARKER)).toBeInTheDocument();
564
+ expect(
565
+ within(deniedRow).queryByText(FAILED_MARKER),
566
+ ).not.toBeInTheDocument();
567
+
568
+ const failedRow: HTMLElement = getRow(FAILED_CHAT_NAME);
569
+ expect(within(failedRow).getByText(FAILED_MARKER)).toBeInTheDocument();
570
+ expect(
571
+ within(failedRow).queryByText(DENIED_MARKER),
572
+ ).not.toBeInTheDocument();
573
+
574
+ for (const name of [TEAMS_NAME, "Jane Doe"]) {
575
+ const row: HTMLElement = getRow(name);
576
+ expect(within(row).queryByText(DENIED_MARKER)).not.toBeInTheDocument();
577
+ expect(within(row).queryByText(FAILED_MARKER)).not.toBeInTheDocument();
578
+ }
579
+
580
+ expect(screen.getAllByText(DENIED_MARKER)).toHaveLength(1);
581
+ expect(screen.getAllByText(FAILED_MARKER)).toHaveLength(1);
582
+ });
583
+
584
+ test("an id that matches no row marks nothing", async () => {
585
+ postResponder = refreshResponse({
586
+ chats: MIXED_CHATS,
587
+ permissionDeniedChatIds: [DENIED_CHAT_ID, "19:removed@thread.v2"],
588
+ failedChatIds: ["19:also-removed@thread.v2"],
589
+ });
590
+
591
+ await renderCard();
592
+ await clickRefresh();
593
+
594
+ expect(screen.getAllByText(DENIED_MARKER)).toHaveLength(1);
595
+ expect(
596
+ within(getRow(DENIED_CHAT_NAME)).getByText(DENIED_MARKER),
597
+ ).toBeInTheDocument();
598
+ expect(screen.queryByText(FAILED_MARKER)).not.toBeInTheDocument();
599
+ });
600
+
601
+ test.each([
602
+ ["a string", GROUP_CHAT_ID],
603
+ ["an object", { [GROUP_CHAT_ID]: true }],
604
+ ["a number", 1],
605
+ ["null", null],
606
+ ])(
607
+ "id lists that arrive as %s are ignored",
608
+ async (_label: string, payload: JSONObject[string]) => {
609
+ postResponder = respond({
610
+ chats: REFRESHED_CHATS,
611
+ chatNamePermissionDeniedChatIds: payload,
612
+ chatNamePermissionDeniedCount: 1,
613
+ chatNameFailedChatIds: payload,
614
+ chatNameFailedCount: 1,
615
+ });
616
+
617
+ await renderCard();
618
+ await clickRefresh();
619
+
620
+ expect(getRow(TEAMS_NAME)).toBeInTheDocument();
621
+ expect(screen.queryByText(DENIED_MARKER)).not.toBeInTheDocument();
622
+ expect(screen.queryByText(FAILED_MARKER)).not.toBeInTheDocument();
623
+ expect(permissionNotice()).not.toBeInTheDocument();
624
+ expect(failedNotice()).not.toBeInTheDocument();
625
+ // The counts are not what the card goes by; the ids are.
626
+ expect(getLiveRegion().textContent).toBe("Chat names refreshed.");
627
+ },
628
+ );
629
+
630
+ test("entries that are not chat ids are skipped, and not counted", async () => {
631
+ postResponder = respond({
632
+ chats: REFRESHED_CHATS,
633
+ chatNamePermissionDeniedChatIds: [
634
+ 42,
635
+ null,
636
+ { id: PERSONAL_CHAT_ID },
637
+ "",
638
+ GROUP_CHAT_ID,
639
+ ],
640
+ chatNamePermissionDeniedCount: 5,
641
+ });
642
+
643
+ await renderCard();
644
+ await clickRefresh();
645
+
646
+ expect(
647
+ within(getRow(TEAMS_NAME)).getByText(DENIED_MARKER),
648
+ ).toBeInTheDocument();
649
+ expect(screen.getAllByText(DENIED_MARKER)).toHaveLength(1);
650
+ // One real id, so one chat - not the five entries sent.
651
+ expect(getPermissionNotice()).toHaveTextContent(
652
+ "did not let OneUptime read the name of 1 group chat (marked above), so its name here may be out of date",
653
+ );
654
+ });
655
+ });
656
+
657
+ describe("MicrosoftTeamsChatsCard — when Microsoft will not share chat names", () => {
658
+ test("no permission notice when every name was read", async () => {
659
+ await renderCard();
660
+ await clickRefresh();
661
+
662
+ expect(permissionNotice()).not.toBeInTheDocument();
663
+ expect(failedNotice()).not.toBeInTheDocument();
664
+ });
665
+
666
+ test("self-hosted, one chat: singular wording, re-upload the manifest, or grant the Graph permission", async () => {
667
+ billingEnabled = false;
668
+ postResponder = refreshResponse({
669
+ chats: MIXED_CHATS,
670
+ permissionDeniedChatIds: [DENIED_CHAT_ID],
671
+ });
672
+
673
+ await renderCard();
674
+ await clickRefresh();
675
+
676
+ const notice: HTMLElement = getPermissionNotice();
677
+ expect(notice).toHaveTextContent(
678
+ "Microsoft Teams did not let OneUptime read the name of 1 group chat (marked above), so its name here may be out of date or built from its members.",
679
+ );
680
+ expect(notice).toHaveTextContent(
681
+ "once the OneUptime app in that chat asks for the ChatSettings.Read.Chat permission",
682
+ );
683
+ expect(notice).toHaveTextContent(
684
+ "Click Download App Manifest Zip on this page, upload the zip to Microsoft Teams as an update of the OneUptime app, accept the update in that chat, then click Refresh Chats.",
685
+ );
686
+ expect(notice).toHaveTextContent(
687
+ "Or grant your app registration the Chat.ReadBasic.WhereInstalled application permission (with admin consent) to read every chat's name without updating the app in each chat; it can take up to an hour to take effect.",
688
+ );
689
+ expect(notice).not.toHaveTextContent("their names");
690
+ expect(notice).not.toHaveTextContent("those chats");
691
+ // That button only exists on SaaS.
692
+ expect(notice).not.toHaveTextContent(
693
+ "Download App Manifest for Sideloading",
694
+ );
695
+ });
696
+
697
+ test("self-hosted, several chats: plural wording with the count", async () => {
698
+ billingEnabled = false;
699
+ postResponder = refreshResponse({
700
+ chats: [...MIXED_CHATS, ...EXTRA_GROUP_CHATS],
701
+ permissionDeniedChatIds: [
702
+ DENIED_CHAT_ID,
703
+ "19:second@thread.v2",
704
+ "19:third@thread.v2",
705
+ ],
706
+ });
707
+
708
+ await renderCard();
709
+ await clickRefresh();
710
+
711
+ const notice: HTMLElement = getPermissionNotice();
712
+ expect(notice).toHaveTextContent(
713
+ "Microsoft Teams did not let OneUptime read the names of 3 group chats (marked above), so their names here may be out of date or built from their members.",
714
+ );
715
+ expect(notice).toHaveTextContent(
716
+ "accept the update in those chats, then click Refresh Chats.",
717
+ );
718
+ expect(notice).not.toHaveTextContent("its name here");
719
+ expect(notice).not.toHaveTextContent("that chat,");
720
+ });
721
+
722
+ test("SaaS, one chat: accept the update Teams offers; no application permission to grant", async () => {
723
+ billingEnabled = true;
724
+ postResponder = refreshResponse({
725
+ chats: MIXED_CHATS,
726
+ permissionDeniedChatIds: [DENIED_CHAT_ID],
727
+ });
728
+
729
+ await renderCard();
730
+ await clickRefresh();
731
+
732
+ const notice: HTMLElement = getPermissionNotice();
733
+ expect(notice).toHaveTextContent(
734
+ "Microsoft Teams did not let OneUptime read the name of 1 group chat (marked above), so its name here may be out of date or built from its members.",
735
+ );
736
+ expect(notice).toHaveTextContent("ChatSettings.Read.Chat");
737
+ expect(notice).toHaveTextContent(
738
+ "When Teams offers an update for the OneUptime app in that chat, accept it, then click Refresh Chats.",
739
+ );
740
+ expect(notice).toHaveTextContent(
741
+ "If you sideloaded the app, first download its manifest again (Download App Manifest for Sideloading",
742
+ );
743
+ /*
744
+ * A SaaS customer does not own the app registration, so there is no
745
+ * application permission for them to grant, and no zip button either.
746
+ */
747
+ expect(notice).not.toHaveTextContent("Chat.ReadBasic.WhereInstalled");
748
+ expect(notice).not.toHaveTextContent("Download App Manifest Zip");
749
+ });
750
+
751
+ test("SaaS, several chats: plural wording with the count", async () => {
752
+ billingEnabled = true;
753
+ postResponder = refreshResponse({
754
+ chats: [...MIXED_CHATS, ...EXTRA_GROUP_CHATS],
755
+ permissionDeniedChatIds: [
756
+ DENIED_CHAT_ID,
757
+ "19:second@thread.v2",
758
+ "19:third@thread.v2",
759
+ "19:fourth@thread.v2",
760
+ ],
761
+ });
762
+
763
+ await renderCard();
764
+ await clickRefresh();
765
+
766
+ const notice: HTMLElement = getPermissionNotice();
767
+ expect(notice).toHaveTextContent(
768
+ "did not let OneUptime read the names of 4 group chats (marked above), so their names here may be out of date",
769
+ );
770
+ expect(notice).toHaveTextContent(
771
+ "When Teams offers an update for the OneUptime app in those chats, accept it",
772
+ );
773
+ expect(notice).not.toHaveTextContent("that chat,");
774
+ });
775
+
776
+ test("the notice is not a live region itself; the card's summary announces it", async () => {
777
+ /*
778
+ * The notice arrives already filled (it mounts with the list after the
779
+ * loader), which screen readers tend to skip, and a second live region
780
+ * would read the same news twice.
781
+ */
782
+ postResponder = refreshResponse({
783
+ chats: MIXED_CHATS,
784
+ permissionDeniedChatIds: [DENIED_CHAT_ID],
785
+ failedChatIds: [FAILED_CHAT_ID],
786
+ });
787
+
788
+ await renderCard();
789
+ await clickRefresh();
790
+
791
+ expect(getPermissionNotice()).not.toHaveAttribute("role");
792
+ expect(getPermissionNotice()).not.toHaveAttribute("aria-live");
793
+ expect(getFailedNotice()).not.toHaveAttribute("role");
794
+ expect(getFailedNotice()).not.toHaveAttribute("aria-live");
795
+ });
796
+ });
797
+
798
+ describe("MicrosoftTeamsChatsCard — when a chat name could not be read for another reason", () => {
799
+ test("one chat: singular wording, keeps its current name, try again later", async () => {
800
+ postResponder = refreshResponse({
801
+ chats: MIXED_CHATS,
802
+ failedChatIds: [FAILED_CHAT_ID],
803
+ });
804
+
805
+ await renderCard();
806
+ await clickRefresh();
807
+
808
+ expect(getFailedNotice()).toHaveTextContent(
809
+ "OneUptime could not read the name of 1 group chat from Microsoft Teams just now, so it keeps its current name (marked above). Click Refresh Chats again later; if it keeps happening, check the OneUptime server logs for Microsoft Graph errors.",
810
+ );
811
+ // Nothing for an admin to fix, so no permission advice.
812
+ expect(permissionNotice()).not.toBeInTheDocument();
813
+ });
814
+
815
+ test("several chats: plural wording with the count", async () => {
816
+ postResponder = refreshResponse({
817
+ chats: [...MIXED_CHATS, ...EXTRA_GROUP_CHATS],
818
+ failedChatIds: [FAILED_CHAT_ID, "19:second@thread.v2"],
819
+ });
820
+
821
+ await renderCard();
822
+ await clickRefresh();
823
+
824
+ const notice: HTMLElement = getFailedNotice();
825
+ expect(notice).toHaveTextContent(
826
+ "OneUptime could not read the names of 2 group chats from Microsoft Teams just now, so they keep their current name (marked above).",
827
+ );
828
+ expect(notice).not.toHaveTextContent("it keeps its");
829
+ });
830
+
831
+ test("shown alongside the permission notice when both happened", async () => {
832
+ postResponder = refreshResponse({
833
+ chats: [...MIXED_CHATS, ...EXTRA_GROUP_CHATS],
834
+ permissionDeniedChatIds: [DENIED_CHAT_ID, "19:second@thread.v2"],
835
+ failedChatIds: [FAILED_CHAT_ID],
836
+ });
837
+
838
+ await renderCard();
839
+ await clickRefresh();
840
+
841
+ expect(getPermissionNotice()).toHaveTextContent(
842
+ "did not let OneUptime read the names of 2 group chats",
843
+ );
844
+ expect(getFailedNotice()).toHaveTextContent(
845
+ "could not read the name of 1 group chat from",
846
+ );
847
+ });
848
+ });
849
+
850
+ describe("MicrosoftTeamsChatsCard — when the notices and markers go away", () => {
851
+ const TROUBLED_REFRESH: Responder = refreshResponse({
852
+ chats: MIXED_CHATS,
853
+ permissionDeniedChatIds: [DENIED_CHAT_ID],
854
+ failedChatIds: [FAILED_CHAT_ID],
855
+ });
856
+
857
+ test("a later refresh that reads every name clears them", async () => {
858
+ postResponder = TROUBLED_REFRESH;
859
+
860
+ await renderCard();
861
+ await clickRefresh();
862
+ expect(permissionNotice()).toBeInTheDocument();
863
+ expect(failedNotice()).toBeInTheDocument();
864
+
865
+ postResponder = refreshResponse({ chats: MIXED_CHATS });
866
+ await clickRefresh();
867
+
868
+ // Same rows as before, so only the refresh's outcome could remove these.
869
+ expect(getRow(DENIED_CHAT_NAME)).toBeInTheDocument();
870
+ expect(getRow(FAILED_CHAT_NAME)).toBeInTheDocument();
871
+ expect(permissionNotice()).not.toBeInTheDocument();
872
+ expect(failedNotice()).not.toBeInTheDocument();
873
+ expect(screen.queryByText(DENIED_MARKER)).not.toBeInTheDocument();
874
+ expect(screen.queryByText(FAILED_MARKER)).not.toBeInTheDocument();
875
+ });
876
+
877
+ test("a failed refresh clears them", async () => {
878
+ postResponder = TROUBLED_REFRESH;
879
+
880
+ await renderCard();
881
+ await clickRefresh();
882
+ expect(permissionNotice()).toBeInTheDocument();
883
+
884
+ postResponder = async (): Promise<ApiResult> => {
885
+ return new HTTPErrorResponse(500, { message: "Graph is down." }, {});
886
+ };
887
+ await clickRefresh();
888
+
889
+ expect(
890
+ screen.getByText(`${REFRESH_ERROR_PREFIX} Graph is down.`),
891
+ ).toBeInTheDocument();
892
+ expect(permissionNotice()).not.toBeInTheDocument();
893
+ expect(failedNotice()).not.toBeInTheDocument();
894
+ expect(screen.queryByText(DENIED_MARKER)).not.toBeInTheDocument();
895
+ expect(screen.queryByText(FAILED_MARKER)).not.toBeInTheDocument();
896
+ // Nor does the summary still report the earlier refresh's counts.
897
+ expect(getLiveRegion().textContent).toBe(
898
+ "Chats could not be refreshed. Graph is down.",
899
+ );
900
+ });
901
+
902
+ test("the first load never shows them, even if its payload carried ids", async () => {
903
+ /*
904
+ * The GET only reads what is stored; it asked Microsoft nothing, so there
905
+ * is no outcome to report.
906
+ */
907
+ getResponder = respond({
908
+ chats: MIXED_CHATS,
909
+ chatNamePermissionDeniedChatIds: [DENIED_CHAT_ID],
910
+ chatNamePermissionDeniedCount: 1,
911
+ chatNameFailedChatIds: [FAILED_CHAT_ID],
912
+ chatNameFailedCount: 1,
913
+ });
914
+
915
+ await renderCard();
916
+
917
+ expect(getRow(DENIED_CHAT_NAME)).toBeInTheDocument();
918
+ expect(permissionNotice()).not.toBeInTheDocument();
919
+ expect(failedNotice()).not.toBeInTheDocument();
920
+ expect(screen.queryByText(DENIED_MARKER)).not.toBeInTheDocument();
921
+ expect(screen.queryByText(FAILED_MARKER)).not.toBeInTheDocument();
922
+ expect(getLiveRegion().textContent).toBe("");
923
+ });
924
+ });
925
+
926
+ describe("MicrosoftTeamsChatsCard — what a screen reader hears after Refresh Chats", () => {
927
+ test("the card has exactly one polite status region, visually hidden and empty after the first load", async () => {
928
+ await renderCard();
929
+
930
+ const region: HTMLElement = getLiveRegion();
931
+ expect(region).toHaveAttribute("role", "status");
932
+ expect(region).toHaveAttribute("aria-live", "polite");
933
+ expect(region).toHaveClass("sr-only");
934
+ expect(region.textContent).toBe("");
935
+ });
936
+
937
+ test("the region stays mounted through a refresh: emptied while loading, then filled with the outcome", async () => {
938
+ await renderCard();
939
+ await clickRefresh();
940
+
941
+ const region: HTMLElement = getLiveRegion();
942
+ expect(region.textContent).toBe("Chat names refreshed.");
943
+
944
+ const pending: Deferred = createDeferred();
945
+ postResponder = (): Promise<ApiResult> => {
946
+ return pending.promise;
947
+ };
948
+
949
+ await act(async (): Promise<void> => {
950
+ fireEvent.click(screen.getByRole("button", { name: "Refresh Chats" }));
951
+ });
952
+
953
+ // Loading: the list has been swapped for the loader...
954
+ expect(screen.getByTestId("component-loader")).toBeInTheDocument();
955
+ expect(screen.queryByRole("list")).not.toBeInTheDocument();
956
+ /*
957
+ * ...but the region is the same node, and it has been emptied so the
958
+ * same sentence arriving again is still a change a reader announces.
959
+ */
960
+ expect(getLiveRegion()).toBe(region);
961
+ expect(region.textContent).toBe("");
962
+
963
+ await act(async (): Promise<void> => {
964
+ pending.resolve({
965
+ data: {
966
+ chats: REFRESHED_CHATS,
967
+ chatNamePermissionDeniedChatIds: [GROUP_CHAT_ID],
968
+ chatNamePermissionDeniedCount: 1,
969
+ chatNameFailedChatIds: [],
970
+ chatNameFailedCount: 0,
971
+ },
972
+ });
973
+ });
974
+ await waitForLoaderToGo();
975
+
976
+ expect(region.isConnected).toBe(true);
977
+ expect(getLiveRegion()).toBe(region);
978
+ expect(region.textContent).toBe(
979
+ "Chat names refreshed. Microsoft Teams did not let OneUptime read the name of 1 group chat.",
980
+ );
981
+ });
982
+
983
+ test.each([
984
+ ["every name was read", [], [], "Chat names refreshed."],
985
+ [
986
+ "one name was refused",
987
+ [DENIED_CHAT_ID],
988
+ [],
989
+ "Chat names refreshed. Microsoft Teams did not let OneUptime read the name of 1 group chat.",
990
+ ],
991
+ [
992
+ "two names failed",
993
+ [],
994
+ [FAILED_CHAT_ID, "19:second@thread.v2"],
995
+ "Chat names refreshed. OneUptime could not read the names of 2 group chats just now.",
996
+ ],
997
+ [
998
+ "names were both refused and failed",
999
+ [DENIED_CHAT_ID, "19:second@thread.v2", "19:third@thread.v2"],
1000
+ [FAILED_CHAT_ID],
1001
+ "Chat names refreshed. Microsoft Teams did not let OneUptime read the names of 3 group chats. OneUptime could not read the name of 1 group chat just now.",
1002
+ ],
1003
+ ])(
1004
+ "summary when %s",
1005
+ async (
1006
+ _label: string,
1007
+ permissionDeniedChatIds: Array<string>,
1008
+ failedChatIds: Array<string>,
1009
+ expected: string,
1010
+ ) => {
1011
+ postResponder = refreshResponse({
1012
+ chats: [...MIXED_CHATS, ...EXTRA_GROUP_CHATS],
1013
+ permissionDeniedChatIds: permissionDeniedChatIds,
1014
+ failedChatIds: failedChatIds,
1015
+ });
1016
+
1017
+ await renderCard();
1018
+ await clickRefresh();
1019
+
1020
+ expect(getLiveRegion().textContent).toBe(expected);
1021
+ },
1022
+ );
1023
+ });