@oneuptime/common 14.0.8 → 14.0.10

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 (976) hide show
  1. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  2. package/Models/DatabaseModels/CephCluster.ts +213 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +213 -0
  4. package/Models/DatabaseModels/DockerHost.ts +213 -0
  5. package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
  6. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  7. package/Models/DatabaseModels/Host.ts +213 -0
  8. package/Models/DatabaseModels/Index.ts +4 -0
  9. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  10. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  11. package/Models/DatabaseModels/PodmanHost.ts +213 -0
  12. package/Models/DatabaseModels/Project.ts +55 -5
  13. package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
  14. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  15. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  16. package/Models/DatabaseModels/RunnerJob.ts +219 -0
  17. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  18. package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
  19. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  20. package/Server/API/AIChatAPI.ts +6 -3
  21. package/Server/API/AIInvestigationAPI.ts +17 -10
  22. package/Server/API/AutoRemediationAPI.ts +598 -25
  23. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  24. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  25. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  26. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  27. package/Server/Infrastructure/GlobalCache.ts +32 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  33. package/Server/Infrastructure/Semaphore.ts +22 -0
  34. package/Server/Middleware/TelemetryIngest.ts +51 -18
  35. package/Server/Services/AIService.ts +70 -1
  36. package/Server/Services/AlertService.ts +352 -11
  37. package/Server/Services/AlertSeverityService.ts +65 -15
  38. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  39. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  40. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  41. package/Server/Services/CephClusterService.ts +109 -1
  42. package/Server/Services/DatabaseServerService.ts +116 -1
  43. package/Server/Services/DockerHostService.ts +109 -1
  44. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  45. package/Server/Services/HostService.ts +126 -4
  46. package/Server/Services/IncidentService.ts +280 -104
  47. package/Server/Services/IncidentSeverityService.ts +66 -15
  48. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  49. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  50. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  51. package/Server/Services/KubernetesClusterService.ts +129 -21
  52. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  53. package/Server/Services/PodmanHostService.ts +109 -1
  54. package/Server/Services/ProjectService.ts +32 -0
  55. package/Server/Services/ProxmoxClusterService.ts +110 -1
  56. package/Server/Services/ProxmoxResourceService.ts +322 -5
  57. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  58. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  59. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  60. package/Server/Services/RunnerJobService.ts +852 -108
  61. package/Server/Services/RunnerService.ts +1 -1
  62. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  63. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  64. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  65. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  66. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  67. package/Server/Services/VMwareVCenterService.ts +110 -1
  68. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  69. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  70. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  71. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1475 -51
  72. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +1000 -43
  73. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  74. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
  75. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  76. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  77. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  78. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  79. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
  80. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +120 -7
  81. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
  82. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
  83. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  84. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  85. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  86. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +402 -14
  87. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  88. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  89. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  90. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  91. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  92. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  93. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  94. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  95. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  96. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  97. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  98. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  99. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
  100. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
  101. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  102. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +1073 -213
  103. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  104. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  105. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  106. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  107. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  108. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  109. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  110. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  111. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  112. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  113. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  114. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  115. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  116. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  117. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  118. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  119. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  120. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  121. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  122. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  123. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  124. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  125. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  126. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  127. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +184 -19
  128. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  129. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  130. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  131. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  132. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  133. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  134. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  135. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  136. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  137. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  138. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  139. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  140. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  141. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  142. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
  143. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  144. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  145. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  146. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  147. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  148. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  149. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  150. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  151. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  152. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  153. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  154. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  155. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  156. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  157. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  158. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  159. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  160. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  161. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  162. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  163. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  164. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  165. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  166. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  167. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  168. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  169. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  170. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  171. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  172. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  173. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  174. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  175. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  176. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  177. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  178. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  179. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  180. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  181. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  182. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  183. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  184. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  185. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  186. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  187. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  188. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  189. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  190. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  191. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  192. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  193. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  194. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  195. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  196. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  197. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  198. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  199. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  200. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  201. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  202. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  203. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  204. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  205. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  206. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  207. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  208. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  209. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  210. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  211. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  212. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  213. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  214. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  215. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  216. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  217. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  218. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  219. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  220. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  221. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  222. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  223. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  224. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  225. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  226. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  227. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  228. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  229. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  230. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  231. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  232. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  233. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  234. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  235. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  236. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  237. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
  238. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
  239. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  240. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  241. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  242. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  243. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  244. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  245. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  246. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  247. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  248. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  249. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  250. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  251. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  252. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  253. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  254. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  255. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  256. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  257. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  258. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  259. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  260. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  261. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  262. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  263. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  264. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  265. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  266. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  267. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  268. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  269. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  270. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  271. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  272. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  273. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  274. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  275. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  276. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  277. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
  278. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  279. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  280. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  281. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  282. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  283. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  284. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  285. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  286. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  287. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  288. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  289. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  290. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  291. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  292. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  293. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  294. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  295. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  296. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  297. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  298. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  299. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  300. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  301. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +119 -4
  302. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +196 -2
  303. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +123 -2
  304. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  305. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  306. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  307. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  308. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  309. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  310. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  311. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  312. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  313. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  314. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  315. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  316. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  317. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  318. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  319. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  320. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  321. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  322. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  323. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  324. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  325. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  326. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  327. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  328. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  329. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  330. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  331. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  332. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  333. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  334. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  335. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  336. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  337. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  338. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  339. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  340. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  341. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  342. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +153 -15
  343. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  344. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  345. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  346. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  347. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  348. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  349. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  350. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  351. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  352. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  353. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  354. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  355. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  356. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  357. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  358. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  359. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  360. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
  361. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  362. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  363. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  364. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  365. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  366. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  367. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  368. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  369. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  370. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  371. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  372. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  373. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
  374. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  375. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  376. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  377. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
  378. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  379. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  380. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  381. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
  382. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  383. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  384. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  385. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  386. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  387. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  388. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  389. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  390. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  391. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  392. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  393. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  394. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  395. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  396. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  397. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +75 -3
  398. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  399. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  400. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  401. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  402. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  403. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  404. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  405. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  406. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  407. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  408. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  409. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  410. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  411. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  412. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  413. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  414. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  415. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  416. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  417. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  418. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  419. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  420. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  421. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  422. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  423. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  424. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  425. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
  426. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  427. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  428. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  429. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  430. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  431. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  432. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  433. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  434. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  435. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  436. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  437. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  438. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  439. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  440. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  441. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  442. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  443. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  444. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  445. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  446. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  447. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  448. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  449. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  450. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  451. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  452. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  453. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  454. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  455. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  456. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  457. package/Tests/UI/Components/Input.test.tsx +80 -1
  458. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  459. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  460. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  461. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  462. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  463. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  464. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  465. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  466. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  467. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  468. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  469. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  470. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  471. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  472. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  473. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  474. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  475. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  476. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  477. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  478. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  479. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  480. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  481. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  482. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  483. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  484. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  485. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  486. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  487. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  488. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  489. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  490. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  491. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  492. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  493. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  494. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  495. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  496. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  497. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  498. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  499. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  500. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  501. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  502. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  503. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  504. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  505. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  506. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  507. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  508. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  509. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  510. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  511. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  512. package/Types/AI/AIRunType.ts +9 -4
  513. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  514. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  515. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  516. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +197 -18
  517. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  518. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  519. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  520. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  521. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  522. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  523. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  524. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  525. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  526. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  527. package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
  528. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  529. package/Types/Runbook/RunbookStepType.ts +29 -0
  530. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  531. package/Types/Team/ComplianceRule.ts +434 -0
  532. package/Types/Team/ComplianceRuleType.ts +21 -0
  533. package/Types/Team/TeamComplianceStatus.ts +81 -0
  534. package/Types/Telemetry/TelemetryIngestSurface.ts +40 -0
  535. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  536. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  537. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  538. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  539. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  540. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  541. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  542. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  543. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  544. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  545. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  546. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  547. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  548. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  549. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  550. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  551. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  552. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  553. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  554. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  555. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  556. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  557. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  558. package/UI/Components/Input/Input.tsx +90 -57
  559. package/UI/Components/List/ListRow.tsx +8 -74
  560. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  561. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  562. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  563. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  564. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  565. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  566. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  567. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  568. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  569. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  570. package/UI/Components/Table/TableBody.tsx +8 -2
  571. package/UI/Components/Table/TableRow.tsx +20 -140
  572. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  573. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  574. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  575. package/UI/Components/TelemetryViewer/types.ts +2 -0
  576. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  577. package/UI/Components/Toggle/Toggle.tsx +26 -0
  578. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  579. package/UI/Styles/Theme.css +14 -2
  580. package/Utils/AI/InvestigationReport.ts +121 -15
  581. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  582. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  583. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  584. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  585. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  586. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  587. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  588. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  589. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  590. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  591. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  592. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  593. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  594. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  595. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  596. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  597. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  598. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  599. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  600. package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
  601. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  602. package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
  603. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  604. package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
  605. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  606. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
  607. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  608. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  609. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  610. package/build/dist/Models/DatabaseModels/Host.js +219 -0
  611. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  612. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  613. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  614. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  615. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  616. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  617. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  618. package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
  619. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  620. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  621. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  622. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
  623. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  624. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  625. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  626. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  627. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  628. package/build/dist/Models/DatabaseModels/RunnerJob.js +219 -0
  629. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  630. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  631. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  632. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
  633. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  634. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  635. package/build/dist/Server/API/AIChatAPI.js +6 -1
  636. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  637. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  638. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  639. package/build/dist/Server/API/AutoRemediationAPI.js +374 -24
  640. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  641. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  642. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  643. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  644. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  645. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  646. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  647. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  648. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  649. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  650. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  651. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  652. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  653. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  654. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  655. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  656. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  657. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  658. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  659. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  660. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  661. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  662. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  663. package/build/dist/Server/Middleware/TelemetryIngest.js +41 -19
  664. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  665. package/build/dist/Server/Services/AIService.js +59 -1
  666. package/build/dist/Server/Services/AIService.js.map +1 -1
  667. package/build/dist/Server/Services/AlertService.js +248 -13
  668. package/build/dist/Server/Services/AlertService.js.map +1 -1
  669. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  670. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  671. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  672. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  673. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  674. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  675. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  676. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  677. package/build/dist/Server/Services/CephClusterService.js +104 -0
  678. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  679. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  680. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  681. package/build/dist/Server/Services/DockerHostService.js +104 -0
  682. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  683. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  684. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  685. package/build/dist/Server/Services/HostService.js +114 -2
  686. package/build/dist/Server/Services/HostService.js.map +1 -1
  687. package/build/dist/Server/Services/IncidentService.js +199 -72
  688. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  689. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  690. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  691. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  692. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  693. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  694. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  695. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  696. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  697. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  698. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  699. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  700. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  701. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  702. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  703. package/build/dist/Server/Services/ProjectService.js +22 -0
  704. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  705. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  706. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  707. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  708. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  709. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  710. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  711. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  712. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  713. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  714. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  715. package/build/dist/Server/Services/RunnerJobService.js +522 -97
  716. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  717. package/build/dist/Server/Services/RunnerService.js +1 -1
  718. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  719. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  720. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  721. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  722. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  723. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  724. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  725. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  726. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  727. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  728. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  729. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  730. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  731. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  732. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  733. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  734. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  735. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  736. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  737. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1056 -43
  738. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  739. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +683 -42
  740. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  741. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  742. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  743. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
  744. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  745. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  746. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  747. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  748. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  749. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  750. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  751. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  752. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  753. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
  754. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  755. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +89 -9
  756. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  757. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
  758. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  759. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
  760. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  761. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  762. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  763. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  764. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  765. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  766. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +294 -15
  767. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  768. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  769. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  770. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  771. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  772. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  773. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  774. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  775. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  776. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  777. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  778. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  779. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  780. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  781. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  782. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  783. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  784. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  785. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  786. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  787. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  788. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  789. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  790. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  791. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  792. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
  793. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  794. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
  795. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  796. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  797. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  798. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +768 -152
  799. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  800. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  801. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  802. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  803. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  804. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  805. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  806. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  807. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  808. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  809. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  810. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  811. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  812. package/build/dist/Types/AI/AIRunType.js +9 -4
  813. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  814. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  815. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  816. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  817. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  818. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +126 -45
  819. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  820. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  821. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  822. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  823. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  824. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  825. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  826. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  827. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  828. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  829. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  830. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  831. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  832. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  833. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  834. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  835. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  836. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  837. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  838. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
  839. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  840. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  841. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  842. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  843. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  844. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  845. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  846. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  847. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  848. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  849. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  850. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  851. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  852. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +36 -0
  853. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  854. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  855. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  856. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  857. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  858. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  859. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  860. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  861. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  862. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  863. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  864. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  865. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  866. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  867. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  868. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  869. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  870. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  871. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  872. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  873. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  874. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  875. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  876. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  877. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  878. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  879. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  880. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  881. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  882. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  883. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  884. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  885. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  886. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  887. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  888. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  889. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  890. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  891. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  892. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  893. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  894. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  895. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  896. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  897. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  898. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  899. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  900. package/build/dist/UI/Components/Input/Input.js +69 -54
  901. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  902. package/build/dist/UI/Components/List/ListRow.js +4 -39
  903. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  904. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  905. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  906. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  907. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  908. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  909. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  910. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  911. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  912. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  913. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  914. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  915. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  916. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  917. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  918. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  919. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  920. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  921. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  922. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  923. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  924. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  925. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  926. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  927. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  928. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  929. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  930. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  931. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  932. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  933. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  934. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  935. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  936. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  937. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  938. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  939. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  940. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  941. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  942. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  943. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  944. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  945. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  946. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  947. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  948. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  949. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  950. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  951. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  952. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  953. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  954. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  955. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  956. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  957. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  958. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  959. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  960. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  961. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  962. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  963. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  964. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  965. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  966. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  967. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  968. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  969. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  970. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  971. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  972. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  973. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  974. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  975. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  976. package/package.json +1 -1
@@ -0,0 +1,1967 @@
1
+ /*
2
+ * Pins MicrosoftTeamsReplies: how the Microsoft Teams bot answers, and the
3
+ * rule it answers by - an inbound message gets one answer, and an answer that
4
+ * cannot be sent never turns into a second one.
5
+ *
6
+ * Issue #4111: in Teams, "create incident" and "create maintenance" answered
7
+ * "Sorry, I encountered an error processing your request. Please try again
8
+ * later.", and every such bubble showed up twice, while "show scheduled
9
+ * maintenance" worked. The two forms listed every monitor, label and on-call
10
+ * policy of the project, so Teams refused them with HTTP 413
11
+ * MessageSizeTooBig ("Message size too large."). And the bot replied with the
12
+ * error and then rethrew it: the adapter answered Teams with HTTP 500, Teams
13
+ * delivered the same message again, and the error was posted a second time.
14
+ *
15
+ * Covered here:
16
+ * - sendBestEffort / deleteBestEffort: a reply, or the removal of a submitted
17
+ * form, is tried once and waited for; a failure is logged and swallowed,
18
+ * never retried and never followed by another bubble.
19
+ * - sendCardWithinSizeLimit: the form goes out at 40 KiB, and only a "too
20
+ * large" refusal moves on to 20 KiB and then to a card without the lists; a
21
+ * card identical to the one just refused (at whichever budget) is not sent
22
+ * again; any other error, including one building a smaller card, and the
23
+ * last "too large" reach the caller; each refusal is logged as a warning
24
+ * naming that card's size and budget.
25
+ * - describeError: the one line the log gets for an error: a Bot Framework
26
+ * error with its status and Teams' code, and a OneUptime exception by its
27
+ * class (they all keep Error's name, so the class comes from the
28
+ * constructor).
29
+ * - logFailure: that line after a summary of what failed, then the error
30
+ * itself, which keeps its stack and class - except for a Bot Framework HTTP
31
+ * error, whose status and code say it all.
32
+ * - getUserFacingErrorMessage: which errors are worded for the user, and the
33
+ * fixed reply for a submit that references a record the project does not
34
+ * have: the reference check's own message names the other project's
35
+ * record, which is not for the chat.
36
+ * - getDashboardLink / getAccountNotLinkedMessage: the links the bot hands
37
+ * out (never changing the dashboard URL they start from), and what the bot
38
+ * says when the dashboard URL is not known.
39
+ */
40
+
41
+ import {
42
+ afterEach,
43
+ beforeEach,
44
+ describe,
45
+ expect,
46
+ jest,
47
+ test,
48
+ } from "@jest/globals";
49
+ import type { Activity, TurnContext } from "botbuilder";
50
+ import fs from "fs";
51
+ import type { Mock, SpyInstance } from "jest-mock";
52
+ import path from "path";
53
+ import vm from "vm";
54
+ import type DatabaseBaseModel from "../../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
55
+ import IncidentSeverity from "../../../../../Models/DatabaseModels/IncidentSeverity";
56
+ import DatabaseConfig from "../../../../../Server/DatabaseConfig";
57
+ import type DatabaseService from "../../../../../Server/Services/DatabaseService";
58
+ import ProjectScopedReferenceValidator, {
59
+ ProjectScopedReferenceException,
60
+ } from "../../../../../Server/Utils/Database/ProjectScopedReferenceValidator";
61
+ import logger, { LogAttributes } from "../../../../../Server/Utils/Logger";
62
+ import { MicrosoftTeamsAccountNotLinkedException } from "../../../../../Server/Utils/Workspace/MicrosoftTeams/Actions/Auth";
63
+ import MicrosoftTeamsMessageSize, {
64
+ MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES,
65
+ } from "../../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize";
66
+ import MicrosoftTeamsReplies, {
67
+ MICROSOFT_TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,
68
+ MICROSOFT_TEAMS_UNAVAILABLE_REFERENCE_MESSAGE,
69
+ } from "../../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies";
70
+ import URL from "../../../../../Types/API/URL";
71
+ import BadDataException from "../../../../../Types/Exception/BadDataException";
72
+ import NotAuthorizedException from "../../../../../Types/Exception/NotAuthorizedException";
73
+ import ServerException from "../../../../../Types/Exception/ServerException";
74
+ import { JSONObject } from "../../../../../Types/JSON";
75
+ import ObjectID from "../../../../../Types/ObjectID";
76
+
77
+ const PROJECT_ID: ObjectID = new ObjectID(
78
+ "4af3a31b-58b0-4746-8025-f9cd4db1945e",
79
+ );
80
+ const DASHBOARD_URL: string = "https://oneuptime.test/dashboard";
81
+ const USER_SETTINGS_ROUTE: string =
82
+ "/user-settings/microsoft-teams-integration";
83
+
84
+ /*
85
+ * A submit that references another project's severity (a tampered submit), or
86
+ * one deleted since the form was sent, and what the reference check says
87
+ * about each: its message names the record, or echoes the id.
88
+ */
89
+ const OTHER_PROJECT_ID: ObjectID = new ObjectID(
90
+ "3376855b-361c-427c-8982-bad7ada30414",
91
+ );
92
+ const OTHER_PROJECT_SEVERITY_ID: string =
93
+ "cfc2f04f-79cb-4344-8c54-dafe5e3a290c";
94
+ const OTHER_PROJECT_SEVERITY_NAME: string = "Sev 1 - Acme Corp";
95
+ const DELETED_SEVERITY_ID: string = "9c0ba0b3-2f8e-4c02-a8d5-6a4d2f5b9c11";
96
+ const OTHER_PROJECT_REFERENCE_MESSAGE: string = `This incident references records that belong to a different project: Incident Severity "${OTHER_PROJECT_SEVERITY_NAME}". Please pick values from this project and try again.`;
97
+ const DELETED_REFERENCE_MESSAGE: string = `This incident references records that do not exist: Incident Severity "${DELETED_SEVERITY_ID}". Please pick values that exist in this project and try again.`;
98
+
99
+ const FIRST_BUDGET_IN_BYTES: number = 40 * 1024;
100
+ const SECOND_BUDGET_IN_BYTES: number = 20 * 1024;
101
+ const LAST_BUDGET_IN_BYTES: number = 0;
102
+
103
+ type SendActivity = (reply: string | Partial<Activity>) => Promise<unknown>;
104
+ type DeleteActivity = (activityId: string) => Promise<void>;
105
+ type BuildCard = (budgetInBytes: number) => JSONObject;
106
+
107
+ interface FakeTurn {
108
+ turnContext: TurnContext;
109
+ sendActivity: Mock<SendActivity>;
110
+ deleteActivity: Mock<DeleteActivity>;
111
+ }
112
+
113
+ interface CardAttachment {
114
+ contentType: string;
115
+ content: JSONObject;
116
+ }
117
+
118
+ interface CardActivity {
119
+ attachments: Array<CardAttachment>;
120
+ }
121
+
122
+ interface RestErrorOptions {
123
+ message: string;
124
+ statusCode?: number | undefined;
125
+ code?: string | undefined;
126
+ responseStatus?: number | undefined;
127
+ }
128
+
129
+ // Teams' answer to a call, held back until the test lets it through.
130
+ interface Deferred {
131
+ promise: Promise<void>;
132
+ resolve: () => void;
133
+ }
134
+
135
+ function createDeferred(): Deferred {
136
+ let resolvePromise: () => void = (): void => {
137
+ return undefined;
138
+ };
139
+ const promise: Promise<void> = new Promise<void>(
140
+ (resolve: () => void): void => {
141
+ resolvePromise = resolve;
142
+ },
143
+ );
144
+
145
+ return {
146
+ promise: promise,
147
+ resolve: (): void => {
148
+ resolvePromise();
149
+ },
150
+ };
151
+ }
152
+
153
+ // Lets every promise that can settle now settle (real timers throughout).
154
+ async function waitForPendingWork(): Promise<void> {
155
+ await new Promise<void>((resolve: () => void): void => {
156
+ setTimeout(resolve, 0);
157
+ });
158
+ }
159
+
160
+ // A conversation in which Teams accepts every reply and every removal.
161
+ function buildFakeTurn(): FakeTurn {
162
+ const sendActivity: Mock<SendActivity> = jest.fn<SendActivity>(
163
+ async (): Promise<unknown> => {
164
+ return { id: "reply-activity-id" };
165
+ },
166
+ );
167
+ const deleteActivity: Mock<DeleteActivity> = jest.fn<DeleteActivity>(
168
+ async (): Promise<void> => {
169
+ return undefined;
170
+ },
171
+ );
172
+
173
+ return {
174
+ turnContext: {
175
+ activity: { id: "inbound-activity-id", type: "message" },
176
+ sendActivity: sendActivity,
177
+ deleteActivity: deleteActivity,
178
+ } as unknown as TurnContext,
179
+ sendActivity: sendActivity,
180
+ deleteActivity: deleteActivity,
181
+ };
182
+ }
183
+
184
+ /*
185
+ * What the Bot Framework connector throws (@azure/core-rest-pipeline's
186
+ * RestError): an Error named "RestError" with code and statusCode of its own,
187
+ * the parsed error body as details, and the response as a NON-enumerable
188
+ * property.
189
+ */
190
+ function buildRestError(options: RestErrorOptions): Error {
191
+ const error: Error = new Error(options.message);
192
+ error.name = "RestError";
193
+
194
+ Object.assign(error, {
195
+ code: options.code,
196
+ statusCode: options.statusCode,
197
+ });
198
+
199
+ if (options.code) {
200
+ Object.assign(error, {
201
+ details: { error: { code: options.code, message: options.message } },
202
+ });
203
+ }
204
+
205
+ Object.defineProperty(error, "response", {
206
+ value:
207
+ options.responseStatus === undefined
208
+ ? undefined
209
+ : { status: options.responseStatus },
210
+ enumerable: false,
211
+ });
212
+
213
+ return error;
214
+ }
215
+
216
+ // Teams refusing a bot message as too large, as the connector reports it.
217
+ function buildMessageTooLargeError(): Error {
218
+ return buildRestError({
219
+ message: "Message size too large.",
220
+ statusCode: 413,
221
+ code: "MessageSizeTooBig",
222
+ responseStatus: 413,
223
+ });
224
+ }
225
+
226
+ // Teams refusing a reply because the bot is no longer in the conversation.
227
+ function buildNotInRosterError(): Error {
228
+ return buildRestError({
229
+ message: "The bot is not part of the conversation roster.",
230
+ statusCode: 403,
231
+ code: "BotNotInConversationRoster",
232
+ responseStatus: 403,
233
+ });
234
+ }
235
+
236
+ // A card that says which budget it was built for, so sends can be told apart.
237
+ function buildCardForBudget(budgetInBytes: number): JSONObject {
238
+ return {
239
+ type: "AdaptiveCard",
240
+ version: "1.4",
241
+ body: [
242
+ {
243
+ type: "TextBlock",
244
+ text: `Built for a budget of ${budgetInBytes} bytes`,
245
+ },
246
+ ],
247
+ };
248
+ }
249
+
250
+ function mockBuildCard(build: BuildCard): Mock<BuildCard> {
251
+ return jest.fn<BuildCard>(build);
252
+ }
253
+
254
+ function getBudgetsBuiltFor(buildCard: Mock<BuildCard>): Array<number> {
255
+ return buildCard.mock.calls.map((call: Parameters<BuildCard>): number => {
256
+ return call[0];
257
+ });
258
+ }
259
+
260
+ function getSentActivities(fake: FakeTurn): Array<CardActivity> {
261
+ return fake.sendActivity.mock.calls.map(
262
+ (call: Parameters<SendActivity>): CardActivity => {
263
+ return call[0] as unknown as CardActivity;
264
+ },
265
+ );
266
+ }
267
+
268
+ function getSentCards(fake: FakeTurn): Array<JSONObject> {
269
+ return getSentActivities(fake).map((activity: CardActivity): JSONObject => {
270
+ const attachment: CardAttachment | undefined = activity.attachments[0];
271
+
272
+ if (!attachment) {
273
+ throw new Error("A card activity was sent without an attachment.");
274
+ }
275
+
276
+ return attachment.content;
277
+ });
278
+ }
279
+
280
+ /*
281
+ * Teams as it behaved in #4111: a message over its limit is refused with
282
+ * HTTP 413 MessageSizeTooBig, anything smaller is posted.
283
+ */
284
+ function refuseMessagesOver(fake: FakeTurn, limitInBytes: number): void {
285
+ fake.sendActivity.mockImplementation(
286
+ async (reply: string | Partial<Activity>): Promise<unknown> => {
287
+ const sizeInBytes: number = MicrosoftTeamsMessageSize.getSizeInBytes(
288
+ typeof reply === "string" ? reply : (reply as unknown as JSONObject),
289
+ );
290
+
291
+ if (sizeInBytes > limitInBytes) {
292
+ throw buildMessageTooLargeError();
293
+ }
294
+
295
+ return { id: "reply-activity-id" };
296
+ },
297
+ );
298
+ }
299
+
300
+ /*
301
+ * A "create incident" form for a project with `monitorCount` monitors: as
302
+ * many monitor choices as the budget holds, and none at a budget of 0.
303
+ */
304
+ function buildMonitorFormBuilder(monitorCount: number): BuildCard {
305
+ return (budgetInBytes: number): JSONObject => {
306
+ const choices: Array<JSONObject> = [];
307
+ const card: JSONObject = {
308
+ type: "AdaptiveCard",
309
+ version: "1.4",
310
+ body: [
311
+ { type: "TextBlock", text: "Create a new incident", weight: "Bolder" },
312
+ {
313
+ type: "Input.Text",
314
+ id: "incidentTitle",
315
+ label: "Title",
316
+ isRequired: true,
317
+ },
318
+ {
319
+ type: "Input.ChoiceSet",
320
+ id: "incidentMonitors",
321
+ label: "Monitors",
322
+ isMultiSelect: true,
323
+ choices: choices,
324
+ },
325
+ ],
326
+ actions: [{ type: "Action.Submit", title: "Create incident" }],
327
+ };
328
+
329
+ let sizeInBytes: number = MicrosoftTeamsMessageSize.getSizeInBytes(card);
330
+
331
+ for (let index: number = 0; index < monitorCount; index++) {
332
+ const choice: JSONObject = {
333
+ title: `Monitor ${index}`,
334
+ value: `monitor-${index}`,
335
+ };
336
+ // One more choice costs its JSON, plus a comma after the first.
337
+ const costInBytes: number =
338
+ MicrosoftTeamsMessageSize.getSizeInBytes(choice) +
339
+ (choices.length > 0 ? 2 : 0);
340
+
341
+ if (sizeInBytes + costInBytes > budgetInBytes) {
342
+ break;
343
+ }
344
+
345
+ choices.push(choice);
346
+ sizeInBytes += costInBytes;
347
+ }
348
+
349
+ return card;
350
+ };
351
+ }
352
+
353
+ function getMonitorChoiceCount(card: JSONObject): number {
354
+ const body: Array<JSONObject> = card["body"] as Array<JSONObject>;
355
+ const choiceSet: JSONObject | undefined = body.find(
356
+ (element: JSONObject): boolean => {
357
+ return element["id"] === "incidentMonitors";
358
+ },
359
+ );
360
+
361
+ return ((choiceSet?.["choices"] as Array<JSONObject> | undefined) || [])
362
+ .length;
363
+ }
364
+
365
+ function getLoggedMessages(
366
+ spy: SpyInstance<typeof logger.error>,
367
+ ): Array<string> {
368
+ return spy.mock.calls.map((call: Parameters<typeof logger.error>): string => {
369
+ return String(call[0]);
370
+ });
371
+ }
372
+
373
+ let errorLogSpy: SpyInstance<typeof logger.error>;
374
+ let warnLogSpy: SpyInstance<typeof logger.warn>;
375
+ let debugLogSpy: SpyInstance<typeof logger.debug>;
376
+
377
+ beforeEach((): void => {
378
+ errorLogSpy = jest.spyOn(logger, "error").mockImplementation((): void => {
379
+ return undefined;
380
+ });
381
+ warnLogSpy = jest.spyOn(logger, "warn").mockImplementation((): void => {
382
+ return undefined;
383
+ });
384
+ debugLogSpy = jest.spyOn(logger, "debug").mockImplementation((): void => {
385
+ return undefined;
386
+ });
387
+ });
388
+
389
+ afterEach((): void => {
390
+ jest.restoreAllMocks();
391
+ });
392
+
393
+ describe("MicrosoftTeamsReplies.sendBestEffort", () => {
394
+ test("sends a text reply once and returns true", async () => {
395
+ const fake: FakeTurn = buildFakeTurn();
396
+
397
+ const sent: boolean = await MicrosoftTeamsReplies.sendBestEffort(
398
+ fake.turnContext,
399
+ "✅ Incident created successfully!",
400
+ );
401
+
402
+ expect(sent).toBe(true);
403
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
404
+ expect(fake.sendActivity).toHaveBeenCalledWith(
405
+ "✅ Incident created successfully!",
406
+ );
407
+ expect(errorLogSpy).not.toHaveBeenCalled();
408
+ });
409
+
410
+ test("sends an activity, such as an invoke response, as it is", async () => {
411
+ const fake: FakeTurn = buildFakeTurn();
412
+ const invokeResponse: Partial<Activity> = {
413
+ type: "invokeResponse",
414
+ value: { status: 200 },
415
+ };
416
+
417
+ const sent: boolean = await MicrosoftTeamsReplies.sendBestEffort(
418
+ fake.turnContext,
419
+ invokeResponse,
420
+ );
421
+
422
+ expect(sent).toBe(true);
423
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
424
+ expect(fake.sendActivity.mock.calls[0]?.[0]).toBe(invokeResponse);
425
+ });
426
+
427
+ test("returns false and logs one error line when Teams refuses the reply, instead of throwing", async () => {
428
+ const fake: FakeTurn = buildFakeTurn();
429
+ fake.sendActivity.mockRejectedValue(buildNotInRosterError());
430
+
431
+ await expect(
432
+ MicrosoftTeamsReplies.sendBestEffort(
433
+ fake.turnContext,
434
+ "Sorry, something went wrong in OneUptime while handling that message.",
435
+ ),
436
+ ).resolves.toBe(false);
437
+
438
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
439
+ expect(getLoggedMessages(errorLogSpy)).toEqual([
440
+ "Could not send a Microsoft Teams reply: RestError 403 BotNotInConversationRoster: The bot is not part of the conversation roster.",
441
+ ]);
442
+ });
443
+
444
+ test("tries a refused reply once: no retry, so no second bubble (#4111)", async () => {
445
+ const fake: FakeTurn = buildFakeTurn();
446
+ fake.sendActivity.mockRejectedValue(buildMessageTooLargeError());
447
+
448
+ const sent: boolean = await MicrosoftTeamsReplies.sendBestEffort(
449
+ fake.turnContext,
450
+ "x".repeat(60 * 1024),
451
+ );
452
+
453
+ expect(sent).toBe(false);
454
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
455
+ expect(getLoggedMessages(errorLogSpy)).toEqual([
456
+ "Could not send a Microsoft Teams reply: RestError 413 MessageSizeTooBig: Message size too large.",
457
+ ]);
458
+ });
459
+
460
+ test("swallows a rejection that carries no Error at all", async () => {
461
+ const fake: FakeTurn = buildFakeTurn();
462
+ fake.sendActivity.mockRejectedValueOnce("socket closed");
463
+ fake.sendActivity.mockRejectedValueOnce(undefined);
464
+
465
+ await expect(
466
+ MicrosoftTeamsReplies.sendBestEffort(fake.turnContext, "first"),
467
+ ).resolves.toBe(false);
468
+ await expect(
469
+ MicrosoftTeamsReplies.sendBestEffort(fake.turnContext, "second"),
470
+ ).resolves.toBe(false);
471
+
472
+ // One try per reply.
473
+ expect(fake.sendActivity).toHaveBeenCalledTimes(2);
474
+ expect(getLoggedMessages(errorLogSpy)).toEqual([
475
+ "Could not send a Microsoft Teams reply: socket closed",
476
+ "Could not send a Microsoft Teams reply: undefined",
477
+ ]);
478
+ });
479
+
480
+ test("swallows a sendActivity that throws before returning a promise", async () => {
481
+ const fake: FakeTurn = buildFakeTurn();
482
+ fake.sendActivity.mockImplementation((): Promise<unknown> => {
483
+ throw new Error("The turn context has been revoked.");
484
+ });
485
+
486
+ await expect(
487
+ MicrosoftTeamsReplies.sendBestEffort(fake.turnContext, "hello"),
488
+ ).resolves.toBe(false);
489
+
490
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
491
+ expect(getLoggedMessages(errorLogSpy)).toEqual([
492
+ "Could not send a Microsoft Teams reply: Error: The turn context has been revoked.",
493
+ ]);
494
+ });
495
+
496
+ test("resolves only once Teams has taken the reply, so a caller's next step comes after it", async () => {
497
+ /*
498
+ * The create handlers send the confirmation and only then remove the
499
+ * submitted form; that order holds only if this waits for Teams.
500
+ */
501
+ const fake: FakeTurn = buildFakeTurn();
502
+ const teamsAnswer: Deferred = createDeferred();
503
+ fake.sendActivity.mockImplementation(async (): Promise<unknown> => {
504
+ await teamsAnswer.promise;
505
+ return { id: "reply-activity-id" };
506
+ });
507
+ let settled: boolean = false;
508
+
509
+ const sending: Promise<boolean> = MicrosoftTeamsReplies.sendBestEffort(
510
+ fake.turnContext,
511
+ "✅ Incident created successfully!",
512
+ ).then((sent: boolean): boolean => {
513
+ settled = true;
514
+ return sent;
515
+ });
516
+
517
+ await waitForPendingWork();
518
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
519
+ expect(settled).toBe(false);
520
+
521
+ teamsAnswer.resolve();
522
+ await expect(sending).resolves.toBe(true);
523
+ expect(settled).toBe(true);
524
+ });
525
+ });
526
+
527
+ describe("MicrosoftTeamsReplies.deleteBestEffort", () => {
528
+ test("removes the activity with the given id", async () => {
529
+ const fake: FakeTurn = buildFakeTurn();
530
+
531
+ await expect(
532
+ MicrosoftTeamsReplies.deleteBestEffort(
533
+ fake.turnContext,
534
+ "submitted-form-activity-id",
535
+ ),
536
+ ).resolves.toBeUndefined();
537
+
538
+ expect(fake.deleteActivity).toHaveBeenCalledTimes(1);
539
+ expect(fake.deleteActivity).toHaveBeenCalledWith(
540
+ "submitted-form-activity-id",
541
+ );
542
+ });
543
+
544
+ test("does nothing without an id to remove", async () => {
545
+ const fake: FakeTurn = buildFakeTurn();
546
+
547
+ await MicrosoftTeamsReplies.deleteBestEffort(fake.turnContext, undefined);
548
+ await MicrosoftTeamsReplies.deleteBestEffort(fake.turnContext, "");
549
+
550
+ expect(fake.deleteActivity).not.toHaveBeenCalled();
551
+ expect(fake.sendActivity).not.toHaveBeenCalled();
552
+ expect(debugLogSpy).not.toHaveBeenCalled();
553
+ });
554
+
555
+ test("swallows a failed removal and logs it at debug only: the record already exists", async () => {
556
+ const fake: FakeTurn = buildFakeTurn();
557
+ fake.deleteActivity.mockRejectedValue(
558
+ buildRestError({
559
+ message: "The bot may not delete this message.",
560
+ statusCode: 403,
561
+ code: "Forbidden",
562
+ responseStatus: 403,
563
+ }),
564
+ );
565
+
566
+ await expect(
567
+ MicrosoftTeamsReplies.deleteBestEffort(
568
+ fake.turnContext,
569
+ "submitted-form-activity-id",
570
+ ),
571
+ ).resolves.toBeUndefined();
572
+
573
+ expect(fake.deleteActivity).toHaveBeenCalledTimes(1);
574
+ expect(getLoggedMessages(debugLogSpy)).toEqual([
575
+ "Could not remove a submitted Microsoft Teams card: RestError 403 Forbidden: The bot may not delete this message.",
576
+ ]);
577
+ // No error line, and no bubble contradicting the success reply.
578
+ expect(errorLogSpy).not.toHaveBeenCalled();
579
+ expect(fake.sendActivity).not.toHaveBeenCalled();
580
+ });
581
+
582
+ test("swallows a deleteActivity that throws before returning a promise", async () => {
583
+ const fake: FakeTurn = buildFakeTurn();
584
+ fake.deleteActivity.mockImplementation((): Promise<void> => {
585
+ throw new Error("The turn context has been revoked.");
586
+ });
587
+
588
+ await expect(
589
+ MicrosoftTeamsReplies.deleteBestEffort(fake.turnContext, "activity-id"),
590
+ ).resolves.toBeUndefined();
591
+
592
+ expect(getLoggedMessages(debugLogSpy)).toEqual([
593
+ "Could not remove a submitted Microsoft Teams card: Error: The turn context has been revoked.",
594
+ ]);
595
+ expect(errorLogSpy).not.toHaveBeenCalled();
596
+ expect(fake.sendActivity).not.toHaveBeenCalled();
597
+ });
598
+
599
+ test("resolves only once Teams has answered the removal, so the turn does not end before it", async () => {
600
+ /*
601
+ * Bot Framework revokes the turn context as soon as the turn ends
602
+ * (BotAdapter.runMiddleware), so a removal still under way then can fail
603
+ * and leave the submitted form behind.
604
+ */
605
+ const fake: FakeTurn = buildFakeTurn();
606
+ const teamsAnswer: Deferred = createDeferred();
607
+ fake.deleteActivity.mockImplementation(async (): Promise<void> => {
608
+ await teamsAnswer.promise;
609
+ });
610
+ let settled: boolean = false;
611
+
612
+ const removing: Promise<void> = MicrosoftTeamsReplies.deleteBestEffort(
613
+ fake.turnContext,
614
+ "submitted-form-activity-id",
615
+ ).then((): void => {
616
+ settled = true;
617
+ });
618
+
619
+ await waitForPendingWork();
620
+ expect(fake.deleteActivity).toHaveBeenCalledTimes(1);
621
+ expect(settled).toBe(false);
622
+
623
+ teamsAnswer.resolve();
624
+ await removing;
625
+ expect(settled).toBe(true);
626
+ });
627
+ });
628
+
629
+ describe("MicrosoftTeamsReplies.sendCardWithinSizeLimit", () => {
630
+ test("the attachment content type is the adaptive card media type", () => {
631
+ expect(MICROSOFT_TEAMS_ADAPTIVE_CARD_CONTENT_TYPE).toBe(
632
+ "application/vnd.microsoft.card.adaptive",
633
+ );
634
+ });
635
+
636
+ test("sends the card built for the first budget, as an adaptive card attachment, when Teams accepts it", async () => {
637
+ const fake: FakeTurn = buildFakeTurn();
638
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
639
+
640
+ await expect(
641
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
642
+ turnContext: fake.turnContext,
643
+ buildCard: buildCard,
644
+ }),
645
+ ).resolves.toBeUndefined();
646
+
647
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([FIRST_BUDGET_IN_BYTES]);
648
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
649
+ expect(fake.sendActivity.mock.calls[0]?.[0]).toStrictEqual({
650
+ attachments: [
651
+ {
652
+ contentType: "application/vnd.microsoft.card.adaptive",
653
+ content: buildCardForBudget(FIRST_BUDGET_IN_BYTES),
654
+ },
655
+ ],
656
+ });
657
+ // The card goes out as built: the very object buildCard returned.
658
+ expect(getSentCards(fake)[0]).toBe(buildCard.mock.results[0]?.value);
659
+ expect(warnLogSpy).not.toHaveBeenCalled();
660
+ expect(errorLogSpy).not.toHaveBeenCalled();
661
+ });
662
+
663
+ test("after a 413, builds the card again for the next, smaller budget and sends that", async () => {
664
+ const fake: FakeTurn = buildFakeTurn();
665
+ fake.sendActivity.mockRejectedValueOnce(buildMessageTooLargeError());
666
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
667
+
668
+ await expect(
669
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
670
+ turnContext: fake.turnContext,
671
+ buildCard: buildCard,
672
+ }),
673
+ ).resolves.toBeUndefined();
674
+
675
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([
676
+ FIRST_BUDGET_IN_BYTES,
677
+ SECOND_BUDGET_IN_BYTES,
678
+ ]);
679
+ expect(getSentCards(fake)).toEqual([
680
+ buildCardForBudget(FIRST_BUDGET_IN_BYTES),
681
+ buildCardForBudget(SECOND_BUDGET_IN_BYTES),
682
+ ]);
683
+ for (const activity of getSentActivities(fake)) {
684
+ expect(activity.attachments).toHaveLength(1);
685
+ expect(activity.attachments[0]?.contentType).toBe(
686
+ MICROSOFT_TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,
687
+ );
688
+ }
689
+ });
690
+
691
+ test("tries 40 KiB, then 20 KiB, then the card without lists, while Teams keeps refusing", async () => {
692
+ const fake: FakeTurn = buildFakeTurn();
693
+ fake.sendActivity
694
+ .mockRejectedValueOnce(buildMessageTooLargeError())
695
+ .mockRejectedValueOnce(buildMessageTooLargeError());
696
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
697
+
698
+ await MicrosoftTeamsReplies.sendCardWithinSizeLimit({
699
+ turnContext: fake.turnContext,
700
+ buildCard: buildCard,
701
+ });
702
+
703
+ expect(MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES).toEqual([
704
+ FIRST_BUDGET_IN_BYTES,
705
+ SECOND_BUDGET_IN_BYTES,
706
+ LAST_BUDGET_IN_BYTES,
707
+ ]);
708
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([
709
+ FIRST_BUDGET_IN_BYTES,
710
+ SECOND_BUDGET_IN_BYTES,
711
+ LAST_BUDGET_IN_BYTES,
712
+ ]);
713
+ expect(getSentCards(fake)).toEqual([
714
+ buildCardForBudget(FIRST_BUDGET_IN_BYTES),
715
+ buildCardForBudget(SECOND_BUDGET_IN_BYTES),
716
+ buildCardForBudget(LAST_BUDGET_IN_BYTES),
717
+ ]);
718
+ });
719
+
720
+ test("does not send again a card identical to the one just refused, even when built as a new object", async () => {
721
+ const fake: FakeTurn = buildFakeTurn();
722
+ fake.sendActivity.mockRejectedValueOnce(buildMessageTooLargeError());
723
+ /*
724
+ * A small project: its whole form fits in 20 KiB already, so the 20 KiB
725
+ * build is the same card as the 40 KiB one. Only the card without lists
726
+ * differs.
727
+ */
728
+ const buildCard: Mock<BuildCard> = mockBuildCard(
729
+ (budgetInBytes: number): JSONObject => {
730
+ return buildCardForBudget(
731
+ budgetInBytes === LAST_BUDGET_IN_BYTES
732
+ ? LAST_BUDGET_IN_BYTES
733
+ : FIRST_BUDGET_IN_BYTES,
734
+ );
735
+ },
736
+ );
737
+
738
+ await expect(
739
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
740
+ turnContext: fake.turnContext,
741
+ buildCard: buildCard,
742
+ }),
743
+ ).resolves.toBeUndefined();
744
+
745
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([
746
+ FIRST_BUDGET_IN_BYTES,
747
+ SECOND_BUDGET_IN_BYTES,
748
+ LAST_BUDGET_IN_BYTES,
749
+ ]);
750
+ expect(getSentCards(fake)).toEqual([
751
+ buildCardForBudget(FIRST_BUDGET_IN_BYTES),
752
+ buildCardForBudget(LAST_BUDGET_IN_BYTES),
753
+ ]);
754
+ expect(warnLogSpy).toHaveBeenCalledTimes(1);
755
+ });
756
+
757
+ test("does not send the card without lists when it is the 20 KiB card just refused, and throws that refusal", async () => {
758
+ const fake: FakeTurn = buildFakeTurn();
759
+ const refusedAtFirstBudget: Error = buildMessageTooLargeError();
760
+ const refusedAtSecondBudget: Error = buildMessageTooLargeError();
761
+ fake.sendActivity
762
+ .mockRejectedValueOnce(refusedAtFirstBudget)
763
+ .mockRejectedValueOnce(refusedAtSecondBudget);
764
+ /*
765
+ * A form whose fixed part alone is over 20 KiB: at 20 KiB every list is
766
+ * already left off, so the card for the last budget is the 20 KiB card
767
+ * again, while the 40 KiB card still had its lists. The comparison is with
768
+ * the card just refused, not only with the first one.
769
+ */
770
+ const buildCard: Mock<BuildCard> = mockBuildCard(
771
+ (budgetInBytes: number): JSONObject => {
772
+ return buildCardForBudget(
773
+ budgetInBytes === FIRST_BUDGET_IN_BYTES
774
+ ? FIRST_BUDGET_IN_BYTES
775
+ : LAST_BUDGET_IN_BYTES,
776
+ );
777
+ },
778
+ );
779
+
780
+ await expect(
781
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
782
+ turnContext: fake.turnContext,
783
+ buildCard: buildCard,
784
+ }),
785
+ ).rejects.toBe(refusedAtSecondBudget);
786
+
787
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([
788
+ FIRST_BUDGET_IN_BYTES,
789
+ SECOND_BUDGET_IN_BYTES,
790
+ LAST_BUDGET_IN_BYTES,
791
+ ]);
792
+ expect(getSentCards(fake)).toEqual([
793
+ buildCardForBudget(FIRST_BUDGET_IN_BYTES),
794
+ buildCardForBudget(LAST_BUDGET_IN_BYTES),
795
+ ]);
796
+ expect(warnLogSpy).toHaveBeenCalledTimes(2);
797
+ });
798
+
799
+ test("sends a card once when every budget builds the same card, and throws Teams' refusal", async () => {
800
+ const fake: FakeTurn = buildFakeTurn();
801
+ const refusal: Error = buildMessageTooLargeError();
802
+ fake.sendActivity.mockRejectedValue(refusal);
803
+ const buildCard: Mock<BuildCard> = mockBuildCard((): JSONObject => {
804
+ return buildCardForBudget(FIRST_BUDGET_IN_BYTES);
805
+ });
806
+
807
+ await expect(
808
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
809
+ turnContext: fake.turnContext,
810
+ buildCard: buildCard,
811
+ }),
812
+ ).rejects.toBe(refusal);
813
+
814
+ expect(buildCard).toHaveBeenCalledTimes(3);
815
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
816
+ });
817
+
818
+ test("throws the last refusal when Teams refuses the card at every budget", async () => {
819
+ const fake: FakeTurn = buildFakeTurn();
820
+ const refusals: Array<Error> = [
821
+ buildMessageTooLargeError(),
822
+ buildMessageTooLargeError(),
823
+ buildMessageTooLargeError(),
824
+ ];
825
+ for (const refusal of refusals) {
826
+ fake.sendActivity.mockRejectedValueOnce(refusal);
827
+ }
828
+
829
+ const sending: Promise<void> =
830
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
831
+ turnContext: fake.turnContext,
832
+ buildCard: mockBuildCard(buildCardForBudget),
833
+ });
834
+
835
+ await expect(sending).rejects.toBe(refusals[2]);
836
+ expect(fake.sendActivity).toHaveBeenCalledTimes(3);
837
+ expect(warnLogSpy).toHaveBeenCalledTimes(3);
838
+ });
839
+
840
+ test("throws any other refusal at once, without trying a smaller budget", async () => {
841
+ const fake: FakeTurn = buildFakeTurn();
842
+ const notInRoster: Error = buildNotInRosterError();
843
+ fake.sendActivity.mockRejectedValue(notInRoster);
844
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
845
+
846
+ await expect(
847
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
848
+ turnContext: fake.turnContext,
849
+ buildCard: buildCard,
850
+ }),
851
+ ).rejects.toBe(notInRoster);
852
+
853
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([FIRST_BUDGET_IN_BYTES]);
854
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
855
+ expect(warnLogSpy).not.toHaveBeenCalled();
856
+ });
857
+
858
+ test("throws an error that follows a 413 at once, leaving the last budget untried", async () => {
859
+ const fake: FakeTurn = buildFakeTurn();
860
+ const serverError: Error = buildRestError({
861
+ message: "Internal server error.",
862
+ statusCode: 500,
863
+ code: "InternalServerError",
864
+ responseStatus: 500,
865
+ });
866
+ fake.sendActivity
867
+ .mockRejectedValueOnce(buildMessageTooLargeError())
868
+ .mockRejectedValueOnce(serverError);
869
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
870
+
871
+ await expect(
872
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
873
+ turnContext: fake.turnContext,
874
+ buildCard: buildCard,
875
+ }),
876
+ ).rejects.toBe(serverError);
877
+
878
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([
879
+ FIRST_BUDGET_IN_BYTES,
880
+ SECOND_BUDGET_IN_BYTES,
881
+ ]);
882
+ expect(fake.sendActivity).toHaveBeenCalledTimes(2);
883
+ });
884
+
885
+ test("throws an error from building the card as it is, and sends nothing", async () => {
886
+ const fake: FakeTurn = buildFakeTurn();
887
+ const buildError: BadDataException = new BadDataException(
888
+ "Could not build the form.",
889
+ );
890
+
891
+ await expect(
892
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
893
+ turnContext: fake.turnContext,
894
+ buildCard: (): JSONObject => {
895
+ throw buildError;
896
+ },
897
+ }),
898
+ ).rejects.toBe(buildError);
899
+
900
+ expect(fake.sendActivity).not.toHaveBeenCalled();
901
+ });
902
+
903
+ test("throws an error from building a smaller card as it is, not the 413 before it", async () => {
904
+ const fake: FakeTurn = buildFakeTurn();
905
+ fake.sendActivity.mockRejectedValueOnce(buildMessageTooLargeError());
906
+ const buildError: BadDataException = new BadDataException(
907
+ "Could not build the form.",
908
+ );
909
+ const buildCard: Mock<BuildCard> = mockBuildCard(
910
+ (budgetInBytes: number): JSONObject => {
911
+ if (budgetInBytes === FIRST_BUDGET_IN_BYTES) {
912
+ return buildCardForBudget(budgetInBytes);
913
+ }
914
+
915
+ throw buildError;
916
+ },
917
+ );
918
+
919
+ await expect(
920
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
921
+ turnContext: fake.turnContext,
922
+ buildCard: buildCard,
923
+ }),
924
+ ).rejects.toBe(buildError);
925
+
926
+ // The last budget is not tried: only a "too large" refusal moves on.
927
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([
928
+ FIRST_BUDGET_IN_BYTES,
929
+ SECOND_BUDGET_IN_BYTES,
930
+ ]);
931
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
932
+ });
933
+
934
+ test("recognises a refusal by its fields: a proxy's bare 413 moves on as well", async () => {
935
+ const fake: FakeTurn = buildFakeTurn();
936
+ fake.sendActivity.mockRejectedValueOnce({ response: { status: 413 } });
937
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
938
+
939
+ await expect(
940
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
941
+ turnContext: fake.turnContext,
942
+ buildCard: buildCard,
943
+ }),
944
+ ).resolves.toBeUndefined();
945
+
946
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([
947
+ FIRST_BUDGET_IN_BYTES,
948
+ SECOND_BUDGET_IN_BYTES,
949
+ ]);
950
+ expect(fake.sendActivity).toHaveBeenCalledTimes(2);
951
+ expect(warnLogSpy).toHaveBeenCalledTimes(1);
952
+ });
953
+
954
+ test("honours custom budgets, in the order given", async () => {
955
+ const fake: FakeTurn = buildFakeTurn();
956
+ const refusal: Error = buildMessageTooLargeError();
957
+ fake.sendActivity.mockRejectedValue(refusal);
958
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
959
+
960
+ await expect(
961
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
962
+ turnContext: fake.turnContext,
963
+ buildCard: buildCard,
964
+ budgetsInBytes: [5000, 1000],
965
+ }),
966
+ ).rejects.toBe(refusal);
967
+
968
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([5000, 1000]);
969
+ expect(getSentCards(fake)).toEqual([
970
+ buildCardForBudget(5000),
971
+ buildCardForBudget(1000),
972
+ ]);
973
+ });
974
+
975
+ test("a single custom budget is tried once", async () => {
976
+ const fake: FakeTurn = buildFakeTurn();
977
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
978
+
979
+ await MicrosoftTeamsReplies.sendCardWithinSizeLimit({
980
+ turnContext: fake.turnContext,
981
+ buildCard: buildCard,
982
+ budgetsInBytes: [LAST_BUDGET_IN_BYTES],
983
+ });
984
+
985
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([LAST_BUDGET_IN_BYTES]);
986
+ expect(fake.sendActivity).toHaveBeenCalledTimes(1);
987
+ });
988
+
989
+ test("an explicit undefined budget list means the default budgets", async () => {
990
+ const fake: FakeTurn = buildFakeTurn();
991
+ fake.sendActivity
992
+ .mockRejectedValueOnce(buildMessageTooLargeError())
993
+ .mockRejectedValueOnce(buildMessageTooLargeError());
994
+ const buildCard: Mock<BuildCard> = mockBuildCard(buildCardForBudget);
995
+
996
+ await expect(
997
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
998
+ turnContext: fake.turnContext,
999
+ buildCard: buildCard,
1000
+ budgetsInBytes: undefined,
1001
+ }),
1002
+ ).resolves.toBeUndefined();
1003
+
1004
+ expect(getBudgetsBuiltFor(buildCard)).toEqual([
1005
+ ...MICROSOFT_TEAMS_CARD_SIZE_BUDGETS_IN_BYTES,
1006
+ ]);
1007
+ expect(fake.sendActivity).toHaveBeenCalledTimes(3);
1008
+ });
1009
+
1010
+ test("logs each 413 as a warning naming that card's size and budget, and logs no error itself", async () => {
1011
+ const fake: FakeTurn = buildFakeTurn();
1012
+ refuseMessagesOver(fake, 8 * 1024);
1013
+ const buildCard: Mock<BuildCard> = mockBuildCard(
1014
+ buildMonitorFormBuilder(3000),
1015
+ );
1016
+
1017
+ await expect(
1018
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
1019
+ turnContext: fake.turnContext,
1020
+ buildCard: buildCard,
1021
+ }),
1022
+ ).resolves.toBeUndefined();
1023
+
1024
+ const sentSizesInBytes: Array<number> = getSentCards(fake).map(
1025
+ (card: JSONObject): number => {
1026
+ return MicrosoftTeamsMessageSize.getSizeInBytes(card);
1027
+ },
1028
+ );
1029
+ expect(sentSizesInBytes).toHaveLength(3);
1030
+ // The two refused cards differ in size, so each line must name its own.
1031
+ expect(sentSizesInBytes[0]).toBeGreaterThan(sentSizesInBytes[1] as number);
1032
+ expect(getLoggedMessages(warnLogSpy)).toEqual([
1033
+ `Microsoft Teams refused a ${sentSizesInBytes[0]} byte card as too large (budget ${FIRST_BUDGET_IN_BYTES} bytes); trying a smaller one.`,
1034
+ `Microsoft Teams refused a ${sentSizesInBytes[1]} byte card as too large (budget ${SECOND_BUDGET_IN_BYTES} bytes); trying a smaller one.`,
1035
+ ]);
1036
+ // The caller decides what to tell the user, and logs the final failure.
1037
+ expect(errorLogSpy).not.toHaveBeenCalled();
1038
+ });
1039
+
1040
+ test("#4111: a form Teams refuses at 40 KiB is sent again at 20 KiB, and that one is accepted", async () => {
1041
+ const fake: FakeTurn = buildFakeTurn();
1042
+ /*
1043
+ * Teams refused bot messages well under its documented limit; 30 KiB
1044
+ * stands in for that here.
1045
+ */
1046
+ refuseMessagesOver(fake, 30 * 1024);
1047
+
1048
+ await expect(
1049
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
1050
+ turnContext: fake.turnContext,
1051
+ buildCard: buildMonitorFormBuilder(3000),
1052
+ }),
1053
+ ).resolves.toBeUndefined();
1054
+
1055
+ const sentCards: Array<JSONObject> = getSentCards(fake);
1056
+ expect(sentCards).toHaveLength(2);
1057
+
1058
+ const refused: JSONObject = sentCards[0] as JSONObject;
1059
+ const accepted: JSONObject = sentCards[1] as JSONObject;
1060
+ expect(MicrosoftTeamsMessageSize.getSizeInBytes(refused)).toBeGreaterThan(
1061
+ 30 * 1024,
1062
+ );
1063
+ expect(
1064
+ MicrosoftTeamsMessageSize.getSizeInBytes(refused),
1065
+ ).toBeLessThanOrEqual(FIRST_BUDGET_IN_BYTES);
1066
+ expect(
1067
+ MicrosoftTeamsMessageSize.getSizeInBytes(accepted),
1068
+ ).toBeLessThanOrEqual(SECOND_BUDGET_IN_BYTES);
1069
+ expect(getMonitorChoiceCount(accepted)).toBeGreaterThan(0);
1070
+ expect(getMonitorChoiceCount(accepted)).toBeLessThan(
1071
+ getMonitorChoiceCount(refused),
1072
+ );
1073
+ expect(warnLogSpy).toHaveBeenCalledTimes(1);
1074
+ expect(errorLogSpy).not.toHaveBeenCalled();
1075
+ });
1076
+
1077
+ test("#4111: when Teams refuses even 20 KiB, the form goes out without the lists", async () => {
1078
+ const fake: FakeTurn = buildFakeTurn();
1079
+ refuseMessagesOver(fake, 8 * 1024);
1080
+
1081
+ await expect(
1082
+ MicrosoftTeamsReplies.sendCardWithinSizeLimit({
1083
+ turnContext: fake.turnContext,
1084
+ buildCard: buildMonitorFormBuilder(3000),
1085
+ }),
1086
+ ).resolves.toBeUndefined();
1087
+
1088
+ const sentCards: Array<JSONObject> = getSentCards(fake);
1089
+ expect(sentCards).toHaveLength(3);
1090
+ expect(getMonitorChoiceCount(sentCards[2] as JSONObject)).toBe(0);
1091
+ expect(warnLogSpy).toHaveBeenCalledTimes(2);
1092
+ });
1093
+ });
1094
+
1095
+ interface DescribeErrorCase {
1096
+ description: string;
1097
+ error: unknown;
1098
+ expected: string;
1099
+ }
1100
+
1101
+ // An Error subclass that leaves Error's name as it is, as OneUptime's do.
1102
+ class FormBuildError extends Error {}
1103
+
1104
+ const DESCRIBE_ERROR_CASES: Array<DescribeErrorCase> = [
1105
+ {
1106
+ description: "the connector's RestError for a message that is too large",
1107
+ error: buildMessageTooLargeError(),
1108
+ expected: "RestError 413 MessageSizeTooBig: Message size too large.",
1109
+ },
1110
+ {
1111
+ description: "the same RestError as a plain object",
1112
+ error: {
1113
+ name: "RestError",
1114
+ message: "Message size too large.",
1115
+ statusCode: 413,
1116
+ code: "MessageSizeTooBig",
1117
+ details: {
1118
+ error: {
1119
+ code: "MessageSizeTooBig",
1120
+ message: "Message size too large.",
1121
+ },
1122
+ },
1123
+ },
1124
+ expected: "RestError 413 MessageSizeTooBig: Message size too large.",
1125
+ },
1126
+ {
1127
+ description: "the same RestError created in another JavaScript realm",
1128
+ error: vm.runInNewContext(`
1129
+ const error = new Error("Message size too large.");
1130
+ error.name = "RestError";
1131
+ error.code = "MessageSizeTooBig";
1132
+ error.statusCode = 413;
1133
+ error;
1134
+ `),
1135
+ expected: "RestError 413 MessageSizeTooBig: Message size too large.",
1136
+ },
1137
+ {
1138
+ description: "a RestError whose status is only in response.status",
1139
+ error: buildRestError({ message: "Forbidden", responseStatus: 403 }),
1140
+ expected: "RestError 403: Forbidden",
1141
+ },
1142
+ {
1143
+ description: "a RestError whose code is only in details.error.code",
1144
+ error: {
1145
+ name: "RestError",
1146
+ message: "Message size too large.",
1147
+ details: { error: { code: "MessageSizeTooBig" } },
1148
+ },
1149
+ expected: "RestError MessageSizeTooBig: Message size too large.",
1150
+ },
1151
+ {
1152
+ description: "a RestError that removed the bot from the roster",
1153
+ error: buildNotInRosterError(),
1154
+ expected:
1155
+ "RestError 403 BotNotInConversationRoster: The bot is not part of the conversation roster.",
1156
+ },
1157
+ {
1158
+ description: "a plain Error",
1159
+ error: new Error("Something went wrong."),
1160
+ expected: "Error: Something went wrong.",
1161
+ },
1162
+ {
1163
+ description: "a TypeError",
1164
+ error: new TypeError("card.body is not iterable"),
1165
+ expected: "TypeError: card.body is not iterable",
1166
+ },
1167
+ {
1168
+ description: "a Node system error, with its code",
1169
+ error: Object.assign(new Error("socket hang up"), { code: "ECONNRESET" }),
1170
+ expected: "Error ECONNRESET: socket hang up",
1171
+ },
1172
+ {
1173
+ /*
1174
+ * OneUptime's exceptions keep Error's name, so the class comes from the
1175
+ * constructor; their numeric code is no HTTP status or Teams code.
1176
+ */
1177
+ description: "a OneUptime exception, by its class and without its code",
1178
+ error: new BadDataException("Title is required."),
1179
+ expected: "BadDataException: Title is required.",
1180
+ },
1181
+ {
1182
+ description: "a permission refusal, by its class",
1183
+ error: new NotAuthorizedException(
1184
+ "You do not have permission to create scheduled maintenance events.",
1185
+ ),
1186
+ expected:
1187
+ "NotAuthorizedException: You do not have permission to create scheduled maintenance events.",
1188
+ },
1189
+ {
1190
+ description: "a server exception, by its class",
1191
+ error: new ServerException("Database connection lost."),
1192
+ expected: "ServerException: Database connection lost.",
1193
+ },
1194
+ {
1195
+ description:
1196
+ "a Teams account nobody linked, by its own class rather than BadDataException",
1197
+ error: new MicrosoftTeamsAccountNotLinkedException(
1198
+ "No OneUptime user linked to this Microsoft Teams user. Please authenticate with Microsoft Teams.",
1199
+ ),
1200
+ expected:
1201
+ "MicrosoftTeamsAccountNotLinkedException: No OneUptime user linked to this Microsoft Teams user. Please authenticate with Microsoft Teams.",
1202
+ },
1203
+ {
1204
+ // The log is for operators: it may name the other project's record.
1205
+ description: "a reference to another project's record, by its class",
1206
+ error: new ProjectScopedReferenceException(OTHER_PROJECT_REFERENCE_MESSAGE),
1207
+ expected: `ProjectScopedReferenceException: ${OTHER_PROJECT_REFERENCE_MESSAGE}`,
1208
+ },
1209
+ {
1210
+ description: "any Error subclass that keeps Error's name, by its class",
1211
+ error: new FormBuildError("card.body is not iterable"),
1212
+ expected: "FormBuildError: card.body is not iterable",
1213
+ },
1214
+ {
1215
+ description: "an error whose own name is not Error, by that name",
1216
+ error: Object.assign(new BadDataException("Title is required."), {
1217
+ name: "ValidationError",
1218
+ }),
1219
+ expected: "ValidationError: Title is required.",
1220
+ },
1221
+ {
1222
+ description: "an error of a class with no name, as Error",
1223
+ error: new (class extends Error {})("card.body is not iterable"),
1224
+ expected: "Error: card.body is not iterable",
1225
+ },
1226
+ {
1227
+ description: "a plain Error from another JavaScript realm, as Error",
1228
+ error: vm.runInNewContext(`new Error("Something went wrong.");`),
1229
+ expected: "Error: Something went wrong.",
1230
+ },
1231
+ {
1232
+ description: "an object with neither a name nor a message",
1233
+ error: {},
1234
+ expected: "Error: ",
1235
+ },
1236
+ {
1237
+ description: "an object with only a message, as Error rather than Object",
1238
+ error: { message: "Something went wrong." },
1239
+ expected: "Error: Something went wrong.",
1240
+ },
1241
+ {
1242
+ description:
1243
+ "an object from another JavaScript realm, as Error rather than Object",
1244
+ error: vm.runInNewContext(`({ message: "Something went wrong." })`),
1245
+ expected: "Error: Something went wrong.",
1246
+ },
1247
+ {
1248
+ description: "an object without a prototype, as Error",
1249
+ error: Object.assign(Object.create(null) as Record<string, unknown>, {
1250
+ message: "Something went wrong.",
1251
+ }),
1252
+ expected: "Error: Something went wrong.",
1253
+ },
1254
+ {
1255
+ description: "an object with only a status and a code",
1256
+ error: { statusCode: 413, code: "MessageSizeTooBig" },
1257
+ expected: "Error 413 MessageSizeTooBig: ",
1258
+ },
1259
+ {
1260
+ description: "an empty name, read as Error",
1261
+ error: { name: "", message: "Something went wrong." },
1262
+ expected: "Error: Something went wrong.",
1263
+ },
1264
+ {
1265
+ description: "a message that is not a string",
1266
+ error: { name: "RestError", message: 42 },
1267
+ expected: "RestError: 42",
1268
+ },
1269
+ {
1270
+ description: "a null message",
1271
+ error: { name: "RestError", message: null, statusCode: 502 },
1272
+ expected: "RestError 502: ",
1273
+ },
1274
+ {
1275
+ description: "a string",
1276
+ error: "Teams is unavailable",
1277
+ expected: "Teams is unavailable",
1278
+ },
1279
+ { description: "null", error: null, expected: "null" },
1280
+ { description: "undefined", error: undefined, expected: "undefined" },
1281
+ { description: "a number", error: 413, expected: "413" },
1282
+ { description: "false", error: false, expected: "false" },
1283
+ ];
1284
+
1285
+ describe("MicrosoftTeamsReplies.describeError", () => {
1286
+ for (const describeErrorCase of DESCRIBE_ERROR_CASES) {
1287
+ test(`describes ${describeErrorCase.description}`, () => {
1288
+ expect(MicrosoftTeamsReplies.describeError(describeErrorCase.error)).toBe(
1289
+ describeErrorCase.expected,
1290
+ );
1291
+ });
1292
+ }
1293
+ });
1294
+
1295
+ // What the create submit logs when an incident could not be created.
1296
+ const LOG_FAILURE_SUMMARY: string =
1297
+ "Could not create an incident from Microsoft Teams";
1298
+ const LOG_FAILURE_ATTRIBUTES: LogAttributes = {
1299
+ projectId: PROJECT_ID.toString(),
1300
+ };
1301
+
1302
+ interface LogFailureCase {
1303
+ description: string;
1304
+ error: unknown;
1305
+ // The first line after the summary: describeError's line.
1306
+ described: string;
1307
+ // Whether the error itself is logged next, for its stack and class.
1308
+ logsTheErrorItself: boolean;
1309
+ }
1310
+
1311
+ const LOG_FAILURE_CASES: Array<LogFailureCase> = [
1312
+ {
1313
+ description: "a validation failure (BadDataException)",
1314
+ error: new BadDataException("Incident title is required."),
1315
+ described: "BadDataException: Incident title is required.",
1316
+ logsTheErrorItself: true,
1317
+ },
1318
+ {
1319
+ description: "a Teams account nobody linked",
1320
+ error: new MicrosoftTeamsAccountNotLinkedException(
1321
+ "No OneUptime user linked to this Microsoft Teams user.",
1322
+ ),
1323
+ described:
1324
+ "MicrosoftTeamsAccountNotLinkedException: No OneUptime user linked to this Microsoft Teams user.",
1325
+ logsTheErrorItself: true,
1326
+ },
1327
+ {
1328
+ description: "a reference to another project's record",
1329
+ error: new ProjectScopedReferenceException(OTHER_PROJECT_REFERENCE_MESSAGE),
1330
+ described: `ProjectScopedReferenceException: ${OTHER_PROJECT_REFERENCE_MESSAGE}`,
1331
+ logsTheErrorItself: true,
1332
+ },
1333
+ {
1334
+ description: "a TypeError from a bug",
1335
+ error: new TypeError("Cannot read properties of undefined (reading 'id')"),
1336
+ described: "TypeError: Cannot read properties of undefined (reading 'id')",
1337
+ logsTheErrorItself: true,
1338
+ },
1339
+ {
1340
+ description: "a Node system error, whose code is no HTTP status",
1341
+ error: Object.assign(new Error("socket hang up"), { code: "ECONNRESET" }),
1342
+ described: "Error ECONNRESET: socket hang up",
1343
+ logsTheErrorItself: true,
1344
+ },
1345
+ {
1346
+ description:
1347
+ "a Bot Framework request that never reached Teams, so has no status",
1348
+ error: buildRestError({
1349
+ message: "getaddrinfo ENOTFOUND smba.trafficmanager.net",
1350
+ code: "REQUEST_SEND_ERROR",
1351
+ }),
1352
+ described:
1353
+ "RestError REQUEST_SEND_ERROR: getaddrinfo ENOTFOUND smba.trafficmanager.net",
1354
+ logsTheErrorItself: true,
1355
+ },
1356
+ {
1357
+ description: "a thrown string",
1358
+ error: "socket closed",
1359
+ described: "socket closed",
1360
+ logsTheErrorItself: true,
1361
+ },
1362
+ {
1363
+ description: "a rejection with nothing in it",
1364
+ error: undefined,
1365
+ described: "undefined",
1366
+ logsTheErrorItself: true,
1367
+ },
1368
+ {
1369
+ description: "Teams refusing a message as too large (413)",
1370
+ error: buildMessageTooLargeError(),
1371
+ described: "RestError 413 MessageSizeTooBig: Message size too large.",
1372
+ logsTheErrorItself: false,
1373
+ },
1374
+ {
1375
+ description: "Teams refusing a reply in a conversation the bot left (403)",
1376
+ error: buildNotInRosterError(),
1377
+ described:
1378
+ "RestError 403 BotNotInConversationRoster: The bot is not part of the conversation roster.",
1379
+ logsTheErrorItself: false,
1380
+ },
1381
+ {
1382
+ description:
1383
+ "a RestError whose status is only in its non-enumerable response",
1384
+ error: buildRestError({
1385
+ message: "Service Unavailable",
1386
+ responseStatus: 503,
1387
+ }),
1388
+ described: "RestError 503: Service Unavailable",
1389
+ logsTheErrorItself: false,
1390
+ },
1391
+ {
1392
+ description: "a proxy's bare 413",
1393
+ error: { response: { status: 413 } },
1394
+ described: "Error 413: ",
1395
+ logsTheErrorItself: false,
1396
+ },
1397
+ ];
1398
+
1399
+ /*
1400
+ * The error log's calls, copied into arrays of this realm: jest records them
1401
+ * in arrays of its own, which toStrictEqual tells apart from these.
1402
+ */
1403
+ function getErrorLogCalls(): Array<Array<unknown>> {
1404
+ return Array.from(
1405
+ errorLogSpy.mock.calls,
1406
+ (call: Parameters<typeof logger.error>): Array<unknown> => {
1407
+ return Array.from(call);
1408
+ },
1409
+ );
1410
+ }
1411
+
1412
+ // A failure is logged at error level only: nothing at warn or debug.
1413
+ function expectNothingLoggedBelowError(): void {
1414
+ expect(warnLogSpy).not.toHaveBeenCalled();
1415
+ expect(debugLogSpy).not.toHaveBeenCalled();
1416
+ }
1417
+
1418
+ describe("MicrosoftTeamsReplies.logFailure", () => {
1419
+ for (const logFailureCase of LOG_FAILURE_CASES) {
1420
+ test(`${
1421
+ logFailureCase.logsTheErrorItself
1422
+ ? "logs the line, then the error itself, for"
1423
+ : "logs the line alone for"
1424
+ } ${logFailureCase.description}`, () => {
1425
+ MicrosoftTeamsReplies.logFailure(
1426
+ LOG_FAILURE_SUMMARY,
1427
+ logFailureCase.error,
1428
+ LOG_FAILURE_ATTRIBUTES,
1429
+ );
1430
+
1431
+ const line: [string, LogAttributes] = [
1432
+ `${LOG_FAILURE_SUMMARY}: ${logFailureCase.described}`,
1433
+ LOG_FAILURE_ATTRIBUTES,
1434
+ ];
1435
+
1436
+ expect(getErrorLogCalls()).toStrictEqual(
1437
+ logFailureCase.logsTheErrorItself
1438
+ ? [line, [logFailureCase.error, LOG_FAILURE_ATTRIBUTES]]
1439
+ : [line],
1440
+ );
1441
+
1442
+ // The same attributes on each line, and the error as it was thrown.
1443
+ for (const call of getErrorLogCalls()) {
1444
+ expect(call[1]).toBe(LOG_FAILURE_ATTRIBUTES);
1445
+ }
1446
+ expect(getErrorLogCalls()[1]?.[0]).toBe(
1447
+ logFailureCase.logsTheErrorItself ? logFailureCase.error : undefined,
1448
+ );
1449
+ expectNothingLoggedBelowError();
1450
+ });
1451
+ }
1452
+
1453
+ test("passes no attributes on when given none, and returns nothing", () => {
1454
+ const error: Error = new Error("Something went wrong.");
1455
+
1456
+ expect(
1457
+ MicrosoftTeamsReplies.logFailure(
1458
+ "Microsoft Teams message activity activity-1 failed",
1459
+ error,
1460
+ ),
1461
+ ).toBeUndefined();
1462
+
1463
+ expect(
1464
+ getErrorLogCalls().map((call: Array<unknown>): unknown => {
1465
+ return call[0];
1466
+ }),
1467
+ ).toStrictEqual([
1468
+ "Microsoft Teams message activity activity-1 failed: Error: Something went wrong.",
1469
+ error,
1470
+ ]);
1471
+ expect(
1472
+ getErrorLogCalls().map((call: Array<unknown>): unknown => {
1473
+ return call[1];
1474
+ }),
1475
+ ).toStrictEqual([undefined, undefined]);
1476
+ });
1477
+ });
1478
+
1479
+ interface UserFacingCase {
1480
+ description: string;
1481
+ error: unknown;
1482
+ expected: string | null;
1483
+ }
1484
+
1485
+ const USER_FACING_CASES: Array<UserFacingCase> = [
1486
+ {
1487
+ description: "a validation failure (BadDataException)",
1488
+ error: new BadDataException("Incident title is required."),
1489
+ expected: "Incident title is required.",
1490
+ },
1491
+ {
1492
+ description: "a permission refusal (NotAuthorizedException)",
1493
+ error: new NotAuthorizedException(
1494
+ "You do not have permission to create scheduled maintenance events.",
1495
+ ),
1496
+ expected:
1497
+ "You do not have permission to create scheduled maintenance events.",
1498
+ },
1499
+ {
1500
+ description:
1501
+ "a Teams account nobody linked (a BadDataException of its own type)",
1502
+ error: new MicrosoftTeamsAccountNotLinkedException(
1503
+ "No OneUptime user linked to this Microsoft Teams user. Please authenticate with Microsoft Teams.",
1504
+ ),
1505
+ expected:
1506
+ "No OneUptime user linked to this Microsoft Teams user. Please authenticate with Microsoft Teams.",
1507
+ },
1508
+ {
1509
+ description:
1510
+ "a reference to another project's record, whose message names that record",
1511
+ error: new ProjectScopedReferenceException(OTHER_PROJECT_REFERENCE_MESSAGE),
1512
+ expected: MICROSOFT_TEAMS_UNAVAILABLE_REFERENCE_MESSAGE,
1513
+ },
1514
+ {
1515
+ description:
1516
+ "a reference to a record deleted since the form was sent, whose message echoes its id",
1517
+ error: new ProjectScopedReferenceException(DELETED_REFERENCE_MESSAGE),
1518
+ expected: MICROSOFT_TEAMS_UNAVAILABLE_REFERENCE_MESSAGE,
1519
+ },
1520
+ {
1521
+ description: "a reference check with an empty message",
1522
+ error: new ProjectScopedReferenceException(""),
1523
+ expected: MICROSOFT_TEAMS_UNAVAILABLE_REFERENCE_MESSAGE,
1524
+ },
1525
+ {
1526
+ /*
1527
+ * The fixed text goes by the reference check's own type, not by the
1528
+ * words: only that check writes these, and it throws its own type.
1529
+ */
1530
+ description:
1531
+ "any other BadDataException, even one worded like the reference check",
1532
+ error: new BadDataException(OTHER_PROJECT_REFERENCE_MESSAGE),
1533
+ expected: OTHER_PROJECT_REFERENCE_MESSAGE,
1534
+ },
1535
+ {
1536
+ description: "a plain Error, whose text is for operators",
1537
+ error: new Error("connect ECONNREFUSED 10.0.0.5:5432"),
1538
+ expected: null,
1539
+ },
1540
+ {
1541
+ description: "a server exception",
1542
+ error: new ServerException("Database connection lost."),
1543
+ expected: null,
1544
+ },
1545
+ {
1546
+ description: "a Bot Framework RestError",
1547
+ error: buildMessageTooLargeError(),
1548
+ expected: null,
1549
+ },
1550
+ {
1551
+ description: "a plain object with a message",
1552
+ error: { message: "Incident title is required." },
1553
+ expected: null,
1554
+ },
1555
+ {
1556
+ description: "a plain object that only claims to be a BadDataException",
1557
+ error: { name: "BadDataException", message: "Incident title is required." },
1558
+ expected: null,
1559
+ },
1560
+ {
1561
+ description: "a BadDataException with an empty message",
1562
+ error: new BadDataException(""),
1563
+ expected: null,
1564
+ },
1565
+ {
1566
+ description: "a NotAuthorizedException with an empty message",
1567
+ error: new NotAuthorizedException(""),
1568
+ expected: null,
1569
+ },
1570
+ {
1571
+ description: "a string",
1572
+ error: "Incident title is required.",
1573
+ expected: null,
1574
+ },
1575
+ { description: "null", error: null, expected: null },
1576
+ { description: "undefined", error: undefined, expected: null },
1577
+ ];
1578
+
1579
+ function describeUserFacingAnswer(expected: string | null): string {
1580
+ if (expected === null) {
1581
+ return "is null for";
1582
+ }
1583
+
1584
+ if (expected === MICROSOFT_TEAMS_UNAVAILABLE_REFERENCE_MESSAGE) {
1585
+ return "answers the fixed text for";
1586
+ }
1587
+
1588
+ return "shows the message of";
1589
+ }
1590
+
1591
+ describe("MicrosoftTeamsReplies.getUserFacingErrorMessage", () => {
1592
+ for (const userFacingCase of USER_FACING_CASES) {
1593
+ test(`${describeUserFacingAnswer(userFacingCase.expected)} ${userFacingCase.description}`, () => {
1594
+ expect(
1595
+ MicrosoftTeamsReplies.getUserFacingErrorMessage(userFacingCase.error),
1596
+ ).toBe(userFacingCase.expected);
1597
+ });
1598
+ }
1599
+ });
1600
+
1601
+ /*
1602
+ * Just what the reference check calls on a service: the model, for its tenant
1603
+ * and name columns, and findBy, which answers with the records given.
1604
+ */
1605
+ function buildSeverityLookup(
1606
+ found: Array<IncidentSeverity>,
1607
+ ): DatabaseService<DatabaseBaseModel> {
1608
+ return {
1609
+ getModel: (): IncidentSeverity => {
1610
+ return new IncidentSeverity();
1611
+ },
1612
+ findBy: async (): Promise<Array<IncidentSeverity>> => {
1613
+ return found;
1614
+ },
1615
+ } as unknown as DatabaseService<DatabaseBaseModel>;
1616
+ }
1617
+
1618
+ // What the reference check throws for an incident with this severity.
1619
+ async function catchReferenceCheckError(data: {
1620
+ severityId: string;
1621
+ found: Array<IncidentSeverity>;
1622
+ }): Promise<unknown> {
1623
+ try {
1624
+ await ProjectScopedReferenceValidator.validateReferencesBelongToProject({
1625
+ projectId: PROJECT_ID,
1626
+ subject: "incident",
1627
+ references: [
1628
+ {
1629
+ modelName: "Incident Severity",
1630
+ id: data.severityId,
1631
+ service: buildSeverityLookup(data.found),
1632
+ },
1633
+ ],
1634
+ });
1635
+ } catch (error) {
1636
+ return error;
1637
+ }
1638
+
1639
+ throw new Error("The reference check accepted the severity.");
1640
+ }
1641
+
1642
+ describe("MicrosoftTeamsReplies: a submit that references a record the project does not have", () => {
1643
+ test("is answered with fixed text that names no record", () => {
1644
+ expect(MICROSOFT_TEAMS_UNAVAILABLE_REFERENCE_MESSAGE).toBe(
1645
+ "One of the values you picked (a monitor, label, on-call policy, severity or status) is not available in this project any more. Please pick it again.",
1646
+ );
1647
+ });
1648
+
1649
+ test("never names another project's record in the reply, although the reference check's message does", async () => {
1650
+ const otherProjectSeverity: IncidentSeverity = new IncidentSeverity();
1651
+ otherProjectSeverity._id = OTHER_PROJECT_SEVERITY_ID;
1652
+ otherProjectSeverity.name = OTHER_PROJECT_SEVERITY_NAME;
1653
+ otherProjectSeverity.projectId = OTHER_PROJECT_ID;
1654
+
1655
+ const error: unknown = await catchReferenceCheckError({
1656
+ severityId: OTHER_PROJECT_SEVERITY_ID,
1657
+ found: [otherProjectSeverity],
1658
+ });
1659
+
1660
+ /*
1661
+ * The check's own type, and still a BadDataException with the check's
1662
+ * message, so API callers see no change.
1663
+ */
1664
+ expect(error).toBeInstanceOf(ProjectScopedReferenceException);
1665
+ expect(error).toBeInstanceOf(BadDataException);
1666
+ expect((error as Error).message).toBe(OTHER_PROJECT_REFERENCE_MESSAGE);
1667
+
1668
+ const reply: string | null =
1669
+ MicrosoftTeamsReplies.getUserFacingErrorMessage(error);
1670
+
1671
+ expect(reply).toBe(MICROSOFT_TEAMS_UNAVAILABLE_REFERENCE_MESSAGE);
1672
+ expect(reply).not.toContain(OTHER_PROJECT_SEVERITY_NAME);
1673
+ expect(reply).not.toContain(OTHER_PROJECT_SEVERITY_ID);
1674
+
1675
+ // The log, which is for operators, still says what it was.
1676
+ expect(MicrosoftTeamsReplies.describeError(error)).toBe(
1677
+ `ProjectScopedReferenceException: ${OTHER_PROJECT_REFERENCE_MESSAGE}`,
1678
+ );
1679
+ });
1680
+
1681
+ test("does not echo the id of a record that no longer exists", async () => {
1682
+ const error: unknown = await catchReferenceCheckError({
1683
+ severityId: DELETED_SEVERITY_ID,
1684
+ found: [],
1685
+ });
1686
+
1687
+ expect(error).toBeInstanceOf(ProjectScopedReferenceException);
1688
+ expect((error as Error).message).toBe(DELETED_REFERENCE_MESSAGE);
1689
+
1690
+ const reply: string | null =
1691
+ MicrosoftTeamsReplies.getUserFacingErrorMessage(error);
1692
+
1693
+ expect(reply).toBe(MICROSOFT_TEAMS_UNAVAILABLE_REFERENCE_MESSAGE);
1694
+ expect(reply).not.toContain(DELETED_SEVERITY_ID);
1695
+ });
1696
+ });
1697
+
1698
+ function stubDashboardUrl(
1699
+ dashboardUrl: string,
1700
+ ): SpyInstance<typeof DatabaseConfig.getDashboardUrl> {
1701
+ return jest
1702
+ .spyOn(DatabaseConfig, "getDashboardUrl")
1703
+ .mockResolvedValue(URL.fromString(dashboardUrl));
1704
+ }
1705
+
1706
+ function stubDashboardUrlUnavailable(
1707
+ error: Error,
1708
+ ): SpyInstance<typeof DatabaseConfig.getDashboardUrl> {
1709
+ return jest.spyOn(DatabaseConfig, "getDashboardUrl").mockRejectedValue(error);
1710
+ }
1711
+
1712
+ describe("MicrosoftTeamsReplies.getDashboardLink", () => {
1713
+ test("builds <dashboard>/<projectId><route>", async () => {
1714
+ const getDashboardUrlSpy: SpyInstance<
1715
+ typeof DatabaseConfig.getDashboardUrl
1716
+ > = stubDashboardUrl(DASHBOARD_URL);
1717
+
1718
+ await expect(
1719
+ MicrosoftTeamsReplies.getDashboardLink({
1720
+ projectId: PROJECT_ID,
1721
+ route: "/incidents/create",
1722
+ }),
1723
+ ).resolves.toBe(
1724
+ `https://oneuptime.test/dashboard/${PROJECT_ID.toString()}/incidents/create`,
1725
+ );
1726
+ expect(getDashboardUrlSpy).toHaveBeenCalledTimes(1);
1727
+ });
1728
+
1729
+ test("links below the project for any route, such as the maintenance form", async () => {
1730
+ stubDashboardUrl(DASHBOARD_URL);
1731
+
1732
+ await expect(
1733
+ MicrosoftTeamsReplies.getDashboardLink({
1734
+ projectId: PROJECT_ID,
1735
+ route: "/scheduled-maintenance-events/create",
1736
+ }),
1737
+ ).resolves.toBe(
1738
+ `https://oneuptime.test/dashboard/${PROJECT_ID.toString()}/scheduled-maintenance-events/create`,
1739
+ );
1740
+ });
1741
+
1742
+ test("does not double the slash when the dashboard URL ends with one", async () => {
1743
+ stubDashboardUrl(`${DASHBOARD_URL}/`);
1744
+
1745
+ await expect(
1746
+ MicrosoftTeamsReplies.getDashboardLink({
1747
+ projectId: PROJECT_ID,
1748
+ route: "/incidents/create",
1749
+ }),
1750
+ ).resolves.toBe(
1751
+ `https://oneuptime.test/dashboard/${PROJECT_ID.toString()}/incidents/create`,
1752
+ );
1753
+ });
1754
+
1755
+ test("keeps a self-hosted dashboard's protocol and port", async () => {
1756
+ stubDashboardUrl("http://oneuptime.internal:8080/dashboard");
1757
+
1758
+ await expect(
1759
+ MicrosoftTeamsReplies.getDashboardLink({
1760
+ projectId: PROJECT_ID,
1761
+ route: "/incidents/create",
1762
+ }),
1763
+ ).resolves.toBe(
1764
+ `http://oneuptime.internal:8080/dashboard/${PROJECT_ID.toString()}/incidents/create`,
1765
+ );
1766
+ });
1767
+
1768
+ test("leaves the dashboard URL it is handed as it was, so one link never carries another's route", async () => {
1769
+ /*
1770
+ * URL.addRoute changes the URL it is called on. The link is built on a
1771
+ * copy, so a URL object handed out more than once stays the dashboard's.
1772
+ */
1773
+ const dashboardUrl: URL = URL.fromString(DASHBOARD_URL);
1774
+ jest
1775
+ .spyOn(DatabaseConfig, "getDashboardUrl")
1776
+ .mockResolvedValue(dashboardUrl);
1777
+
1778
+ await expect(
1779
+ MicrosoftTeamsReplies.getDashboardLink({
1780
+ projectId: PROJECT_ID,
1781
+ route: "/incidents/create",
1782
+ }),
1783
+ ).resolves.toBe(
1784
+ `https://oneuptime.test/dashboard/${PROJECT_ID.toString()}/incidents/create`,
1785
+ );
1786
+ await expect(
1787
+ MicrosoftTeamsReplies.getDashboardLink({
1788
+ projectId: PROJECT_ID,
1789
+ route: "/scheduled-maintenance-events/create",
1790
+ }),
1791
+ ).resolves.toBe(
1792
+ `https://oneuptime.test/dashboard/${PROJECT_ID.toString()}/scheduled-maintenance-events/create`,
1793
+ );
1794
+ expect(dashboardUrl.toString()).toBe(DASHBOARD_URL);
1795
+ });
1796
+
1797
+ test("is null when the dashboard URL cannot be read, and says so at debug level only", async () => {
1798
+ const lookupError: Error = new Error("HOST is not configured.");
1799
+ stubDashboardUrlUnavailable(lookupError);
1800
+
1801
+ await expect(
1802
+ MicrosoftTeamsReplies.getDashboardLink({
1803
+ projectId: PROJECT_ID,
1804
+ route: "/incidents/create",
1805
+ }),
1806
+ ).resolves.toBeNull();
1807
+
1808
+ expect(debugLogSpy).toHaveBeenCalledWith(
1809
+ "Could not build a OneUptime dashboard link",
1810
+ );
1811
+ expect(debugLogSpy).toHaveBeenCalledWith(lookupError);
1812
+ expect(errorLogSpy).not.toHaveBeenCalled();
1813
+ });
1814
+
1815
+ test("is null when looking the dashboard URL up throws before returning a promise", async () => {
1816
+ jest
1817
+ .spyOn(DatabaseConfig, "getDashboardUrl")
1818
+ .mockImplementation((): Promise<URL> => {
1819
+ throw new Error("HOST is not configured.");
1820
+ });
1821
+
1822
+ await expect(
1823
+ MicrosoftTeamsReplies.getDashboardLink({
1824
+ projectId: PROJECT_ID,
1825
+ route: "/incidents/create",
1826
+ }),
1827
+ ).resolves.toBeNull();
1828
+ });
1829
+
1830
+ describe("with the dashboard URL OneUptime is configured with", () => {
1831
+ const originalHost: string | undefined = process.env["HOST"];
1832
+ const originalProtocol: string | undefined = process.env["HTTP_PROTOCOL"];
1833
+
1834
+ afterEach((): void => {
1835
+ if (originalHost === undefined) {
1836
+ delete process.env["HOST"];
1837
+ } else {
1838
+ process.env["HOST"] = originalHost;
1839
+ }
1840
+
1841
+ if (originalProtocol === undefined) {
1842
+ delete process.env["HTTP_PROTOCOL"];
1843
+ } else {
1844
+ process.env["HTTP_PROTOCOL"] = originalProtocol;
1845
+ }
1846
+ });
1847
+
1848
+ test("links into /dashboard on the configured host and protocol", async () => {
1849
+ process.env["HOST"] = "status.example.com";
1850
+ process.env["HTTP_PROTOCOL"] = "https";
1851
+
1852
+ await expect(
1853
+ MicrosoftTeamsReplies.getDashboardLink({
1854
+ projectId: PROJECT_ID,
1855
+ route: "/incidents/create",
1856
+ }),
1857
+ ).resolves.toBe(
1858
+ `https://status.example.com/dashboard/${PROJECT_ID.toString()}/incidents/create`,
1859
+ );
1860
+ });
1861
+
1862
+ test("uses http when HTTP_PROTOCOL is not https", async () => {
1863
+ process.env["HOST"] = "oneuptime.local";
1864
+ delete process.env["HTTP_PROTOCOL"];
1865
+
1866
+ await expect(
1867
+ MicrosoftTeamsReplies.getDashboardLink({
1868
+ projectId: PROJECT_ID,
1869
+ route: USER_SETTINGS_ROUTE,
1870
+ }),
1871
+ ).resolves.toBe(
1872
+ `http://oneuptime.local/dashboard/${PROJECT_ID.toString()}${USER_SETTINGS_ROUTE}`,
1873
+ );
1874
+ });
1875
+
1876
+ test("keeps a port given in HOST, as a self-hosted install may have", async () => {
1877
+ process.env["HOST"] = "oneuptime.internal:8080";
1878
+ process.env["HTTP_PROTOCOL"] = "http";
1879
+
1880
+ await expect(
1881
+ MicrosoftTeamsReplies.getDashboardLink({
1882
+ projectId: PROJECT_ID,
1883
+ route: "/incidents/create",
1884
+ }),
1885
+ ).resolves.toBe(
1886
+ `http://oneuptime.internal:8080/dashboard/${PROJECT_ID.toString()}/incidents/create`,
1887
+ );
1888
+ });
1889
+ });
1890
+ });
1891
+
1892
+ describe("MicrosoftTeamsReplies.getAccountNotLinkedMessage", () => {
1893
+ test("links to the user's Microsoft Teams settings in the project", async () => {
1894
+ stubDashboardUrl(DASHBOARD_URL);
1895
+
1896
+ await expect(
1897
+ MicrosoftTeamsReplies.getAccountNotLinkedMessage({
1898
+ projectId: PROJECT_ID,
1899
+ purpose: "create incidents from Microsoft Teams",
1900
+ }),
1901
+ ).resolves.toBe(
1902
+ `To create incidents from Microsoft Teams, first connect your Microsoft Teams account to OneUptime in [OneUptime → User Settings → Microsoft Teams](https://oneuptime.test/dashboard/${PROJECT_ID.toString()}/user-settings/microsoft-teams-integration), then try again.`,
1903
+ );
1904
+ });
1905
+
1906
+ test("reads as a sentence for the AI assistant as well", async () => {
1907
+ // What "ask" answers a Teams user whose account is not connected yet.
1908
+ stubDashboardUrl(DASHBOARD_URL);
1909
+
1910
+ await expect(
1911
+ MicrosoftTeamsReplies.getAccountNotLinkedMessage({
1912
+ projectId: PROJECT_ID,
1913
+ purpose: "ask OneUptime questions from Microsoft Teams",
1914
+ }),
1915
+ ).resolves.toBe(
1916
+ `To ask OneUptime questions from Microsoft Teams, first connect your Microsoft Teams account to OneUptime in [OneUptime → User Settings → Microsoft Teams](https://oneuptime.test/dashboard/${PROJECT_ID.toString()}/user-settings/microsoft-teams-integration), then try again.`,
1917
+ );
1918
+ });
1919
+
1920
+ test("says where to go in words when the dashboard URL is not known", async () => {
1921
+ stubDashboardUrlUnavailable(new Error("HOST is not configured."));
1922
+
1923
+ const message: string =
1924
+ await MicrosoftTeamsReplies.getAccountNotLinkedMessage({
1925
+ projectId: PROJECT_ID,
1926
+ purpose: "create scheduled maintenance events from Microsoft Teams",
1927
+ });
1928
+
1929
+ expect(message).toBe(
1930
+ "To create scheduled maintenance events from Microsoft Teams, first connect your Microsoft Teams account to OneUptime in OneUptime under User Settings → Microsoft Teams, then try again.",
1931
+ );
1932
+ expect(message).not.toContain("](");
1933
+ });
1934
+
1935
+ test("links to a page the dashboard really has", async () => {
1936
+ /*
1937
+ * A real cross-file check: the bot's link is only useful if the Dashboard
1938
+ * still serves /dashboard/<projectId>/user-settings/microsoft-teams-integration.
1939
+ */
1940
+ stubDashboardUrl(DASHBOARD_URL);
1941
+ const message: string =
1942
+ await MicrosoftTeamsReplies.getAccountNotLinkedMessage({
1943
+ projectId: PROJECT_ID,
1944
+ purpose: "use OneUptime from Microsoft Teams",
1945
+ });
1946
+ expect(message).toContain(
1947
+ `/dashboard/${PROJECT_ID.toString()}/user-settings/microsoft-teams-integration)`,
1948
+ );
1949
+
1950
+ const routeMapSource: string = fs
1951
+ .readFileSync(
1952
+ path.join(
1953
+ __dirname,
1954
+ "../../../../../../App/FeatureSet/Dashboard/src/Utils/RouteMap.ts",
1955
+ ),
1956
+ "utf8",
1957
+ )
1958
+ .replace(/\s+/g, "");
1959
+
1960
+ expect(routeMapSource).toContain(
1961
+ '[PageMap.USER_SETTINGS_MICROSOFT_TEAMS_INTEGRATION]:"microsoft-teams-integration"',
1962
+ );
1963
+ expect(routeMapSource).toContain(
1964
+ "`/dashboard/${RouteParams.ProjectID}/user-settings/${UserSettingsRoutePath[PageMap.USER_SETTINGS_MICROSOFT_TEAMS_INTEGRATION]}`",
1965
+ );
1966
+ });
1967
+ });