@oneuptime/common 14.0.3 → 14.0.5

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 (617) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
  3. package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
  4. package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
  5. package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
  6. package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
  7. package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
  8. package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
  9. package/Models/DatabaseModels/Incident.ts +55 -0
  10. package/Models/DatabaseModels/Index.ts +16 -0
  11. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
  12. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  13. package/Models/DatabaseModels/TelemetryException.ts +2 -0
  14. package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  18. package/Server/Middleware/MultipartFormData.ts +1 -1
  19. package/Server/Middleware/TelemetryIngest.ts +15 -8
  20. package/Server/Services/AlertReminderRuleService.ts +100 -24
  21. package/Server/Services/DatabaseServerEndpointService.ts +895 -0
  22. package/Server/Services/DatabaseServerFeedService.ts +128 -0
  23. package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
  24. package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
  25. package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
  26. package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
  27. package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
  28. package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
  29. package/Server/Services/DatabaseServerService.ts +4546 -0
  30. package/Server/Services/DatabaseService.ts +14 -8
  31. package/Server/Services/DockerResourceService.ts +43 -2
  32. package/Server/Services/IncidentReminderRuleService.ts +103 -23
  33. package/Server/Services/Index.ts +16 -0
  34. package/Server/Services/InventoryItemService.ts +113 -5
  35. package/Server/Services/IoTDeviceService.ts +145 -37
  36. package/Server/Services/OpenTelemetryIngestService.ts +19 -0
  37. package/Server/Services/PodmanResourceService.ts +43 -2
  38. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
  39. package/Server/Services/ServiceService.ts +1 -1
  40. package/Server/Services/TelemetryUsageBillingService.ts +29 -2
  41. package/Server/Services/UserProjectSsoConsentService.ts +74 -0
  42. package/Server/Services/UserService.ts +14 -1
  43. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
  44. package/Server/Types/Database/QueryHelper.ts +30 -1
  45. package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
  46. package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
  47. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
  48. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  49. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
  50. package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
  51. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
  52. package/Server/Utils/Monitor/MonitorResource.ts +47 -11
  53. package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
  54. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
  55. package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
  56. package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
  57. package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
  58. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  59. package/Server/Utils/StartServer.ts +26 -6
  60. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  61. package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
  62. package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
  63. package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
  64. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
  65. package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
  66. package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
  67. package/Server/Utils/Telemetry/Telemetry.ts +16 -0
  68. package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
  69. package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
  70. package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
  71. package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
  72. package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
  73. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
  74. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
  75. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
  76. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
  77. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
  78. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
  79. package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
  80. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
  81. package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
  82. package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
  83. package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
  84. package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
  85. package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
  86. package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
  87. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
  88. package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
  89. package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
  90. package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
  91. package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
  92. package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
  93. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
  94. package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
  95. package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
  96. package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
  97. package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
  98. package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
  99. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
  100. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
  101. package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
  102. package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
  103. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
  104. package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
  105. package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
  106. package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
  107. package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
  108. package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
  109. package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
  110. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
  111. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
  112. package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
  113. package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
  114. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
  115. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  116. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
  117. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
  118. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
  119. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
  120. package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
  121. package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
  122. package/Tests/ClassStrings.ts +178 -0
  123. package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
  124. package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
  125. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
  126. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
  127. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  128. package/Tests/RenderedMarkup.ts +518 -0
  129. package/Tests/ResponsiveVisibility.test.ts +490 -4
  130. package/Tests/ResponsiveVisibility.ts +505 -52
  131. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  132. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
  133. package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
  134. package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
  135. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
  136. package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
  137. package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
  138. package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
  139. package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
  140. package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
  141. package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
  142. package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
  143. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
  144. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
  145. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
  146. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
  147. package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
  148. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
  149. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
  150. package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
  151. package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
  152. package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
  153. package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
  154. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
  155. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
  156. package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
  157. package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
  158. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
  159. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  160. package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
  161. package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
  162. package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
  163. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
  164. package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
  165. package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
  166. package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
  167. package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
  168. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
  169. package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
  170. package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
  171. package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
  172. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
  173. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
  174. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
  175. package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
  176. package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
  177. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
  178. package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
  179. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
  180. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
  181. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
  182. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
  183. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
  184. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
  185. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
  186. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
  187. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
  188. package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
  189. package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
  190. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
  191. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
  192. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
  193. package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
  194. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
  195. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
  196. package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
  197. package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
  198. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
  199. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
  200. package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
  201. package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
  202. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
  203. package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
  204. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
  205. package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
  206. package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
  207. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
  208. package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
  209. package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
  210. package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
  211. package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
  212. package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
  213. package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
  214. package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
  215. package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
  216. package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
  217. package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
  218. package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
  219. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
  220. package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
  221. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
  222. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
  223. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
  224. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
  225. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
  226. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
  227. package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
  228. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
  229. package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
  230. package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
  231. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
  232. package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
  233. package/Tests/UI/Components/Card.test.tsx +93 -2
  234. package/Tests/UI/Components/FeedOptions.test.ts +6 -0
  235. package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
  236. package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
  237. package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
  238. package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
  239. package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
  240. package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
  241. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  242. package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
  243. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
  244. package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
  245. package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
  246. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
  247. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
  248. package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
  249. package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
  250. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +70 -0
  251. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +21 -0
  252. package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
  253. package/Tests/UI/Utils/Navigation.test.ts +102 -0
  254. package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
  255. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  256. package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
  257. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
  258. package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
  259. package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
  260. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
  261. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
  262. package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
  263. package/Tests/Utils/ValueFormatter.test.ts +50 -0
  264. package/Tests/tsconfig.json +16 -0
  265. package/Types/AI/AIResourceContext.ts +7 -0
  266. package/Types/Dashboard/DashboardTemplates.ts +2 -1
  267. package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
  268. package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
  269. package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
  270. package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
  271. package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
  272. package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
  273. package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
  274. package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
  275. package/Types/Email/EmailTemplateType.ts +1 -0
  276. package/Types/Monitor/CephAlertTemplates.ts +18 -0
  277. package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
  278. package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
  279. package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
  280. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
  281. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
  282. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
  283. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
  284. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
  285. package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
  286. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
  287. package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
  288. package/Types/Permission.ts +267 -0
  289. package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
  290. package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
  291. package/Types/Rules/RuleRun.ts +12 -0
  292. package/Types/Telemetry/EntityType.ts +23 -0
  293. package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
  294. package/Types/Telemetry/ServiceType.ts +8 -0
  295. package/UI/Components/Banner/Banner.tsx +1 -1
  296. package/UI/Components/Card/Card.tsx +1 -1
  297. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
  298. package/UI/Components/Charts/Line/LineChart.tsx +7 -0
  299. package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
  300. package/UI/Components/Date/StartAndEndDate.tsx +5 -5
  301. package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
  302. package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
  303. package/UI/Components/Forms/BasicForm.tsx +1 -1
  304. package/UI/Components/Header/Header.tsx +1 -1
  305. package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
  306. package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
  307. package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
  308. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
  309. package/UI/Components/Modal/ModalFooter.tsx +1 -1
  310. package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
  311. package/UI/Components/ModelTable/BaseModelTable.tsx +4 -4
  312. package/UI/Components/Navbar/NavBar.tsx +9 -2
  313. package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
  314. package/UI/Components/Page/Page.tsx +1 -1
  315. package/UI/Components/Pagination/Pagination.tsx +3 -3
  316. package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
  317. package/UI/Components/SideMenu/SideMenu.tsx +4 -1
  318. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
  319. package/UI/Components/StatusPage/ResourceList.tsx +6 -6
  320. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
  321. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
  322. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
  323. package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
  324. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  325. package/UI/Styles/Theme.css +8 -1
  326. package/UI/Utils/Navigation.ts +89 -42
  327. package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
  328. package/UI/Utils/TelemetryService.ts +4 -3
  329. package/Utils/LabelRuleImportExport.ts +2 -0
  330. package/Utils/MetricUnitUtil.ts +103 -11
  331. package/Utils/Monitor/MetricValueFormatter.ts +66 -13
  332. package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
  333. package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
  334. package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
  335. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
  336. package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
  337. package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
  338. package/Utils/Telemetry/EntityKey.ts +176 -0
  339. package/Utils/ValueFormatter.ts +16 -0
  340. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  341. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  342. package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
  343. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
  344. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
  345. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
  346. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
  347. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
  348. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
  349. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
  350. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
  351. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
  352. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
  353. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
  354. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
  355. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
  356. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  357. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  358. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  359. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  360. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  361. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  362. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  363. package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
  364. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  365. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
  366. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
  367. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
  368. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
  369. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
  370. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js.map +1 -0
  371. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  372. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  373. package/build/dist/Server/Middleware/MultipartFormData.js +1 -1
  374. package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
  375. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  376. package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
  377. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
  378. package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
  379. package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
  380. package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
  381. package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
  382. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
  383. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
  384. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
  385. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
  386. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
  387. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
  388. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
  389. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
  390. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
  391. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
  392. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
  393. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
  394. package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
  395. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
  396. package/build/dist/Server/Services/DatabaseService.js +8 -4
  397. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  398. package/build/dist/Server/Services/DockerResourceService.js +30 -3
  399. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  400. package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
  401. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
  402. package/build/dist/Server/Services/Index.js +16 -0
  403. package/build/dist/Server/Services/Index.js.map +1 -1
  404. package/build/dist/Server/Services/InventoryItemService.js +88 -5
  405. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  406. package/build/dist/Server/Services/IoTDeviceService.js +111 -24
  407. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  408. package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
  409. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  410. package/build/dist/Server/Services/PodmanResourceService.js +30 -3
  411. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  412. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
  413. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
  414. package/build/dist/Server/Services/ServiceService.js +1 -1
  415. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  416. package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
  417. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  418. package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
  419. package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
  420. package/build/dist/Server/Services/UserService.js +13 -1
  421. package/build/dist/Server/Services/UserService.js.map +1 -1
  422. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
  423. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  424. package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
  425. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  426. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
  427. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  428. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
  429. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
  430. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
  431. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  432. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
  433. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  434. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
  435. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  436. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
  437. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  438. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
  439. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  440. package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
  441. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  442. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
  443. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
  444. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
  445. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
  446. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
  447. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
  448. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
  449. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  450. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
  451. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  452. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  453. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  454. package/build/dist/Server/Utils/StartServer.js +20 -4
  455. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  456. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  457. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  458. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
  459. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
  460. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
  461. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  462. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
  463. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
  464. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
  465. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  466. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
  467. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
  468. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
  469. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
  470. package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
  471. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  472. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
  473. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  474. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
  475. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
  476. package/build/dist/Types/AI/AIResourceContext.js +7 -0
  477. package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
  478. package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
  479. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  480. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
  481. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
  482. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
  483. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
  484. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
  485. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
  486. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
  487. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
  488. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
  489. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
  490. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
  491. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
  492. package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
  493. package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
  494. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
  495. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
  496. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  497. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  498. package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
  499. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
  500. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
  501. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
  502. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
  503. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  504. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
  505. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
  506. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
  507. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  508. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
  509. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  510. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
  511. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  512. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
  513. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
  514. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
  515. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
  516. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
  517. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
  518. package/build/dist/Types/Permission.js +234 -0
  519. package/build/dist/Types/Permission.js.map +1 -1
  520. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
  521. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  522. package/build/dist/Types/Rules/RuleRun.js +4 -0
  523. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  524. package/build/dist/Types/Telemetry/EntityType.js +23 -0
  525. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  526. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
  527. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
  528. package/build/dist/Types/Telemetry/ServiceType.js +8 -0
  529. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  530. package/build/dist/UI/Components/Banner/Banner.js +1 -1
  531. package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
  532. package/build/dist/UI/Components/Card/Card.js +1 -1
  533. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  534. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  535. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  536. package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
  537. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  538. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
  539. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
  540. package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
  541. package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
  542. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
  543. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
  544. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
  545. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
  546. package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
  547. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  548. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
  549. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  550. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
  551. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
  552. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
  553. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
  554. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
  555. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  556. package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
  557. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  558. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  559. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  560. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +4 -4
  561. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  562. package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
  563. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  564. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
  565. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  566. package/build/dist/UI/Components/Page/Page.js +1 -1
  567. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  568. package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
  569. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  570. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
  571. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
  572. package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
  573. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  574. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
  575. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
  576. package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
  577. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  578. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
  579. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  580. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
  581. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
  582. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
  583. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
  584. package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
  585. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  586. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  587. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  588. package/build/dist/UI/Utils/Navigation.js +66 -33
  589. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  590. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
  591. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
  592. package/build/dist/UI/Utils/TelemetryService.js +3 -2
  593. package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
  594. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  595. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  596. package/build/dist/Utils/MetricUnitUtil.js +77 -13
  597. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  598. package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
  599. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
  600. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
  601. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
  602. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
  603. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
  604. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
  605. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
  606. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
  607. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  608. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
  609. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
  610. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
  611. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
  612. package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
  613. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  614. package/build/dist/Utils/ValueFormatter.js +15 -0
  615. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  616. package/package.json +1 -1
  617. package/tsconfig.tests.json +7 -5
@@ -0,0 +1,1486 @@
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
+ cleanup,
12
+ fireEvent,
13
+ render,
14
+ screen,
15
+ waitFor,
16
+ within,
17
+ } from "@testing-library/react";
18
+ import * as React from "react";
19
+ import { MemoryRouter } from "react-router-dom";
20
+ import getJestMockFunction, { MockFunction } from "../../MockType";
21
+ import { PROJECT_ID, goTo } from "./SideMenuHarness";
22
+
23
+ /*
24
+ * The Databases components other pages render: the chip for the
25
+ * databaseServers relation, the "no telemetry scope" banner, the calling
26
+ * services table and the engine-metrics section of the Overview. Rendered
27
+ * for real (charts replaced by a marker) on a project URL so every link is
28
+ * a concrete, project-scoped href.
29
+ */
30
+
31
+ const chartCardMock: MockFunction = getJestMockFunction();
32
+ const aggregateMock: MockFunction = getJestMockFunction();
33
+ const getListAnalyticsMock: MockFunction = getJestMockFunction();
34
+
35
+ jest.mock(
36
+ "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/ChartCard",
37
+ () => {
38
+ return {
39
+ __esModule: true,
40
+ default: (props: { title: string }) => {
41
+ chartCardMock(props);
42
+ return <div data-testid="chart-card">{props.title}</div>;
43
+ },
44
+ };
45
+ },
46
+ );
47
+
48
+ // The arrow wrapper is load bearing: jest.mock is hoisted above the mock.
49
+ jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
50
+ return {
51
+ __esModule: true,
52
+ default: {
53
+ aggregate: (...args: Array<unknown>) => {
54
+ return aggregateMock(...args);
55
+ },
56
+ getList: (...args: Array<unknown>) => {
57
+ return getListAnalyticsMock(...args);
58
+ },
59
+ },
60
+ };
61
+ });
62
+
63
+ jest.mock("../../../UI/Components/Modal/Modal", () => {
64
+ return {
65
+ __esModule: true,
66
+ ModalWidth: { Normal: 0, Medium: 1, Large: 2 },
67
+ default: (props: {
68
+ title: string;
69
+ description?: string;
70
+ children?: React.ReactNode;
71
+ onClose?: () => void;
72
+ }) => {
73
+ return (
74
+ <section role="dialog" aria-label={props.title}>
75
+ <p>{props.description}</p>
76
+ {props.children}
77
+ <button onClick={props.onClose}>Close</button>
78
+ </section>
79
+ );
80
+ },
81
+ };
82
+ });
83
+
84
+ jest.mock(
85
+ "../../../UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker",
86
+ () => {
87
+ return {
88
+ __esModule: true,
89
+ default: (props: { value?: { range?: string } }) => {
90
+ return (
91
+ <div data-testid="time-range-picker">{props.value?.range || ""}</div>
92
+ );
93
+ },
94
+ };
95
+ },
96
+ );
97
+
98
+ jest.mock("../../../UI/Components/ComponentLoader/ComponentLoader", () => {
99
+ return {
100
+ __esModule: true,
101
+ default: () => {
102
+ return <div data-testid="component-loader" />;
103
+ },
104
+ };
105
+ });
106
+
107
+ jest.mock("../../../UI/Components/Card/Card", () => {
108
+ return {
109
+ __esModule: true,
110
+ default: (props: {
111
+ title?: string;
112
+ description?: string;
113
+ children?: React.ReactNode;
114
+ }) => {
115
+ return (
116
+ <section data-testid="card" aria-label={props.title}>
117
+ <p>{props.description}</p>
118
+ {props.children}
119
+ </section>
120
+ );
121
+ },
122
+ };
123
+ });
124
+
125
+ import DatabaseServerElement from "../../../../App/FeatureSet/Dashboard/src/Components/DatabaseServer/DatabaseServerElement";
126
+ import DatabaseServersElement from "../../../../App/FeatureSet/Dashboard/src/Components/DatabaseServer/DatabaseServersElement";
127
+ import DatabaseServerUnscopedBanner, {
128
+ DATABASE_SERVER_ID_ONLY_TITLE,
129
+ getDatabaseServerScopeBannerDescription,
130
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/DatabaseServer/DatabaseServerUnscopedBanner";
131
+ import { isDatabaseServerScopedByIdOnly } from "../../../../App/FeatureSet/Dashboard/src/Pages/Database/Utils/DatabaseTelemetryScope";
132
+ import DatabaseCallingServicesCard, {
133
+ getCallingServicesFooter,
134
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/DatabaseServer/DatabaseCallingServicesCard";
135
+ import DatabaseEngineMetricsSection, {
136
+ ENGINE_METRICS_DISCONNECTED_TITLE,
137
+ ENGINE_METRICS_NO_CURATED_OVERVIEW_TITLE,
138
+ ENGINE_METRICS_NO_DATA_TITLE,
139
+ ENGINE_METRICS_NOT_CONNECTED_TITLE,
140
+ EngineMetricsGuidance,
141
+ getEngineMetricsGuidance,
142
+ getEngineMetricsMissingDescription,
143
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/DatabaseServer/DatabaseEngineMetricsSection";
144
+ import DatabaseMetricChartModal from "../../../../App/FeatureSet/Dashboard/src/Components/DatabaseServer/DatabaseMetricChartModal";
145
+ import DatabaseRuntimeSection from "../../../../App/FeatureSet/Dashboard/src/Components/DatabaseServer/DatabaseRuntimeSection";
146
+ import {
147
+ DatabaseRunsOnLink,
148
+ DatabaseWorkloadLink,
149
+ getDatabaseRunsOnRoute,
150
+ getDatabaseWorkloadRoute,
151
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/DatabaseServer/DatabaseRunsOnLink";
152
+ import {
153
+ DatabaseEngineMetricsStatus,
154
+ DatabaseRuntimePlatform,
155
+ formatDatabaseFraction,
156
+ formatDatabaseMetricValue,
157
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Database/Utils/DatabaseServerPresentation";
158
+ import {
159
+ DatabaseEngineMetricResult,
160
+ toEngineMetricResult,
161
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Database/Utils/DatabaseServerTelemetryQueries";
162
+ import {
163
+ getDatabaseAgentCollectedMetricsText,
164
+ getDatabaseAgentEngine,
165
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Database/Utils/DocumentationMarkdown";
166
+ import DatabaseServer from "../../../Models/DatabaseModels/DatabaseServer";
167
+ import {
168
+ DatabaseServerMetricDefinition,
169
+ getDatabaseServerMetrics,
170
+ } from "../../../Types/DatabaseServer/DatabaseServerMetricCatalog";
171
+ import AggregationType from "../../../Types/BaseDatabase/AggregationType";
172
+ import Includes from "../../../Types/BaseDatabase/Includes";
173
+ import ObjectID from "../../../Types/ObjectID";
174
+ import OneUptimeDate from "../../../Types/Date";
175
+ import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
176
+ import TimeRange from "../../../Types/Time/TimeRange";
177
+
178
+ const MODEL_ID: ObjectID = new ObjectID("84858d6c-1111-4aaa-8bbb-000000000001");
179
+ const SERVICE_ID: string = "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d";
180
+ const START: Date = new Date("2026-09-24T10:00:00.000Z");
181
+
182
+ function at(minute: number): Date {
183
+ return new Date(START.getTime() + minute * 60 * 1000);
184
+ }
185
+
186
+ function inRouter(element: React.ReactElement): React.ReactElement {
187
+ return <MemoryRouter>{element}</MemoryRouter>;
188
+ }
189
+
190
+ function databasePath(suffix: string = ""): string {
191
+ return `/dashboard/${PROJECT_ID}/databases/${MODEL_ID.toString()}${suffix}`;
192
+ }
193
+
194
+ beforeEach(() => {
195
+ chartCardMock.mockReset();
196
+ aggregateMock.mockReset();
197
+ getListAnalyticsMock.mockReset();
198
+ goTo(`/dashboard/${PROJECT_ID}/databases`);
199
+ });
200
+
201
+ afterEach(() => {
202
+ cleanup();
203
+ });
204
+
205
+ describe("DatabaseServerElement", () => {
206
+ test("links to the database's page in the current project", () => {
207
+ const database: DatabaseServer = new DatabaseServer();
208
+ database._id = MODEL_ID.toString();
209
+ database.name = "PostgreSQL db.prod.internal:5432";
210
+
211
+ render(inRouter(<DatabaseServerElement databaseServer={database} />));
212
+
213
+ const link: HTMLElement = screen.getByRole("link", {
214
+ name: /PostgreSQL db\.prod\.internal:5432/,
215
+ });
216
+ expect(link).toHaveAttribute("href", databasePath());
217
+ });
218
+
219
+ test("without an id it renders the plain name", () => {
220
+ const database: DatabaseServer = new DatabaseServer();
221
+ database.name = "orders-db";
222
+
223
+ render(inRouter(<DatabaseServerElement databaseServer={database} />));
224
+
225
+ expect(screen.getByText("orders-db")).toBeInTheDocument();
226
+ expect(screen.queryByRole("link")).not.toBeInTheDocument();
227
+ });
228
+
229
+ test("the list renders one chip per database and an empty message", () => {
230
+ const first: DatabaseServer = new DatabaseServer();
231
+ first._id = MODEL_ID.toString();
232
+ first.name = "orders-db";
233
+
234
+ const { unmount } = render(
235
+ inRouter(<DatabaseServersElement databaseServers={[first]} />),
236
+ );
237
+ expect(screen.getByRole("link", { name: /orders-db/ })).toBeVisible();
238
+ unmount();
239
+
240
+ render(inRouter(<DatabaseServersElement databaseServers={[]} />));
241
+ expect(screen.getByText("No databases.")).toBeInTheDocument();
242
+ });
243
+ });
244
+
245
+ describe("DatabaseServerUnscopedBanner", () => {
246
+ test("explains the missing scope and offers both ways out", () => {
247
+ render(
248
+ inRouter(
249
+ <DatabaseServerUnscopedBanner modelId={MODEL_ID} signal="logs" />,
250
+ ),
251
+ );
252
+
253
+ const card: HTMLElement = screen.getByRole("region", {
254
+ name: "No telemetry scope yet",
255
+ });
256
+ expect(card).toHaveTextContent("no logs can be attributed to it");
257
+ expect(
258
+ within(card).getByRole("link", { name: /Add an endpoint/ }),
259
+ ).toHaveAttribute("href", databasePath("/endpoints"));
260
+ expect(
261
+ within(card).getByRole("link", { name: /Connect the Database Agent/ }),
262
+ ).toHaveAttribute("href", databasePath("/documentation"));
263
+ });
264
+
265
+ test("defaults the signal to telemetry", () => {
266
+ render(inRouter(<DatabaseServerUnscopedBanner modelId={MODEL_ID} />));
267
+
268
+ expect(
269
+ screen.getByRole("region", { name: "No telemetry scope yet" }),
270
+ ).toHaveTextContent("no telemetry can be attributed to it");
271
+ });
272
+
273
+ test("the 'id only' hint says only data sent with its id shows, and how to get more", () => {
274
+ render(
275
+ inRouter(
276
+ <DatabaseServerUnscopedBanner
277
+ modelId={MODEL_ID}
278
+ signal="metrics"
279
+ variant="id-only"
280
+ />,
281
+ ),
282
+ );
283
+
284
+ const card: HTMLElement = screen.getByRole("region", {
285
+ name: DATABASE_SERVER_ID_ONLY_TITLE,
286
+ });
287
+ expect(card).toHaveTextContent(
288
+ "what shows here is only the metrics sent with its id",
289
+ );
290
+ expect(card).toHaveTextContent("DATABASE_SERVER_ID");
291
+ expect(card).toHaveTextContent("oneuptime.database.server.id.");
292
+ // Never claims nothing can be attributed: the row key is a real scope.
293
+ expect(card).not.toHaveTextContent("can be attributed");
294
+ expect(
295
+ screen.queryByRole("region", { name: "No telemetry scope yet" }),
296
+ ).not.toBeInTheDocument();
297
+ expect(
298
+ within(card).getByTestId("database-server-unscoped-banner"),
299
+ ).toHaveAttribute("data-variant", "id-only");
300
+ expect(
301
+ within(card).getByRole("link", { name: /Add an endpoint/ }),
302
+ ).toHaveAttribute("href", databasePath("/endpoints"));
303
+ expect(
304
+ within(card).getByRole("link", { name: /Connect the Database Agent/ }),
305
+ ).toHaveAttribute("href", databasePath("/documentation"));
306
+ });
307
+
308
+ test("the description for each variant", () => {
309
+ expect(getDatabaseServerScopeBannerDescription("unscoped", "logs")).toBe(
310
+ "This database has no endpoint and no Kubernetes or container members yet, so no logs can be attributed to it.",
311
+ );
312
+ expect(getDatabaseServerScopeBannerDescription("id-only")).toBe(
313
+ "This database has no endpoint and no Kubernetes or container members yet, so what shows here is only the telemetry sent with its id. The queries your applications send it are matched by endpoint.",
314
+ );
315
+ });
316
+ });
317
+
318
+ /*
319
+ * A loaded row always has its row key, so "unscoped" only happens without a
320
+ * project; "id only" is a row whose row key is its whole scope.
321
+ */
322
+ describe("isDatabaseServerScopedByIdOnly", () => {
323
+ const PROJECT: string = "10000000-0000-4000-8000-000000000001";
324
+
325
+ test("a row with no parseable endpoint and no members is scoped by its id alone", () => {
326
+ expect(
327
+ isDatabaseServerScopedByIdOnly({
328
+ projectId: PROJECT,
329
+ id: MODEL_ID,
330
+ endpoints: ["localhost:5432", "not a host", { endpoint: null }],
331
+ dbSystem: "postgresql",
332
+ memberEntityKeys: {},
333
+ }),
334
+ ).toBe(true);
335
+ expect(
336
+ isDatabaseServerScopedByIdOnly({
337
+ projectId: PROJECT,
338
+ id: MODEL_ID.toString(),
339
+ endpoints: [],
340
+ }),
341
+ ).toBe(true);
342
+ });
343
+
344
+ test("an endpoint or a member is more than the id", () => {
345
+ expect(
346
+ isDatabaseServerScopedByIdOnly({
347
+ projectId: PROJECT,
348
+ id: MODEL_ID,
349
+ endpoints: ["db.prod.internal:5432"],
350
+ dbSystem: "postgresql",
351
+ }),
352
+ ).toBe(false);
353
+ expect(
354
+ isDatabaseServerScopedByIdOnly({
355
+ projectId: PROJECT,
356
+ id: MODEL_ID,
357
+ endpoints: [],
358
+ dbSystem: "postgresql",
359
+ memberEntityKeys: { "0123456789abcdef": "2026-09-23T10:00:00.000Z" },
360
+ }),
361
+ ).toBe(false);
362
+ });
363
+
364
+ test("without an id or a project it is unscoped, not id-only", () => {
365
+ expect(
366
+ isDatabaseServerScopedByIdOnly({
367
+ projectId: PROJECT,
368
+ id: null,
369
+ endpoints: [],
370
+ }),
371
+ ).toBe(false);
372
+ expect(
373
+ isDatabaseServerScopedByIdOnly({
374
+ projectId: null,
375
+ id: MODEL_ID,
376
+ endpoints: [],
377
+ }),
378
+ ).toBe(false);
379
+ expect(isDatabaseServerScopedByIdOnly(null)).toBe(false);
380
+ });
381
+ });
382
+
383
+ describe("DatabaseCallingServicesCard", () => {
384
+ test("shows a loader while loading", () => {
385
+ render(
386
+ inRouter(
387
+ <DatabaseCallingServicesCard
388
+ services={[]}
389
+ serviceNames={{}}
390
+ isLoading={true}
391
+ />,
392
+ ),
393
+ );
394
+
395
+ expect(screen.getByTestId("component-loader")).toBeInTheDocument();
396
+ });
397
+
398
+ test("says so when nothing queried the database", () => {
399
+ render(
400
+ inRouter(
401
+ <DatabaseCallingServicesCard
402
+ services={[]}
403
+ serviceNames={{}}
404
+ isLoading={false}
405
+ />,
406
+ ),
407
+ );
408
+
409
+ expect(
410
+ screen.getByTestId("database-calling-services-empty"),
411
+ ).toHaveTextContent("No instrumented application queried this database");
412
+ });
413
+
414
+ test("lists named services as links and unknown ids as text", () => {
415
+ render(
416
+ inRouter(
417
+ <DatabaseCallingServicesCard
418
+ services={[
419
+ {
420
+ serviceId: SERVICE_ID,
421
+ calls: 1500,
422
+ errors: 15,
423
+ errorRatePercent: 1,
424
+ p95DurationMs: 12.5,
425
+ },
426
+ {
427
+ serviceId: "unknown-service",
428
+ calls: 3,
429
+ errors: 0,
430
+ errorRatePercent: 0,
431
+ p95DurationMs: null,
432
+ },
433
+ ]}
434
+ serviceNames={{ [SERVICE_ID]: "checkout" }}
435
+ isLoading={false}
436
+ />,
437
+ ),
438
+ );
439
+
440
+ const rows: Array<HTMLElement> = screen.getAllByTestId(
441
+ "database-calling-service-row",
442
+ );
443
+ expect(rows).toHaveLength(2);
444
+
445
+ expect(
446
+ within(rows[0]!).getByRole("link", { name: "checkout" }),
447
+ ).toHaveAttribute("href", `/dashboard/${PROJECT_ID}/service/${SERVICE_ID}`);
448
+ expect(rows[0]).toHaveTextContent("1.5k");
449
+ expect(rows[0]).toHaveTextContent("1.0%");
450
+ expect(rows[0]).toHaveTextContent("13 ms");
451
+
452
+ expect(within(rows[1]!).queryByRole("link")).not.toBeInTheDocument();
453
+ expect(rows[1]).toHaveTextContent("unknown-service");
454
+ expect(rows[1]).toHaveTextContent("—");
455
+ });
456
+ });
457
+
458
+ describe("DatabaseCallingServicesCard totals", () => {
459
+ const service: {
460
+ serviceId: string;
461
+ calls: number;
462
+ errors: number;
463
+ errorRatePercent: number;
464
+ p95DurationMs: number;
465
+ } = {
466
+ serviceId: SERVICE_ID,
467
+ calls: 10,
468
+ errors: 0,
469
+ errorRatePercent: 0,
470
+ p95DurationMs: 1,
471
+ };
472
+
473
+ test("says how many services called when the table holds fewer", () => {
474
+ render(
475
+ inRouter(
476
+ <DatabaseCallingServicesCard
477
+ services={[service]}
478
+ serviceNames={{}}
479
+ isLoading={false}
480
+ totalServices={25}
481
+ />,
482
+ ),
483
+ );
484
+
485
+ expect(
486
+ screen.getByTestId("database-calling-services-footer"),
487
+ ).toHaveTextContent("Showing the 1 busiest of 25 calling services.");
488
+ });
489
+
490
+ test("no footer when the table is the whole list", () => {
491
+ render(
492
+ inRouter(
493
+ <DatabaseCallingServicesCard
494
+ services={[service]}
495
+ serviceNames={{}}
496
+ isLoading={false}
497
+ totalServices={1}
498
+ />,
499
+ ),
500
+ );
501
+
502
+ expect(
503
+ screen.queryByTestId("database-calling-services-footer"),
504
+ ).not.toBeInTheDocument();
505
+ expect(getCallingServicesFooter(10, 10)).toBe("");
506
+ expect(getCallingServicesFooter(10, undefined)).toBe("");
507
+ expect(getCallingServicesFooter(10, 1500)).toBe(
508
+ "Showing the 10 busiest of 1.5k calling services.",
509
+ );
510
+ });
511
+ });
512
+
513
+ describe("DatabaseEngineMetricsSection", () => {
514
+ const catalog: Array<DatabaseServerMetricDefinition> =
515
+ getDatabaseServerMetrics("postgresql");
516
+
517
+ function renderSection(data: {
518
+ status: DatabaseEngineMetricsStatus;
519
+ results: Array<DatabaseEngineMetricResult>;
520
+ dbSystem?: string;
521
+ hasCatalog?: boolean;
522
+ isLoading?: boolean;
523
+ lastReceivedAt?: Date | null;
524
+ engineLabel?: string;
525
+ }): void {
526
+ render(
527
+ inRouter(
528
+ <DatabaseEngineMetricsSection
529
+ modelId={MODEL_ID}
530
+ engineLabel={data.engineLabel || "PostgreSQL"}
531
+ status={data.status}
532
+ hasCatalog={data.hasCatalog ?? true}
533
+ dbSystem={data.dbSystem ?? "postgresql"}
534
+ lastReceivedAt={data.lastReceivedAt}
535
+ results={data.results}
536
+ isLoading={data.isLoading ?? false}
537
+ windowStart={at(0)}
538
+ windowEnd={at(60)}
539
+ />,
540
+ ),
541
+ );
542
+ }
543
+
544
+ test("shows a loader while loading", () => {
545
+ renderSection({
546
+ status: DatabaseEngineMetricsStatus.Connected,
547
+ results: [],
548
+ isLoading: true,
549
+ });
550
+
551
+ expect(screen.getByTestId("component-loader")).toBeInTheDocument();
552
+ expect(chartCardMock).not.toHaveBeenCalled();
553
+ });
554
+
555
+ test("never connected: a gray 'Not connected', and the install guide — never 'stopped reporting'", () => {
556
+ renderSection({
557
+ status: DatabaseEngineMetricsStatus.NotConnected,
558
+ results: catalog.map((definition: DatabaseServerMetricDefinition) => {
559
+ return toEngineMetricResult(definition, []);
560
+ }),
561
+ });
562
+
563
+ const card: HTMLElement = screen.getByRole("region", {
564
+ name: ENGINE_METRICS_NOT_CONNECTED_TITLE,
565
+ });
566
+ expect(card).toHaveTextContent("Install the OneUptime Database Agent");
567
+ expect(card).toHaveTextContent("Not connected");
568
+ expect(card).not.toHaveTextContent("stopped");
569
+ expect(
570
+ within(card).getByTestId("database-engine-metrics-status"),
571
+ ).toHaveAttribute("data-status", DatabaseEngineMetricsStatus.NotConnected);
572
+ expect(
573
+ within(card).getByRole("link", { name: /Install the Database Agent/ }),
574
+ ).toHaveAttribute("href", databasePath("/documentation"));
575
+ expect(chartCardMock).not.toHaveBeenCalled();
576
+ });
577
+
578
+ test("disconnected: a red status, when it last reported, and a way to check the agent", () => {
579
+ const lastReceivedAt: Date = new Date("2026-09-20T08:00:00.000Z");
580
+ renderSection({
581
+ status: DatabaseEngineMetricsStatus.Disconnected,
582
+ results: [],
583
+ lastReceivedAt,
584
+ });
585
+
586
+ const card: HTMLElement = screen.getByRole("region", {
587
+ name: ENGINE_METRICS_DISCONNECTED_TITLE,
588
+ });
589
+ expect(card).toHaveTextContent("stopped reporting");
590
+ expect(card).toHaveTextContent(
591
+ OneUptimeDate.getDateAsLocalFormattedString(lastReceivedAt),
592
+ );
593
+ expect(card).not.toHaveTextContent("Install the OneUptime Database Agent");
594
+ expect(
595
+ within(card).getByTestId("database-engine-metrics-status"),
596
+ ).toHaveAttribute("data-status", DatabaseEngineMetricsStatus.Disconnected);
597
+ expect(
598
+ within(card).getByTestId("database-engine-metrics-status").className,
599
+ ).toContain("red");
600
+ expect(
601
+ within(card).getByRole("link", { name: /Check the agent setup/ }),
602
+ ).toHaveAttribute("href", databasePath("/documentation"));
603
+ });
604
+
605
+ test("the missing-metrics wording for each situation", () => {
606
+ expect(
607
+ getEngineMetricsMissingDescription({
608
+ status: DatabaseEngineMetricsStatus.NotConnected,
609
+ engineLabel: "MySQL",
610
+ dbSystem: "mysql",
611
+ }),
612
+ ).toContain("no Database Agent or OpenTelemetry Collector has sent them");
613
+ expect(
614
+ getEngineMetricsMissingDescription({
615
+ status: DatabaseEngineMetricsStatus.Disconnected,
616
+ engineLabel: "MySQL",
617
+ dbSystem: "mysql",
618
+ }),
619
+ ).toContain("has stopped reporting");
620
+ // The engine's metrics CAN be collected: never "there is no receiver".
621
+ for (const dbSystem of [
622
+ "cockroachdb",
623
+ "couchdb",
624
+ "aws.dynamodb",
625
+ "ibm.db2",
626
+ "acme-unknown-db",
627
+ ]) {
628
+ const text: string = getEngineMetricsMissingDescription({
629
+ status: DatabaseEngineMetricsStatus.NotConnected,
630
+ engineLabel: "X",
631
+ dbSystem,
632
+ });
633
+ expect({
634
+ dbSystem,
635
+ saysNoReceiver: text.includes("There is no OpenTelemetry Collector"),
636
+ pointsAtGuide: text.includes("Documentation tab"),
637
+ }).toEqual({ dbSystem, saysNoReceiver: false, pointsAtGuide: true });
638
+ }
639
+ });
640
+
641
+ /*
642
+ * UI review: every engine's card said "Connections, throughput, cache hit
643
+ * ratio, locks and replication come from the … engine itself" — Memcached
644
+ * has no locks or replication. An engine the agent ships a config for now
645
+ * names what that config really collects, the list the Documentation tab
646
+ * opens with, as plain text (the card is not markdown).
647
+ */
648
+ test.each([
649
+ ["memcached", "Memcached", "memcached", "get hits and misses"],
650
+ ["opensearch", "OpenSearch", "elasticsearch", "cluster health"],
651
+ ["valkey", "Valkey", "redis", "keyspace hits and misses"],
652
+ ["mariadb", "MariaDB", "mysql", "the InnoDB buffer pool"],
653
+ ["postgresql", "PostgreSQL", "postgresql", "max_connections"],
654
+ ])(
655
+ "not connected, %s: names the metrics its agent config collects",
656
+ (
657
+ dbSystem: string,
658
+ engineLabel: string,
659
+ agentEngine: string,
660
+ named: string,
661
+ ) => {
662
+ const text: string = getEngineMetricsMissingDescription({
663
+ status: DatabaseEngineMetricsStatus.NotConnected,
664
+ engineLabel,
665
+ dbSystem,
666
+ });
667
+
668
+ expect(getDatabaseAgentEngine(dbSystem)).toBe(agentEngine);
669
+ expect(text).toContain(
670
+ `Engine metrics — ${getDatabaseAgentCollectedMetricsText(
671
+ getDatabaseAgentEngine(dbSystem)!,
672
+ )} — come from the ${engineLabel} engine itself, and no Database Agent or OpenTelemetry Collector has sent them for this database yet.`,
673
+ );
674
+ expect(text).toContain(named);
675
+ expect(text).not.toContain("`");
676
+ expect(text).not.toContain("DATABASE_QUERY_EVENTS");
677
+ expect(text).not.toContain("throughput, cache hit ratio, locks");
678
+ },
679
+ );
680
+
681
+ test("Memcached and Elasticsearch are never promised locks or replication", () => {
682
+ for (const dbSystem of ["memcached", "elasticsearch"]) {
683
+ const text: string = getEngineMetricsMissingDescription({
684
+ status: DatabaseEngineMetricsStatus.NotConnected,
685
+ engineLabel: "X",
686
+ dbSystem,
687
+ });
688
+ expect({
689
+ dbSystem,
690
+ locks: text.split(/\W+/).some((word: string): boolean => {
691
+ return word === "lock" || word === "locks";
692
+ }),
693
+ replication: text.includes("replication"),
694
+ }).toEqual({ dbSystem, locks: false, replication: false });
695
+ }
696
+ });
697
+
698
+ test("an engine the agent has no config for keeps the generic words", () => {
699
+ expect(
700
+ getEngineMetricsMissingDescription({
701
+ status: DatabaseEngineMetricsStatus.NotConnected,
702
+ engineLabel: "CouchDB",
703
+ dbSystem: "couchdb",
704
+ }),
705
+ ).toContain(
706
+ "Connections, throughput, cache hit ratio, locks and replication come from the CouchDB engine itself",
707
+ );
708
+ });
709
+
710
+ /*
711
+ * What "not connected" offers follows where the engine's metrics come
712
+ * from — the same source the Documentation tab builds its guide from.
713
+ */
714
+ test.each([
715
+ [
716
+ "an engine the Database Agent ships a config for",
717
+ "mysql",
718
+ "MySQL",
719
+ "Install the OneUptime Database Agent",
720
+ "Install the Database Agent →",
721
+ ],
722
+ [
723
+ "a fork the agent monitors with its family's config",
724
+ "mariadb",
725
+ "MariaDB",
726
+ "Install the OneUptime Database Agent",
727
+ "Install the Database Agent →",
728
+ ],
729
+ [
730
+ "an engine with a collector receiver but no agent config",
731
+ "couchdb",
732
+ "CouchDB",
733
+ "ready-made collector config",
734
+ "Connect engine metrics →",
735
+ ],
736
+ [
737
+ "an engine that serves Prometheus metrics",
738
+ "cockroachdb",
739
+ "CockroachDB",
740
+ "serves Prometheus metrics itself",
741
+ "Connect engine metrics →",
742
+ ],
743
+ [
744
+ "a managed service read from its provider's monitoring API",
745
+ "aws.dynamodb",
746
+ "Amazon DynamoDB",
747
+ "provider's monitoring API",
748
+ "Connect engine metrics →",
749
+ ],
750
+ [
751
+ "an engine with no ready-made path",
752
+ "ibm.db2",
753
+ "IBM Db2",
754
+ "can still be collected",
755
+ "Connect engine metrics →",
756
+ ],
757
+ [
758
+ "an engine OneUptime does not know",
759
+ "acme-unknown-db",
760
+ "acme-unknown-db",
761
+ "can still be collected",
762
+ "Connect engine metrics →",
763
+ ],
764
+ ])(
765
+ "not connected, %s: says how and links the Documentation tab",
766
+ (
767
+ _case: string,
768
+ dbSystem: string,
769
+ engineLabel: string,
770
+ wording: string,
771
+ linkLabel: string,
772
+ ) => {
773
+ renderSection({
774
+ status: DatabaseEngineMetricsStatus.NotConnected,
775
+ results: [],
776
+ hasCatalog: false,
777
+ dbSystem,
778
+ engineLabel,
779
+ });
780
+
781
+ const card: HTMLElement = screen.getByRole("region", {
782
+ name: ENGINE_METRICS_NOT_CONNECTED_TITLE,
783
+ });
784
+ expect(card).toHaveTextContent(wording);
785
+ expect(card).not.toHaveTextContent("There is no OpenTelemetry Collector");
786
+ expect(card).not.toHaveTextContent("stopped");
787
+ const link: HTMLElement = within(card).getByRole("link");
788
+ expect(link).toHaveTextContent(linkLabel);
789
+ expect(link).toHaveAttribute("href", databasePath("/documentation"));
790
+ },
791
+ );
792
+
793
+ test("a disconnected engine always offers the setup check, whatever its source", () => {
794
+ const guidance: EngineMetricsGuidance = getEngineMetricsGuidance({
795
+ status: DatabaseEngineMetricsStatus.Disconnected,
796
+ engineLabel: "CockroachDB",
797
+ dbSystem: "cockroachdb",
798
+ });
799
+ expect(guidance.description).toContain("has stopped reporting");
800
+ expect(guidance.linkLabel).toBe("Check the agent setup →");
801
+ });
802
+
803
+ test("connected with nothing in range: points at the Metrics tab, not the install guide", () => {
804
+ renderSection({
805
+ status: DatabaseEngineMetricsStatus.Connected,
806
+ results: catalog.map((definition: DatabaseServerMetricDefinition) => {
807
+ return toEngineMetricResult(definition, []);
808
+ }),
809
+ });
810
+
811
+ const card: HTMLElement = screen.getByRole("region", {
812
+ name: ENGINE_METRICS_NO_DATA_TITLE,
813
+ });
814
+ expect(card).toHaveTextContent("is connected");
815
+ expect(card).toHaveTextContent("Connected");
816
+ expect(card).not.toHaveTextContent("Install the OneUptime Database Agent");
817
+ expect(
818
+ within(card).getByRole("link", { name: /All metrics/ }),
819
+ ).toHaveAttribute("href", databasePath("/metrics"));
820
+ expect(
821
+ screen.queryByRole("region", {
822
+ name: ENGINE_METRICS_NOT_CONNECTED_TITLE,
823
+ }),
824
+ ).not.toBeInTheDocument();
825
+ expect(chartCardMock).not.toHaveBeenCalled();
826
+ });
827
+
828
+ /*
829
+ * E2E: a Connected Memcached (7f1cbfa6, engine metrics last at 00:38) read
830
+ * "No engine metrics in this range" while 69-76 metrics arrived every
831
+ * 30 s. Its metrics DO arrive; only the curated overview is missing.
832
+ */
833
+ test("connected, but the engine has no curated overview", () => {
834
+ renderSection({
835
+ status: DatabaseEngineMetricsStatus.Connected,
836
+ results: [],
837
+ hasCatalog: false,
838
+ dbSystem: "memcached",
839
+ engineLabel: "Memcached",
840
+ });
841
+
842
+ const card: HTMLElement = screen.getByRole("region", {
843
+ name: ENGINE_METRICS_NO_CURATED_OVERVIEW_TITLE,
844
+ });
845
+ expect(
846
+ screen.queryByRole("region", { name: ENGINE_METRICS_NO_DATA_TITLE }),
847
+ ).not.toBeInTheDocument();
848
+ expect(card).toHaveTextContent("no curated overview for Memcached");
849
+ expect(
850
+ within(card).getByRole("link", { name: /All metrics/ }),
851
+ ).toHaveAttribute("href", databasePath("/metrics"));
852
+ });
853
+
854
+ test("an engine inside the application's process has nothing to connect, and says so", () => {
855
+ renderSection({
856
+ status: DatabaseEngineMetricsStatus.NotConnected,
857
+ results: [],
858
+ hasCatalog: false,
859
+ dbSystem: "sqlite",
860
+ engineLabel: "SQLite",
861
+ });
862
+
863
+ const card: HTMLElement = screen.getByRole("region", {
864
+ name: ENGINE_METRICS_NOT_CONNECTED_TITLE,
865
+ });
866
+ expect(card).toHaveTextContent(
867
+ "SQLite runs inside your application's process",
868
+ );
869
+ expect(card).toHaveTextContent("the queries they send");
870
+ expect(within(card).queryByRole("link")).not.toBeInTheDocument();
871
+ expect(
872
+ getEngineMetricsGuidance({
873
+ status: DatabaseEngineMetricsStatus.NotConnected,
874
+ engineLabel: "DuckDB",
875
+ dbSystem: "duckdb",
876
+ }).linkLabel,
877
+ ).toBeNull();
878
+ });
879
+
880
+ test("with data: a tile per catalog metric and a chart per metric with points", () => {
881
+ const gauge: DatabaseServerMetricDefinition = catalog.find(
882
+ (definition: DatabaseServerMetricDefinition): boolean => {
883
+ return definition.metricName === "postgresql.backends";
884
+ },
885
+ )!;
886
+ const counter: DatabaseServerMetricDefinition = catalog.find(
887
+ (definition: DatabaseServerMetricDefinition): boolean => {
888
+ return definition.metricName === "postgresql.commits";
889
+ },
890
+ )!;
891
+
892
+ renderSection({
893
+ status: DatabaseEngineMetricsStatus.Connected,
894
+ results: [
895
+ toEngineMetricResult(gauge, [
896
+ { x: at(0), y: 10 },
897
+ { x: at(1), y: 12 },
898
+ ]),
899
+ // A counter's series arrives as its per-second rate.
900
+ toEngineMetricResult(counter, [{ x: at(1), y: 10 }]),
901
+ ...catalog
902
+ .filter((definition: DatabaseServerMetricDefinition): boolean => {
903
+ return definition !== gauge && definition !== counter;
904
+ })
905
+ .map((definition: DatabaseServerMetricDefinition) => {
906
+ return toEngineMetricResult(definition, []);
907
+ }),
908
+ ],
909
+ });
910
+
911
+ expect(screen.getByTestId("database-engine-metrics")).toBeInTheDocument();
912
+ expect(screen.getAllByTestId("database-engine-metric-tile")).toHaveLength(
913
+ catalog.length,
914
+ );
915
+ expect(screen.getByText("12 connections")).toBeInTheDocument();
916
+ expect(screen.getByText("10 commits/s")).toBeInTheDocument();
917
+ // Every tile explains itself in an (i), not a bare title attribute.
918
+ expect(
919
+ screen.getByRole("button", { name: "About Connections" }),
920
+ ).toBeInTheDocument();
921
+
922
+ // Only metrics that returned points are charted; counters say "per second".
923
+ const titles: Array<string> = screen
924
+ .getAllByTestId("chart-card")
925
+ .map((element: HTMLElement): string => {
926
+ return element.textContent || "";
927
+ });
928
+ expect(titles).toEqual(["Connections", "Commits (per second)"]);
929
+ expect(
930
+ (chartCardMock.mock.calls[0]![0] as { description: string }).description,
931
+ ).toBe(gauge.description);
932
+ expect(screen.getByRole("link", { name: /All metrics/ })).toHaveAttribute(
933
+ "href",
934
+ databasePath("/metrics"),
935
+ );
936
+ });
937
+
938
+ test("two entries of one metric (MySQL threads) render as two tiles and charts", () => {
939
+ const mysql: Array<DatabaseServerMetricDefinition> =
940
+ getDatabaseServerMetrics("mysql");
941
+ const consoleError: ReturnType<typeof jest.spyOn> = jest
942
+ .spyOn(console, "error")
943
+ .mockImplementation((): void => {});
944
+
945
+ renderSection({
946
+ status: DatabaseEngineMetricsStatus.Connected,
947
+ engineLabel: "MySQL",
948
+ results: mysql.map((definition: DatabaseServerMetricDefinition) => {
949
+ return toEngineMetricResult(definition, [{ x: at(0), y: 3 }]);
950
+ }),
951
+ });
952
+
953
+ expect(screen.getAllByTestId("database-engine-metric-tile")).toHaveLength(
954
+ mysql.length,
955
+ );
956
+ // One tile and one chart each.
957
+ expect(screen.getAllByText("Connected threads")).toHaveLength(2);
958
+ expect(screen.getAllByText("Running threads")).toHaveLength(2);
959
+ // No duplicate React keys.
960
+ const duplicateKeyWarnings: Array<unknown> = consoleError.mock.calls.filter(
961
+ (call: Array<unknown>): boolean => {
962
+ return String(call[0]).includes("same key");
963
+ },
964
+ );
965
+ expect(duplicateKeyWarnings).toEqual([]);
966
+ consoleError.mockRestore();
967
+ });
968
+
969
+ test("Oracle's tiles read in their own units: a fullness as a percentage, DB time per second", () => {
970
+ const oracle: Array<DatabaseServerMetricDefinition> =
971
+ getDatabaseServerMetrics("oracle.db");
972
+ const values: Record<string, number> = {
973
+ "oracledb.tablespace.utilization": 0.853,
974
+ "oracledb.sga.usage": 1610612736,
975
+ "oracledb.db.time": 0.35,
976
+ };
977
+
978
+ renderSection({
979
+ status: DatabaseEngineMetricsStatus.Connected,
980
+ engineLabel: "Oracle",
981
+ dbSystem: "oracle.db",
982
+ results: oracle.map((definition: DatabaseServerMetricDefinition) => {
983
+ const value: number | undefined = values[definition.metricName];
984
+ return toEngineMetricResult(
985
+ definition,
986
+ value === undefined ? [] : [{ x: at(0), y: value }],
987
+ );
988
+ }),
989
+ });
990
+
991
+ const tile: (title: string) => HTMLElement = (
992
+ title: string,
993
+ ): HTMLElement => {
994
+ return screen
995
+ .getAllByTestId("database-engine-metric-tile")
996
+ .find((element: HTMLElement): boolean => {
997
+ return (element.textContent || "").startsWith(title);
998
+ })!;
999
+ };
1000
+ expect(tile("Fullest tablespace")).toHaveTextContent("85%");
1001
+ expect(tile("SGA in use")).toHaveTextContent("1.5 GiB");
1002
+ expect(tile("DB time")).toHaveTextContent("0.35 s/s");
1003
+ // No tile for a metric a pluggable database never reports.
1004
+ expect(screen.queryByText("Session limit")).not.toBeInTheDocument();
1005
+ expect(screen.queryByText("Processes")).not.toBeInTheDocument();
1006
+ });
1007
+
1008
+ test("a 0..1 share reads as a percentage without rounding a small one away", () => {
1009
+ expect(formatDatabaseFraction(0.853)).toBe("85%");
1010
+ expect(formatDatabaseFraction(0.042)).toBe("4.2%");
1011
+ expect(formatDatabaseFraction(0.000389)).toBe("0.04%");
1012
+ expect(formatDatabaseFraction(0)).toBe("0%");
1013
+ expect(formatDatabaseFraction(1)).toBe("100%");
1014
+ expect(formatDatabaseFraction(null)).toBe("—");
1015
+ expect(formatDatabaseFraction(Number.NaN)).toBe("—");
1016
+ expect(formatDatabaseMetricValue(0.5, "fraction", "gauge")).toBe("50%");
1017
+ });
1018
+
1019
+ test("with data from a stopped agent, the header still says so", () => {
1020
+ renderSection({
1021
+ status: DatabaseEngineMetricsStatus.Disconnected,
1022
+ results: [toEngineMetricResult(catalog[0]!, [{ x: at(0), y: 1 }])],
1023
+ });
1024
+
1025
+ expect(
1026
+ within(screen.getByTestId("database-engine-metrics")).getByTestId(
1027
+ "database-engine-metrics-status",
1028
+ ),
1029
+ ).toHaveTextContent("Disconnected");
1030
+ });
1031
+
1032
+ test("charts with no collector sighting carry no status pill", () => {
1033
+ renderSection({
1034
+ status: DatabaseEngineMetricsStatus.NotConnected,
1035
+ results: [toEngineMetricResult(catalog[0]!, [{ x: at(0), y: 1 }])],
1036
+ });
1037
+
1038
+ expect(screen.getByTestId("database-engine-metrics")).toBeInTheDocument();
1039
+ expect(
1040
+ screen.queryByTestId("database-engine-metrics-status"),
1041
+ ).not.toBeInTheDocument();
1042
+ });
1043
+ });
1044
+
1045
+ describe("DatabaseRunsOnLink", () => {
1046
+ const CLUSTER_ID: string = "c1a5e000-0000-4000-8000-000000000001";
1047
+ const HOST_ID: string = "d0c4e000-0000-4000-8000-000000000001";
1048
+
1049
+ function server(data: Partial<DatabaseServer>): DatabaseServer {
1050
+ const row: DatabaseServer = new DatabaseServer();
1051
+ Object.assign(row, data);
1052
+ return row;
1053
+ }
1054
+
1055
+ test("a Kubernetes database links to its cluster, its StatefulSet to the StatefulSet page", () => {
1056
+ const row: DatabaseServer = server({
1057
+ kubernetesClusterId: new ObjectID(CLUSTER_ID),
1058
+ kubernetesNamespace: "payments",
1059
+ workloadKind: "StatefulSet",
1060
+ workloadName: "postgres",
1061
+ });
1062
+ (
1063
+ row as unknown as { kubernetesCluster: { name: string } }
1064
+ ).kubernetesCluster = { name: "prod-eu" };
1065
+
1066
+ render(
1067
+ inRouter(
1068
+ <div>
1069
+ <DatabaseRunsOnLink source={row} />
1070
+ <DatabaseWorkloadLink source={row} />
1071
+ </div>,
1072
+ ),
1073
+ );
1074
+
1075
+ expect(
1076
+ screen.getByRole("link", { name: "Kubernetes · prod-eu" }),
1077
+ ).toHaveAttribute(
1078
+ "href",
1079
+ `/dashboard/${PROJECT_ID}/kubernetes/${CLUSTER_ID}`,
1080
+ );
1081
+ expect(
1082
+ screen.getByRole("link", { name: "payments/StatefulSet/postgres" }),
1083
+ ).toHaveAttribute(
1084
+ "href",
1085
+ `/dashboard/${PROJECT_ID}/kubernetes/${CLUSTER_ID}/statefulsets/postgres`,
1086
+ );
1087
+ });
1088
+
1089
+ test.each([
1090
+ ["Deployment", "deployments"],
1091
+ ["DaemonSet", "daemonsets"],
1092
+ ["Pod", "pods"],
1093
+ ])("a %s workload links to its page", (kind: string, segment: string) => {
1094
+ const route: string | undefined = getDatabaseWorkloadRoute({
1095
+ kubernetesClusterId: CLUSTER_ID,
1096
+ workloadKind: kind,
1097
+ workloadName: "redis",
1098
+ })?.toString();
1099
+ expect(route).toBe(
1100
+ `/dashboard/${PROJECT_ID}/kubernetes/${CLUSTER_ID}/${segment}/redis`,
1101
+ );
1102
+ });
1103
+
1104
+ test("an operator's Cluster or a ReplicaSet has no page to link", () => {
1105
+ for (const kind of ["Cluster", "ReplicaSet", ""]) {
1106
+ expect(
1107
+ getDatabaseWorkloadRoute({
1108
+ kubernetesClusterId: CLUSTER_ID,
1109
+ workloadKind: kind,
1110
+ workloadName: "pg",
1111
+ }),
1112
+ ).toBeNull();
1113
+ }
1114
+ });
1115
+
1116
+ test("Docker and Podman databases link to their host and its containers", () => {
1117
+ expect(
1118
+ getDatabaseRunsOnRoute({
1119
+ dockerHostId: new ObjectID(HOST_ID),
1120
+ })?.toString(),
1121
+ ).toBe(`/dashboard/${PROJECT_ID}/docker/${HOST_ID}`);
1122
+ expect(
1123
+ getDatabaseWorkloadRoute({
1124
+ dockerHostId: HOST_ID,
1125
+ workloadKind: "Container",
1126
+ workloadName: "shop-db",
1127
+ })?.toString(),
1128
+ ).toBe(`/dashboard/${PROJECT_ID}/docker/${HOST_ID}/containers`);
1129
+ expect(getDatabaseRunsOnRoute({ podmanHostId: HOST_ID })?.toString()).toBe(
1130
+ `/dashboard/${PROJECT_ID}/podman/${HOST_ID}`,
1131
+ );
1132
+ expect(
1133
+ getDatabaseWorkloadRoute({
1134
+ podmanHostId: HOST_ID,
1135
+ workloadName: "shop-db",
1136
+ })?.toString(),
1137
+ ).toBe(`/dashboard/${PROJECT_ID}/podman/${HOST_ID}/containers`);
1138
+ });
1139
+
1140
+ test("without a parent id it is plain text; off-platform it is a dash", () => {
1141
+ const { unmount } = render(
1142
+ inRouter(
1143
+ <DatabaseRunsOnLink
1144
+ source={{ discoverySource: "kubernetes", workloadName: "pg" }}
1145
+ />,
1146
+ ),
1147
+ );
1148
+ expect(screen.getByText("Kubernetes")).toBeInTheDocument();
1149
+ expect(screen.queryByRole("link")).not.toBeInTheDocument();
1150
+ unmount();
1151
+
1152
+ render(inRouter(<DatabaseRunsOnLink source={{ serverAddress: "db" }} />));
1153
+ expect(screen.getByText("—")).toBeInTheDocument();
1154
+ expect(getDatabaseRunsOnRoute(null)).toBeNull();
1155
+ expect(getDatabaseWorkloadRoute({ serverAddress: "db" })).toBeNull();
1156
+ });
1157
+ });
1158
+
1159
+ describe("DatabaseRuntimeSection", () => {
1160
+ test("renders Runs on and Workload as links, and explains its charts", () => {
1161
+ const row: DatabaseServer = new DatabaseServer();
1162
+ row.kubernetesClusterId = new ObjectID(
1163
+ "c1a5e000-0000-4000-8000-000000000001",
1164
+ );
1165
+ row.kubernetesNamespace = "payments";
1166
+ row.workloadKind = "StatefulSet";
1167
+ row.workloadName = "postgres";
1168
+
1169
+ render(
1170
+ inRouter(
1171
+ <DatabaseRuntimeSection
1172
+ modelId={MODEL_ID}
1173
+ platform={DatabaseRuntimePlatform.Kubernetes}
1174
+ source={row}
1175
+ instanceCount={3}
1176
+ memberCount={3}
1177
+ cpuSeries={[]}
1178
+ memorySeries={[]}
1179
+ isLoading={false}
1180
+ windowStart={at(0)}
1181
+ windowEnd={at(60)}
1182
+ />,
1183
+ ),
1184
+ );
1185
+
1186
+ expect(screen.getByRole("link", { name: "Kubernetes" })).toHaveAttribute(
1187
+ "href",
1188
+ `/dashboard/${PROJECT_ID}/kubernetes/c1a5e000-0000-4000-8000-000000000001`,
1189
+ );
1190
+ expect(
1191
+ screen.getByRole("link", { name: "payments/StatefulSet/postgres" }),
1192
+ ).toBeInTheDocument();
1193
+ for (const call of chartCardMock.mock.calls) {
1194
+ expect(
1195
+ ((call[0] as { description?: string }).description || "").length,
1196
+ ).toBeGreaterThan(0);
1197
+ }
1198
+ });
1199
+ });
1200
+
1201
+ /*
1202
+ * The Metrics tab charts a clicked metric here, over the database's key set
1203
+ * (the explorer would chart the whole project). The query goes through the
1204
+ * scoped helper: keys only, no request at all without keys, each metric
1205
+ * charted by what it is — a curated counter as a per-second rate with no
1206
+ * aggregation to pick, a histogram by percentile, a cumulative counter as a
1207
+ * rate — with values in the metric's unit.
1208
+ */
1209
+ describe("DatabaseMetricChartModal", () => {
1210
+ const KEY: string = "0123456789abcdef";
1211
+
1212
+ type ChartProps = {
1213
+ title: string;
1214
+ loading?: boolean;
1215
+ yFormatter?: (value: number) => string;
1216
+ series: Array<{ seriesName: string; data: Array<{ x: Date; y: number }> }>;
1217
+ };
1218
+
1219
+ function lastChart(): ChartProps {
1220
+ const calls: Array<Array<unknown>> = chartCardMock.mock.calls;
1221
+ expect(calls.length).toBeGreaterThan(0);
1222
+ return calls[calls.length - 1]![0] as ChartProps;
1223
+ }
1224
+
1225
+ function lastAggregateBy(): Record<string, unknown> {
1226
+ const calls: Array<Array<unknown>> = aggregateMock.mock.calls;
1227
+ expect(calls.length).toBeGreaterThan(0);
1228
+ return (
1229
+ calls[calls.length - 1]![0] as { aggregateBy: Record<string, unknown> }
1230
+ ).aggregateBy;
1231
+ }
1232
+
1233
+ function shapeRow(row: Record<string, unknown>): void {
1234
+ getListAnalyticsMock.mockResolvedValue({ data: [row], count: 1 });
1235
+ }
1236
+
1237
+ function renderChart(data: {
1238
+ metricName: string;
1239
+ keys: Array<string>;
1240
+ unit?: string;
1241
+ dbSystem?: string;
1242
+ initialTimeRange?: RangeStartAndEndDateTime;
1243
+ onClose?: () => void;
1244
+ }): void {
1245
+ render(
1246
+ <DatabaseMetricChartModal
1247
+ metricName={data.metricName}
1248
+ unit={data.unit}
1249
+ keys={data.keys}
1250
+ projectId={PROJECT_ID}
1251
+ dbSystem={data.dbSystem || "postgresql"}
1252
+ initialTimeRange={data.initialTimeRange}
1253
+ onClose={data.onClose || ((): void => {})}
1254
+ />,
1255
+ );
1256
+ }
1257
+
1258
+ test("a curated counter is charted as a per-second rate over the key set", async () => {
1259
+ aggregateMock.mockResolvedValue({
1260
+ data: [
1261
+ { timestamp: at(0), value: 0, attributes: { "db.namespace": "a" } },
1262
+ { timestamp: at(1), value: 600, attributes: { "db.namespace": "a" } },
1263
+ ],
1264
+ });
1265
+
1266
+ renderChart({ metricName: "postgresql.commits", keys: [KEY] });
1267
+
1268
+ await waitFor(() => {
1269
+ expect(lastChart().loading).toBe(false);
1270
+ });
1271
+ expect(lastChart().series[0]!.data).toEqual([{ x: at(1), y: 10 }]);
1272
+ expect(
1273
+ screen.getByRole("dialog", { name: "Commits (per second)" }),
1274
+ ).toBeInTheDocument();
1275
+ expect(
1276
+ screen.queryByRole("group", { name: "Aggregation" }),
1277
+ ).not.toBeInTheDocument();
1278
+ // A curated metric needs no shape lookup.
1279
+ expect(getListAnalyticsMock).not.toHaveBeenCalled();
1280
+
1281
+ const aggregateBy: Record<string, unknown> = lastAggregateBy();
1282
+ expect(aggregateBy["aggregationType"]).toBe(AggregationType.Max);
1283
+ expect(aggregateBy["groupBy"]).toEqual({ attributes: true });
1284
+ const query: Record<string, unknown> = aggregateBy["query"] as Record<
1285
+ string,
1286
+ unknown
1287
+ >;
1288
+ expect(query["name"]).toBe("postgresql.commits");
1289
+ expect((query["entityKeys"] as Includes).values).toEqual([KEY]);
1290
+ expect(query["attributes"]).toBeUndefined();
1291
+ /*
1292
+ * The axis carries the number; the unit is said once, by the title (a
1293
+ * 64 px axis clipped "2.5 commits/s" to "ommits/s").
1294
+ */
1295
+ expect(lastChart().yFormatter!(2.5)).toBe("2.5");
1296
+ expect(lastChart().title).toBe("postgresql.commits (per second)");
1297
+ });
1298
+
1299
+ /*
1300
+ * e2e: MariaDB 11.4's mysql.buffer_pool.limit is its page count (8112),
1301
+ * sent under the receiver's unit "By" — the chart read it as "7.9 KiB".
1302
+ */
1303
+ test("MariaDB's buffer pool limit is charted as a page count, with a note saying why", async () => {
1304
+ shapeRow({ metricPointType: "Sum", isMonotonic: false });
1305
+ aggregateMock.mockResolvedValue({
1306
+ data: [{ timestamp: at(0), value: 8112 }],
1307
+ });
1308
+
1309
+ renderChart({
1310
+ metricName: "mysql.buffer_pool.limit",
1311
+ keys: [KEY],
1312
+ unit: "By",
1313
+ dbSystem: "mariadb",
1314
+ });
1315
+
1316
+ await waitFor(() => {
1317
+ expect(lastChart().loading).toBe(false);
1318
+ });
1319
+ expect(lastChart().series[0]!.data).toEqual([{ x: at(0), y: 8112 }]);
1320
+ expect(lastChart().title).toBe("mysql.buffer_pool.limit (pages)");
1321
+ expect(lastChart().yFormatter!(8112)).not.toMatch(/B\b/);
1322
+ expect(screen.getByTestId("database-metric-chart-note")).toHaveTextContent(
1323
+ "size in pages (Innodb_buffer_pool_pages_total), not in bytes",
1324
+ );
1325
+ });
1326
+
1327
+ test("a gauge offers the aggregations and refetches on a change", async () => {
1328
+ shapeRow({ metricPointType: "Gauge", isMonotonic: false });
1329
+ aggregateMock.mockResolvedValue({
1330
+ data: [{ timestamp: at(0), value: 3 }],
1331
+ });
1332
+
1333
+ renderChart({
1334
+ metricName: "db.client.connection.count",
1335
+ keys: [KEY],
1336
+ unit: "{connection}",
1337
+ });
1338
+
1339
+ await waitFor(() => {
1340
+ expect(lastChart().loading).toBe(false);
1341
+ });
1342
+ expect(
1343
+ screen.getByRole("dialog", { name: "db.client.connection.count" }),
1344
+ ).toBeInTheDocument();
1345
+ expect(lastAggregateBy()["aggregationType"]).toBe(AggregationType.Avg);
1346
+ // The annotation word moves from every tick to the title.
1347
+ expect(lastChart().yFormatter!(3)).toBe("3");
1348
+ expect(lastChart().title).toBe("db.client.connection.count (connection)");
1349
+
1350
+ const group: HTMLElement = screen.getByRole("group", {
1351
+ name: "Aggregation",
1352
+ });
1353
+ expect(
1354
+ within(group).getByRole("button", { name: "Average" }),
1355
+ ).toHaveAttribute("aria-pressed", "true");
1356
+
1357
+ fireEvent.click(within(group).getByRole("button", { name: "Max" }));
1358
+
1359
+ await waitFor(() => {
1360
+ expect(lastAggregateBy()["aggregationType"]).toBe(AggregationType.Max);
1361
+ });
1362
+ expect(within(group).getByRole("button", { name: "Max" })).toHaveAttribute(
1363
+ "aria-pressed",
1364
+ "true",
1365
+ );
1366
+ });
1367
+
1368
+ test("a histogram opens on p95, offers percentiles and reads in its unit", async () => {
1369
+ shapeRow({
1370
+ metricPointType: "Histogram",
1371
+ isMonotonic: false,
1372
+ aggregationTemporality: "Cumulative",
1373
+ });
1374
+ aggregateMock.mockResolvedValue({
1375
+ data: [{ timestamp: at(0), value: 0.004 }],
1376
+ });
1377
+
1378
+ renderChart({
1379
+ metricName: "db.client.operation.duration",
1380
+ keys: [KEY],
1381
+ unit: "s",
1382
+ });
1383
+
1384
+ await waitFor(() => {
1385
+ expect(lastChart().loading).toBe(false);
1386
+ });
1387
+ expect(lastAggregateBy()["aggregationType"]).toBe(AggregationType.P95);
1388
+ const group: HTMLElement = screen.getByRole("group", {
1389
+ name: "Aggregation",
1390
+ });
1391
+ for (const label of ["p50", "p90", "p95", "p99", "Average"]) {
1392
+ expect(
1393
+ within(group).getByRole("button", { name: label }),
1394
+ ).toBeInTheDocument();
1395
+ }
1396
+ expect(
1397
+ within(group).queryByRole("button", { name: "Sum" }),
1398
+ ).not.toBeInTheDocument();
1399
+ expect(within(group).getByRole("button", { name: "p95" })).toHaveAttribute(
1400
+ "aria-pressed",
1401
+ "true",
1402
+ );
1403
+ // 4 ms in seconds reads as a latency, not "0".
1404
+ expect(lastChart().yFormatter!(0.004)).toBe("4 ms");
1405
+ expect(screen.getByTestId("database-metric-chart-note")).toHaveTextContent(
1406
+ "percentiles",
1407
+ );
1408
+
1409
+ fireEvent.click(within(group).getByRole("button", { name: "p99" }));
1410
+ await waitFor(() => {
1411
+ expect(lastAggregateBy()["aggregationType"]).toBe(AggregationType.P99);
1412
+ });
1413
+ });
1414
+
1415
+ test("a cumulative counter outside the catalog is a per-second rate", async () => {
1416
+ shapeRow({
1417
+ metricPointType: "Sum",
1418
+ isMonotonic: true,
1419
+ aggregationTemporality: "Cumulative",
1420
+ });
1421
+ aggregateMock.mockResolvedValue({
1422
+ data: [
1423
+ { timestamp: at(0), value: 0, attributes: { "db.namespace": "a" } },
1424
+ { timestamp: at(1), value: 120, attributes: { "db.namespace": "a" } },
1425
+ ],
1426
+ });
1427
+
1428
+ renderChart({ metricName: "postgresql.deadlocks", keys: [KEY], unit: "1" });
1429
+
1430
+ await waitFor(() => {
1431
+ expect(lastChart().loading).toBe(false);
1432
+ });
1433
+ expect(
1434
+ screen.getByRole("dialog", { name: "postgresql.deadlocks (per second)" }),
1435
+ ).toBeInTheDocument();
1436
+ expect(lastChart().series[0]!.data).toEqual([{ x: at(1), y: 2 }]);
1437
+ expect(lastAggregateBy()["groupBy"]).toEqual({ attributes: true });
1438
+ expect(lastChart().yFormatter!(2)).toBe("2/s");
1439
+ expect(
1440
+ screen.queryByRole("group", { name: "Aggregation" }),
1441
+ ).not.toBeInTheDocument();
1442
+ });
1443
+
1444
+ test("opens on the range the metric list shows", async () => {
1445
+ shapeRow({ metricPointType: "Gauge" });
1446
+ aggregateMock.mockResolvedValue({ data: [] });
1447
+
1448
+ renderChart({
1449
+ metricName: "db.client.connection.count",
1450
+ keys: [KEY],
1451
+ initialTimeRange: { range: TimeRange.PAST_ONE_DAY },
1452
+ });
1453
+
1454
+ expect(screen.getByTestId("time-range-picker")).toHaveTextContent(
1455
+ TimeRange.PAST_ONE_DAY,
1456
+ );
1457
+ await waitFor(() => {
1458
+ expect(lastChart().loading).toBe(false);
1459
+ });
1460
+ const start: Date = lastAggregateBy()["startTimestamp"] as Date;
1461
+ const end: Date = lastAggregateBy()["endTimestamp"] as Date;
1462
+ expect((end.getTime() - start.getTime()) / 3_600_000).toBeCloseTo(24, 0);
1463
+ });
1464
+
1465
+ test("never queries without keys and closes on request", async () => {
1466
+ const onClose: MockFunction = getJestMockFunction();
1467
+
1468
+ renderChart({
1469
+ metricName: "postgresql.backends",
1470
+ keys: [],
1471
+ onClose: () => {
1472
+ onClose();
1473
+ },
1474
+ });
1475
+
1476
+ await waitFor(() => {
1477
+ expect(lastChart().loading).toBe(false);
1478
+ });
1479
+ expect(aggregateMock).not.toHaveBeenCalled();
1480
+ expect(getListAnalyticsMock).not.toHaveBeenCalled();
1481
+ expect(lastChart().series[0]!.data).toEqual([]);
1482
+
1483
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
1484
+ expect(onClose).toHaveBeenCalledTimes(1);
1485
+ });
1486
+ });