@oneuptime/common 14.0.10 → 14.0.11

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 (837) hide show
  1. package/Models/DatabaseModels/AIConversation.ts +127 -1
  2. package/Models/DatabaseModels/CephCluster.ts +12 -0
  3. package/Models/DatabaseModels/CloudResource.ts +12 -0
  4. package/Models/DatabaseModels/DatabaseServer.ts +12 -0
  5. package/Models/DatabaseModels/DockerHost.ts +12 -0
  6. package/Models/DatabaseModels/DockerSwarmCluster.ts +12 -0
  7. package/Models/DatabaseModels/GlobalOidc.ts +0 -4
  8. package/Models/DatabaseModels/GlobalOidcProject.ts +0 -4
  9. package/Models/DatabaseModels/GlobalSso.ts +0 -4
  10. package/Models/DatabaseModels/GlobalSsoProject.ts +0 -4
  11. package/Models/DatabaseModels/Host.ts +12 -0
  12. package/Models/DatabaseModels/IncidentForm.ts +594 -0
  13. package/Models/DatabaseModels/IncidentFormSubmission.ts +283 -0
  14. package/Models/DatabaseModels/IncidentTemplate.ts +59 -0
  15. package/Models/DatabaseModels/Index.ts +14 -0
  16. package/Models/DatabaseModels/IoTFleet.ts +12 -0
  17. package/Models/DatabaseModels/KubernetesCluster.ts +12 -0
  18. package/Models/DatabaseModels/MessageQueue.ts +1102 -0
  19. package/Models/DatabaseModels/MessageQueueLabelRule.ts +553 -0
  20. package/Models/DatabaseModels/MessageQueueOwnerRule.ts +633 -0
  21. package/Models/DatabaseModels/MessageQueueOwnerTeam.ts +491 -0
  22. package/Models/DatabaseModels/MessageQueueOwnerUser.ts +490 -0
  23. package/Models/DatabaseModels/PodmanHost.ts +12 -0
  24. package/Models/DatabaseModels/Project.ts +59 -0
  25. package/Models/DatabaseModels/ProjectOidc.ts +0 -4
  26. package/Models/DatabaseModels/ProjectSso.ts +0 -4
  27. package/Models/DatabaseModels/ProxmoxCluster.ts +12 -0
  28. package/Models/DatabaseModels/RumApplication.ts +12 -0
  29. package/Models/DatabaseModels/ServerlessFunction.ts +12 -0
  30. package/Models/DatabaseModels/Service.ts +12 -0
  31. package/Models/DatabaseModels/StatusPageOidc.ts +0 -4
  32. package/Models/DatabaseModels/StatusPageSso.ts +0 -4
  33. package/Models/DatabaseModels/TeamComplianceSetting.ts +47 -2
  34. package/Models/DatabaseModels/VMwareVCenter.ts +12 -0
  35. package/Server/API/AIChatAPI.ts +13 -114
  36. package/Server/API/AIInvestigationAPI.ts +3 -3
  37. package/Server/API/AIInvestigationConversationAPI.ts +266 -0
  38. package/Server/API/IncidentFormAPI.ts +376 -0
  39. package/Server/API/ProjectOIDC.ts +16 -25
  40. package/Server/API/ProjectSSO.ts +16 -24
  41. package/Server/API/StatusPageAPI.ts +42 -77
  42. package/Server/Enterprise/EnterpriseEdition.ts +93 -28
  43. package/Server/Enterprise/EnterpriseFeature.ts +25 -3
  44. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +1 -1
  45. package/Server/Enterprise/EnterpriseServerModule.ts +4 -1
  46. package/Server/EnvironmentConfig.ts +1 -1
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.ts +129 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.ts +52 -0
  49. package/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.ts +75 -0
  50. package/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.ts +477 -0
  51. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  52. package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
  53. package/Server/Middleware/UserAuthorization.ts +16 -38
  54. package/Server/Services/AIService.ts +10 -13
  55. package/Server/Services/IncidentCustomFieldService.ts +112 -2
  56. package/Server/Services/IncidentEpisodeMemberService.ts +24 -4
  57. package/Server/Services/IncidentEpisodeService.ts +10 -1
  58. package/Server/Services/IncidentFormService.ts +1566 -0
  59. package/Server/Services/IncidentFormSubmissionService.ts +97 -0
  60. package/Server/Services/IncidentGroupingEngineService.ts +45 -8
  61. package/Server/Services/IncidentService.ts +123 -5
  62. package/Server/Services/IncidentTemplateService.ts +22 -0
  63. package/Server/Services/Index.ts +14 -0
  64. package/Server/Services/MessageQueueLabelRuleEngineService.ts +321 -0
  65. package/Server/Services/MessageQueueLabelRuleService.ts +14 -0
  66. package/Server/Services/MessageQueueOwnerRuleEngineService.ts +412 -0
  67. package/Server/Services/MessageQueueOwnerRuleService.ts +14 -0
  68. package/Server/Services/MessageQueueOwnerTeamService.ts +62 -0
  69. package/Server/Services/MessageQueueOwnerUserService.ts +62 -0
  70. package/Server/Services/MessageQueueService.ts +1463 -0
  71. package/Server/Services/OpenTelemetryIngestService.ts +37 -4
  72. package/Server/Services/ProjectService.ts +0 -59
  73. package/Server/Services/StatusPagePrivateUserService.ts +1 -4
  74. package/Server/Services/StatusPageService.ts +1 -51
  75. package/Server/Services/TeamComplianceSettingService.ts +163 -42
  76. package/Server/Services/UserNotificationRuleService.ts +8 -1
  77. package/Server/Services/WorkspaceNotificationSummaryService.ts +12 -3
  78. package/Server/Types/Database/Permissions/CreatePermission.ts +17 -3
  79. package/Server/Types/Database/Permissions/EditionPermission.ts +140 -38
  80. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +16 -0
  81. package/Server/Types/Database/Permissions/TablePermission.ts +3 -3
  82. package/Server/Types/Database/Permissions/UpdatePermission.ts +20 -6
  83. package/Server/Utils/AI/Chat/AgentContextCompactor.ts +200 -0
  84. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +307 -48
  85. package/Server/Utils/AI/Chat/ChatRunCancellation.ts +156 -0
  86. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +78 -18
  87. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  88. package/Server/Utils/AI/Chat/ToolOutputPager.ts +322 -0
  89. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +88 -30
  90. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +13 -1
  91. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +0 -32
  92. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +27 -25
  93. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +7 -1
  94. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +34 -14
  95. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +69 -24
  96. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +9 -0
  97. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +53 -10
  98. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +27 -10
  99. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +27 -10
  100. package/Server/Utils/AI/SRE/InvestigationQueue.ts +16 -19
  101. package/Server/Utils/AI/SRE/InvestigationThread.ts +646 -0
  102. package/Server/Utils/AI/SRE/InvestigationThreadService.ts +715 -0
  103. package/Server/Utils/AI/Toolbox/Serializer.ts +10 -3
  104. package/Server/Utils/EditionEnforcement.ts +19 -202
  105. package/Server/Utils/LLM/LLMService.ts +259 -63
  106. package/Server/Utils/RelaxedScimTeamLocksReport.ts +245 -0
  107. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  108. package/Server/Utils/SameOriginRequest.ts +203 -0
  109. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  110. package/Server/Utils/Telemetry/EntityRegistry.ts +4 -1
  111. package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +1402 -0
  112. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +4 -1
  113. package/Server/Utils/VendorAssets.ts +3 -3
  114. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +3 -1
  115. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +3 -1
  116. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +10 -1
  117. package/Tests/App/Accounts/IncidentFormPage.test.tsx +2036 -0
  118. package/Tests/App/Accounts/IncidentFormServerMessages.test.ts +119 -0
  119. package/Tests/App/Accounts/IncidentFormUtils.test.ts +1157 -0
  120. package/Tests/App/Accounts/LoginPasskey.test.tsx +6 -4
  121. package/Tests/App/Accounts/LoginSso.test.tsx +724 -0
  122. package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +338 -0
  123. package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +331 -0
  124. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +47 -33
  125. package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +1021 -0
  126. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +876 -78
  127. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +20 -3
  128. package/Tests/App/Dashboard/CephProxmoxVMwareDocumentationCards.test.tsx +359 -0
  129. package/Tests/App/Dashboard/CephSetupGuide.test.ts +961 -0
  130. package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +381 -95
  131. package/Tests/App/Dashboard/CloudSetupGuide.test.ts +1463 -0
  132. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +174 -12
  133. package/Tests/App/Dashboard/ContainerSetupGuideCards.test.tsx +345 -0
  134. package/Tests/App/Dashboard/CoreListEmptyStates.test.tsx +501 -0
  135. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +0 -1
  136. package/Tests/App/Dashboard/DatabaseDocumentationCard.test.tsx +400 -0
  137. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +5 -0
  138. package/Tests/App/Dashboard/DatabaseSetupGuide.test.ts +1869 -0
  139. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +44 -0
  140. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +50 -9
  141. package/Tests/App/Dashboard/DockerSetupGuide.test.ts +899 -0
  142. package/Tests/App/Dashboard/DockerSwarmSetupGuide.test.ts +964 -0
  143. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +141 -7
  144. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +52 -39
  145. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +7 -1
  146. package/Tests/App/Dashboard/GettingStartedTaskRoutes.test.tsx +331 -0
  147. package/Tests/App/Dashboard/HomeOverviewStatsFirstRun.test.tsx +925 -0
  148. package/Tests/App/Dashboard/HostDocumentationCard.test.tsx +225 -0
  149. package/Tests/App/Dashboard/HostSetupGuide.test.ts +1183 -0
  150. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +69 -45
  151. package/Tests/App/Dashboard/IncidentCreateTemplateCustomFieldSettings.test.tsx +745 -0
  152. package/Tests/App/Dashboard/IncidentCreateTemplateOwners.test.tsx +414 -0
  153. package/Tests/App/Dashboard/IncidentCustomFieldCreateSettingsForm.test.ts +1042 -0
  154. package/Tests/App/Dashboard/IncidentCustomFieldSettingsCard.test.tsx +1816 -0
  155. package/Tests/App/Dashboard/IncidentFormPlan.test.ts +89 -0
  156. package/Tests/App/Dashboard/IncidentFormShareLinkCard.test.tsx +846 -0
  157. package/Tests/App/Dashboard/IncidentFormsPages.test.tsx +1324 -0
  158. package/Tests/App/Dashboard/IncidentTemplateCustomFieldSettings.test.tsx +630 -0
  159. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +13 -0
  160. package/Tests/App/Dashboard/IncomingEmailMonitorLinkVerifyHint.test.tsx +104 -0
  161. package/Tests/App/Dashboard/InvestigationConversation.test.tsx +1107 -0
  162. package/Tests/App/Dashboard/InvestigationConversationData.test.ts +546 -0
  163. package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +170 -5
  164. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +207 -2
  165. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +729 -34
  166. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +9 -21
  167. package/Tests/App/Dashboard/InvestigationReportView.test.tsx +274 -37
  168. package/Tests/App/Dashboard/InvestigationStatusBadge.test.tsx +130 -0
  169. package/Tests/App/Dashboard/IoTDocumentationCard.test.tsx +218 -0
  170. package/Tests/App/Dashboard/IoTSetupGuide.test.ts +1124 -0
  171. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +20 -6
  172. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +107 -83
  173. package/Tests/App/Dashboard/KubernetesSetupGuide.test.ts +787 -0
  174. package/Tests/App/Dashboard/MessageQueueBreadcrumbs.test.tsx +228 -0
  175. package/Tests/App/Dashboard/MessageQueueBrokerHealth.test.tsx +1538 -0
  176. package/Tests/App/Dashboard/MessageQueueBrokerHealthMetricChart.test.tsx +969 -0
  177. package/Tests/App/Dashboard/MessageQueueBrokerHealthReads.test.ts +889 -0
  178. package/Tests/App/Dashboard/MessageQueueBrokerMetricsReach.test.ts +236 -0
  179. package/Tests/App/Dashboard/MessageQueueDocumentationCard.test.tsx +479 -0
  180. package/Tests/App/Dashboard/MessageQueueListPages.test.tsx +1232 -0
  181. package/Tests/App/Dashboard/MessageQueueMetricDescriptions.test.ts +244 -0
  182. package/Tests/App/Dashboard/MessageQueueMonitorLink.test.ts +952 -0
  183. package/Tests/App/Dashboard/MessageQueueMonitorLinkMonitorCreate.test.tsx +859 -0
  184. package/Tests/App/Dashboard/MessageQueueOverview.test.tsx +1773 -0
  185. package/Tests/App/Dashboard/MessageQueueOverviewPresentation.test.ts +196 -0
  186. package/Tests/App/Dashboard/MessageQueuePresentation.test.ts +799 -0
  187. package/Tests/App/Dashboard/MessageQueueProducersConsumers.test.tsx +680 -0
  188. package/Tests/App/Dashboard/MessageQueueRoutes.test.tsx +383 -0
  189. package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +278 -0
  190. package/Tests/App/Dashboard/MessageQueueSetupGuide.test.ts +2778 -0
  191. package/Tests/App/Dashboard/MessageQueueSideMenus.test.tsx +273 -0
  192. package/Tests/App/Dashboard/MessageQueueSpanPopulations.test.ts +930 -0
  193. package/Tests/App/Dashboard/MessageQueueSpanStore.ts +351 -0
  194. package/Tests/App/Dashboard/MessageQueueTelemetryScope.test.ts +375 -0
  195. package/Tests/App/Dashboard/MessageQueueTelemetryScopeTabs.test.tsx +668 -0
  196. package/Tests/App/Dashboard/MessageQueueViewPages.test.tsx +685 -0
  197. package/Tests/App/Dashboard/MonitorAndHomeNotOperationalMenus.test.tsx +342 -0
  198. package/Tests/App/Dashboard/MonitorCreateFirstRunDefaults.test.tsx +623 -0
  199. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +442 -0
  200. package/Tests/App/Dashboard/MonitorLogsPageViewSummary.test.tsx +814 -0
  201. package/Tests/App/Dashboard/MonitorSetupCardBodies.test.tsx +246 -0
  202. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +8 -0
  203. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +27 -12
  204. package/Tests/App/Dashboard/PlanGatedPage.test.tsx +292 -0
  205. package/Tests/App/Dashboard/PodmanSetupGuide.test.ts +948 -0
  206. package/Tests/App/Dashboard/ProductOrientation.test.tsx +5 -4
  207. package/Tests/App/Dashboard/ProductSettingsSectionCollapsed.test.tsx +266 -0
  208. package/Tests/App/Dashboard/ProxmoxSetupGuide.test.ts +1318 -0
  209. package/Tests/App/Dashboard/ResourceAiAgentAttentionServerParity.test.ts +555 -0
  210. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +298 -9
  211. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +453 -0
  212. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +16 -0
  213. package/Tests/App/Dashboard/ResourceConnectionGuideCard.test.tsx +444 -0
  214. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsInfra.test.tsx +798 -0
  215. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsPlatforms.test.tsx +717 -0
  216. package/Tests/App/Dashboard/ResourceConnectionGuides.test.ts +981 -0
  217. package/Tests/App/Dashboard/ResourceConnectionState.test.ts +132 -0
  218. package/Tests/App/Dashboard/RumDocumentationCard.test.tsx +293 -0
  219. package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +467 -0
  220. package/Tests/App/Dashboard/RumSetupGuide.test.ts +758 -0
  221. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +25 -0
  222. package/Tests/App/Dashboard/ServerlessDocumentationCard.test.tsx +254 -0
  223. package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1074 -0
  224. package/Tests/App/Dashboard/SetupGuide.test.ts +246 -0
  225. package/Tests/App/Dashboard/SetupGuideCard.test.tsx +756 -0
  226. package/Tests/App/Dashboard/SloListSideMenu.test.tsx +120 -0
  227. package/Tests/App/Dashboard/SsoPages.test.tsx +833 -0
  228. package/Tests/App/Dashboard/TelemetryDocumentation.test.tsx +253 -0
  229. package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +730 -0
  230. package/Tests/App/Dashboard/VMwareSetupGuide.test.ts +1033 -0
  231. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +87 -7
  232. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +28 -9
  233. package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
  234. package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -0
  235. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  236. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +190 -14
  237. package/Tests/Models/IncidentTemplateCustomFieldSettingsColumn.test.ts +291 -0
  238. package/Tests/Server/API/AIInvestigationConversationAPI.test.ts +412 -0
  239. package/Tests/Server/API/GlobalConfigLicense.test.ts +38 -9
  240. package/Tests/Server/API/IncidentFormAPI.test.ts +942 -0
  241. package/Tests/Server/API/IncidentFormPublicRoutes.test.ts +1653 -0
  242. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -1
  243. package/Tests/Server/API/{SsoProviderListingsEdition.test.ts → SsoProviderListings.test.ts} +164 -110
  244. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +76 -54
  245. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +125 -42
  246. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +36 -10
  247. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +51 -9
  248. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +13 -8
  249. package/Tests/Server/Enterprise/SsoInCommunityEdition.test.ts +540 -0
  250. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  251. package/Tests/Server/Infrastructure/Postgres/AddIncidentFormsMigration.test.ts +438 -0
  252. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
  253. package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
  254. package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
  255. package/Tests/Server/Middleware/UserAuthorization.test.ts +16 -17
  256. package/Tests/Server/Middleware/UserAuthorizationSsoEnforcement.test.ts +544 -0
  257. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +37 -8
  258. package/Tests/Server/Services/IncidentCreatedFeedTitle.test.ts +212 -0
  259. package/Tests/Server/Services/IncidentCustomFieldDeleteFormQuestions.test.ts +297 -0
  260. package/Tests/Server/Services/IncidentFormOwnersCreatedNotification.test.ts +497 -0
  261. package/Tests/Server/Services/IncidentFormPostgres.test.ts +958 -0
  262. package/Tests/Server/Services/IncidentFormPublicForm.test.ts +1260 -0
  263. package/Tests/Server/Services/IncidentFormPublicSubmission.test.ts +2520 -0
  264. package/Tests/Server/Services/IncidentFormRegistration.test.ts +159 -0
  265. package/Tests/Server/Services/IncidentFormService.test.ts +663 -0
  266. package/Tests/Server/Services/IncidentFormSubmissionService.test.ts +430 -0
  267. package/Tests/Server/Services/IncidentFormSubmissionTemplateBranch.test.ts +473 -0
  268. package/Tests/Server/Services/IncidentGroupingTemplateSubstitution.test.ts +81 -0
  269. package/Tests/Server/Services/IncidentTemplateCustomFieldSettings.test.ts +262 -0
  270. package/Tests/Server/Services/IncidentTemplateOwnersOnDeclare.test.ts +566 -0
  271. package/Tests/Server/Services/IncidentTitleMarkdownSinks.test.ts +640 -0
  272. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +3 -3
  273. package/Tests/Server/Services/MessageQueueOwnerServices.test.ts +440 -0
  274. package/Tests/Server/Services/MessageQueueRegistries.test.ts +577 -0
  275. package/Tests/Server/Services/MessageQueueRuleEngines.test.ts +900 -0
  276. package/Tests/Server/Services/MessageQueueService.test.ts +2210 -0
  277. package/Tests/Server/Services/MessageQueueSqlPostgres.test.ts +1271 -0
  278. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
  279. package/Tests/Server/Services/OpenTelemetryRetentionOverrideEdition.test.ts +474 -0
  280. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +21 -0
  281. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +3 -0
  282. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +0 -1
  283. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
  284. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
  285. package/Tests/Server/Services/SsoRequirementReads.test.ts +454 -0
  286. package/Tests/Server/Services/SsoRequirementWrites.test.ts +529 -0
  287. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  288. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +107 -89
  289. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +1068 -176
  290. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +1962 -200
  291. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +654 -297
  292. package/Tests/Server/Types/Database/Permissions/RetentionOverrideColumns.test.ts +784 -0
  293. package/Tests/Server/Types/Database/Permissions/SsoScalePlanGate.test.ts +418 -0
  294. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +0 -1
  295. package/Tests/Server/Utils/AI/AIInvestigationEngineRunLimits.test.ts +136 -0
  296. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +15 -14
  297. package/Tests/Server/Utils/AI/AgentContextCompactor.test.ts +351 -0
  298. package/Tests/Server/Utils/AI/ChatAgentRunnerSharedThread.test.ts +842 -0
  299. package/Tests/Server/Utils/AI/ChatRunCancellation.test.ts +195 -0
  300. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +10 -57
  301. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +20 -24
  302. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +7 -30
  303. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +225 -29
  304. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +266 -25
  305. package/Tests/Server/Utils/AI/InvestigationThread.test.ts +580 -0
  306. package/Tests/Server/Utils/AI/InvestigationThreadService.test.ts +801 -0
  307. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +525 -43
  308. package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +4 -2
  309. package/Tests/Server/Utils/AI/LLMServiceUnreachableProvider.test.ts +435 -0
  310. package/Tests/Server/Utils/AI/ObservabilityAssistantLimits.test.ts +462 -0
  311. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +14 -25
  312. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +2 -0
  313. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +297 -15
  314. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +10 -6
  315. package/Tests/Server/Utils/AI/ToolOutputPager.test.ts +382 -0
  316. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +23 -4
  317. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +25 -6
  318. package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +14 -3
  319. package/Tests/Server/Utils/EditionEnforcement.test.ts +107 -147
  320. package/Tests/Server/Utils/{CommunityEditionSsoReport.test.ts → RelaxedScimTeamLocksReport.test.ts} +212 -208
  321. package/Tests/Server/Utils/SameOriginRequest.test.ts +372 -0
  322. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +20 -0
  323. package/Tests/Server/Utils/Telemetry/MessageQueueDiscovery.test.ts +2590 -0
  324. package/Tests/Server/Utils/Telemetry/MessageQueueSpanQueryTwin.ts +246 -0
  325. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +23 -0
  326. package/Tests/Types/AI/AIAgentRunLimits.test.ts +255 -0
  327. package/Tests/Types/Code/JsonSyntax.test.ts +1300 -0
  328. package/Tests/Types/CustomField/CustomFieldCreateSettings.test.ts +1113 -0
  329. package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +91 -0
  330. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +30 -0
  331. package/Tests/Types/Incident/IncidentFormIpAllowlist.test.ts +227 -0
  332. package/Tests/Types/Incident/IncidentFormPublic.test.ts +1916 -0
  333. package/Tests/Types/MessageQueue/MessageQueueEntityKey.test.ts +916 -0
  334. package/Tests/Types/MessageQueue/MessageQueueIdentity.test.ts +567 -0
  335. package/Tests/Types/MessageQueue/MessageQueueManualIdentity.test.ts +686 -0
  336. package/Tests/Types/MessageQueue/MessageQueueMetricCatalog.test.ts +1726 -0
  337. package/Tests/Types/MessageQueue/MessageQueueMetricResolution.test.ts +1758 -0
  338. package/Tests/Types/MessageQueue/MessagingInstrumentationFixtures.test.ts +4686 -0
  339. package/Tests/Types/MessageQueue/MessagingSystem.test.ts +840 -0
  340. package/Tests/Types/MessageQueue/MessagingTelemetryFixtures.ts +3287 -0
  341. package/Tests/Types/MessageQueue/MessagingTelemetryResolver.test.ts +2966 -0
  342. package/Tests/Types/Monitor/MessageQueueAlertTemplates.test.ts +5401 -0
  343. package/Tests/Types/Monitor/MetricEngineFold.ts +154 -0
  344. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +5 -3
  345. package/Tests/Types/Team/ComplianceRule.test.ts +588 -37
  346. package/Tests/Types/Team/TeamComplianceStatus.test.ts +64 -11
  347. package/Tests/Types/XML.test.ts +51 -0
  348. package/Tests/UI/Components/CodeEditor/CodeEditor.test.tsx +2362 -0
  349. package/Tests/UI/Components/CodeEditor/CodeEditorCommands.test.ts +810 -0
  350. package/Tests/UI/Components/CodeEditor/CodeEditorHighlight.test.ts +338 -0
  351. package/Tests/UI/Components/CodeEditor/CodeEditorLanguages.test.ts +222 -0
  352. package/Tests/UI/Components/CodeEditor/CodeEditorStyles.test.ts +807 -0
  353. package/Tests/UI/Components/CodeEditor/NoMonaco.test.ts +245 -0
  354. package/Tests/UI/Components/CodeEditor/YamlEditor.test.tsx +729 -0
  355. package/Tests/UI/Components/CopyTextButton.test.tsx +76 -0
  356. package/Tests/UI/Components/EditionLabelLapseCopy.ts +51 -14
  357. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +131 -23
  358. package/Tests/UI/Components/ErrorMessageAction.test.tsx +142 -0
  359. package/Tests/UI/Components/FacetSidebarInScope.test.tsx +459 -0
  360. package/Tests/UI/Components/FacetValuesInScope.test.ts +270 -0
  361. package/Tests/UI/Components/Forms/FormFieldCodeEditors.test.tsx +294 -0
  362. package/Tests/UI/Components/Forms/FormFieldMarkdown.test.tsx +126 -0
  363. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +22 -75
  364. package/Tests/UI/Components/Forms/ModelFormOnBeforeCreateMiscData.test.tsx +254 -0
  365. package/Tests/UI/Components/Icon.test.tsx +32 -0
  366. package/Tests/UI/Components/Input.test.tsx +31 -0
  367. package/Tests/UI/Components/LockedFilterChipScope.test.tsx +501 -0
  368. package/Tests/UI/Components/MarkdownConverters.test.ts +253 -1
  369. package/Tests/UI/Components/MarkdownEditor.test.tsx +2785 -2
  370. package/Tests/UI/Components/MarkdownEditorHistory.test.ts +243 -0
  371. package/Tests/UI/Components/MarkdownListEditing.test.ts +1118 -0
  372. package/Tests/UI/Components/MarkdownPaste.test.ts +1180 -0
  373. package/Tests/UI/Components/MarkdownViewerCopy.test.tsx +466 -0
  374. package/Tests/UI/Components/MarkdownVisualEditing.test.ts +1089 -0
  375. package/Tests/UI/Components/ModelTable/BaseModelTableEmptyStateAction.test.tsx +623 -0
  376. package/Tests/UI/Components/ResetObjectID.test.tsx +282 -0
  377. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  378. package/Tests/UI/Components/Table/EmptyTableMessage.test.ts +439 -0
  379. package/Tests/UI/Components/Table/TableEmptyState.test.tsx +352 -0
  380. package/Tests/UI/Components/TableRowMobileTitles.test.tsx +78 -0
  381. package/Tests/UI/Components/TextArea.test.tsx +19 -0
  382. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +12 -29
  383. package/Tests/UI/Components/fixtures/MarkdownPasteFixtures.ts +460 -0
  384. package/Tests/UI/EsbuildConfig.test.ts +115 -39
  385. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +4 -4
  386. package/Tests/UI/Monitor/MonitorLogSummaryRender.test.tsx +377 -0
  387. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
  388. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -0
  389. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  390. package/Tests/Utils/Markdown/DashboardMarkdownRenderer.ts +81 -0
  391. package/Tests/Utils/Markdown/UntrustedMarkdown.test.ts +1339 -0
  392. package/Tests/Utils/Monitor/MonitorLogSummaryUtil.test.ts +862 -0
  393. package/Types/AI/AIAgentRunLimits.ts +146 -0
  394. package/Types/Code/CodeType.ts +2 -0
  395. package/Types/Code/JsonSyntax.ts +923 -0
  396. package/Types/CustomField/CustomFieldCreateSettings.ts +549 -0
  397. package/Types/CustomField/CustomFieldMappingCatalog.ts +77 -0
  398. package/Types/CustomField/CustomFieldValueValidator.ts +10 -7
  399. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +1 -1
  400. package/Types/Icon/IconProp.ts +2 -0
  401. package/Types/Incident/IncidentFormIpAllowlist.ts +206 -0
  402. package/Types/Incident/IncidentFormPublic.ts +1145 -0
  403. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +10 -3
  404. package/Types/MessageQueue/MessageQueueIdentity.ts +256 -0
  405. package/Types/MessageQueue/MessageQueueManualIdentity.ts +325 -0
  406. package/Types/MessageQueue/MessageQueueMetricCatalog.ts +1357 -0
  407. package/Types/MessageQueue/MessagingSystem.ts +518 -0
  408. package/Types/MessageQueue/MessagingTelemetryResolver.ts +2311 -0
  409. package/Types/Monitor/MessageQueueAlertTemplates.ts +2128 -0
  410. package/Types/Permission.ts +310 -0
  411. package/Types/ResourceAiAgent/ResourceAiAccess.ts +10 -4
  412. package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
  413. package/Types/Rules/RuleRun.ts +4 -0
  414. package/Types/Team/ComplianceNotificationChannel.ts +4 -3
  415. package/Types/Team/ComplianceRule.ts +71 -18
  416. package/Types/Team/ComplianceRuleType.ts +2 -2
  417. package/Types/Team/TeamComplianceStatus.ts +6 -2
  418. package/Types/Telemetry/EntityType.ts +16 -0
  419. package/Types/Telemetry/LockedFilterDetail.ts +22 -0
  420. package/Types/XML.ts +26 -0
  421. package/UI/Components/CodeEditor/CodeEditor.css +379 -0
  422. package/UI/Components/CodeEditor/CodeEditor.tsx +1128 -294
  423. package/UI/Components/CodeEditor/CodeEditorCommands.ts +774 -0
  424. package/UI/Components/CodeEditor/CodeEditorHighlight.ts +106 -0
  425. package/UI/Components/CodeEditor/CodeEditorLanguages.ts +164 -0
  426. package/UI/Components/CodeEditor/YamlEditor.tsx +28 -326
  427. package/UI/Components/CopyTextButton/CopyTextButton.tsx +5 -1
  428. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +6 -25
  429. package/UI/Components/EditionLabel/EditionLabel.tsx +14 -13
  430. package/UI/Components/ErrorMessage/ErrorMessage.tsx +11 -1
  431. package/UI/Components/Forms/Fields/FormField.tsx +8 -0
  432. package/UI/Components/Forms/Types/Field.ts +8 -0
  433. package/UI/Components/Icon/Icon.tsx +18 -0
  434. package/UI/Components/Input/Input.tsx +11 -4
  435. package/UI/Components/LogsViewer/LogsViewer.tsx +4 -0
  436. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +21 -3
  437. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  438. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +877 -79
  439. package/UI/Components/Markdown.tsx/MarkdownEditorHistory.ts +284 -0
  440. package/UI/Components/Markdown.tsx/MarkdownListEditing.ts +1362 -0
  441. package/UI/Components/Markdown.tsx/MarkdownPaste.ts +1326 -0
  442. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +106 -10
  443. package/UI/Components/Markdown.tsx/MarkdownVisualEditing.ts +1032 -0
  444. package/UI/Components/ModelTable/BaseModelTable.tsx +85 -7
  445. package/UI/Components/ResetObjectID/ResetObjectID.tsx +35 -9
  446. package/UI/Components/Table/EmptyTableMessage.ts +81 -0
  447. package/UI/Components/Table/Table.tsx +50 -21
  448. package/UI/Components/Table/TableRow.tsx +9 -1
  449. package/UI/Components/TelemetryViewer/FacetVisibility.ts +97 -2
  450. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +13 -0
  451. package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +214 -7
  452. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +21 -3
  453. package/UI/Components/TextArea/TextArea.tsx +9 -2
  454. package/UI/Components/Workflow/ModelFieldPicker.tsx +10 -12
  455. package/UI/Styles/Theme.css +8 -0
  456. package/UI/esbuild-config.js +2 -32
  457. package/Utils/LabelRuleImportExport.ts +2 -0
  458. package/Utils/Markdown/UntrustedMarkdown.ts +1513 -0
  459. package/Utils/Monitor/IncomingEmailMonitorRequestUtil.ts +41 -0
  460. package/Utils/Monitor/MonitorLogSummaryUtil.ts +212 -0
  461. package/Utils/Telemetry/EntityKey.ts +48 -0
  462. package/build/dist/Models/DatabaseModels/AIConversation.js +125 -1
  463. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  464. package/build/dist/Models/DatabaseModels/CephCluster.js +12 -0
  465. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  466. package/build/dist/Models/DatabaseModels/CloudResource.js +12 -0
  467. package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
  468. package/build/dist/Models/DatabaseModels/DatabaseServer.js +12 -0
  469. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  470. package/build/dist/Models/DatabaseModels/DockerHost.js +12 -0
  471. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  472. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +12 -0
  473. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  474. package/build/dist/Models/DatabaseModels/GlobalOidc.js +0 -4
  475. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
  476. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +0 -4
  477. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  478. package/build/dist/Models/DatabaseModels/GlobalSso.js +0 -4
  479. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
  480. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +0 -4
  481. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  482. package/build/dist/Models/DatabaseModels/Host.js +12 -0
  483. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  484. package/build/dist/Models/DatabaseModels/IncidentForm.js +605 -0
  485. package/build/dist/Models/DatabaseModels/IncidentForm.js.map +1 -0
  486. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js +299 -0
  487. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js.map +1 -0
  488. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +60 -0
  489. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  490. package/build/dist/Models/DatabaseModels/Index.js +14 -0
  491. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  492. package/build/dist/Models/DatabaseModels/IoTFleet.js +12 -0
  493. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  494. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +12 -0
  495. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  496. package/build/dist/Models/DatabaseModels/MessageQueue.js +1106 -0
  497. package/build/dist/Models/DatabaseModels/MessageQueue.js.map +1 -0
  498. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js +562 -0
  499. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js.map +1 -0
  500. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js +643 -0
  501. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js.map +1 -0
  502. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js +506 -0
  503. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js.map +1 -0
  504. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js +505 -0
  505. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js.map +1 -0
  506. package/build/dist/Models/DatabaseModels/PodmanHost.js +12 -0
  507. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  508. package/build/dist/Models/DatabaseModels/Project.js +61 -0
  509. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  510. package/build/dist/Models/DatabaseModels/ProjectOidc.js +0 -4
  511. package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
  512. package/build/dist/Models/DatabaseModels/ProjectSso.js +0 -4
  513. package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
  514. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +12 -0
  515. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  516. package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
  517. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  518. package/build/dist/Models/DatabaseModels/ServerlessFunction.js +12 -0
  519. package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
  520. package/build/dist/Models/DatabaseModels/Service.js +12 -0
  521. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  522. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +0 -4
  523. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  524. package/build/dist/Models/DatabaseModels/StatusPageSso.js +0 -4
  525. package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
  526. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +49 -3
  527. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  528. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +12 -0
  529. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  530. package/build/dist/Server/API/AIChatAPI.js +9 -103
  531. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  532. package/build/dist/Server/API/AIInvestigationAPI.js +3 -3
  533. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  534. package/build/dist/Server/API/AIInvestigationConversationAPI.js +162 -0
  535. package/build/dist/Server/API/AIInvestigationConversationAPI.js.map +1 -0
  536. package/build/dist/Server/API/IncidentFormAPI.js +245 -0
  537. package/build/dist/Server/API/IncidentFormAPI.js.map +1 -0
  538. package/build/dist/Server/API/ProjectOIDC.js +16 -24
  539. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  540. package/build/dist/Server/API/ProjectSSO.js +16 -24
  541. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  542. package/build/dist/Server/API/StatusPageAPI.js +42 -75
  543. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  544. package/build/dist/Server/Enterprise/EnterpriseEdition.js +79 -25
  545. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -1
  546. package/build/dist/Server/Enterprise/EnterpriseFeature.js +24 -3
  547. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -1
  548. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +1 -1
  549. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -1
  550. package/build/dist/Server/EnvironmentConfig.js +1 -1
  551. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js +63 -0
  552. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js.map +1 -0
  553. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js +41 -0
  554. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js.map +1 -0
  555. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js +40 -0
  556. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js.map +1 -0
  557. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js +183 -0
  558. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js.map +1 -0
  559. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  560. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  561. package/build/dist/Server/Middleware/IncidentFormRateLimit.js +559 -0
  562. package/build/dist/Server/Middleware/IncidentFormRateLimit.js.map +1 -0
  563. package/build/dist/Server/Middleware/UserAuthorization.js +11 -30
  564. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  565. package/build/dist/Server/Services/AIService.js +10 -13
  566. package/build/dist/Server/Services/AIService.js.map +1 -1
  567. package/build/dist/Server/Services/IncidentCustomFieldService.js +83 -0
  568. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  569. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
  570. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  571. package/build/dist/Server/Services/IncidentEpisodeService.js +10 -1
  572. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  573. package/build/dist/Server/Services/IncidentFormService.js +1202 -0
  574. package/build/dist/Server/Services/IncidentFormService.js.map +1 -0
  575. package/build/dist/Server/Services/IncidentFormSubmissionService.js +91 -0
  576. package/build/dist/Server/Services/IncidentFormSubmissionService.js.map +1 -0
  577. package/build/dist/Server/Services/IncidentGroupingEngineService.js +20 -8
  578. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  579. package/build/dist/Server/Services/IncidentService.js +106 -4
  580. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  581. package/build/dist/Server/Services/IncidentTemplateService.js +18 -0
  582. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  583. package/build/dist/Server/Services/Index.js +14 -0
  584. package/build/dist/Server/Services/Index.js.map +1 -1
  585. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js +262 -0
  586. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js.map +1 -0
  587. package/build/dist/Server/Services/MessageQueueLabelRuleService.js +13 -0
  588. package/build/dist/Server/Services/MessageQueueLabelRuleService.js.map +1 -0
  589. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js +334 -0
  590. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js.map +1 -0
  591. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js +13 -0
  592. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js.map +1 -0
  593. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js +62 -0
  594. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js.map +1 -0
  595. package/build/dist/Server/Services/MessageQueueOwnerUserService.js +62 -0
  596. package/build/dist/Server/Services/MessageQueueOwnerUserService.js.map +1 -0
  597. package/build/dist/Server/Services/MessageQueueService.js +1140 -0
  598. package/build/dist/Server/Services/MessageQueueService.js.map +1 -0
  599. package/build/dist/Server/Services/OpenTelemetryIngestService.js +25 -4
  600. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  601. package/build/dist/Server/Services/ProjectService.js +0 -54
  602. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  603. package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -4
  604. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  605. package/build/dist/Server/Services/StatusPageService.js +0 -46
  606. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  607. package/build/dist/Server/Services/TeamComplianceSettingService.js +120 -31
  608. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  609. package/build/dist/Server/Services/UserNotificationRuleService.js +8 -1
  610. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  611. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +12 -3
  612. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  613. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +6 -3
  614. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  615. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +109 -37
  616. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  617. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +16 -0
  618. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  619. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +3 -3
  620. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +9 -6
  621. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  622. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js +128 -0
  623. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js.map +1 -0
  624. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +192 -50
  625. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  626. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js +133 -0
  627. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js.map +1 -0
  628. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +53 -13
  629. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  630. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  631. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js +206 -0
  632. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js.map +1 -0
  633. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +53 -26
  634. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  635. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +4 -2
  636. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  637. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +1 -29
  638. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  639. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +24 -24
  640. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  641. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +7 -2
  642. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  643. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +29 -14
  644. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  645. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +38 -14
  646. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -1
  647. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +2 -0
  648. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -1
  649. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +40 -10
  650. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  651. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +19 -7
  652. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  653. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +19 -7
  654. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  655. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +15 -18
  656. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  657. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js +412 -0
  658. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js.map +1 -0
  659. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js +495 -0
  660. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js.map +1 -0
  661. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +10 -3
  662. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  663. package/build/dist/Server/Utils/EditionEnforcement.js +20 -158
  664. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -1
  665. package/build/dist/Server/Utils/LLM/LLMService.js +193 -54
  666. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  667. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js +160 -0
  668. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js.map +1 -0
  669. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  670. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  671. package/build/dist/Server/Utils/SameOriginRequest.js +151 -0
  672. package/build/dist/Server/Utils/SameOriginRequest.js.map +1 -0
  673. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  674. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  675. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -1
  676. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  677. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +1015 -0
  678. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -0
  679. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +4 -1
  680. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  681. package/build/dist/Server/Utils/VendorAssets.js +3 -3
  682. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +3 -1
  683. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  684. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +3 -1
  685. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  686. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +10 -1
  687. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  688. package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
  689. package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
  690. package/build/dist/Types/Code/CodeType.js +2 -0
  691. package/build/dist/Types/Code/CodeType.js.map +1 -1
  692. package/build/dist/Types/Code/JsonSyntax.js +604 -0
  693. package/build/dist/Types/Code/JsonSyntax.js.map +1 -0
  694. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js +310 -0
  695. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js.map +1 -0
  696. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +38 -0
  697. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -1
  698. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +10 -7
  699. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -1
  700. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +1 -1
  701. package/build/dist/Types/Icon/IconProp.js +2 -0
  702. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  703. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js +119 -0
  704. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js.map +1 -0
  705. package/build/dist/Types/Incident/IncidentFormPublic.js +678 -0
  706. package/build/dist/Types/Incident/IncidentFormPublic.js.map +1 -0
  707. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +10 -3
  708. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  709. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js +184 -0
  710. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js.map +1 -0
  711. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js +190 -0
  712. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js.map +1 -0
  713. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js +1057 -0
  714. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js.map +1 -0
  715. package/build/dist/Types/MessageQueue/MessagingSystem.js +394 -0
  716. package/build/dist/Types/MessageQueue/MessagingSystem.js.map +1 -0
  717. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js +1768 -0
  718. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js.map +1 -0
  719. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js +1207 -0
  720. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js.map +1 -0
  721. package/build/dist/Types/Permission.js +274 -0
  722. package/build/dist/Types/Permission.js.map +1 -1
  723. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +10 -4
  724. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -1
  725. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +12 -0
  726. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  727. package/build/dist/Types/Rules/RuleRun.js +4 -0
  728. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  729. package/build/dist/Types/Team/ComplianceNotificationChannel.js +4 -3
  730. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -1
  731. package/build/dist/Types/Team/ComplianceRule.js +48 -14
  732. package/build/dist/Types/Team/ComplianceRule.js.map +1 -1
  733. package/build/dist/Types/Team/ComplianceRuleType.js +2 -2
  734. package/build/dist/Types/Telemetry/EntityType.js +16 -0
  735. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  736. package/build/dist/Types/XML.js +23 -0
  737. package/build/dist/Types/XML.js.map +1 -1
  738. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +641 -266
  739. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  740. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js +464 -0
  741. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js.map +1 -0
  742. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js +69 -0
  743. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js.map +1 -0
  744. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js +108 -0
  745. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js.map +1 -0
  746. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +14 -186
  747. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -1
  748. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
  749. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
  750. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +5 -14
  751. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -1
  752. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +14 -13
  753. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  754. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +1 -1
  755. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
  756. package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -3
  757. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  758. package/build/dist/UI/Components/Icon/Icon.js +8 -0
  759. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  760. package/build/dist/UI/Components/Input/Input.js +11 -3
  761. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  762. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +2 -1
  763. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  764. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +14 -4
  765. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  766. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  767. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  768. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +710 -65
  769. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  770. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js +238 -0
  771. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js.map +1 -0
  772. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js +1028 -0
  773. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js.map +1 -0
  774. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js +1088 -0
  775. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js.map +1 -0
  776. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +70 -10
  777. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  778. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js +887 -0
  779. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js.map +1 -0
  780. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +57 -6
  781. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  782. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +7 -5
  783. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
  784. package/build/dist/UI/Components/Table/EmptyTableMessage.js +51 -0
  785. package/build/dist/UI/Components/Table/EmptyTableMessage.js.map +1 -0
  786. package/build/dist/UI/Components/Table/Table.js +30 -11
  787. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  788. package/build/dist/UI/Components/Table/TableRow.js +9 -2
  789. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  790. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +63 -1
  791. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -1
  792. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +4 -2
  793. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  794. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +91 -10
  795. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
  796. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +14 -4
  797. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  798. package/build/dist/UI/Components/TextArea/TextArea.js +9 -3
  799. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  800. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js +1 -2
  801. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js.map +1 -1
  802. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  803. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  804. package/build/dist/Utils/Markdown/UntrustedMarkdown.js +988 -0
  805. package/build/dist/Utils/Markdown/UntrustedMarkdown.js.map +1 -0
  806. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js +34 -0
  807. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js.map +1 -0
  808. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js +148 -0
  809. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js.map +1 -0
  810. package/build/dist/Utils/Telemetry/EntityKey.js +36 -0
  811. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  812. package/package.json +1 -3
  813. package/Server/Utils/CommunityEditionSsoReport.ts +0 -361
  814. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +0 -616
  815. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +0 -217
  816. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +0 -271
  817. package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +0 -215
  818. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +0 -784
  819. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +0 -518
  820. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +0 -419
  821. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +0 -769
  822. package/Tests/UI/Components/CodeEditor.test.tsx +0 -638
  823. package/Tests/UI/Components/MonacoLoader.test.ts +0 -67
  824. package/Tests/UI/Components/MonacoRuntime.test.ts +0 -121
  825. package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +0 -329
  826. package/Tests/UI/Components/YamlEditor.test.tsx +0 -847
  827. package/UI/Components/CodeEditor/MonacoLoader.ts +0 -36
  828. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -319
  829. package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +0 -223
  830. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +0 -223
  831. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +0 -1
  832. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +0 -34
  833. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +0 -1
  834. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +0 -171
  835. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +0 -1
  836. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +0 -119
  837. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +0 -1
@@ -0,0 +1,1773 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from "@jest/globals";
10
+ import {
11
+ act,
12
+ cleanup,
13
+ fireEvent,
14
+ render,
15
+ screen,
16
+ waitFor,
17
+ within,
18
+ } from "@testing-library/react";
19
+ import * as React from "react";
20
+ import { MemoryRouter } from "react-router-dom";
21
+ import getJestMockFunction, { MockFunction } from "../../MockType";
22
+
23
+ /*
24
+ * A queue's Overview, rendered for real — ResourceOverview, the Producers
25
+ * and Consumers cards, the Broker health section — with the API answered
26
+ * by a mock that reads each request the way the server would and the charts
27
+ * replaced by markers. What it pins:
28
+ *
29
+ * - every telemetry request is scoped by the queue's ONE entity key (built
30
+ * from its family identifier) and its project; an attribute filter only
31
+ * tells its spans apart (the operation they record, a batch's size);
32
+ * - the tiles read the spans by kind and operation
33
+ * (MessageQueueSpanPopulation; MessageQueueSpanPopulations pins which
34
+ * spans each group holds): Published = PRODUCER spans plus the client
35
+ * sends of services with none, Consumed = CONSUMER spans that handled
36
+ * messages, Errors = failed spans of any kind over all of them, p95
37
+ * processing time = ONE percentile of those consumer spans;
38
+ * - the charts are per interval; the cards group the spans by service, and
39
+ * an empty card says whether the queue had no span or none of its side;
40
+ * - Broker health reads the row's SPECIFIC system's catalog (an ActiveMQ
41
+ * row reads ActiveMQ's metrics under its JMS family key; a JMS row reads
42
+ * none and shows the JMS guidance), pooled on the server where the fold
43
+ * composes (Kafka's lag), and gives each gauge with data a Create monitor
44
+ * link filtered on its observed series; a Service Bus queue without a
45
+ * namespace, which its broker metrics never reach, is not offered their
46
+ * setup;
47
+ * - no key (an identifier that does not parse) is no request at all;
48
+ * - a slow, wider range never overwrites a newer one, and an auto-refresh
49
+ * tick never supersedes a load still running.
50
+ */
51
+
52
+ const MODEL_ID_STRING: string = "5c1e0d2a-7b3c-4d5e-8f60-000000000123";
53
+ const PROJECT_ID_STRING: string = "8f2a1b3c-4d5e-4f60-9a7b-1c2d3e4f5a6b";
54
+ const CHECKOUT_ID: string = "3c0e7b2a-1111-4111-8111-000000000001";
55
+ const BILLING_ID: string = "3c0e7b2a-1111-4111-8111-000000000002";
56
+ const MAILER_ID: string = "3c0e7b2a-1111-4111-8111-000000000003";
57
+
58
+ const getItemMock: MockFunction = getJestMockFunction();
59
+ const getListMock: MockFunction = getJestMockFunction();
60
+ const aggregateMock: MockFunction = getJestMockFunction();
61
+ const chartCardMock: MockFunction = getJestMockFunction();
62
+
63
+ jest.mock("react-i18next", () => {
64
+ return {
65
+ useTranslation: () => {
66
+ return {
67
+ t: (key: string, options?: { defaultValue?: string }): string => {
68
+ return options?.defaultValue ?? key;
69
+ },
70
+ };
71
+ },
72
+ };
73
+ });
74
+
75
+ // The arrow wrappers are load bearing: jest.mock is hoisted above the mocks.
76
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
77
+ return {
78
+ __esModule: true,
79
+ default: {
80
+ getItem: (...args: Array<unknown>) => {
81
+ return getItemMock(...args);
82
+ },
83
+ getList: (...args: Array<unknown>) => {
84
+ return getListMock(...args);
85
+ },
86
+ },
87
+ };
88
+ });
89
+
90
+ jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
91
+ return {
92
+ __esModule: true,
93
+ default: {
94
+ aggregate: (...args: Array<unknown>) => {
95
+ return aggregateMock(...args);
96
+ },
97
+ getList: () => {
98
+ return Promise.resolve({ data: [], count: 0 });
99
+ },
100
+ },
101
+ };
102
+ });
103
+
104
+ jest.mock("../../../UI/Utils/API/API", () => {
105
+ return {
106
+ __esModule: true,
107
+ default: {
108
+ getFriendlyMessage: (error: unknown) => {
109
+ return (
110
+ ((error as { message?: unknown } | null)?.message as string) ||
111
+ "Could not load"
112
+ );
113
+ },
114
+ },
115
+ };
116
+ });
117
+
118
+ jest.mock("../../../UI/Utils/Navigation", () => {
119
+ return {
120
+ __esModule: true,
121
+ default: {
122
+ getLastParamAsObjectID: () => {
123
+ const { default: ObjectIDType } = jest.requireActual(
124
+ "../../../Types/ObjectID",
125
+ ) as { default: new (id: string) => unknown };
126
+ return new ObjectIDType("5c1e0d2a-7b3c-4d5e-8f60-000000000123");
127
+ },
128
+ },
129
+ };
130
+ });
131
+
132
+ jest.mock("../../../UI/Utils/Project", () => {
133
+ return {
134
+ __esModule: true,
135
+ default: {
136
+ getCurrentProjectId: () => {
137
+ const { default: ObjectIDType } = jest.requireActual(
138
+ "../../../Types/ObjectID",
139
+ ) as { default: new (id: string) => unknown };
140
+ return new ObjectIDType("8f2a1b3c-4d5e-4f60-9a7b-1c2d3e4f5a6b");
141
+ },
142
+ },
143
+ };
144
+ });
145
+
146
+ jest.mock("../../../UI/Components/Loader/PageLoader", () => {
147
+ return {
148
+ __esModule: true,
149
+ default: () => {
150
+ return <div data-testid="page-loader" />;
151
+ },
152
+ };
153
+ });
154
+
155
+ jest.mock("../../../UI/Components/ErrorMessage/ErrorMessage", () => {
156
+ return {
157
+ __esModule: true,
158
+ default: (props: { message: string }) => {
159
+ return <div data-testid="error-message">{props.message}</div>;
160
+ },
161
+ };
162
+ });
163
+
164
+ // The picker, as buttons that pick a preset.
165
+ jest.mock(
166
+ "../../../UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker",
167
+ () => {
168
+ return {
169
+ __esModule: true,
170
+ default: (props: {
171
+ value?: { range?: string };
172
+ onChange: (value: { range: string }) => void;
173
+ }) => {
174
+ return (
175
+ <div data-testid="time-range-picker" data-range={props.value?.range}>
176
+ <button
177
+ onClick={() => {
178
+ props.onChange({ range: "Past 1 Day" });
179
+ }}
180
+ >
181
+ Pick past day
182
+ </button>
183
+ </div>
184
+ );
185
+ },
186
+ };
187
+ },
188
+ );
189
+
190
+ jest.mock(
191
+ "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/AutoRefreshControl",
192
+ () => {
193
+ return {
194
+ __esModule: true,
195
+ default: (props: {
196
+ onManualRefresh: () => void;
197
+ timeRangePicker?: React.ReactElement;
198
+ }) => {
199
+ return (
200
+ <div data-testid="auto-refresh">
201
+ {props.timeRangePicker}
202
+ <button onClick={props.onManualRefresh}>Refresh now</button>
203
+ </div>
204
+ );
205
+ },
206
+ };
207
+ },
208
+ );
209
+
210
+ // The auto-refresh timer, as a callback a test calls for a tick.
211
+ let mockAutoRefreshTick: (() => void) | null = null;
212
+
213
+ jest.mock(
214
+ "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/useAutoRefresh",
215
+ () => {
216
+ return {
217
+ __esModule: true,
218
+ default: (options: { onRefresh: () => void }) => {
219
+ mockAutoRefreshTick = options.onRefresh;
220
+ return {
221
+ autoRefreshInterval: "30s",
222
+ setAutoRefreshInterval: () => {},
223
+ };
224
+ },
225
+ };
226
+ },
227
+ );
228
+
229
+ jest.mock(
230
+ "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/ChartCard",
231
+ () => {
232
+ return {
233
+ __esModule: true,
234
+ default: (props: { title: string }) => {
235
+ chartCardMock(props);
236
+ return (
237
+ <div data-testid="chart-card" data-title={props.title}>
238
+ {props.title}
239
+ </div>
240
+ );
241
+ },
242
+ };
243
+ },
244
+ );
245
+
246
+ import MessageQueueOverview from "../../../../App/FeatureSet/Dashboard/src/Pages/MessageQueue/View/Overview";
247
+ import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
248
+ import { MESSAGE_QUEUE_NOT_FOUND_MESSAGE } from "../../../../App/FeatureSet/Dashboard/src/Pages/MessageQueue/Utils/MessageQueuePresentation";
249
+ import {
250
+ BROKER_HEALTH_MONITOR_UNAVAILABLE_REASON,
251
+ BROKER_HEALTH_NO_DATA_TITLE,
252
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/MessageQueue/MessageQueueBrokerHealthSection";
253
+ import { getMessageQueueServicesEmptyText } from "../../../../App/FeatureSet/Dashboard/src/Components/MessageQueue/MessageQueueServicesCard";
254
+ import {
255
+ MESSAGE_QUEUE_OBSERVED_SERIES_TTL_MS,
256
+ MessageQueueObservedSeriesCache,
257
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/MessageQueue/MessageQueueTelemetryQueries";
258
+ import MessageQueue from "../../../Models/DatabaseModels/MessageQueue";
259
+ import Service from "../../../Models/DatabaseModels/Service";
260
+ import Metric from "../../../Models/AnalyticsModels/Metric";
261
+ import Span, {
262
+ SpanKind,
263
+ SpanStatus,
264
+ } from "../../../Models/AnalyticsModels/Span";
265
+ import Route from "../../../Types/API/Route";
266
+ import AggregationInterval from "../../../Types/BaseDatabase/AggregationInterval";
267
+ import AggregationType from "../../../Types/BaseDatabase/AggregationType";
268
+ import Includes from "../../../Types/BaseDatabase/Includes";
269
+ import IncludesNone from "../../../Types/BaseDatabase/IncludesNone";
270
+ import {
271
+ MessageQueueMetricDescriptor,
272
+ getMessageQueueMetricsForSystem,
273
+ } from "../../../Types/MessageQueue/MessageQueueMetricCatalog";
274
+ import ObjectID from "../../../Types/ObjectID";
275
+ import MetricExplorerUrl, {
276
+ SerializedMetricQuery,
277
+ } from "../../../Utils/Metrics/MetricExplorerUrl";
278
+ import { keyForMessageQueue } from "../../../Utils/Telemetry/EntityKey";
279
+
280
+ const PAGE_PROPS: PageComponentProps = {
281
+ pageRoute: new Route("/dashboard/test/queues/test"),
282
+ currentProject: null,
283
+ hasPaymentMethod: true,
284
+ };
285
+
286
+ const KAFKA_ORDERS_KEY: string = keyForMessageQueue(PROJECT_ID_STRING, {
287
+ system: "kafka",
288
+ brokerScope: "",
289
+ destination: "orders",
290
+ });
291
+
292
+ const JMS_ORDERS_KEY: string = keyForMessageQueue(PROJECT_ID_STRING, {
293
+ system: "jms",
294
+ brokerScope: "",
295
+ destination: "orders",
296
+ });
297
+
298
+ // ---- the backend ---------------------------------------------------------
299
+
300
+ interface AggregateRequest {
301
+ modelType: unknown;
302
+ aggregateBy: {
303
+ query: Record<string, unknown>;
304
+ aggregationType: AggregationType;
305
+ aggregationInterval?: AggregationInterval | undefined;
306
+ groupBy?: Record<string, unknown> | undefined;
307
+ groupByAttributeKeys?: Array<string> | undefined;
308
+ startTimestamp: Date;
309
+ endTimestamp: Date;
310
+ };
311
+ }
312
+
313
+ // Twelve one-minute buckets, 30 to 19 minutes ago, inside any default window.
314
+ const NOW: number = Date.now();
315
+ const MINUTES: Array<Date> = Array.from(
316
+ { length: 12 },
317
+ (_: unknown, index: number): Date => {
318
+ return new Date(
319
+ Math.floor((NOW - (30 - index) * 60 * 1000) / 60000) * 60000,
320
+ );
321
+ },
322
+ );
323
+
324
+ type Rows = Array<Record<string, unknown>>;
325
+
326
+ function perMinute(value: number, extra?: Record<string, unknown>): Rows {
327
+ return MINUTES.map((timestamp: Date): Record<string, unknown> => {
328
+ return { timestamp: timestamp, value: value, ...(extra || {}) };
329
+ });
330
+ }
331
+
332
+ interface ServiceSpans {
333
+ id: string;
334
+ count: number;
335
+ errors: number;
336
+ p95Ns: number;
337
+ }
338
+
339
+ /*
340
+ * What the queue's spans and metrics are in one scenario. `scale` lets a
341
+ * wider window hold more, so a test can tell which window's answer landed.
342
+ */
343
+ interface Scenario {
344
+ publishedPerMinute: number;
345
+ consumedPerMinute: number;
346
+ allPerMinute: number;
347
+ errorsPerMinute: number;
348
+ windowP95Ns: number | null;
349
+ producers: Array<ServiceSpans>;
350
+ consumers: Array<ServiceSpans>;
351
+ // Per service: its client spans that record a send (the "send" group).
352
+ sends?: Array<ServiceSpans> | undefined;
353
+ // The "send" group's spans per minute, of the services in `sends`.
354
+ sendsPerMinute?: number | undefined;
355
+ // Metric name → the rows its chart query answers.
356
+ metrics: Record<string, (request: AggregateRequest) => Rows>;
357
+ // Metric name → the observed series (attributes) its monitor query sees.
358
+ observed: Record<string, Array<Record<string, string>>>;
359
+ }
360
+
361
+ function kafkaScenario(): Scenario {
362
+ return {
363
+ publishedPerMinute: 10,
364
+ consumedPerMinute: 9,
365
+ allPerMinute: 25,
366
+ errorsPerMinute: 1,
367
+ windowP95Ns: 12_300_000,
368
+ producers: [
369
+ { id: CHECKOUT_ID, count: 100, errors: 0, p95Ns: 3_000_000 },
370
+ { id: BILLING_ID, count: 20, errors: 2, p95Ns: 5_000_000 },
371
+ ],
372
+ consumers: [{ id: MAILER_ID, count: 108, errors: 4, p95Ns: 40_000_000 }],
373
+ metrics: {
374
+ "kafka.consumer_group.lag_sum": (request: AggregateRequest): Rows => {
375
+ // Read per group, billing trails by 40 and shipping by 70.
376
+ if (request.aggregateBy.groupByAttributeKeys) {
377
+ return [
378
+ ...perMinute(40, { attributes: { group: "billing" } }),
379
+ ...perMinute(70, { attributes: { group: "shipping" } }),
380
+ ];
381
+ }
382
+ // Pooled, the server folds them: the Max, 70.
383
+ return perMinute(70);
384
+ },
385
+ "kafka.consumer_group.offset_sum": (): Rows => {
386
+ // Each group commits 60 messages a minute: 2 messages/s in all.
387
+ return ["billing", "shipping"].flatMap((group: string): Rows => {
388
+ return MINUTES.map(
389
+ (timestamp: Date, index: number): Record<string, unknown> => {
390
+ return {
391
+ timestamp: timestamp,
392
+ value: 1000 + 60 * index,
393
+ attributes: { topic: "orders", group: group },
394
+ };
395
+ },
396
+ );
397
+ });
398
+ },
399
+ },
400
+ observed: {
401
+ "kafka.consumer_group.lag_sum": [{ topic: "orders" }],
402
+ },
403
+ };
404
+ }
405
+
406
+ let scenario: Scenario = kafkaScenario();
407
+ let requests: Array<AggregateRequest> = [];
408
+
409
+ /*
410
+ * While set, every telemetry request it picks waits until a test releases
411
+ * it; the rest are answered at once (a load's follow-up requests included).
412
+ */
413
+ let parkWhen: ((request: AggregateRequest) => boolean) | null = null;
414
+ let parked: Array<{ request: AggregateRequest; release: () => void }> = [];
415
+
416
+ function isDayWindow(request: AggregateRequest): boolean {
417
+ return (
418
+ request.aggregateBy.endTimestamp.getTime() -
419
+ request.aggregateBy.startTimestamp.getTime() >
420
+ 3 * 60 * 60 * 1000
421
+ );
422
+ }
423
+
424
+ /*
425
+ * Which of the queue's span groups (MessageQueueSpanPopulation) a request
426
+ * reads, told apart by its kind and operation filters — so a request for a
427
+ * group this mock does not know fails the test instead of reading another.
428
+ */
429
+ function populationOf(query: Record<string, unknown>): string {
430
+ const kind: unknown = query["kind"];
431
+ const attributes: Record<string, unknown> =
432
+ (query["attributes"] as Record<string, unknown> | undefined) || {};
433
+ if (kind === undefined) {
434
+ return "all";
435
+ }
436
+ if (kind === SpanKind.Producer) {
437
+ return "publish";
438
+ }
439
+ if (kind === SpanKind.Client) {
440
+ return attributes["rpc.method"] !== undefined ? "awsSend" : "send";
441
+ }
442
+ if (kind === SpanKind.Consumer) {
443
+ return attributes["messaging.operation.type"] === "receive"
444
+ ? "receivedBatch"
445
+ : "consume";
446
+ }
447
+ throw new Error(`No span group reads kind ${String(kind)}`);
448
+ }
449
+
450
+ function spanRows(request: AggregateRequest, scale: number): Rows {
451
+ const by: AggregateRequest["aggregateBy"] = request.aggregateBy;
452
+ const population: string = populationOf(by.query);
453
+ const errorsOnly: boolean = by.query["statusCode"] === SpanStatus.Error;
454
+ // Only the client-only publishers' own line asks by service id.
455
+ const serviceIds: Array<string> | null = by.query["primaryEntityId"]
456
+ ? (by.query["primaryEntityId"] as Includes).values.map(
457
+ (id: unknown): string => {
458
+ return String(id);
459
+ },
460
+ )
461
+ : null;
462
+
463
+ if (by.groupBy && by.groupBy["primaryEntityId"]) {
464
+ const services: Array<ServiceSpans> =
465
+ population === "publish"
466
+ ? scenario.producers
467
+ : population === "consume"
468
+ ? scenario.consumers
469
+ : population === "send"
470
+ ? scenario.sends || []
471
+ : [];
472
+ return services.map((service: ServiceSpans): Record<string, unknown> => {
473
+ return {
474
+ timestamp: MINUTES[0],
475
+ primaryEntityId: service.id,
476
+ value:
477
+ by.aggregationType === AggregationType.P95
478
+ ? service.p95Ns
479
+ : errorsOnly
480
+ ? service.errors
481
+ : service.count,
482
+ };
483
+ });
484
+ }
485
+
486
+ if (by.aggregationType === AggregationType.P95) {
487
+ if (by.aggregationInterval === AggregationInterval.Total) {
488
+ return scenario.windowP95Ns === null
489
+ ? []
490
+ : [{ timestamp: MINUTES[0], value: scenario.windowP95Ns }];
491
+ }
492
+ return perMinute(10_000_000);
493
+ }
494
+
495
+ if (population === "send") {
496
+ // The client-only publishers' line: their sends per minute.
497
+ expect(serviceIds).not.toBeNull();
498
+ return perMinute((scenario.sendsPerMinute || 0) * scale);
499
+ }
500
+ if (population === "awsSend" || population === "receivedBatch") {
501
+ return [];
502
+ }
503
+ if (errorsOnly) {
504
+ expect(population).toBe("all");
505
+ return perMinute(scenario.errorsPerMinute * scale);
506
+ }
507
+ if (population === "publish") {
508
+ return perMinute(scenario.publishedPerMinute * scale);
509
+ }
510
+ if (population === "consume") {
511
+ return perMinute(scenario.consumedPerMinute * scale);
512
+ }
513
+ return perMinute(scenario.allPerMinute * scale);
514
+ }
515
+
516
+ function metricRows(request: AggregateRequest): Rows {
517
+ const by: AggregateRequest["aggregateBy"] = request.aggregateBy;
518
+ const name: string = String(by.query["name"] || "");
519
+ if (by.aggregationInterval === AggregationInterval.Total) {
520
+ return (scenario.observed[name] || []).map(
521
+ (attributes: Record<string, string>): Record<string, unknown> => {
522
+ return { timestamp: MINUTES[0], value: 12, attributes: attributes };
523
+ },
524
+ );
525
+ }
526
+ const rows: ((request: AggregateRequest) => Rows) | undefined =
527
+ scenario.metrics[name];
528
+ return rows ? rows(request) : [];
529
+ }
530
+
531
+ function answer(request: AggregateRequest): { data: Rows } {
532
+ const scale: number = isDayWindow(request) ? 5 : 1;
533
+ if (request.modelType === Span) {
534
+ return { data: spanRows(request, scale) };
535
+ }
536
+ if (request.modelType === Metric) {
537
+ return { data: metricRows(request) };
538
+ }
539
+ return { data: [] };
540
+ }
541
+
542
+ function spanRequests(): Array<AggregateRequest> {
543
+ return requests.filter((request: AggregateRequest): boolean => {
544
+ return request.modelType === Span;
545
+ });
546
+ }
547
+
548
+ function metricRequests(): Array<AggregateRequest> {
549
+ return requests.filter((request: AggregateRequest): boolean => {
550
+ return request.modelType === Metric;
551
+ });
552
+ }
553
+
554
+ async function releaseParked(
555
+ which: (request: AggregateRequest) => boolean,
556
+ ): Promise<void> {
557
+ const taken: Array<{ request: AggregateRequest; release: () => void }> =
558
+ parked.filter((entry: { request: AggregateRequest }): boolean => {
559
+ return which(entry.request);
560
+ });
561
+ expect(taken.length).toBeGreaterThan(0);
562
+ parked = parked.filter(
563
+ (entry: { request: AggregateRequest; release: () => void }): boolean => {
564
+ return !taken.includes(entry);
565
+ },
566
+ );
567
+ await act(async () => {
568
+ for (const entry of taken) {
569
+ entry.release();
570
+ }
571
+ await Promise.resolve();
572
+ });
573
+ }
574
+
575
+ // ---- the row -------------------------------------------------------------
576
+
577
+ function minutesAgo(minutes: number): Date {
578
+ return new Date(Date.now() - minutes * 60 * 1000);
579
+ }
580
+
581
+ function queueRow(data?: Partial<MessageQueue>): MessageQueue {
582
+ const row: MessageQueue = new MessageQueue();
583
+ row.id = new ObjectID(MODEL_ID_STRING);
584
+ row.projectId = new ObjectID(PROJECT_ID_STRING);
585
+ row.name = "orders";
586
+ row.queueIdentifier = "kafka||orders";
587
+ row.messagingSystem = "kafka";
588
+ row.destinationName = "orders";
589
+ row.discoverySource = "traces";
590
+ row.lastSeenAt = minutesAgo(2);
591
+ Object.assign(row, data || {});
592
+ return row;
593
+ }
594
+
595
+ function serveServiceNames(): void {
596
+ getListMock.mockImplementation((request: unknown) => {
597
+ const modelType: unknown = (request as { modelType?: unknown }).modelType;
598
+ if (modelType === Service) {
599
+ return Promise.resolve({
600
+ data: [
601
+ { _id: CHECKOUT_ID, name: "checkout" },
602
+ { _id: BILLING_ID, name: "billing" },
603
+ { _id: MAILER_ID, name: "mailer" },
604
+ ],
605
+ count: 3,
606
+ });
607
+ }
608
+ return Promise.resolve({ data: [], count: 0 });
609
+ });
610
+ }
611
+
612
+ async function renderOverview(): Promise<void> {
613
+ render(
614
+ <MemoryRouter>
615
+ <MessageQueueOverview {...PAGE_PROPS} />
616
+ </MemoryRouter>,
617
+ );
618
+ await screen.findByText("Producers", undefined, { timeout: 3000 });
619
+ }
620
+
621
+ // Waits until the telemetry has landed: the tiles have left their skeleton.
622
+ async function renderLoaded(): Promise<void> {
623
+ await renderOverview();
624
+ await waitFor(() => {
625
+ expect(screen.queryAllByText(/^Loading /)).toHaveLength(0);
626
+ });
627
+ }
628
+
629
+ // A tile's title is the uppercase label above its value.
630
+ const TILE_LABEL_CLASS: RegExp = /uppercase/;
631
+
632
+ function tile(title: string): HTMLElement {
633
+ const label: HTMLElement = screen
634
+ .getAllByText(title)
635
+ .find((element: HTMLElement): boolean => {
636
+ return (
637
+ element.tagName === "SPAN" && TILE_LABEL_CLASS.test(element.className)
638
+ );
639
+ })!;
640
+ return label.closest("div.relative") as HTMLElement;
641
+ }
642
+
643
+ function chartProps(title: string): Record<string, unknown> {
644
+ const calls: Array<Array<unknown>> = chartCardMock.mock.calls.filter(
645
+ (call: Array<unknown>): boolean => {
646
+ return (call[0] as { title: string }).title === title;
647
+ },
648
+ );
649
+ expect(calls.length).toBeGreaterThan(0);
650
+ return calls[calls.length - 1]![0] as Record<string, unknown>;
651
+ }
652
+
653
+ beforeEach(() => {
654
+ getItemMock.mockReset();
655
+ getListMock.mockReset();
656
+ aggregateMock.mockReset();
657
+ chartCardMock.mockReset();
658
+ requests = [];
659
+ parked = [];
660
+ parkWhen = null;
661
+ scenario = kafkaScenario();
662
+ mockAutoRefreshTick = null;
663
+ serveServiceNames();
664
+ aggregateMock.mockImplementation((request: unknown): Promise<unknown> => {
665
+ const typed: AggregateRequest = request as AggregateRequest;
666
+ requests.push(typed);
667
+ const result: { data: Rows } = answer(typed);
668
+ if (parkWhen && parkWhen(typed)) {
669
+ return new Promise((resolve: (value: unknown) => void): void => {
670
+ parked.push({
671
+ request: typed,
672
+ release: (): void => {
673
+ resolve(result);
674
+ },
675
+ });
676
+ });
677
+ }
678
+ return Promise.resolve(result);
679
+ });
680
+ });
681
+
682
+ afterEach(() => {
683
+ cleanup();
684
+ });
685
+
686
+ // ---- the tests -------------------------------------------------------------
687
+
688
+ describe("the queue Overview's scope", () => {
689
+ test("every telemetry request is scoped by the queue's key and project; an attribute only tells its spans apart", async () => {
690
+ getItemMock.mockResolvedValue(queueRow());
691
+
692
+ await renderLoaded();
693
+
694
+ expect(requests.length).toBeGreaterThan(10);
695
+ for (const request of requests) {
696
+ const query: Record<string, unknown> = request.aggregateBy.query;
697
+ expect(query["entityKeys"]).toBeInstanceOf(Includes);
698
+ expect((query["entityKeys"] as Includes).values).toEqual([
699
+ KAFKA_ORDERS_KEY,
700
+ ]);
701
+ expect(String(query["projectId"])).toBe(PROJECT_ID_STRING);
702
+ if (request.modelType === Metric) {
703
+ // The key IS the queue: never an attribute filter on its metrics.
704
+ expect(query["attributes"]).toBeUndefined();
705
+ continue;
706
+ }
707
+ // A span attribute filter reads the operation or a batch's size only.
708
+ for (const key of Object.keys(
709
+ (query["attributes"] as Record<string, unknown> | undefined) || {},
710
+ )) {
711
+ expect([
712
+ "messaging.operation.type",
713
+ "messaging.operation",
714
+ "rpc.method",
715
+ "messaging.batch.message_count",
716
+ ]).toContain(key);
717
+ }
718
+ }
719
+ });
720
+
721
+ test("reads the row it needs by the page's id", async () => {
722
+ getItemMock.mockResolvedValue(queueRow());
723
+
724
+ await renderLoaded();
725
+
726
+ const request: {
727
+ modelType: unknown;
728
+ id: ObjectID;
729
+ select: Record<string, unknown>;
730
+ } = getItemMock.mock.calls[0]![0] as {
731
+ modelType: unknown;
732
+ id: ObjectID;
733
+ select: Record<string, unknown>;
734
+ };
735
+ expect(request.modelType).toBe(MessageQueue);
736
+ expect(request.id.toString()).toBe(MODEL_ID_STRING);
737
+ expect(request.select).toEqual(
738
+ expect.objectContaining({
739
+ queueIdentifier: true,
740
+ messagingSystem: true,
741
+ destinationName: true,
742
+ brokerScope: true,
743
+ brokerAddress: true,
744
+ lastSeenAt: true,
745
+ brokerMetricsLastSeenAt: true,
746
+ labels: { name: true, color: true },
747
+ }),
748
+ );
749
+ });
750
+
751
+ test("an identifier that does not parse sends no telemetry request at all", async () => {
752
+ getItemMock.mockResolvedValue(
753
+ queueRow({ queueIdentifier: "not-an-identifier" }),
754
+ );
755
+
756
+ await renderOverview();
757
+
758
+ expect(
759
+ await screen.findByTestId("message-queue-unscoped-banner"),
760
+ ).toBeInTheDocument();
761
+ // The sections render empty, not as loaders forever.
762
+ expect(
763
+ await screen.findByTestId("message-queue-producers-empty"),
764
+ ).toBeInTheDocument();
765
+ expect(
766
+ screen.getByTestId("message-queue-consumers-empty"),
767
+ ).toBeInTheDocument();
768
+ expect(
769
+ screen.getByTestId("message-queue-broker-health-guidance"),
770
+ ).toBeInTheDocument();
771
+ expect(aggregateMock).not.toHaveBeenCalled();
772
+ });
773
+
774
+ test("a deleted or unknown id (the API's empty model) is 'Queue not found.' and queries nothing", async () => {
775
+ getItemMock.mockResolvedValue(new MessageQueue());
776
+
777
+ render(
778
+ <MemoryRouter>
779
+ <MessageQueueOverview {...PAGE_PROPS} />
780
+ </MemoryRouter>,
781
+ );
782
+
783
+ expect(await screen.findByTestId("error-message")).toHaveTextContent(
784
+ MESSAGE_QUEUE_NOT_FOUND_MESSAGE,
785
+ );
786
+ expect(aggregateMock).not.toHaveBeenCalled();
787
+ });
788
+
789
+ test("a failed row lookup shows its error", async () => {
790
+ getItemMock.mockRejectedValue(new Error("Permission denied"));
791
+
792
+ render(
793
+ <MemoryRouter>
794
+ <MessageQueueOverview {...PAGE_PROPS} />
795
+ </MemoryRouter>,
796
+ );
797
+
798
+ expect(await screen.findByTestId("error-message")).toHaveTextContent(
799
+ "Permission denied",
800
+ );
801
+ expect(aggregateMock).not.toHaveBeenCalled();
802
+ });
803
+ });
804
+
805
+ describe("the message tiles and charts", () => {
806
+ test("Published counts the producer spans, Consumed the consumer spans that handled messages", async () => {
807
+ getItemMock.mockResolvedValue(queueRow());
808
+
809
+ await renderLoaded();
810
+
811
+ // 10 and 9 a minute over twelve minutes.
812
+ expect(within(tile("Published")).getByText("120")).toBeInTheDocument();
813
+ expect(within(tile("Consumed")).getByText("108")).toBeInTheDocument();
814
+ // Consumed leaves out the receives that returned nothing.
815
+ const consumed: AggregateRequest | undefined = spanRequests().find(
816
+ (request: AggregateRequest): boolean => {
817
+ return (
818
+ request.aggregateBy.aggregationType === AggregationType.Count &&
819
+ !request.aggregateBy.groupBy &&
820
+ request.aggregateBy.query["kind"] === SpanKind.Consumer
821
+ );
822
+ },
823
+ );
824
+ expect(
825
+ (consumed!.aggregateBy.query["attributes"] as Record<string, unknown>)[
826
+ "messaging.batch.message_count"
827
+ ],
828
+ ).toEqual(new IncludesNone(["0"]));
829
+
830
+ const counts: Array<AggregateRequest> = spanRequests().filter(
831
+ (request: AggregateRequest): boolean => {
832
+ return (
833
+ request.aggregateBy.aggregationType === AggregationType.Count &&
834
+ !request.aggregateBy.groupBy
835
+ );
836
+ },
837
+ );
838
+ const kinds: Array<unknown> = counts.map(
839
+ (request: AggregateRequest): unknown => {
840
+ return request.aggregateBy.query["kind"];
841
+ },
842
+ );
843
+ expect(kinds).toEqual(
844
+ expect.arrayContaining([SpanKind.Producer, SpanKind.Consumer]),
845
+ );
846
+ // Never a SERVER filter; a CLIENT one only for client-only publishers.
847
+ expect(kinds).not.toContain(SpanKind.Server);
848
+ expect(kinds).not.toContain(SpanKind.Client);
849
+ });
850
+
851
+ test("a publisher that records its sends only as client spans is counted: the tile, the chart and its card", async () => {
852
+ // An SNS topic the Java agent publishes to: CLIENT spans, no PRODUCER.
853
+ scenario = {
854
+ ...kafkaScenario(),
855
+ publishedPerMinute: 0,
856
+ producers: [],
857
+ sends: [{ id: BILLING_ID, count: 60, errors: 3, p95Ns: 7_000_000 }],
858
+ sendsPerMinute: 5,
859
+ };
860
+ getItemMock.mockResolvedValue(queueRow());
861
+
862
+ await renderLoaded();
863
+
864
+ // Its 60 sends, not the 0 producer spans.
865
+ expect(within(tile("Published")).getByText("60")).toBeInTheDocument();
866
+ const published: Array<{ y: number }> = (
867
+ chartProps("Messages")["series"] as Array<{
868
+ seriesName: string;
869
+ data: Array<{ y: number }>;
870
+ }>
871
+ )[0]!.data;
872
+ expect(published.length).toBeGreaterThan(0);
873
+ expect(published[0]!.y).toBe(5);
874
+ // The line reads exactly the client-only publishers' sends.
875
+ const line: AggregateRequest | undefined = spanRequests().find(
876
+ (request: AggregateRequest): boolean => {
877
+ return Boolean(request.aggregateBy.query["primaryEntityId"]);
878
+ },
879
+ );
880
+ expect(line!.aggregateBy.query["kind"]).toBe(SpanKind.Client);
881
+ expect(
882
+ (line!.aggregateBy.query["primaryEntityId"] as Includes).values.map(
883
+ (id: unknown): string => {
884
+ return String(id);
885
+ },
886
+ ),
887
+ ).toEqual([BILLING_ID]);
888
+
889
+ await waitFor(() => {
890
+ expect(screen.getByText("billing")).toBeInTheDocument();
891
+ });
892
+ const [producer] = screen.getAllByTestId("message-queue-producers-row");
893
+ expect(within(producer!).getByText("60")).toBeInTheDocument();
894
+ expect(within(producer!).getByText("5.0%")).toBeInTheDocument();
895
+ expect(within(producer!).getByText("7.0 ms")).toBeInTheDocument();
896
+ });
897
+
898
+ test("Errors counts failed spans of every kind, over all of the queue's spans", async () => {
899
+ getItemMock.mockResolvedValue(queueRow());
900
+
901
+ await renderLoaded();
902
+
903
+ // 1 of 25 a minute: 12 of 300, 4.0%.
904
+ const errors: HTMLElement = tile("Errors");
905
+ expect(within(errors).getByText("12")).toBeInTheDocument();
906
+ expect(within(errors).getByText("4.0% of 300 spans")).toBeInTheDocument();
907
+
908
+ const errorRequests: Array<AggregateRequest> = spanRequests().filter(
909
+ (request: AggregateRequest): boolean => {
910
+ return (
911
+ request.aggregateBy.query["statusCode"] === SpanStatus.Error &&
912
+ !request.aggregateBy.groupBy
913
+ );
914
+ },
915
+ );
916
+ expect(errorRequests).toHaveLength(1);
917
+ expect(errorRequests[0]!.aggregateBy.query["kind"]).toBeUndefined();
918
+
919
+ const all: Array<AggregateRequest> = spanRequests().filter(
920
+ (request: AggregateRequest): boolean => {
921
+ return (
922
+ request.aggregateBy.aggregationType === AggregationType.Count &&
923
+ !request.aggregateBy.groupBy &&
924
+ request.aggregateBy.query["kind"] === undefined &&
925
+ request.aggregateBy.query["statusCode"] === undefined
926
+ );
927
+ },
928
+ );
929
+ expect(all).toHaveLength(1);
930
+ });
931
+
932
+ test("p95 processing time is ONE percentile of the CONSUMER spans over the range", async () => {
933
+ getItemMock.mockResolvedValue(queueRow());
934
+
935
+ await renderLoaded();
936
+
937
+ expect(
938
+ within(tile("p95 processing time")).getByText("12 ms"),
939
+ ).toBeInTheDocument();
940
+
941
+ const p95: Array<AggregateRequest> = spanRequests().filter(
942
+ (request: AggregateRequest): boolean => {
943
+ return (
944
+ request.aggregateBy.aggregationType === AggregationType.P95 &&
945
+ !request.aggregateBy.groupBy
946
+ );
947
+ },
948
+ );
949
+ expect(p95).toHaveLength(2);
950
+ for (const request of p95) {
951
+ expect(request.aggregateBy.query["kind"]).toBe(SpanKind.Consumer);
952
+ }
953
+ expect(
954
+ p95.map((request: AggregateRequest): unknown => {
955
+ return request.aggregateBy.aggregationInterval;
956
+ }),
957
+ ).toEqual(expect.arrayContaining([AggregationInterval.Total, undefined]));
958
+ });
959
+
960
+ test("a queue with no span at all reads '—', not 0", async () => {
961
+ scenario = {
962
+ ...kafkaScenario(),
963
+ publishedPerMinute: 0,
964
+ consumedPerMinute: 0,
965
+ allPerMinute: 0,
966
+ errorsPerMinute: 0,
967
+ windowP95Ns: null,
968
+ producers: [],
969
+ consumers: [],
970
+ };
971
+ getItemMock.mockResolvedValue(queueRow());
972
+
973
+ await renderLoaded();
974
+
975
+ for (const title of ["Published", "Consumed", "Errors"]) {
976
+ expect(within(tile(title)).getByText("—")).toBeInTheDocument();
977
+ }
978
+ expect(
979
+ within(tile("p95 processing time")).getByText("—"),
980
+ ).toBeInTheDocument();
981
+ });
982
+
983
+ test("spans of other kinds only: Published and Consumed read 0", async () => {
984
+ scenario = {
985
+ ...kafkaScenario(),
986
+ publishedPerMinute: 0,
987
+ consumedPerMinute: 0,
988
+ windowP95Ns: null,
989
+ };
990
+ getItemMock.mockResolvedValue(queueRow());
991
+
992
+ await renderLoaded();
993
+
994
+ expect(within(tile("Published")).getByText("0")).toBeInTheDocument();
995
+ expect(within(tile("Consumed")).getByText("0")).toBeInTheDocument();
996
+ });
997
+
998
+ test("the charts: Published, Consumed and Errors per interval, and the p95 per interval", async () => {
999
+ getItemMock.mockResolvedValue(queueRow());
1000
+
1001
+ await renderLoaded();
1002
+
1003
+ const messages: Record<string, unknown> = chartProps("Messages");
1004
+ const series: Array<{ seriesName: string; data: Array<{ y: number }> }> =
1005
+ messages["series"] as Array<{
1006
+ seriesName: string;
1007
+ data: Array<{ y: number }>;
1008
+ }>;
1009
+ expect(
1010
+ series.map((entry: { seriesName: string }): string => {
1011
+ return entry.seriesName;
1012
+ }),
1013
+ ).toEqual(["Published", "Consumed", "Errors"]);
1014
+ expect(series[0]!.data).toHaveLength(12);
1015
+ expect(series[0]!.data[0]!.y).toBe(10);
1016
+ expect(series[1]!.data[0]!.y).toBe(9);
1017
+ expect(series[2]!.data[0]!.y).toBe(1);
1018
+ expect(messages["syncId"]).toBe(`message-queue-${MODEL_ID_STRING}`);
1019
+
1020
+ const p95: Record<string, unknown> = chartProps("p95 processing time");
1021
+ const p95Series: Array<{ data: Array<{ y: number }> }> = p95[
1022
+ "series"
1023
+ ] as Array<{ data: Array<{ y: number }> }>;
1024
+ // 10,000,000 ns per interval: 10 ms.
1025
+ expect(p95Series[0]!.data[0]!.y).toBe(10);
1026
+ expect(p95["syncId"]).toBe(`message-queue-${MODEL_ID_STRING}`);
1027
+ });
1028
+
1029
+ test("the hero: name, destination, system, discovery source and liveness", async () => {
1030
+ getItemMock.mockResolvedValue(queueRow());
1031
+
1032
+ await renderLoaded();
1033
+
1034
+ expect(screen.getByRole("heading", { name: "orders" })).toBeInTheDocument();
1035
+ expect(screen.getByText("destination: orders")).toBeInTheDocument();
1036
+ expect(screen.getAllByText("Apache Kafka").length).toBeGreaterThan(0);
1037
+ expect(screen.getAllByText("Application traces").length).toBeGreaterThan(0);
1038
+ expect(screen.getByTestId("resource-overview-status")).toHaveTextContent(
1039
+ "Seen recently",
1040
+ );
1041
+ });
1042
+
1043
+ test("a Service Bus queue shows its namespace", async () => {
1044
+ scenario = { ...kafkaScenario(), metrics: {}, observed: {} };
1045
+ getItemMock.mockResolvedValue(
1046
+ queueRow({
1047
+ queueIdentifier: "servicebus|orders-prod|orders",
1048
+ messagingSystem: "servicebus",
1049
+ brokerScope: "orders-prod",
1050
+ brokerAddress: "orders-prod.servicebus.windows.net:5671",
1051
+ }),
1052
+ );
1053
+
1054
+ await renderLoaded();
1055
+
1056
+ expect(screen.getByText("Namespace: orders-prod")).toBeInTheDocument();
1057
+ expect(screen.getByText("Namespace")).toBeInTheDocument();
1058
+ });
1059
+
1060
+ test("a queue nothing has seen lately reads so", async () => {
1061
+ getItemMock.mockResolvedValue(queueRow({ lastSeenAt: minutesAgo(90) }));
1062
+
1063
+ await renderLoaded();
1064
+
1065
+ expect(screen.getByTestId("resource-overview-status")).toHaveTextContent(
1066
+ "Not seen recently",
1067
+ );
1068
+ });
1069
+ });
1070
+
1071
+ describe("Producers and Consumers", () => {
1072
+ test("group the publishes and the consumes by the service that recorded them", async () => {
1073
+ getItemMock.mockResolvedValue(queueRow());
1074
+
1075
+ await renderLoaded();
1076
+
1077
+ const grouped: Array<AggregateRequest> = spanRequests().filter(
1078
+ (request: AggregateRequest): boolean => {
1079
+ return Boolean(request.aggregateBy.groupBy);
1080
+ },
1081
+ );
1082
+ /*
1083
+ * Count, failed count and p95 of each group a Kafka queue reads: its
1084
+ * producer spans, its client sends and its consumer spans.
1085
+ */
1086
+ expect(grouped).toHaveLength(9);
1087
+ expect(
1088
+ grouped
1089
+ .map((request: AggregateRequest): string => {
1090
+ return populationOf(request.aggregateBy.query);
1091
+ })
1092
+ .sort(),
1093
+ ).toEqual([
1094
+ "consume",
1095
+ "consume",
1096
+ "consume",
1097
+ "publish",
1098
+ "publish",
1099
+ "publish",
1100
+ "send",
1101
+ "send",
1102
+ "send",
1103
+ ]);
1104
+ for (const request of grouped) {
1105
+ expect(request.aggregateBy.groupBy).toEqual({ primaryEntityId: true });
1106
+ expect(request.aggregateBy.aggregationInterval).toBe(
1107
+ AggregationInterval.Total,
1108
+ );
1109
+ }
1110
+
1111
+ await waitFor(() => {
1112
+ expect(screen.getByText("checkout")).toBeInTheDocument();
1113
+ });
1114
+
1115
+ const producers: Array<HTMLElement> = screen.getAllByTestId(
1116
+ "message-queue-producers-row",
1117
+ );
1118
+ expect(
1119
+ producers.map((row: HTMLElement): string | null => {
1120
+ return row.getAttribute("data-service-id");
1121
+ }),
1122
+ ).toEqual([CHECKOUT_ID, BILLING_ID]);
1123
+ expect(within(producers[0]!).getByText("checkout")).toBeInTheDocument();
1124
+ expect(within(producers[1]!).getByText("10.0%")).toBeInTheDocument();
1125
+ expect(within(producers[1]!).getByText("5.0 ms")).toBeInTheDocument();
1126
+
1127
+ const consumers: Array<HTMLElement> = screen.getAllByTestId(
1128
+ "message-queue-consumers-row",
1129
+ );
1130
+ expect(consumers).toHaveLength(1);
1131
+ expect(within(consumers[0]!).getByText("mailer")).toBeInTheDocument();
1132
+ expect(within(consumers[0]!).getByText("108")).toBeInTheDocument();
1133
+ expect(within(consumers[0]!).getByText("40 ms")).toBeInTheDocument();
1134
+
1135
+ // One name lookup for both sides.
1136
+ const serviceLookups: Array<Array<unknown>> = getListMock.mock.calls.filter(
1137
+ (call: Array<unknown>): boolean => {
1138
+ return (call[0] as { modelType?: unknown }).modelType === Service;
1139
+ },
1140
+ );
1141
+ expect(serviceLookups).toHaveLength(1);
1142
+ const ids: Array<string> = (
1143
+ (serviceLookups[0]![0] as { query: { _id: Includes } }).query._id
1144
+ .values as Array<ObjectID>
1145
+ )
1146
+ .map((id: ObjectID): string => {
1147
+ return id.toString();
1148
+ })
1149
+ .sort();
1150
+ expect(ids).toEqual([CHECKOUT_ID, BILLING_ID, MAILER_ID].sort());
1151
+ });
1152
+
1153
+ test("spans, but none of a side's: each card says so, and never that nothing published", async () => {
1154
+ scenario = {
1155
+ ...kafkaScenario(),
1156
+ publishedPerMinute: 0,
1157
+ consumedPerMinute: 0,
1158
+ windowP95Ns: null,
1159
+ producers: [],
1160
+ consumers: [],
1161
+ };
1162
+ getItemMock.mockResolvedValue(queueRow());
1163
+
1164
+ await renderLoaded();
1165
+
1166
+ expect(
1167
+ screen.getByTestId("message-queue-producers-empty"),
1168
+ ).toHaveTextContent(getMessageQueueServicesEmptyText("producers", true));
1169
+ expect(
1170
+ screen.getByTestId("message-queue-consumers-empty"),
1171
+ ).toHaveTextContent(getMessageQueueServicesEmptyText("consumers", true));
1172
+ expect(
1173
+ screen.queryByText(
1174
+ "No instrumented application published to this queue in the selected range.",
1175
+ ),
1176
+ ).toBeNull();
1177
+ // Nothing to name: no lookup.
1178
+ expect(
1179
+ getListMock.mock.calls.filter((call: Array<unknown>): boolean => {
1180
+ return (call[0] as { modelType?: unknown }).modelType === Service;
1181
+ }),
1182
+ ).toHaveLength(0);
1183
+ });
1184
+
1185
+ test("no span at all: each card says nothing was there", async () => {
1186
+ scenario = {
1187
+ ...kafkaScenario(),
1188
+ publishedPerMinute: 0,
1189
+ consumedPerMinute: 0,
1190
+ allPerMinute: 0,
1191
+ errorsPerMinute: 0,
1192
+ windowP95Ns: null,
1193
+ producers: [],
1194
+ consumers: [],
1195
+ };
1196
+ getItemMock.mockResolvedValue(queueRow());
1197
+
1198
+ await renderLoaded();
1199
+
1200
+ expect(
1201
+ screen.getByTestId("message-queue-producers-empty"),
1202
+ ).toHaveTextContent(
1203
+ "No instrumented application published to this queue in the selected range.",
1204
+ );
1205
+ expect(
1206
+ screen.getByTestId("message-queue-consumers-empty"),
1207
+ ).toHaveTextContent(
1208
+ "No instrumented application consumed from this queue in the selected range.",
1209
+ );
1210
+ });
1211
+ });
1212
+
1213
+ describe("Broker health", () => {
1214
+ test("reads the Kafka catalog: the lag pooled as its monitor reads it, counters per series as a rate", async () => {
1215
+ getItemMock.mockResolvedValue(queueRow());
1216
+
1217
+ await renderLoaded();
1218
+
1219
+ const catalog: ReadonlyArray<MessageQueueMetricDescriptor> =
1220
+ getMessageQueueMetricsForSystem("kafka");
1221
+ const charted: Array<AggregateRequest> = metricRequests().filter(
1222
+ (request: AggregateRequest): boolean => {
1223
+ return (
1224
+ request.aggregateBy.aggregationInterval !== AggregationInterval.Total
1225
+ );
1226
+ },
1227
+ );
1228
+ expect(
1229
+ charted
1230
+ .map((request: AggregateRequest): string => {
1231
+ return String(request.aggregateBy.query["name"]);
1232
+ })
1233
+ .sort(),
1234
+ ).toEqual(
1235
+ catalog
1236
+ .map((descriptor: MessageQueueMetricDescriptor): string => {
1237
+ return descriptor.metricName;
1238
+ })
1239
+ .sort(),
1240
+ );
1241
+
1242
+ const lag: AggregateRequest = charted.find(
1243
+ (request: AggregateRequest): boolean => {
1244
+ return (
1245
+ request.aggregateBy.query["name"] === "kafka.consumer_group.lag_sum"
1246
+ );
1247
+ },
1248
+ )!;
1249
+ // The Max of every group's Max is the Max of all: one row per interval.
1250
+ expect(lag.aggregateBy.aggregationType).toBe(AggregationType.Max);
1251
+ expect(lag.aggregateBy.groupByAttributeKeys).toBeUndefined();
1252
+ expect(lag.aggregateBy.groupBy).toBeUndefined();
1253
+
1254
+ const offsets: AggregateRequest = charted.find(
1255
+ (request: AggregateRequest): boolean => {
1256
+ return (
1257
+ request.aggregateBy.query["name"] ===
1258
+ "kafka.consumer_group.offset_sum"
1259
+ );
1260
+ },
1261
+ )!;
1262
+ expect(offsets.aggregateBy.aggregationType).toBe(AggregationType.Max);
1263
+ expect(offsets.aggregateBy.groupBy).toEqual({ attributes: true });
1264
+
1265
+ await screen.findByTestId("message-queue-broker-health");
1266
+ const tiles: Array<HTMLElement> = screen.getAllByTestId(
1267
+ "message-queue-broker-metric-tile",
1268
+ );
1269
+ // Only the metrics with data.
1270
+ expect(
1271
+ tiles.map((element: HTMLElement): string | null => {
1272
+ return element.getAttribute("data-metric-id");
1273
+ }),
1274
+ ).toEqual([
1275
+ "kafka:kafka.consumer_group.lag_sum",
1276
+ "kafka:kafka.consumer_group.offset_sum",
1277
+ ]);
1278
+ // The group furthest behind; the offsets' growth per second, both groups.
1279
+ expect(within(tiles[0]!).getByText("70 messages")).toBeInTheDocument();
1280
+ expect(within(tiles[1]!).getByText("2 messages/s")).toBeInTheDocument();
1281
+ });
1282
+
1283
+ test("each gauge with data gets a Create monitor link on its observed series; counters none", async () => {
1284
+ getItemMock.mockResolvedValue(queueRow());
1285
+
1286
+ await renderLoaded();
1287
+ await screen.findByTestId("message-queue-broker-health");
1288
+
1289
+ // The observed series are read for the gauge with data, and only for it.
1290
+ const observed: Array<AggregateRequest> = metricRequests().filter(
1291
+ (request: AggregateRequest): boolean => {
1292
+ return (
1293
+ request.aggregateBy.aggregationInterval === AggregationInterval.Total
1294
+ );
1295
+ },
1296
+ );
1297
+ expect(
1298
+ observed.map((request: AggregateRequest): unknown => {
1299
+ return [
1300
+ request.aggregateBy.query["name"],
1301
+ request.aggregateBy.groupByAttributeKeys,
1302
+ ];
1303
+ }),
1304
+ ).toEqual([["kafka.consumer_group.lag_sum", ["topic"]]]);
1305
+
1306
+ const links: Array<HTMLElement> = screen.getAllByTestId(
1307
+ "message-queue-create-monitor",
1308
+ );
1309
+ expect(
1310
+ links.map((element: HTMLElement): string | null => {
1311
+ return element.getAttribute("data-metric-id");
1312
+ }),
1313
+ ).toEqual(["kafka:kafka.consumer_group.lag_sum"]);
1314
+
1315
+ const href: string = within(links[0]!)
1316
+ .getByText("Create monitor")
1317
+ .closest("a")!
1318
+ .getAttribute("href")!;
1319
+ expect(
1320
+ href.startsWith(`/dashboard/${PROJECT_ID_STRING}/monitors/create?`),
1321
+ ).toBe(true);
1322
+ const search: URLSearchParams = new URLSearchParams(href.split("?")[1]);
1323
+ const queries: Array<SerializedMetricQuery> =
1324
+ MetricExplorerUrl.parseMetricQueriesParam(
1325
+ search.get("metricQueries") || "",
1326
+ );
1327
+ expect(queries).toEqual([
1328
+ expect.objectContaining({
1329
+ metricName: "kafka.consumer_group.lag_sum",
1330
+ attributes: { topic: "orders" },
1331
+ warningThreshold: 10000,
1332
+ }),
1333
+ ]);
1334
+ expect(search.get("monitorDescription")).toBe("Created from queue orders.");
1335
+ expect(
1336
+ screen.getByTestId("message-queue-create-monitor-hint"),
1337
+ ).toHaveTextContent(
1338
+ "Warning when any point in the last 10 minutes is above 10,000 messages",
1339
+ );
1340
+
1341
+ // The counter's chart has no link and no disabled button.
1342
+ const counterChart: HTMLElement = screen
1343
+ .getAllByTestId("message-queue-broker-metric-chart")
1344
+ .find((element: HTMLElement): boolean => {
1345
+ return (
1346
+ element.getAttribute("data-metric-id") ===
1347
+ "kafka:kafka.consumer_group.offset_sum"
1348
+ );
1349
+ })!;
1350
+ expect(within(counterChart).queryByText("Create monitor")).toBeNull();
1351
+ });
1352
+
1353
+ test("a gauge whose observed series are another queue's gets no link, and says why", async () => {
1354
+ scenario = {
1355
+ ...kafkaScenario(),
1356
+ observed: { "kafka.consumer_group.lag_sum": [{ topic: "payments" }] },
1357
+ };
1358
+ getItemMock.mockResolvedValue(queueRow());
1359
+
1360
+ await renderLoaded();
1361
+ await screen.findByTestId("message-queue-broker-health");
1362
+
1363
+ expect(screen.queryByTestId("message-queue-create-monitor")).toBeNull();
1364
+ const unavailable: HTMLElement = screen.getByTestId(
1365
+ "message-queue-create-monitor-unavailable",
1366
+ );
1367
+ expect(unavailable).toBeDisabled();
1368
+ expect(unavailable.getAttribute("title")).toContain("would watch nothing");
1369
+ /*
1370
+ * The reason is on screen, not only on hover: a disabled button takes no
1371
+ * focus and a title shows to a mouse alone.
1372
+ */
1373
+ const hint: HTMLElement = screen.getByTestId(
1374
+ "message-queue-create-monitor-hint",
1375
+ );
1376
+ expect(hint).toBeVisible();
1377
+ expect(hint).toHaveTextContent(BROKER_HEALTH_MONITOR_UNAVAILABLE_REASON);
1378
+ expect(unavailable).toHaveAccessibleDescription(
1379
+ BROKER_HEALTH_MONITOR_UNAVAILABLE_REASON,
1380
+ );
1381
+ });
1382
+
1383
+ test("an ActiveMQ queue reads ActiveMQ's metrics under its JMS family key", async () => {
1384
+ scenario = {
1385
+ ...kafkaScenario(),
1386
+ metrics: {
1387
+ "activemq.message.queue.size": (): Rows => {
1388
+ return perMinute(5, {
1389
+ attributes: {
1390
+ "activemq.broker.name": "localhost",
1391
+ "activemq.destination.type": "queue",
1392
+ },
1393
+ });
1394
+ },
1395
+ },
1396
+ observed: {
1397
+ "activemq.message.queue.size": [
1398
+ { "messaging.destination.name": "orders" },
1399
+ ],
1400
+ },
1401
+ };
1402
+ getItemMock.mockResolvedValue(
1403
+ queueRow({ queueIdentifier: "jms||orders", messagingSystem: "activemq" }),
1404
+ );
1405
+
1406
+ await renderLoaded();
1407
+ await screen.findByTestId("message-queue-broker-health");
1408
+
1409
+ for (const request of requests) {
1410
+ expect(
1411
+ (request.aggregateBy.query["entityKeys"] as Includes).values,
1412
+ ).toEqual([JMS_ORDERS_KEY]);
1413
+ }
1414
+ const names: Array<string> = metricRequests()
1415
+ .filter((request: AggregateRequest): boolean => {
1416
+ return (
1417
+ request.aggregateBy.aggregationInterval !== AggregationInterval.Total
1418
+ );
1419
+ })
1420
+ .map((request: AggregateRequest): string => {
1421
+ return String(request.aggregateBy.query["name"]);
1422
+ })
1423
+ .sort();
1424
+ expect(names).toEqual(
1425
+ getMessageQueueMetricsForSystem("activemq")
1426
+ .map((descriptor: MessageQueueMetricDescriptor): string => {
1427
+ return descriptor.metricName;
1428
+ })
1429
+ .sort(),
1430
+ );
1431
+ // Its monitor is matched on the JMS family identity.
1432
+ expect(screen.getByTestId("message-queue-create-monitor")).toHaveAttribute(
1433
+ "data-metric-id",
1434
+ "activemq:activemq.message.queue.size",
1435
+ );
1436
+ // The per-broker note of an open series set.
1437
+ expect(
1438
+ screen.getByTestId("message-queue-create-monitor-note"),
1439
+ ).toHaveTextContent("alerts on each series separately");
1440
+ });
1441
+
1442
+ test("a JMS queue reads no broker metric and shows the JMS guidance", async () => {
1443
+ getItemMock.mockResolvedValue(
1444
+ queueRow({ queueIdentifier: "jms||orders", messagingSystem: "jms" }),
1445
+ );
1446
+
1447
+ await renderLoaded();
1448
+
1449
+ expect(metricRequests()).toHaveLength(0);
1450
+ const guidance: HTMLElement = await screen.findByTestId(
1451
+ "message-queue-broker-health-guidance",
1452
+ );
1453
+ expect(guidance).toHaveAttribute("data-source-kind", "none");
1454
+ expect(guidance).toHaveTextContent("JMS is an API");
1455
+ expect(
1456
+ within(guidance)
1457
+ .getByText("JMS broker metrics in the Queues guide →")
1458
+ .closest("a"),
1459
+ ).toHaveAttribute("href", "/docs/telemetry/queues#jms");
1460
+ });
1461
+
1462
+ test("a Kafka queue with no broker metric yet: where they come from, and its section of the guide", async () => {
1463
+ scenario = { ...kafkaScenario(), metrics: {}, observed: {} };
1464
+ getItemMock.mockResolvedValue(queueRow());
1465
+
1466
+ await renderLoaded();
1467
+
1468
+ const guidance: HTMLElement = await screen.findByTestId(
1469
+ "message-queue-broker-health-guidance",
1470
+ );
1471
+ expect(guidance).toHaveAttribute("data-source-kind", "receiver");
1472
+ expect(guidance).toHaveTextContent(
1473
+ "No Apache Kafka broker metric has arrived for this queue yet.",
1474
+ );
1475
+ expect(guidance).toHaveTextContent(
1476
+ "OpenTelemetry Collector receiver: kafka_metrics",
1477
+ );
1478
+ const docs: HTMLElement = within(guidance)
1479
+ .getByText("Apache Kafka broker metrics in the Queues guide →")
1480
+ .closest("a")!;
1481
+ expect(docs).toHaveAttribute("href", "/docs/telemetry/queues#apache-kafka");
1482
+ expect(docs).toHaveAttribute("target", "_blank");
1483
+ // The Documentation tab has this queue's collector config.
1484
+ expect(
1485
+ within(guidance)
1486
+ .getByText("Set up broker metrics for this queue →")
1487
+ .closest("a"),
1488
+ ).toHaveAttribute(
1489
+ "href",
1490
+ `/dashboard/${PROJECT_ID_STRING}/queues/${MODEL_ID_STRING}/documentation`,
1491
+ );
1492
+ });
1493
+
1494
+ test("a Service Bus queue without a namespace: told where its broker metrics go, never offered their setup", async () => {
1495
+ scenario = { ...kafkaScenario(), metrics: {}, observed: {} };
1496
+ // Its spans came from the emulator: no namespace in its identity.
1497
+ getItemMock.mockResolvedValue(
1498
+ queueRow({
1499
+ queueIdentifier: "servicebus||orders",
1500
+ messagingSystem: "servicebus",
1501
+ brokerScope: "",
1502
+ brokerAddress: "localhost:5672",
1503
+ }),
1504
+ );
1505
+
1506
+ await renderLoaded();
1507
+
1508
+ const guidance: HTMLElement = await screen.findByTestId(
1509
+ "message-queue-broker-health-guidance",
1510
+ );
1511
+ expect(guidance).toHaveAttribute("data-source-kind", "cloud-monitoring");
1512
+ expect(guidance).toHaveTextContent(
1513
+ "Azure Service Bus broker metrics name the namespace they come from, and this queue has none",
1514
+ );
1515
+ expect(guidance).not.toHaveTextContent("has arrived");
1516
+ expect(
1517
+ within(guidance).queryByText("Set up broker metrics for this queue →"),
1518
+ ).toBeNull();
1519
+ expect(
1520
+ within(guidance).getByText("Open the Metrics explorer →").closest("a"),
1521
+ ).toHaveAttribute("href", `/dashboard/${PROJECT_ID_STRING}/metrics`);
1522
+ });
1523
+
1524
+ test("broker metrics arrived before but not in this range: says so, with when", async () => {
1525
+ scenario = { ...kafkaScenario(), metrics: {}, observed: {} };
1526
+ getItemMock.mockResolvedValue(
1527
+ queueRow({ brokerMetricsLastSeenAt: minutesAgo(600) }),
1528
+ );
1529
+
1530
+ await renderLoaded();
1531
+
1532
+ expect(
1533
+ await screen.findByTestId("message-queue-broker-health-no-data"),
1534
+ ).toBeInTheDocument();
1535
+ expect(screen.getByText(BROKER_HEALTH_NO_DATA_TITLE)).toBeInTheDocument();
1536
+ expect(
1537
+ screen.queryByTestId("message-queue-broker-health-guidance"),
1538
+ ).toBeNull();
1539
+ });
1540
+ });
1541
+
1542
+ /*
1543
+ * The observed series of the broker metrics only build the Create monitor
1544
+ * links. Re-read on every auto-refresh tick they cost a request per
1545
+ * monitorable gauge each time, for attributes that change when a partition
1546
+ * or a consumer group appears, not every 30 seconds.
1547
+ */
1548
+ describe("the Create monitor links' observed series", () => {
1549
+ // The observed-series reads: one per gauge, over the whole window.
1550
+ function observedReads(): number {
1551
+ return metricRequests().filter((request: AggregateRequest): boolean => {
1552
+ return (
1553
+ request.aggregateBy.aggregationInterval === AggregationInterval.Total
1554
+ );
1555
+ }).length;
1556
+ }
1557
+
1558
+ async function settle(): Promise<void> {
1559
+ for (let turn: number = 0; turn < 5; turn++) {
1560
+ await act(async () => {
1561
+ await new Promise((resolve: (value: unknown) => void): void => {
1562
+ setTimeout(resolve, 10);
1563
+ });
1564
+ });
1565
+ }
1566
+ }
1567
+
1568
+ test("an auto-refresh tick reuses them; a new range reads them again", async () => {
1569
+ getItemMock.mockResolvedValue(queueRow());
1570
+
1571
+ await renderLoaded();
1572
+ await screen.findByTestId("message-queue-create-monitor");
1573
+ await settle();
1574
+ const first: number = observedReads();
1575
+ expect(first).toBeGreaterThan(0);
1576
+
1577
+ // A tick reloads every chart, but none of the observed series.
1578
+ const loaded: number = requests.length;
1579
+ await act(async () => {
1580
+ mockAutoRefreshTick!();
1581
+ await Promise.resolve();
1582
+ });
1583
+ await waitFor(() => {
1584
+ expect(requests.length).toBeGreaterThan(loaded);
1585
+ });
1586
+ await settle();
1587
+ expect(observedReads()).toBe(first);
1588
+ // The link is still built from them.
1589
+ expect(
1590
+ screen.getByTestId("message-queue-create-monitor"),
1591
+ ).toBeInTheDocument();
1592
+
1593
+ // A range the reader picks reads them for that range.
1594
+ fireEvent.click(screen.getByText("Pick past day"));
1595
+ await waitFor(() => {
1596
+ expect(observedReads()).toBeGreaterThan(first);
1597
+ });
1598
+ });
1599
+
1600
+ test("the cache keeps a metric's series per queue, range and metric, for a while, and never an empty answer", () => {
1601
+ const cache: MessageQueueObservedSeriesCache =
1602
+ new MessageQueueObservedSeriesCache();
1603
+ const window: {
1604
+ projectId: string;
1605
+ keys: Array<string>;
1606
+ start: Date;
1607
+ end: Date;
1608
+ } = {
1609
+ projectId: PROJECT_ID_STRING,
1610
+ keys: [KAFKA_ORDERS_KEY],
1611
+ start: new Date(NOW - 60 * 60 * 1000),
1612
+ end: new Date(NOW),
1613
+ };
1614
+ const key: string = MessageQueueObservedSeriesCache.keyOf({
1615
+ window,
1616
+ rangeKey: '{"range":"Past 1 Hour"}',
1617
+ metricName: "kafka.consumer_group.lag_sum",
1618
+ });
1619
+
1620
+ cache.set(key, [{ topic: "orders" }], NOW);
1621
+ expect(cache.get(key, NOW + 30 * 1000)).toEqual([{ topic: "orders" }]);
1622
+ // The window a relative range moves each tick is not part of the key.
1623
+ expect(
1624
+ MessageQueueObservedSeriesCache.keyOf({
1625
+ window: {
1626
+ ...window,
1627
+ start: new Date(window.start.getTime() + 30 * 1000),
1628
+ end: new Date(window.end.getTime() + 30 * 1000),
1629
+ },
1630
+ rangeKey: '{"range":"Past 1 Hour"}',
1631
+ metricName: "kafka.consumer_group.lag_sum",
1632
+ }),
1633
+ ).toBe(key);
1634
+ // Another range, metric or queue is another entry.
1635
+ for (const other of [
1636
+ MessageQueueObservedSeriesCache.keyOf({
1637
+ window,
1638
+ rangeKey: '{"range":"Past 1 Day"}',
1639
+ metricName: "kafka.consumer_group.lag_sum",
1640
+ }),
1641
+ MessageQueueObservedSeriesCache.keyOf({
1642
+ window,
1643
+ rangeKey: '{"range":"Past 1 Hour"}',
1644
+ metricName: "kafka.consumer_group.lag",
1645
+ }),
1646
+ MessageQueueObservedSeriesCache.keyOf({
1647
+ window: { ...window, keys: [JMS_ORDERS_KEY] },
1648
+ rangeKey: '{"range":"Past 1 Hour"}',
1649
+ metricName: "kafka.consumer_group.lag_sum",
1650
+ }),
1651
+ ]) {
1652
+ expect(other).not.toBe(key);
1653
+ expect(cache.get(other, NOW)).toBeNull();
1654
+ }
1655
+ // Expired.
1656
+ expect(
1657
+ cache.get(key, NOW + MESSAGE_QUEUE_OBSERVED_SERIES_TTL_MS),
1658
+ ).toBeNull();
1659
+ // An empty answer (none yet, or a failed read) is read again next time.
1660
+ cache.set(key, [], NOW);
1661
+ expect(cache.get(key, NOW)).toBeNull();
1662
+ });
1663
+ });
1664
+
1665
+ describe("the telemetry load", () => {
1666
+ test("a slow wider range never overwrites the newer one", async () => {
1667
+ getItemMock.mockResolvedValue(queueRow());
1668
+ // The hour's requests wait; the day's are answered as they come.
1669
+ parkWhen = (request: AggregateRequest): boolean => {
1670
+ return !isDayWindow(request);
1671
+ };
1672
+
1673
+ await renderOverview();
1674
+ await waitFor(() => {
1675
+ expect(parked.length).toBeGreaterThan(0);
1676
+ });
1677
+
1678
+ // The reader picks a day while the hour is still loading: the day lands.
1679
+ fireEvent.click(screen.getByText("Pick past day"));
1680
+ await waitFor(() => {
1681
+ expect(within(tile("Published")).getByText("600")).toBeInTheDocument();
1682
+ });
1683
+ expect(
1684
+ requests.some((request: AggregateRequest): boolean => {
1685
+ return isDayWindow(request);
1686
+ }),
1687
+ ).toBe(true);
1688
+
1689
+ // Then the hour, late — every request of it: it must not replace the day.
1690
+ parkWhen = null;
1691
+ await releaseParked((request: AggregateRequest): boolean => {
1692
+ return !isDayWindow(request);
1693
+ });
1694
+ await act(async () => {
1695
+ await new Promise((resolve: (value: unknown) => void): void => {
1696
+ setTimeout(resolve, 50);
1697
+ });
1698
+ });
1699
+ await act(async () => {
1700
+ await Promise.resolve();
1701
+ });
1702
+ expect(within(tile("Published")).getByText("600")).toBeInTheDocument();
1703
+ expect(within(tile("Published")).queryByText("120")).toBeNull();
1704
+ });
1705
+
1706
+ test("an auto-refresh tick while a load runs leaves it alone; Refresh replaces it", async () => {
1707
+ getItemMock.mockResolvedValue(queueRow());
1708
+ parkWhen = (): boolean => {
1709
+ return true;
1710
+ };
1711
+
1712
+ await renderOverview();
1713
+ await waitFor(() => {
1714
+ expect(parked.length).toBeGreaterThan(0);
1715
+ });
1716
+ const first: number = requests.length;
1717
+
1718
+ // A tick reloads the row, but not the telemetry still loading.
1719
+ await act(async () => {
1720
+ mockAutoRefreshTick!();
1721
+ await Promise.resolve();
1722
+ });
1723
+ expect(getItemMock).toHaveBeenCalledTimes(2);
1724
+ expect(requests.length).toBe(first);
1725
+
1726
+ // Refresh is the reader's own: it replaces the running load.
1727
+ fireEvent.click(screen.getByText("Refresh now"));
1728
+ await waitFor(() => {
1729
+ expect(requests.length).toBeGreaterThan(first);
1730
+ });
1731
+
1732
+ // Once everything has landed, the next tick reloads the telemetry.
1733
+ parkWhen = null;
1734
+ await releaseParked((): boolean => {
1735
+ return true;
1736
+ });
1737
+ await waitFor(() => {
1738
+ expect(within(tile("Published")).getByText("120")).toBeInTheDocument();
1739
+ });
1740
+ const settled: number = requests.length;
1741
+ await act(async () => {
1742
+ mockAutoRefreshTick!();
1743
+ await Promise.resolve();
1744
+ });
1745
+ await waitFor(() => {
1746
+ expect(requests.length).toBeGreaterThan(settled);
1747
+ });
1748
+ });
1749
+
1750
+ test("a reload that changes nothing the scope depends on starts no new load", async () => {
1751
+ getItemMock.mockResolvedValue(queueRow());
1752
+
1753
+ await renderLoaded();
1754
+ const loaded: number = requests.length;
1755
+
1756
+ // A background reload re-stamps lastSeenAt: the same scope.
1757
+ getItemMock.mockResolvedValue(queueRow({ lastSeenAt: minutesAgo(1) }));
1758
+ await act(async () => {
1759
+ mockAutoRefreshTick!();
1760
+ await Promise.resolve();
1761
+ });
1762
+ // The tick itself reloads the telemetry once, as a refresh…
1763
+ await waitFor(() => {
1764
+ expect(requests.length).toBeGreaterThan(loaded);
1765
+ });
1766
+ const afterTick: number = requests.length;
1767
+ // …but the new row object does not start a second one.
1768
+ await act(async () => {
1769
+ await Promise.resolve();
1770
+ });
1771
+ expect(requests.length).toBe(afterTick);
1772
+ });
1773
+ });