@oneuptime/common 14.0.2 → 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 (765) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/AlertFeed.ts +2 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
  4. package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
  5. package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
  6. package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
  7. package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
  8. package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
  9. package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
  10. package/Models/DatabaseModels/Incident.ts +55 -0
  11. package/Models/DatabaseModels/IncidentAlert.ts +540 -0
  12. package/Models/DatabaseModels/IncidentFeed.ts +2 -0
  13. package/Models/DatabaseModels/Index.ts +18 -0
  14. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
  15. package/Models/DatabaseModels/Project.ts +60 -0
  16. package/Models/DatabaseModels/RumApplication.ts +52 -1
  17. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  18. package/Models/DatabaseModels/TelemetryException.ts +2 -0
  19. package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  25. package/Server/Middleware/MultipartFormData.ts +1 -1
  26. package/Server/Middleware/TelemetryIngest.ts +15 -8
  27. package/Server/Services/AlertReminderRuleService.ts +100 -24
  28. package/Server/Services/DatabaseServerEndpointService.ts +895 -0
  29. package/Server/Services/DatabaseServerFeedService.ts +128 -0
  30. package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
  31. package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
  32. package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
  33. package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
  34. package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
  35. package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
  36. package/Server/Services/DatabaseServerService.ts +4546 -0
  37. package/Server/Services/DatabaseService.ts +14 -8
  38. package/Server/Services/DockerResourceService.ts +43 -2
  39. package/Server/Services/IncidentAlertService.ts +1811 -0
  40. package/Server/Services/IncidentReminderRuleService.ts +103 -23
  41. package/Server/Services/IncidentService.ts +210 -10
  42. package/Server/Services/IncidentStateTimelineService.ts +24 -0
  43. package/Server/Services/Index.ts +18 -0
  44. package/Server/Services/InventoryItemService.ts +113 -5
  45. package/Server/Services/IoTDeviceService.ts +145 -37
  46. package/Server/Services/OpenTelemetryIngestService.ts +19 -0
  47. package/Server/Services/PodmanResourceService.ts +43 -2
  48. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
  49. package/Server/Services/ServiceService.ts +1 -1
  50. package/Server/Services/TelemetryUsageBillingService.ts +29 -2
  51. package/Server/Services/UserProjectSsoConsentService.ts +74 -0
  52. package/Server/Services/UserService.ts +14 -1
  53. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
  54. package/Server/Types/Database/QueryHelper.ts +30 -1
  55. package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
  56. package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
  57. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
  58. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  59. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
  60. package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
  61. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
  62. package/Server/Utils/Monitor/MonitorResource.ts +47 -11
  63. package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
  64. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
  65. package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
  66. package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
  67. package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
  68. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  69. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
  70. package/Server/Utils/StartServer.ts +26 -6
  71. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  72. package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
  73. package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
  74. package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
  75. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
  76. package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
  77. package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
  78. package/Server/Utils/Telemetry/Telemetry.ts +16 -0
  79. package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
  80. package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
  81. package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
  82. package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
  83. package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
  84. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
  85. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
  86. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
  87. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
  88. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
  89. package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
  90. package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
  91. package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
  92. package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
  93. package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
  94. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
  95. package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
  96. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
  97. package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
  98. package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
  99. package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
  100. package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
  101. package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
  102. package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
  103. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
  104. package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
  105. package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
  106. package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
  107. package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
  108. package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
  109. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
  110. package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
  111. package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
  112. package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
  113. package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
  114. package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
  115. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
  116. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
  117. package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
  118. package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
  119. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
  120. package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
  121. package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
  122. package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
  123. package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
  124. package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
  125. package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
  126. package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
  127. package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
  128. package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
  129. package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
  130. package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
  131. package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
  132. package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
  133. package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
  134. package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
  135. package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
  136. package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
  137. package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
  138. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
  139. package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
  140. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
  141. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
  142. package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
  143. package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
  144. package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
  145. package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
  146. package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
  147. package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
  148. package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
  149. package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
  150. package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
  151. package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
  152. package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
  153. package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
  154. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
  155. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
  156. package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
  157. package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
  158. package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
  159. package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
  160. package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
  161. package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
  162. package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
  163. package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
  164. package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
  165. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
  166. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  167. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
  168. package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
  169. package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
  170. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
  171. package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
  172. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
  173. package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
  174. package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
  175. package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
  176. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
  177. package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
  178. package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
  179. package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
  180. package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
  181. package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
  182. package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
  183. package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
  184. package/Tests/ClassStrings.ts +178 -0
  185. package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
  186. package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
  187. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
  188. package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
  189. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
  190. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
  191. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  192. package/Tests/RenderedMarkup.ts +518 -0
  193. package/Tests/ResponsiveVisibility.test.ts +490 -4
  194. package/Tests/ResponsiveVisibility.ts +505 -52
  195. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  196. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
  197. package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
  198. package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
  199. package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
  200. package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
  201. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
  202. package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
  203. package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
  204. package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
  205. package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
  206. package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
  207. package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
  208. package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
  209. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
  210. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
  211. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
  212. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
  213. package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
  214. package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
  215. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
  216. package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
  217. package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
  218. package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
  219. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
  220. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
  221. package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
  222. package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
  223. package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
  224. package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
  225. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
  226. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
  227. package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
  228. package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
  229. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
  230. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  231. package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
  232. package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
  233. package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
  234. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
  235. package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
  236. package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
  237. package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
  238. package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
  239. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
  240. package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
  241. package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
  242. package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
  243. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
  244. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
  245. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
  246. package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
  247. package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
  248. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
  249. package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
  250. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
  251. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
  252. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
  253. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
  254. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
  255. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
  256. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
  257. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
  258. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
  259. package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
  260. package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
  261. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
  262. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
  263. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
  264. package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
  265. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
  266. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
  267. package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
  268. package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
  269. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
  270. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
  271. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
  272. package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
  273. package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
  274. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
  275. package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
  276. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
  277. package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
  278. package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
  279. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
  280. package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
  281. package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
  282. package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
  283. package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
  284. package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
  285. package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
  286. package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
  287. package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
  288. package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
  289. package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
  290. package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
  291. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
  292. package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
  293. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
  294. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
  295. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
  296. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
  297. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
  298. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
  299. package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
  300. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
  301. package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
  302. package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
  303. package/Tests/Types/Rum/WebVitals.test.ts +173 -0
  304. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
  305. package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
  306. package/Tests/UI/Components/Card.test.tsx +93 -2
  307. package/Tests/UI/Components/FeedOptions.test.ts +6 -0
  308. package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
  309. package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
  310. package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
  311. package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
  312. package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
  313. package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
  314. package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
  315. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
  316. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
  317. package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
  318. package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
  319. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  320. package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
  321. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
  322. package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
  323. package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
  324. package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
  325. package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
  326. package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
  327. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
  328. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
  329. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
  330. package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
  331. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
  332. package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
  333. package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
  334. package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
  335. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
  336. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
  337. package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
  338. package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
  339. package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
  340. package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
  341. package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
  342. package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
  343. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +80 -0
  344. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +23 -0
  345. package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
  346. package/Tests/UI/Utils/Navigation.test.ts +102 -0
  347. package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
  348. package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
  349. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
  350. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  351. package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
  352. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
  353. package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
  354. package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
  355. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
  356. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
  357. package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
  358. package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
  359. package/Tests/Utils/ValueFormatter.test.ts +50 -0
  360. package/Tests/tsconfig.json +16 -0
  361. package/Types/AI/AIResourceContext.ts +7 -0
  362. package/Types/Dashboard/DashboardTemplates.ts +2 -1
  363. package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
  364. package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
  365. package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
  366. package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
  367. package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
  368. package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
  369. package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
  370. package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
  371. package/Types/Email/EmailTemplateType.ts +1 -0
  372. package/Types/Incident/IncidentAlertLink.ts +38 -0
  373. package/Types/Monitor/CephAlertTemplates.ts +18 -0
  374. package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
  375. package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
  376. package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
  377. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
  378. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
  379. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
  380. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
  381. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
  382. package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
  383. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
  384. package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
  385. package/Types/Permission.ts +315 -0
  386. package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
  387. package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
  388. package/Types/Rules/RuleRun.ts +12 -0
  389. package/Types/Rum/SessionReplay.ts +25 -6
  390. package/Types/Rum/WebVitals.ts +52 -0
  391. package/Types/Telemetry/EntityType.ts +23 -0
  392. package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
  393. package/Types/Telemetry/ServiceType.ts +8 -0
  394. package/UI/Components/Banner/Banner.tsx +1 -1
  395. package/UI/Components/Card/Card.tsx +1 -1
  396. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
  397. package/UI/Components/Charts/Line/LineChart.tsx +7 -0
  398. package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
  399. package/UI/Components/Date/StartAndEndDate.tsx +5 -5
  400. package/UI/Components/Detail/FieldLabel.tsx +8 -0
  401. package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
  402. package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
  403. package/UI/Components/Forms/BasicForm.tsx +1 -1
  404. package/UI/Components/Header/Header.tsx +1 -1
  405. package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
  406. package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
  407. package/UI/Components/InfoCard/InfoCard.tsx +47 -1
  408. package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
  409. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
  410. package/UI/Components/Modal/ModalFooter.tsx +1 -1
  411. package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
  412. package/UI/Components/ModelTable/BaseModelTable.tsx +99 -21
  413. package/UI/Components/ModelTable/Column.ts +5 -0
  414. package/UI/Components/Navbar/NavBar.tsx +9 -2
  415. package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
  416. package/UI/Components/Page/Page.tsx +1 -1
  417. package/UI/Components/Pagination/Pagination.tsx +3 -3
  418. package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
  419. package/UI/Components/SideMenu/SideMenu.tsx +4 -1
  420. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
  421. package/UI/Components/StatusPage/ResourceList.tsx +6 -6
  422. package/UI/Components/Table/TableHeader.tsx +47 -2
  423. package/UI/Components/Table/Types/Column.ts +7 -0
  424. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
  425. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
  426. package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
  427. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
  428. package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
  429. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  430. package/UI/Styles/Theme.css +8 -1
  431. package/UI/Utils/Navigation.ts +89 -42
  432. package/UI/Utils/PermissionGate.ts +11 -2
  433. package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
  434. package/UI/Utils/TelemetryService.ts +4 -3
  435. package/Utils/Incident/IncidentFromAlerts.ts +516 -0
  436. package/Utils/LabelRuleImportExport.ts +2 -0
  437. package/Utils/MetricUnitUtil.ts +103 -11
  438. package/Utils/Monitor/MetricValueFormatter.ts +66 -13
  439. package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
  440. package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
  441. package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
  442. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
  443. package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
  444. package/Utils/Rum/SessionTraceState.ts +212 -0
  445. package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
  446. package/Utils/Telemetry/EntityKey.ts +176 -0
  447. package/Utils/ValueFormatter.ts +16 -0
  448. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  449. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  450. package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
  451. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  452. package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
  453. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
  454. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
  455. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
  456. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
  457. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
  458. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
  459. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
  460. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
  461. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
  462. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
  463. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
  464. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
  465. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
  466. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  467. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  468. package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
  469. package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
  470. package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
  471. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  472. package/build/dist/Models/DatabaseModels/Index.js +18 -0
  473. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  474. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  475. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  476. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  477. package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
  478. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  479. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  480. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  481. package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
  482. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  483. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
  484. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
  485. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
  486. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
  487. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
  488. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
  489. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
  490. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
  491. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
  492. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js.map +1 -0
  493. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  494. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  495. package/build/dist/Server/Middleware/MultipartFormData.js +1 -1
  496. package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
  497. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  498. package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
  499. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
  500. package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
  501. package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
  502. package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
  503. package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
  504. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
  505. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
  506. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
  507. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
  508. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
  509. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
  510. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
  511. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
  512. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
  513. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
  514. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
  515. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
  516. package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
  517. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
  518. package/build/dist/Server/Services/DatabaseService.js +8 -4
  519. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  520. package/build/dist/Server/Services/DockerResourceService.js +30 -3
  521. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  522. package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
  523. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
  524. package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
  525. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
  526. package/build/dist/Server/Services/IncidentService.js +164 -12
  527. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  528. package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
  529. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  530. package/build/dist/Server/Services/Index.js +18 -0
  531. package/build/dist/Server/Services/Index.js.map +1 -1
  532. package/build/dist/Server/Services/InventoryItemService.js +88 -5
  533. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  534. package/build/dist/Server/Services/IoTDeviceService.js +111 -24
  535. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  536. package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
  537. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  538. package/build/dist/Server/Services/PodmanResourceService.js +30 -3
  539. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  540. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
  541. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
  542. package/build/dist/Server/Services/ServiceService.js +1 -1
  543. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  544. package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
  545. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  546. package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
  547. package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
  548. package/build/dist/Server/Services/UserService.js +13 -1
  549. package/build/dist/Server/Services/UserService.js.map +1 -1
  550. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
  551. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  552. package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
  553. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  554. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
  555. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  556. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
  557. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
  558. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
  559. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  560. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
  561. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  562. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
  563. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  564. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
  565. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  566. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
  567. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  568. package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
  569. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  570. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
  571. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
  572. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
  573. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
  574. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
  575. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
  576. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
  577. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  578. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
  579. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  580. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  581. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  582. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
  583. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  584. package/build/dist/Server/Utils/StartServer.js +20 -4
  585. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  586. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  587. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  588. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
  589. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
  590. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
  591. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  592. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
  593. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
  594. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
  595. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  596. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
  597. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
  598. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
  599. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
  600. package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
  601. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  602. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
  603. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  604. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
  605. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
  606. package/build/dist/Types/AI/AIResourceContext.js +7 -0
  607. package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
  608. package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
  609. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  610. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
  611. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
  612. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
  613. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
  614. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
  615. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
  616. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
  617. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
  618. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
  619. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
  620. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
  621. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
  622. package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
  623. package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
  624. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
  625. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
  626. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  627. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  628. package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
  629. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
  630. package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
  631. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
  632. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
  633. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
  634. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
  635. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  636. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
  637. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
  638. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
  639. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  640. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
  641. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  642. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
  643. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  644. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
  645. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
  646. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
  647. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
  648. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
  649. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
  650. package/build/dist/Types/Permission.js +276 -0
  651. package/build/dist/Types/Permission.js.map +1 -1
  652. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
  653. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  654. package/build/dist/Types/Rules/RuleRun.js +4 -0
  655. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  656. package/build/dist/Types/Rum/WebVitals.js +25 -0
  657. package/build/dist/Types/Rum/WebVitals.js.map +1 -1
  658. package/build/dist/Types/Telemetry/EntityType.js +23 -0
  659. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  660. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
  661. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
  662. package/build/dist/Types/Telemetry/ServiceType.js +8 -0
  663. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  664. package/build/dist/UI/Components/Banner/Banner.js +1 -1
  665. package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
  666. package/build/dist/UI/Components/Card/Card.js +1 -1
  667. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  668. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  669. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  670. package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
  671. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  672. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
  673. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
  674. package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
  675. package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
  676. package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
  677. package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
  678. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
  679. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
  680. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
  681. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
  682. package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
  683. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  684. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
  685. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  686. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
  687. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
  688. package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
  689. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  690. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
  691. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
  692. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
  693. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  694. package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
  695. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  696. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  697. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  698. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +70 -18
  699. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  700. package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
  701. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  702. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
  703. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  704. package/build/dist/UI/Components/Page/Page.js +1 -1
  705. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  706. package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
  707. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  708. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
  709. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
  710. package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
  711. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  712. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
  713. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
  714. package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
  715. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  716. package/build/dist/UI/Components/Table/TableHeader.js +18 -2
  717. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  718. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
  719. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  720. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
  721. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
  722. package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
  723. package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
  724. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
  725. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
  726. package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
  727. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  728. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  729. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  730. package/build/dist/UI/Utils/Navigation.js +66 -33
  731. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  732. package/build/dist/UI/Utils/PermissionGate.js +4 -2
  733. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  734. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
  735. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
  736. package/build/dist/UI/Utils/TelemetryService.js +3 -2
  737. package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
  738. package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
  739. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
  740. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  741. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  742. package/build/dist/Utils/MetricUnitUtil.js +77 -13
  743. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  744. package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
  745. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
  746. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
  747. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
  748. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
  749. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
  750. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
  751. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
  752. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
  753. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  754. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
  755. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
  756. package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
  757. package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
  758. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
  759. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
  760. package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
  761. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  762. package/build/dist/Utils/ValueFormatter.js +15 -0
  763. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  764. package/package.json +1 -1
  765. package/tsconfig.tests.json +7 -5
@@ -0,0 +1,1801 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import fs from "fs";
3
+ import yaml from "js-yaml";
4
+ import path from "path";
5
+ import {
6
+ DATABASE_AGENT_COLLECTOR_IMAGE,
7
+ DATABASE_AGENT_CONFIGS,
8
+ DATABASE_AGENT_DOCKER_COMPOSE,
9
+ DATABASE_AGENT_ENGINES,
10
+ DatabaseAgentEngine,
11
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Database/Utils/DatabaseAgentConfigs";
12
+ import {
13
+ DATABASE_HEALTH_MONITOR_SYSTEMS,
14
+ DatabaseAgentIdentity,
15
+ DatabaseDocumentationTarget,
16
+ getDatabaseAgentEndpoint,
17
+ getDatabaseAgentEngine,
18
+ getDatabaseAgentEngineLabel,
19
+ getDatabaseAgentEnvFile,
20
+ getDatabaseAgentInstallationMarkdown,
21
+ getDatabaseAgentKubernetesManifest,
22
+ getDatabaseAgentSystem,
23
+ getDatabaseAgentSystems,
24
+ getDatabaseHealthMonitorCreateUrl,
25
+ getDatabaseOwnCollectorMarkdown,
26
+ getDatabaseProbeEndpoint,
27
+ resolveDatabaseAgentIdentity,
28
+ } from "../../../../App/FeatureSet/Dashboard/src/Pages/Database/Utils/DocumentationMarkdown";
29
+ import {
30
+ DATABASE_SYSTEMS,
31
+ DatabaseSystemDescriptor,
32
+ getCollectorReceiverComponentName,
33
+ } from "../../../Types/DatabaseServer/DatabaseSystem";
34
+ import {
35
+ canonicalizeDatabaseEndpoint,
36
+ DatabaseEndpoint,
37
+ getDatabaseEndpointScope,
38
+ } from "../../../Types/DatabaseServer/DatabaseEndpoint";
39
+ import {
40
+ DatabaseAlertTemplate,
41
+ getDatabaseAlertTemplates,
42
+ } from "../../../Types/Monitor/DatabaseAlertTemplates";
43
+ import MonitorType from "../../../Types/Monitor/MonitorType";
44
+ import SeriesResourceLabels from "../../../Server/Utils/Monitor/SeriesResourceLabels";
45
+
46
+ /*
47
+ * The in-app Database Agent guide. The agent is config-only, so a guide that
48
+ * shows a paraphrase of the config — or an .env that names a variable the
49
+ * compose file does not pass through — installs an agent that silently
50
+ * reports nothing (or reports under the wrong identity). These tests hold the
51
+ * embedded copies byte-identical to agents/DatabaseAgent and pin the values
52
+ * a row's Documentation tab prefills. For the engines the agent ships no
53
+ * config for, they hold the "your own collector" guide to the engine
54
+ * catalog: every engine with a receiver or a metrics endpoint gets a
55
+ * complete, well-formed collector config, and "no engine metrics" is said
56
+ * only of an engine that runs inside the application.
57
+ */
58
+
59
+ const REPO_ROOT: string = path.join(__dirname, "..", "..", "..", "..", "..");
60
+ const AGENT_DIR: string = path.join(REPO_ROOT, "agents", "DatabaseAgent");
61
+ const MARKDOWN_SOURCE: string = fs.readFileSync(
62
+ path.join(
63
+ REPO_ROOT,
64
+ "packages",
65
+ "App",
66
+ "FeatureSet",
67
+ "Dashboard",
68
+ "src",
69
+ "Pages",
70
+ "Database",
71
+ "Utils",
72
+ "DocumentationMarkdown.ts",
73
+ ),
74
+ "utf8",
75
+ );
76
+
77
+ /*
78
+ * The line install.sh writes at the top of every .env (its
79
+ * COLLECTOR_ESCAPE_MARKER, `\$` unescaped): the rule a hand-written file
80
+ * follows too, stated where the password is.
81
+ */
82
+ const ENV_MARKER: string = (
83
+ fs
84
+ .readFileSync(
85
+ path.join(REPO_ROOT, "agents", "DatabaseAgent", "install.sh"),
86
+ "utf8",
87
+ )
88
+ .match(/^COLLECTOR_ESCAPE_MARKER="(.*)"$/m) as RegExpMatchArray
89
+ )[1]!.replace(/\\\$/g, "$");
90
+
91
+ const URL: string = "https://oneuptime.example.com";
92
+ const KEY: string = "ingest-key-123";
93
+ const DATABASE_ID: string = "3c1e9a52-1f2b-4c3d-9e8f-0a1b2c3d4e5f";
94
+
95
+ function readAgentFile(...segments: Array<string>): string {
96
+ return fs.readFileSync(path.join(AGENT_DIR, ...segments), "utf8");
97
+ }
98
+
99
+ /* Environment variable names the compose file passes to the collector. */
100
+ function composeVariables(): Set<string> {
101
+ return new Set<string>(
102
+ Array.from(
103
+ readAgentFile("docker-compose.yml").matchAll(/^\s*-\s*([A-Z_]+)=\$\{/gm),
104
+ ).map((match: RegExpMatchArray): string => {
105
+ return match[1]!;
106
+ }),
107
+ );
108
+ }
109
+
110
+ /* Fenced code blocks of some markdown, with their info string. */
111
+ function codeBlocks(
112
+ markdown: string,
113
+ ): Array<{ language: string; body: string }> {
114
+ const blocks: Array<{ language: string; body: string }> = [];
115
+ let current: { language: string; lines: Array<string> } | null = null;
116
+
117
+ for (const line of markdown.split("\n")) {
118
+ const fence: RegExpMatchArray | null = line.match(/^```(\S*)\s*$/);
119
+ if (fence) {
120
+ if (current) {
121
+ blocks.push({
122
+ language: current.language,
123
+ body: current.lines.join("\n"),
124
+ });
125
+ current = null;
126
+ } else {
127
+ current = { language: fence[1] || "", lines: [] };
128
+ }
129
+ continue;
130
+ }
131
+ if (current) {
132
+ current.lines.push(line);
133
+ }
134
+ }
135
+
136
+ expect(current).toBeNull();
137
+ return blocks;
138
+ }
139
+
140
+ function rowFor(
141
+ descriptor: DatabaseSystemDescriptor,
142
+ ): DatabaseDocumentationTarget {
143
+ return {
144
+ id: DATABASE_ID,
145
+ dbSystem: descriptor.system,
146
+ serverAddress: "db.prod.example.com",
147
+ serverPort: descriptor.defaultPort,
148
+ };
149
+ }
150
+
151
+ /* A row's Documentation tab, as DocumentationCard picks it. */
152
+ function guideFor(database: DatabaseDocumentationTarget): string {
153
+ const engine: DatabaseAgentEngine | null = getDatabaseAgentEngine(
154
+ database.dbSystem,
155
+ );
156
+ return engine
157
+ ? getDatabaseAgentInstallationMarkdown({
158
+ oneuptimeUrl: URL,
159
+ apiKey: KEY,
160
+ engine: engine,
161
+ system: database.dbSystem,
162
+ database: database,
163
+ })
164
+ : getDatabaseOwnCollectorMarkdown({
165
+ oneuptimeUrl: URL,
166
+ apiKey: KEY,
167
+ database: database,
168
+ });
169
+ }
170
+
171
+ describe("the embedded agent files are the shipped ones", () => {
172
+ test("one embedded config per shipped configs/*.yaml, and nothing else", () => {
173
+ const shipped: Array<string> = fs
174
+ .readdirSync(path.join(AGENT_DIR, "configs"))
175
+ .filter((file: string): boolean => {
176
+ return file.endsWith(".yaml");
177
+ })
178
+ .map((file: string): string => {
179
+ return file.replace(/\.yaml$/, "");
180
+ })
181
+ .sort();
182
+
183
+ expect([...DATABASE_AGENT_ENGINES].sort()).toEqual(shipped);
184
+ expect(Object.keys(DATABASE_AGENT_CONFIGS).sort()).toEqual(shipped);
185
+ });
186
+
187
+ test.each([...DATABASE_AGENT_ENGINES])(
188
+ "configs/%s.yaml is embedded byte for byte",
189
+ (engine: DatabaseAgentEngine) => {
190
+ expect(DATABASE_AGENT_CONFIGS[engine]).toBe(
191
+ readAgentFile("configs", `${engine}.yaml`),
192
+ );
193
+ },
194
+ );
195
+
196
+ test("docker-compose.yml is embedded byte for byte", () => {
197
+ expect(DATABASE_AGENT_DOCKER_COMPOSE).toBe(
198
+ readAgentFile("docker-compose.yml"),
199
+ );
200
+ });
201
+
202
+ test("the collector image is the compose file's pin", () => {
203
+ const match: RegExpMatchArray | null = readAgentFile(
204
+ "docker-compose.yml",
205
+ ).match(/^\s*image:\s*(\S+)\s*$/m);
206
+
207
+ expect(match).not.toBeNull();
208
+ expect(DATABASE_AGENT_COLLECTOR_IMAGE).toBe(match![1]);
209
+ });
210
+
211
+ test("every config is named after the receiver it runs", () => {
212
+ for (const engine of DATABASE_AGENT_ENGINES) {
213
+ expect(DATABASE_AGENT_CONFIGS[engine]).toContain(
214
+ `\nreceivers:\n ${engine}:\n`,
215
+ );
216
+ }
217
+ });
218
+ });
219
+
220
+ describe("getDatabaseAgentEngine", () => {
221
+ test.each([
222
+ ["postgresql", "postgresql"],
223
+ ["postgres", "postgresql"],
224
+ ["mysql", "mysql"],
225
+ ["mariadb", "mysql"],
226
+ ["percona", "mysql"],
227
+ ["Redis", "redis"],
228
+ ["valkey", "redis"],
229
+ ["keydb", "redis"],
230
+ ["dragonflydb", "redis"],
231
+ ["mongodb", "mongodb"],
232
+ ["microsoft.sql_server", "sqlserver"],
233
+ ["mssql", "sqlserver"],
234
+ ["oracle", "oracledb"],
235
+ ["elasticsearch", "elasticsearch"],
236
+ ["opensearch", "elasticsearch"],
237
+ ["memcached", "memcached"],
238
+ ])("a %s row runs configs/%s.yaml", (dbSystem: string, engine: string) => {
239
+ expect(getDatabaseAgentEngine(dbSystem)).toBe(engine);
240
+ });
241
+
242
+ test("is null for an engine the agent ships no config for", () => {
243
+ for (const dbSystem of [
244
+ "sqlite",
245
+ "tidb",
246
+ "cockroachdb",
247
+ "clickhouse",
248
+ "sap.hana",
249
+ "snowflake",
250
+ "couchdb",
251
+ "acmedb",
252
+ "",
253
+ ]) {
254
+ expect({ dbSystem, engine: getDatabaseAgentEngine(dbSystem) }).toEqual({
255
+ dbSystem,
256
+ engine: null,
257
+ });
258
+ }
259
+ expect(getDatabaseAgentEngine(undefined)).toBeNull();
260
+ expect(getDatabaseAgentEngine(null)).toBeNull();
261
+ });
262
+
263
+ test("labels name every engine a config monitors", () => {
264
+ expect(getDatabaseAgentEngineLabel("mysql")).toBe("MySQL / MariaDB");
265
+ expect(getDatabaseAgentEngineLabel("redis")).toBe(
266
+ "Redis / Valkey / KeyDB / Dragonfly",
267
+ );
268
+ expect(getDatabaseAgentEngineLabel("elasticsearch")).toBe(
269
+ "Elasticsearch / OpenSearch",
270
+ );
271
+ expect(getDatabaseAgentEngineLabel("sqlserver")).toBe("SQL Server");
272
+ });
273
+ });
274
+
275
+ describe("getDatabaseAgentSystem / getDatabaseAgentSystems", () => {
276
+ test("a row keeps its own engine when the config monitors it", () => {
277
+ expect(getDatabaseAgentSystem("mysql", "mariadb")).toBe("mariadb");
278
+ expect(getDatabaseAgentSystem("redis", "Valkey")).toBe("valkey");
279
+ expect(getDatabaseAgentSystem("sqlserver", "mssql")).toBe(
280
+ "microsoft.sql_server",
281
+ );
282
+ expect(getDatabaseAgentSystem("elasticsearch", "opensearch")).toBe(
283
+ "opensearch",
284
+ );
285
+ });
286
+
287
+ test("otherwise the engine the config is named after", () => {
288
+ expect(getDatabaseAgentSystem("mysql")).toBe("mysql");
289
+ expect(getDatabaseAgentSystem("oracledb")).toBe("oracle.db");
290
+ expect(getDatabaseAgentSystem("sqlserver", null)).toBe(
291
+ "microsoft.sql_server",
292
+ );
293
+ // A row of another engine never leaks its name into this config.
294
+ expect(getDatabaseAgentSystem("mysql", "postgresql")).toBe("mysql");
295
+ });
296
+
297
+ test("the product page offers every engine a shipped config monitors, forks included", () => {
298
+ const systems: Array<string> = getDatabaseAgentSystems();
299
+
300
+ expect([...systems].sort()).toEqual(
301
+ [
302
+ "dragonfly",
303
+ "elasticsearch",
304
+ "keydb",
305
+ "mariadb",
306
+ "memcached",
307
+ "microsoft.sql_server",
308
+ "mongodb",
309
+ "mysql",
310
+ "opensearch",
311
+ "oracle.db",
312
+ "postgresql",
313
+ "redis",
314
+ "valkey",
315
+ ].sort(),
316
+ );
317
+ for (const system of systems) {
318
+ expect(getDatabaseAgentEngine(system)).not.toBeNull();
319
+ }
320
+ });
321
+ });
322
+
323
+ describe("resolveDatabaseAgentIdentity", () => {
324
+ test("the product page gets placeholders and the engine's default port", () => {
325
+ const identity: DatabaseAgentIdentity =
326
+ resolveDatabaseAgentIdentity("mysql");
327
+
328
+ expect(identity).toEqual({
329
+ serverAddress: "db.example.com",
330
+ serverPort: 3306,
331
+ endpoint: "db.example.com:3306",
332
+ databaseId: "",
333
+ isPrefilled: false,
334
+ instanceName: "",
335
+ });
336
+ });
337
+
338
+ test("a row uses its serverAddress / serverPort", () => {
339
+ expect(
340
+ resolveDatabaseAgentIdentity("postgresql", {
341
+ id: DATABASE_ID,
342
+ serverAddress: "DB.Prod.Internal",
343
+ serverPort: 6432,
344
+ }),
345
+ ).toEqual({
346
+ serverAddress: "db.prod.internal",
347
+ serverPort: 6432,
348
+ endpoint: "db.prod.internal:6432",
349
+ databaseId: DATABASE_ID,
350
+ isPrefilled: true,
351
+ instanceName: "",
352
+ });
353
+ });
354
+
355
+ test("without an address, the first parseable endpoint (cluster qualifier dropped)", () => {
356
+ const identity: DatabaseAgentIdentity = resolveDatabaseAgentIdentity(
357
+ "postgresql",
358
+ {
359
+ id: DATABASE_ID,
360
+ endpoints: [
361
+ "localhost:5432",
362
+ "postgres.payments.svc.cluster.local:5432@prod",
363
+ ],
364
+ kubernetesNamespace: "payments",
365
+ },
366
+ );
367
+
368
+ expect(identity.serverAddress).toBe("postgres.payments.svc.cluster.local");
369
+ expect(identity.serverPort).toBe(5432);
370
+ expect(identity.endpoint).toBe("postgres.payments.svc.cluster.local:5432");
371
+ expect(identity.isPrefilled).toBe(true);
372
+ });
373
+
374
+ test("brackets an IPv6 address in the endpoint", () => {
375
+ expect(
376
+ resolveDatabaseAgentIdentity("postgresql", {
377
+ id: DATABASE_ID,
378
+ serverAddress: "2001:db8::10",
379
+ serverPort: 5432,
380
+ }).endpoint,
381
+ ).toBe("[2001:db8::10]:5432");
382
+ });
383
+
384
+ test("an unknown engine without a port leaves the port out", () => {
385
+ const identity: DatabaseAgentIdentity = resolveDatabaseAgentIdentity(
386
+ "someengine",
387
+ { id: DATABASE_ID, serverAddress: "db.example.com" },
388
+ );
389
+ expect(identity.serverPort).toBeNull();
390
+ expect(identity.endpoint).toBe("db.example.com");
391
+ });
392
+
393
+ test("the agent's endpoint is a URL for the elasticsearch receiver only", () => {
394
+ const identity: DatabaseAgentIdentity = resolveDatabaseAgentIdentity(
395
+ "opensearch",
396
+ { id: DATABASE_ID, serverAddress: "search.prod.example.com" },
397
+ );
398
+
399
+ expect(getDatabaseAgentEndpoint("elasticsearch", identity)).toBe(
400
+ "http://search.prod.example.com:9200",
401
+ );
402
+ expect(getDatabaseAgentEndpoint("postgresql", identity)).toBe(
403
+ "search.prod.example.com:9200",
404
+ );
405
+ });
406
+ });
407
+
408
+ describe("the .env file", () => {
409
+ test("interpolates the viewer's URL and key and single-quotes the password", () => {
410
+ const env: string = getDatabaseAgentEnvFile({
411
+ oneuptimeUrl: URL,
412
+ apiKey: KEY,
413
+ engine: "postgresql",
414
+ identity: resolveDatabaseAgentIdentity("postgresql", {
415
+ id: DATABASE_ID,
416
+ serverAddress: "db.prod.internal",
417
+ serverPort: 5432,
418
+ }),
419
+ });
420
+
421
+ expect(env.split("\n")).toEqual([
422
+ ENV_MARKER,
423
+ `ONEUPTIME_URL=${URL}`,
424
+ `ONEUPTIME_TELEMETRY_INGESTION_KEY=${KEY}`,
425
+ "DATABASE_SYSTEM=postgresql",
426
+ "DATABASE_ENDPOINT=db.prod.internal:5432",
427
+ "DATABASE_ENDPOINT_HOST=db.prod.internal",
428
+ "DATABASE_ENDPOINT_PORT=5432",
429
+ "DATABASE_ORACLE_SERVICE=",
430
+ "DATABASE_SERVER_ADDRESS=db.prod.internal",
431
+ "DATABASE_SERVER_PORT=5432",
432
+ "DATABASE_USERNAME=oneuptime_monitor",
433
+ "DATABASE_PASSWORD='a-strong-password'",
434
+ "DATABASE_TLS_INSECURE=true",
435
+ "DATABASE_TLS_INSECURE_SKIP_VERIFY=false",
436
+ "DATABASE_COLLECTION_INTERVAL=30s",
437
+ "DATABASE_QUERY_EVENTS=false",
438
+ `DATABASE_SERVER_ID=${DATABASE_ID}`,
439
+ ]);
440
+ });
441
+
442
+ /*
443
+ * Regression: the hand-written .env the guide shows had every `$` doubled
444
+ * but not install.sh's line saying so, and install.sh read such a file as
445
+ * holding the password as typed — re-running it (the upgrade) doubled
446
+ * every `$` again. The sample now carries install.sh's own first line.
447
+ */
448
+ test("starts with install.sh's own line saying every $ of the login is doubled", () => {
449
+ expect(ENV_MARKER).toBe(
450
+ "# DATABASE_USERNAME and DATABASE_PASSWORD are escaped for the collector: every $ is written as $$.",
451
+ );
452
+
453
+ for (const engine of DATABASE_AGENT_ENGINES) {
454
+ const env: string = getDatabaseAgentEnvFile({
455
+ oneuptimeUrl: URL,
456
+ apiKey: KEY,
457
+ engine: engine,
458
+ identity: resolveDatabaseAgentIdentity(getDatabaseAgentSystem(engine)),
459
+ });
460
+
461
+ expect({ engine, first: env.split("\n")[0] }).toEqual({
462
+ engine,
463
+ first: ENV_MARKER,
464
+ });
465
+ }
466
+ });
467
+
468
+ test("a fork reports itself: a MariaDB row's .env says mariadb and runs the mysql config", () => {
469
+ const env: string = getDatabaseAgentEnvFile({
470
+ oneuptimeUrl: URL,
471
+ apiKey: KEY,
472
+ engine: "mysql",
473
+ system: "mariadb",
474
+ identity: resolveDatabaseAgentIdentity("mariadb"),
475
+ });
476
+
477
+ expect(env).toContain("\nDATABASE_SYSTEM=mariadb\n");
478
+ });
479
+
480
+ test("per-engine shapes: SQL Server host/port, Oracle service, Elasticsearch URL, Memcached no login", () => {
481
+ const sqlserver: string = getDatabaseAgentEnvFile({
482
+ oneuptimeUrl: URL,
483
+ apiKey: KEY,
484
+ engine: "sqlserver",
485
+ identity: resolveDatabaseAgentIdentity("microsoft.sql_server", {
486
+ id: DATABASE_ID,
487
+ serverAddress: "sql.prod.internal",
488
+ }),
489
+ });
490
+ expect(sqlserver).toContain("\nDATABASE_SYSTEM=microsoft.sql_server\n");
491
+ expect(sqlserver).toContain("\nDATABASE_ENDPOINT_HOST=sql.prod.internal\n");
492
+ expect(sqlserver).toContain("\nDATABASE_ENDPOINT_PORT=1433\n");
493
+
494
+ const oracle: string = getDatabaseAgentEnvFile({
495
+ oneuptimeUrl: URL,
496
+ apiKey: KEY,
497
+ engine: "oracledb",
498
+ identity: resolveDatabaseAgentIdentity("oracle.db"),
499
+ });
500
+ expect(oracle).toContain("\nDATABASE_ORACLE_SERVICE=FREEPDB1\n");
501
+ expect(oracle).toContain("\nDATABASE_ENDPOINT=db.example.com:1521\n");
502
+
503
+ const search: string = getDatabaseAgentEnvFile({
504
+ oneuptimeUrl: URL,
505
+ apiKey: KEY,
506
+ engine: "elasticsearch",
507
+ identity: resolveDatabaseAgentIdentity("elasticsearch"),
508
+ });
509
+ expect(search).toContain(
510
+ "\nDATABASE_ENDPOINT=http://db.example.com:9200\n",
511
+ );
512
+ expect(search).toContain("\nDATABASE_ENDPOINT_HOST=db.example.com\n");
513
+
514
+ const memcached: string = getDatabaseAgentEnvFile({
515
+ oneuptimeUrl: URL,
516
+ apiKey: KEY,
517
+ engine: "memcached",
518
+ identity: resolveDatabaseAgentIdentity("memcached"),
519
+ });
520
+ expect(memcached).toContain("\nDATABASE_USERNAME=\n");
521
+ expect(memcached).toContain("\nDATABASE_PASSWORD=\n");
522
+ });
523
+
524
+ test("Redis leaves the username empty (requirepass-only servers)", () => {
525
+ const env: string = getDatabaseAgentEnvFile({
526
+ oneuptimeUrl: URL,
527
+ apiKey: KEY,
528
+ engine: "redis",
529
+ identity: resolveDatabaseAgentIdentity("redis"),
530
+ });
531
+ expect(env).toContain("\nDATABASE_USERNAME=\n");
532
+ expect(env).toContain("DATABASE_SERVER_PORT=6379");
533
+ expect(env).toContain("\nDATABASE_SERVER_ID=");
534
+ });
535
+
536
+ test.each([...DATABASE_AGENT_ENGINES])(
537
+ "the %s .env names exactly the variables the compose file passes to the collector",
538
+ (engine: DatabaseAgentEngine) => {
539
+ const passed: Set<string> = composeVariables();
540
+ const env: string = getDatabaseAgentEnvFile({
541
+ oneuptimeUrl: URL,
542
+ apiKey: KEY,
543
+ engine: engine,
544
+ identity: resolveDatabaseAgentIdentity(getDatabaseAgentSystem(engine)),
545
+ });
546
+
547
+ const named: Array<string> = env
548
+ .split("\n")
549
+ .filter((line: string): boolean => {
550
+ return !line.startsWith("#");
551
+ })
552
+ .map((line: string): string => {
553
+ return line.split("=")[0]!;
554
+ });
555
+ expect(passed.size).toBeGreaterThan(12);
556
+ expect(new Set(named)).toEqual(passed);
557
+ },
558
+ );
559
+ });
560
+
561
+ describe("the product-level guide", () => {
562
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
563
+ oneuptimeUrl: URL,
564
+ apiKey: KEY,
565
+ engine: "mysql",
566
+ });
567
+
568
+ test("embeds the compose file and the selected engine's config verbatim", () => {
569
+ expect(markdown).toContain(DATABASE_AGENT_DOCKER_COMPOSE.trimEnd());
570
+ expect(markdown).toContain(DATABASE_AGENT_CONFIGS.mysql.trimEnd());
571
+ expect(markdown).not.toContain(DATABASE_AGENT_CONFIGS.postgresql.trimEnd());
572
+ });
573
+
574
+ test("shows the MySQL grants and nothing about the other engines' logins", () => {
575
+ expect(markdown).toContain(
576
+ "GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'oneuptime_monitor'@'%';",
577
+ );
578
+ expect(markdown).toContain("SLAVE MONITOR");
579
+ expect(markdown).not.toContain("GRANT pg_monitor");
580
+ expect(markdown).not.toContain("ACL SETUSER");
581
+ });
582
+
583
+ test("has no row block, no alerting section, an empty DATABASE_SERVER_ID and no Kubernetes manifest", () => {
584
+ expect(markdown).not.toContain("## This database");
585
+ expect(markdown).not.toContain("## Alert on this database");
586
+ expect(markdown).toContain("\nDATABASE_SERVER_ID=\n");
587
+ expect(markdown).not.toContain("kind: Deployment");
588
+ expect(markdown).toContain("DATABASE_SYSTEM=mysql bash install.sh");
589
+ });
590
+
591
+ test("picking a fork on the product page installs it under its own name", () => {
592
+ const mariadb: string = getDatabaseAgentInstallationMarkdown({
593
+ oneuptimeUrl: URL,
594
+ apiKey: KEY,
595
+ engine: "mysql",
596
+ system: "mariadb",
597
+ });
598
+
599
+ expect(mariadb).toContain("DATABASE_SYSTEM=mariadb bash install.sh");
600
+ expect(mariadb).toContain("collects MariaDB engine metrics");
601
+ expect(mariadb).toContain(DATABASE_AGENT_CONFIGS.mysql.trimEnd());
602
+ });
603
+
604
+ test("never ships a placeholder key or URL, and never prints env references unescaped", () => {
605
+ expect(markdown).toContain(`ONEUPTIME_URL=${URL}`);
606
+ expect(markdown).toContain(`ONEUPTIME_TELEMETRY_INGESTION_KEY=${KEY}`);
607
+ expect(MARKDOWN_SOURCE).not.toContain("<YOUR_API_KEY>");
608
+ expect(MARKDOWN_SOURCE).not.toContain("your-telemetry-ingestion-key");
609
+ // The embedded config keeps its ${env:...} references literally.
610
+ expect(markdown).toContain('endpoint: "${env:DATABASE_ENDPOINT}"');
611
+ });
612
+
613
+ test("documents every agent environment variable", () => {
614
+ for (const variable of composeVariables()) {
615
+ expect(markdown).toContain(`| \`${variable}\` |`);
616
+ }
617
+ });
618
+
619
+ test("tells a hand-written .env to double every $ in the password", () => {
620
+ expect(markdown).toContain("write every `$` in it as `$$`");
621
+ expect(markdown).not.toContain(
622
+ "If the password contains `$`, `#`, spaces or quotes, single-quote it",
623
+ );
624
+ });
625
+
626
+ test("offers the Database Health monitor for MySQL, with the link when given", () => {
627
+ expect(markdown).toContain("Database Health monitor");
628
+ const linked: string = getDatabaseAgentInstallationMarkdown({
629
+ oneuptimeUrl: URL,
630
+ apiKey: KEY,
631
+ engine: "postgresql",
632
+ databaseHealthMonitorUrl: "/dashboard/p1/monitors/create",
633
+ });
634
+ expect(linked).toContain(
635
+ "[create a Database Health monitor](/dashboard/p1/monitors/create)",
636
+ );
637
+ });
638
+
639
+ test("does not offer it for Redis or MongoDB", () => {
640
+ for (const engine of ["redis", "mongodb"] as Array<DatabaseAgentEngine>) {
641
+ expect(
642
+ getDatabaseAgentInstallationMarkdown({
643
+ oneuptimeUrl: URL,
644
+ apiKey: KEY,
645
+ engine: engine,
646
+ }),
647
+ ).not.toContain("Database Health monitor");
648
+ }
649
+ expect(DATABASE_HEALTH_MONITOR_SYSTEMS).toEqual([
650
+ "postgresql",
651
+ "mysql",
652
+ "microsoft.sql_server",
653
+ ]);
654
+ });
655
+ });
656
+
657
+ describe("what the guide says the agent collects", () => {
658
+ /*
659
+ * The guide's first sentence. It used to read "connections, throughput,
660
+ * cache hit ratio, locks, replication, memory" for every engine.
661
+ */
662
+ function collectedSentence(engine: DatabaseAgentEngine): string {
663
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
664
+ oneuptimeUrl: URL,
665
+ apiKey: KEY,
666
+ engine: engine,
667
+ });
668
+ const sentence: string | undefined = markdown
669
+ .split("\n")
670
+ .find((line: string): boolean => {
671
+ return line.startsWith("The OneUptime Database Agent collects");
672
+ });
673
+ expect(sentence).toBeDefined();
674
+ return sentence!;
675
+ }
676
+
677
+ /*
678
+ * Every metric the memcached receiver sent in the e2e run (collector
679
+ * 0.161.0, memcached:1.6): nothing about locks or replication.
680
+ */
681
+ const MEMCACHED_METRICS_SEEN: Array<string> = [
682
+ "memcached.operation_hit_ratio",
683
+ "memcached.commands",
684
+ "memcached.bytes",
685
+ "memcached.cpu.usage",
686
+ "memcached.connections.total",
687
+ "memcached.current_items",
688
+ "memcached.connections.current",
689
+ "memcached.evictions",
690
+ "memcached.operations",
691
+ "memcached.network",
692
+ "memcached.threads",
693
+ ];
694
+
695
+ test("Memcached's promises only what its receiver reports: no locks, no replication", () => {
696
+ const sentence: string = collectedSentence("memcached");
697
+
698
+ expect(sentence).not.toMatch(/\blocks?\b/i);
699
+ expect(sentence).not.toMatch(/replica/i);
700
+ expect(sentence).not.toMatch(/throughput/i);
701
+
702
+ // Each thing it names is one of the metrics that arrived.
703
+ const backedBy: Array<[RegExp, string]> = [
704
+ [/\bconnections\b/, "memcached.connections.current"],
705
+ [/\bcommands\b/, "memcached.commands"],
706
+ [/\bhits and misses\b/, "memcached.operations"],
707
+ [/\bevictions\b/, "memcached.evictions"],
708
+ [/\bitems\b/, "memcached.current_items"],
709
+ [/\bmemory\b/, "memcached.bytes"],
710
+ [/\bnetwork\b/, "memcached.network"],
711
+ [/\bthreads\b/, "memcached.threads"],
712
+ [/\bCPU\b/, "memcached.cpu.usage"],
713
+ ];
714
+ for (const [phrase, metric] of backedBy) {
715
+ expect({ phrase: phrase.source, said: phrase.test(sentence) }).toEqual({
716
+ phrase: phrase.source,
717
+ said: true,
718
+ });
719
+ expect(MEMCACHED_METRICS_SEEN).toContain(metric);
720
+ }
721
+ for (const metric of MEMCACHED_METRICS_SEEN) {
722
+ expect(metric).not.toMatch(/lock|repl/);
723
+ }
724
+ });
725
+
726
+ test.each([
727
+ ["postgresql", "`max_connections`"],
728
+ ["mysql", "InnoDB buffer pool"],
729
+ ["redis", "keyspace hits and misses"],
730
+ ["mongodb", "cursors"],
731
+ ["sqlserver", "page life expectancy"],
732
+ ["oracledb", "tablespace usage"],
733
+ ["elasticsearch", "cluster health"],
734
+ ["memcached", "evictions"],
735
+ ] as Array<[DatabaseAgentEngine, string]>)(
736
+ "%s's names that engine's own metrics",
737
+ (engine: DatabaseAgentEngine, signature: string) => {
738
+ expect(collectedSentence(engine)).toContain(signature);
739
+ },
740
+ );
741
+
742
+ test("no two engines share one generic list", () => {
743
+ // The list between "engine metrics — " and " — with a stock …".
744
+ const lists: Array<string> = DATABASE_AGENT_ENGINES.map(
745
+ (engine: DatabaseAgentEngine): string => {
746
+ const list: string | undefined =
747
+ collectedSentence(engine).split(" — ")[1];
748
+ expect(list).toBeTruthy();
749
+ return list!;
750
+ },
751
+ );
752
+
753
+ expect(new Set<string>(lists).size).toBe(DATABASE_AGENT_ENGINES.length);
754
+ });
755
+
756
+ test("offers query samples and top queries only where the receiver ships them", () => {
757
+ const withEvents: Array<DatabaseAgentEngine> =
758
+ DATABASE_AGENT_ENGINES.filter((engine: DatabaseAgentEngine): boolean => {
759
+ return DATABASE_AGENT_CONFIGS[engine].includes("db.server.top_query:");
760
+ });
761
+
762
+ expect([...withEvents].sort()).toEqual(
763
+ ["mongodb", "mysql", "oracledb", "postgresql", "sqlserver"].sort(),
764
+ );
765
+ for (const engine of DATABASE_AGENT_ENGINES) {
766
+ expect({
767
+ engine,
768
+ offered: collectedSentence(engine).includes("query samples"),
769
+ }).toEqual({ engine, offered: withEvents.includes(engine) });
770
+ }
771
+ });
772
+ });
773
+
774
+ describe("the monitoring-user grants match the agent's README", () => {
775
+ const README_BLOCKS: Array<string> = codeBlocks(readAgentFile("README.md"))
776
+ .filter((block: { language: string; body: string }): boolean => {
777
+ return ["sql", "text", "js"].includes(block.language);
778
+ })
779
+ .map((block: { language: string; body: string }): string => {
780
+ return block.body;
781
+ });
782
+
783
+ test.each([...DATABASE_AGENT_ENGINES])(
784
+ "the %s guide's grant blocks are the README's",
785
+ (engine: DatabaseAgentEngine) => {
786
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
787
+ oneuptimeUrl: URL,
788
+ apiKey: KEY,
789
+ engine: engine,
790
+ });
791
+ const section: string = markdown.substring(
792
+ markdown.indexOf("## Create a monitoring user"),
793
+ markdown.indexOf("## Quick Start — Install Script"),
794
+ );
795
+
796
+ for (const block of codeBlocks(section)) {
797
+ expect({
798
+ engine,
799
+ inReadme: README_BLOCKS.includes(block.body),
800
+ }).toEqual({ engine, inReadme: true });
801
+ }
802
+ },
803
+ );
804
+
805
+ /*
806
+ * Regression: with pg_monitor and query events on, every EXPLAIN of a top
807
+ * query failed with "permission denied", and nothing said why.
808
+ */
809
+ /*
810
+ * Regression: the guide named only the semicolon, but the receiver's
811
+ * unquoted connection string also breaks on a double quote and trims the
812
+ * spaces around the login — such a password failed every login.
813
+ */
814
+ test("the SQL Server guide names everything its connection string cannot carry", () => {
815
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
816
+ oneuptimeUrl: URL,
817
+ apiKey: KEY,
818
+ engine: "sqlserver",
819
+ });
820
+
821
+ expect(markdown).toContain(
822
+ 'must not contain a semicolon (`;`) or a double quote (`"`), nor start or end with a space',
823
+ );
824
+ expect(markdown).toContain("For a named instance");
825
+ expect(markdown).toContain("local_tcp_port");
826
+ });
827
+
828
+ test("the PostgreSQL guide explains that plans need table access", () => {
829
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
830
+ oneuptimeUrl: URL,
831
+ apiKey: KEY,
832
+ engine: "postgresql",
833
+ });
834
+
835
+ expect(markdown).toContain("explain plans additionally need `SELECT`");
836
+ expect(markdown).toContain("`failed to explain`");
837
+ });
838
+ });
839
+
840
+ describe("a database's own guide", () => {
841
+ const database: DatabaseDocumentationTarget = {
842
+ id: DATABASE_ID,
843
+ name: "PostgreSQL db.prod.internal:5432",
844
+ dbSystem: "postgresql",
845
+ serverAddress: "db.prod.internal",
846
+ serverPort: 5432,
847
+ };
848
+
849
+ test("prefills the identity and the id everywhere", () => {
850
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
851
+ oneuptimeUrl: URL,
852
+ apiKey: KEY,
853
+ engine: "postgresql",
854
+ database: database,
855
+ });
856
+
857
+ expect(markdown).toContain("## This database");
858
+ expect(markdown).toContain(
859
+ `| \`DATABASE_SERVER_ID\` | \`${DATABASE_ID}\` |`,
860
+ );
861
+ expect(markdown).toContain(`DATABASE_SERVER_ID=${DATABASE_ID}\n`);
862
+ expect(markdown).toContain(
863
+ `DATABASE_SYSTEM=postgresql DATABASE_SERVER_ADDRESS=db.prod.internal DATABASE_SERVER_PORT=5432 DATABASE_SERVER_ID=${DATABASE_ID} bash install.sh`,
864
+ );
865
+ expect(markdown).not.toContain("replace `db.example.com`");
866
+ });
867
+
868
+ /*
869
+ * Regression: a row without an address (every Docker- or Podman-detected
870
+ * database) got the placeholder address on the install
871
+ * command line; install.sh took the placeholder as given instead of
872
+ * asking for the real name, and the row claimed it.
873
+ */
874
+ test("a row without an address gets no placeholder address on the install command line", () => {
875
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
876
+ oneuptimeUrl: URL,
877
+ apiKey: KEY,
878
+ engine: "postgresql",
879
+ database: { id: DATABASE_ID, dbSystem: "postgresql" },
880
+ });
881
+ const command: string | undefined = markdown
882
+ .split("\n")
883
+ .find((line: string): boolean => {
884
+ return line.endsWith(" bash install.sh");
885
+ });
886
+
887
+ expect(command).toBe(
888
+ `DATABASE_SYSTEM=postgresql DATABASE_SERVER_ID=${DATABASE_ID} bash install.sh`,
889
+ );
890
+ // The samples keep the placeholder, and say to replace it.
891
+ expect(markdown).toContain("replace `db.example.com`");
892
+ expect(markdown).toContain("DATABASE_SERVER_ADDRESS=db.example.com");
893
+ expect(markdown).toContain("the install script asks for the host name");
894
+ });
895
+
896
+ test("a fork's row installs under its own engine with its family's config", () => {
897
+ const markdown: string = guideFor({
898
+ id: DATABASE_ID,
899
+ dbSystem: "valkey",
900
+ serverAddress: "cache.prod.internal",
901
+ });
902
+
903
+ expect(markdown).toContain(
904
+ `DATABASE_SYSTEM=valkey DATABASE_SERVER_ADDRESS=cache.prod.internal DATABASE_SERVER_PORT=6379 DATABASE_SERVER_ID=${DATABASE_ID} bash install.sh`,
905
+ );
906
+ expect(markdown).toContain("| `DATABASE_SYSTEM` | `valkey` |");
907
+ expect(markdown).toContain(DATABASE_AGENT_CONFIGS.redis.trimEnd());
908
+ });
909
+
910
+ test("tells the reader how to alert on this database", () => {
911
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
912
+ oneuptimeUrl: URL,
913
+ apiKey: KEY,
914
+ engine: "postgresql",
915
+ database: database,
916
+ });
917
+
918
+ expect(markdown).toContain("## Alert on this database");
919
+ expect(markdown).toContain(
920
+ `\`oneuptime.database.server.id\` = \`${DATABASE_ID}\``,
921
+ );
922
+ });
923
+
924
+ test("points at the row's Recommendations tab for an engine with recommended monitors", () => {
925
+ const recommendationsUrl: string = `/dashboard/p1/databases/${DATABASE_ID}/recommendations`;
926
+ const linked: string = getDatabaseAgentInstallationMarkdown({
927
+ oneuptimeUrl: URL,
928
+ apiKey: KEY,
929
+ engine: "postgresql",
930
+ database: database,
931
+ recommendationsUrl: recommendationsUrl,
932
+ });
933
+
934
+ expect(linked).toContain(
935
+ `The [Recommendations](${recommendationsUrl}) tab offers ready-made PostgreSQL monitors`,
936
+ );
937
+ expect(linked).toContain("To build your own, create a **Metrics** monitor");
938
+
939
+ // Without a URL the tab is still named.
940
+ expect(
941
+ getDatabaseAgentInstallationMarkdown({
942
+ oneuptimeUrl: URL,
943
+ apiKey: KEY,
944
+ engine: "postgresql",
945
+ database: database,
946
+ }),
947
+ ).toContain(
948
+ "The **Recommendations** tab offers ready-made PostgreSQL monitors",
949
+ );
950
+
951
+ // A fork gets its family receiver's set, under its own name.
952
+ expect(
953
+ guideFor({
954
+ id: DATABASE_ID,
955
+ dbSystem: "valkey",
956
+ serverAddress: "cache.example.com",
957
+ }),
958
+ ).toContain("tab offers ready-made Valkey monitors");
959
+ });
960
+
961
+ // The guide says every recommended set has a check for metrics stopping.
962
+ test("every engine with recommended monitors has an Engine Metrics Stopped check", () => {
963
+ const engines: Array<string> = DATABASE_SYSTEMS.filter(
964
+ (descriptor: DatabaseSystemDescriptor): boolean => {
965
+ return getDatabaseAlertTemplates(descriptor.system).length > 0;
966
+ },
967
+ ).map((descriptor: DatabaseSystemDescriptor): string => {
968
+ return descriptor.system;
969
+ });
970
+
971
+ expect(engines.length).toBeGreaterThan(0);
972
+
973
+ for (const system of engines) {
974
+ expect({
975
+ system,
976
+ hasStoppedCheck: getDatabaseAlertTemplates(system).some(
977
+ (template: DatabaseAlertTemplate): boolean => {
978
+ return template.name === "Engine Metrics Stopped";
979
+ },
980
+ ),
981
+ }).toEqual({ system, hasStoppedCheck: true });
982
+ }
983
+ });
984
+
985
+ test("never points at an empty Recommendations tab", () => {
986
+ for (const system of ["clickhouse", "cockroachdb", "ibm.db2", "AcmeDB"]) {
987
+ expect(getDatabaseAlertTemplates(system)).toEqual([]);
988
+
989
+ const markdown: string = guideFor({
990
+ id: DATABASE_ID,
991
+ dbSystem: system,
992
+ serverAddress: "db.example.com",
993
+ });
994
+
995
+ expect({
996
+ system,
997
+ recommends: markdown.includes("Recommendations"),
998
+ }).toEqual({
999
+ system,
1000
+ recommends: false,
1001
+ });
1002
+ expect(markdown).toContain(
1003
+ "Create a **Metrics** monitor over an engine metric and filter it on that attribute",
1004
+ );
1005
+ }
1006
+
1007
+ // An engine the agent has no config for, with recommended monitors.
1008
+ expect(
1009
+ guideFor({
1010
+ id: DATABASE_ID,
1011
+ dbSystem: "couchdb",
1012
+ serverAddress: "couch.example.com",
1013
+ }),
1014
+ ).toContain("tab offers ready-made CouchDB monitors");
1015
+ });
1016
+
1017
+ /*
1018
+ * Regression: the guide said a counter "needs a rate before it can be
1019
+ * alerted on", but monitors have no rate; the collector's
1020
+ * cumulativetodelta processor is what makes a counter thresholdable.
1021
+ */
1022
+ test("sends counters through cumulativetodelta, not a rate monitors do not have", () => {
1023
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
1024
+ oneuptimeUrl: URL,
1025
+ apiKey: KEY,
1026
+ engine: "postgresql",
1027
+ database: database,
1028
+ });
1029
+
1030
+ expect(markdown).toContain("`cumulativetodelta` processor");
1031
+ expect(markdown).toContain("monitors have no rate");
1032
+ expect(MARKDOWN_SOURCE).not.toContain("needs a rate");
1033
+ });
1034
+
1035
+ test("points at Create monitor on the Metrics tab's charts", () => {
1036
+ expect(
1037
+ guideFor({
1038
+ id: DATABASE_ID,
1039
+ dbSystem: "postgresql",
1040
+ serverAddress: "db.example.com",
1041
+ }),
1042
+ ).toContain(
1043
+ "A chart opened from this database's **Metrics** tab has **Create monitor**",
1044
+ );
1045
+ });
1046
+
1047
+ /*
1048
+ * Regression: the guide said the Recommendations tab fills "once its
1049
+ * engine metrics have arrived" (any batch counted, logs included), that
1050
+ * a summed chart becomes a monitor on the same number (a monitor cannot
1051
+ * add series), and that scheduled maintenance "applies to" a filtered
1052
+ * monitor (only grouped series were silenced).
1053
+ */
1054
+ test("says what the recommendations wait for, how a summed chart alerts, and what maintenance silences", () => {
1055
+ const markdown: string = guideFor({
1056
+ id: DATABASE_ID,
1057
+ dbSystem: "postgresql",
1058
+ serverAddress: "db.example.com",
1059
+ });
1060
+
1061
+ expect(markdown).toContain(
1062
+ "once the metrics they read (from the collector's receiver for this engine) have arrived",
1063
+ );
1064
+ expect(markdown).toContain(
1065
+ "(a metric the chart adds up across series becomes a monitor that alerts on each series)",
1066
+ );
1067
+ expect(markdown).toContain(
1068
+ "and none are opened for this database while it is in a scheduled maintenance window.",
1069
+ );
1070
+ expect(MARKDOWN_SOURCE).not.toContain("engine metrics have arrived");
1071
+ expect(MARKDOWN_SOURCE).not.toContain(
1072
+ "scheduled maintenance applies to them",
1073
+ );
1074
+ });
1075
+
1076
+ /*
1077
+ * Measured on SQL Server 2022 on Linux through the agent's config:
1078
+ * sqlserver.batch.request.rate read 8, 13, 18, 23 on consecutive scrapes
1079
+ * of an idle server — the counter's raw total, not a rate.
1080
+ */
1081
+ test("warns SQL Server readers that sqlserver.*.rate metrics are since-start totals, and only them", () => {
1082
+ const sqlServer: string = guideFor({
1083
+ id: DATABASE_ID,
1084
+ dbSystem: "mssql",
1085
+ serverAddress: "sql.example.com",
1086
+ });
1087
+
1088
+ expect(sqlServer).toContain(
1089
+ "`sqlserver.*.rate` metrics behave like counters",
1090
+ );
1091
+ expect(sqlServer).toContain("total since the server started");
1092
+ // The value it recommends instead is one a SQL Server template reads.
1093
+ expect(sqlServer).toContain("`sqlserver.processes.blocked`");
1094
+ expect(
1095
+ getDatabaseAlertTemplates("microsoft.sql_server").some(
1096
+ (template: DatabaseAlertTemplate): boolean => {
1097
+ return template.metricNames.includes("sqlserver.processes.blocked");
1098
+ },
1099
+ ),
1100
+ ).toBe(true);
1101
+
1102
+ for (const system of ["postgresql", "mysql", "oracle.db", "redis"]) {
1103
+ expect({
1104
+ system,
1105
+ warns: guideFor({
1106
+ id: DATABASE_ID,
1107
+ dbSystem: system,
1108
+ serverAddress: "db.example.com",
1109
+ }).includes("sqlserver.*.rate"),
1110
+ }).toEqual({ system, warns: false });
1111
+ }
1112
+ });
1113
+
1114
+ test("a Kubernetes database gets the Deployment for its namespace", () => {
1115
+ const markdown: string = getDatabaseAgentInstallationMarkdown({
1116
+ oneuptimeUrl: URL,
1117
+ apiKey: KEY,
1118
+ engine: "postgresql",
1119
+ database: {
1120
+ id: DATABASE_ID,
1121
+ dbSystem: "postgresql",
1122
+ endpoints: ["postgres.payments.svc.cluster.local:5432@prod"],
1123
+ kubernetesNamespace: "payments",
1124
+ isKubernetes: true,
1125
+ },
1126
+ });
1127
+
1128
+ expect(markdown).toContain("## Run the agent in Kubernetes");
1129
+ expect(markdown).toContain("kubectl -n payments create configmap");
1130
+ expect(markdown).toContain(
1131
+ "/agents/DatabaseAgent/configs/postgresql.yaml -o config.yaml",
1132
+ );
1133
+ expect(markdown).toContain("namespace: payments");
1134
+ expect(markdown).toContain(`value: "${DATABASE_ID}"`);
1135
+ expect(markdown).toContain("write every `$` in it as `$$`");
1136
+ });
1137
+
1138
+ test("a non-Kubernetes database gets no manifest", () => {
1139
+ expect(
1140
+ getDatabaseAgentInstallationMarkdown({
1141
+ oneuptimeUrl: URL,
1142
+ apiKey: KEY,
1143
+ engine: "postgresql",
1144
+ database: database,
1145
+ }),
1146
+ ).not.toContain("kind: Deployment");
1147
+ });
1148
+ });
1149
+
1150
+ describe("the Database Health monitor on a database's guide", () => {
1151
+ test("the create link opens the form with the Database Health type picked", () => {
1152
+ const url: string = getDatabaseHealthMonitorCreateUrl(
1153
+ "/dashboard/p1/monitors/create",
1154
+ );
1155
+
1156
+ expect(url).toBe("/dashboard/p1/monitors/create?monitorType=Database");
1157
+
1158
+ // The value the monitor-create page reads back is a monitor type it knows.
1159
+ const picked: string | null = new URLSearchParams(
1160
+ url.substring(url.indexOf("?")),
1161
+ ).get("monitorType");
1162
+ expect(picked).toBe(MonitorType.Database);
1163
+ expect(Object.values(MonitorType)).toContain(picked);
1164
+
1165
+ // A route that already carries a query keeps it.
1166
+ expect(getDatabaseHealthMonitorCreateUrl("/create?a=1")).toBe(
1167
+ "/create?a=1&monitorType=Database",
1168
+ );
1169
+ });
1170
+
1171
+ /*
1172
+ * Regression: the Documentation card linked the bare monitor-create page,
1173
+ * so "create a Database Health monitor" landed on the type picker.
1174
+ */
1175
+ test("the Documentation card prefills the type and links the row's Recommendations tab", () => {
1176
+ const card: string = fs.readFileSync(
1177
+ path.join(
1178
+ REPO_ROOT,
1179
+ "packages/App/FeatureSet/Dashboard/src/Components/DatabaseServer/DocumentationCard.tsx",
1180
+ ),
1181
+ "utf8",
1182
+ );
1183
+
1184
+ expect(card).toMatch(
1185
+ /getDatabaseHealthMonitorCreateUrl\(\s*RouteUtil\.populateRouteParams\(\s*RouteMap\[PageMap\.MONITOR_CREATE\]/,
1186
+ );
1187
+ expect(card).toContain(
1188
+ "RouteMap[PageMap.DATABASE_SERVER_VIEW_RECOMMENDATIONS]",
1189
+ );
1190
+ expect(card.match(/recommendationsUrl: recommendationsUrl,/g)?.length).toBe(
1191
+ 2,
1192
+ );
1193
+ expect(
1194
+ card.match(/databaseHealthMonitorUrl: databaseHealthMonitorUrl,/g)
1195
+ ?.length,
1196
+ ).toBe(2);
1197
+
1198
+ expect(
1199
+ getDatabaseAgentInstallationMarkdown({
1200
+ oneuptimeUrl: URL,
1201
+ apiKey: KEY,
1202
+ engine: "postgresql",
1203
+ databaseHealthMonitorUrl: getDatabaseHealthMonitorCreateUrl(
1204
+ "/dashboard/p1/monitors/create",
1205
+ ),
1206
+ }),
1207
+ ).toContain(
1208
+ "[create a Database Health monitor](/dashboard/p1/monitors/create?monitorType=Database)",
1209
+ );
1210
+ });
1211
+
1212
+ test("getDatabaseProbeEndpoint picks the first endpoint a probe can name exactly", () => {
1213
+ expect(
1214
+ getDatabaseProbeEndpoint({
1215
+ id: DATABASE_ID,
1216
+ dbSystem: "postgresql",
1217
+ endpoints: [
1218
+ "postgres.payments.svc.cluster.local:5432@prod",
1219
+ "db.prod.internal:5432",
1220
+ "db.example.com:5432",
1221
+ ],
1222
+ }),
1223
+ ).toBe("db.prod.internal:5432");
1224
+
1225
+ // A named instance carries no port for the probe form to hold.
1226
+ expect(
1227
+ getDatabaseProbeEndpoint({
1228
+ id: DATABASE_ID,
1229
+ dbSystem: "microsoft.sql_server",
1230
+ endpoints: ["sql1.corp\\inst01", "sql1.corp:14330"],
1231
+ }),
1232
+ ).toBe("sql1.corp:14330");
1233
+
1234
+ expect(
1235
+ getDatabaseProbeEndpoint({
1236
+ id: DATABASE_ID,
1237
+ dbSystem: "postgresql",
1238
+ endpoints: ["postgres.payments.svc.cluster.local:5432@prod"],
1239
+ }),
1240
+ ).toBeNull();
1241
+ expect(
1242
+ getDatabaseProbeEndpoint({ id: DATABASE_ID, dbSystem: "postgresql" }),
1243
+ ).toBeNull();
1244
+ expect(getDatabaseProbeEndpoint(null)).toBeNull();
1245
+ });
1246
+
1247
+ /*
1248
+ * The endpoint the guide tells the reader to point the probe at must be
1249
+ * the one the monitor's alerts are resolved by: a probe step's host and
1250
+ * port go through SeriesResourceLabels.buildDatabaseEndpointRef, and the
1251
+ * result is looked up among the database's endpoints verbatim.
1252
+ */
1253
+ test.each([
1254
+ ["postgresql", "db.prod.internal:5432"],
1255
+ ["mysql", "orders.example.com:3307"],
1256
+ ["microsoft.sql_server", "10.0.0.5:1433"],
1257
+ ])(
1258
+ "a %s probe at the endpoint the guide names resolves to that endpoint",
1259
+ (system: string, endpoint: string) => {
1260
+ const named: string | null = getDatabaseProbeEndpoint({
1261
+ id: DATABASE_ID,
1262
+ dbSystem: system,
1263
+ endpoints: [endpoint],
1264
+ });
1265
+
1266
+ expect(named).toBe(endpoint);
1267
+
1268
+ const separator: number = endpoint.lastIndexOf(":");
1269
+ expect(
1270
+ SeriesResourceLabels.buildDatabaseEndpointRef({
1271
+ address: endpoint.substring(0, separator),
1272
+ port: Number(endpoint.substring(separator + 1)),
1273
+ system: system,
1274
+ }),
1275
+ ).toBe(named);
1276
+ },
1277
+ );
1278
+
1279
+ test("says which endpoint to connect to for the probe's alerts to land on the database", () => {
1280
+ const withEndpoint: string = guideFor({
1281
+ id: DATABASE_ID,
1282
+ dbSystem: "postgresql",
1283
+ serverAddress: "db.prod.internal",
1284
+ serverPort: 5432,
1285
+ endpoints: ["db.prod.internal:5432"],
1286
+ });
1287
+ expect(withEndpoint).toContain(
1288
+ "Point it at `db.prod.internal:5432`, one of this database's endpoints, and its alerts and incidents appear on this database's Alerts and Incidents tabs.",
1289
+ );
1290
+
1291
+ const onlyQualified: string = guideFor({
1292
+ id: DATABASE_ID,
1293
+ dbSystem: "mysql",
1294
+ endpoints: ["mysql.shop.svc.cluster.local:3306@prod"],
1295
+ kubernetesNamespace: "shop",
1296
+ isKubernetes: true,
1297
+ });
1298
+ expect(onlyQualified).toContain(
1299
+ "written without an `@cluster` suffix — a probe reports no cluster.",
1300
+ );
1301
+
1302
+ const productPage: string = getDatabaseAgentInstallationMarkdown({
1303
+ oneuptimeUrl: URL,
1304
+ apiKey: KEY,
1305
+ engine: "sqlserver",
1306
+ });
1307
+ expect(productPage).toContain(
1308
+ "Its alerts and incidents also appear on the database whose endpoints include the host and port it connects to.",
1309
+ );
1310
+ });
1311
+ });
1312
+
1313
+ /* The `.env` block of a guide: the fenced block that sets ONEUPTIME_URL. */
1314
+ function envBlockOf(markdown: string): Map<string, string> {
1315
+ const block: { language: string; body: string } | undefined = codeBlocks(
1316
+ markdown,
1317
+ ).find((candidate: { language: string; body: string }): boolean => {
1318
+ return candidate.body.includes("\nONEUPTIME_URL=");
1319
+ });
1320
+ expect(block).toBeDefined();
1321
+ const values: Map<string, string> = new Map<string, string>();
1322
+ for (const line of block!.body.split("\n")) {
1323
+ const match: RegExpMatchArray | null = line.match(/^([A-Z_]+)=(.*)$/);
1324
+ if (match) {
1325
+ values.set(match[1]!, match[2]!);
1326
+ }
1327
+ }
1328
+ return values;
1329
+ }
1330
+
1331
+ function installCommandOf(markdown: string): string {
1332
+ const command: string | undefined = markdown
1333
+ .split("\n")
1334
+ .find((line: string): boolean => {
1335
+ return line.endsWith(" bash install.sh");
1336
+ });
1337
+ expect(command).toBeDefined();
1338
+ return command!;
1339
+ }
1340
+
1341
+ /*
1342
+ * Regression: a row for a SQL Server named instance (`host\instance`, no
1343
+ * port) got the default instance's port 1433 everywhere — the agent then
1344
+ * monitored the default instance, charted it on the named instance's page
1345
+ * and claimed its endpoint — and the install command line carried the
1346
+ * backslash unquoted, so the shell dropped it and the identity became
1347
+ * `sql1.corp.example.cominst01`.
1348
+ */
1349
+ describe("a SQL Server named instance the row knows no port for", () => {
1350
+ const INSTANCE_PORT: string = "<instance-tcp-port>";
1351
+ const row: DatabaseDocumentationTarget = {
1352
+ id: DATABASE_ID,
1353
+ dbSystem: "microsoft.sql_server",
1354
+ serverAddress: "sql1.corp.example.com\\inst01",
1355
+ endpoints: ["sql1.corp.example.com\\inst01"],
1356
+ };
1357
+
1358
+ test("its identity is the bare host with no port, and names the instance", () => {
1359
+ const expected: DatabaseAgentIdentity = {
1360
+ serverAddress: "sql1.corp.example.com",
1361
+ serverPort: null,
1362
+ endpoint: `sql1.corp.example.com:${INSTANCE_PORT}`,
1363
+ databaseId: DATABASE_ID,
1364
+ isPrefilled: true,
1365
+ instanceName: "inst01",
1366
+ };
1367
+
1368
+ expect(resolveDatabaseAgentIdentity("microsoft.sql_server", row)).toEqual(
1369
+ expected,
1370
+ );
1371
+ // A row known only by its endpoint (detected from traces) the same.
1372
+ expect(
1373
+ resolveDatabaseAgentIdentity("microsoft.sql_server", {
1374
+ id: DATABASE_ID,
1375
+ endpoints: ["SQL1.corp.example.com\\INST01"],
1376
+ }),
1377
+ ).toEqual(expected);
1378
+ });
1379
+
1380
+ test("the guide asks for the instance's port instead of defaulting to 1433", () => {
1381
+ const markdown: string = guideFor(row);
1382
+ const env: Map<string, string> = envBlockOf(markdown);
1383
+
1384
+ /*
1385
+ * The command line leaves the port to install.sh, which asks for the
1386
+ * endpoint and takes the identity's port from it.
1387
+ */
1388
+ expect(installCommandOf(markdown)).toBe(
1389
+ `DATABASE_SYSTEM=microsoft.sql_server DATABASE_SERVER_ADDRESS=sql1.corp.example.com DATABASE_SERVER_ID=${DATABASE_ID} bash install.sh`,
1390
+ );
1391
+ expect(env.get("DATABASE_ENDPOINT")).toBe(
1392
+ `sql1.corp.example.com:${INSTANCE_PORT}`,
1393
+ );
1394
+ expect(env.get("DATABASE_ENDPOINT_HOST")).toBe("sql1.corp.example.com");
1395
+ expect(env.get("DATABASE_ENDPOINT_PORT")).toBe(INSTANCE_PORT);
1396
+ expect(env.get("DATABASE_SERVER_ADDRESS")).toBe("sql1.corp.example.com");
1397
+ expect(env.get("DATABASE_SERVER_PORT")).toBe(INSTANCE_PORT);
1398
+ expect(markdown).toContain(
1399
+ `| \`DATABASE_SERVER_PORT\` | \`${INSTANCE_PORT}\` |`,
1400
+ );
1401
+ for (const value of env.values()) {
1402
+ expect(value).not.toContain("\\");
1403
+ expect(value).not.toContain("1433");
1404
+ }
1405
+ // How to find the port, said where the reader needs it.
1406
+ expect(markdown).toContain("named instance `inst01`");
1407
+ expect(markdown).toContain(
1408
+ "SELECT local_tcp_port FROM sys.dm_exec_connections WHERE session_id = @@SPID;",
1409
+ );
1410
+ expect(markdown).not.toContain("replace `db.example.com`");
1411
+ });
1412
+
1413
+ test("the Kubernetes manifest leaves the port to fill in too", () => {
1414
+ const manifest: string = getDatabaseAgentKubernetesManifest({
1415
+ oneuptimeUrl: URL,
1416
+ engine: "sqlserver",
1417
+ identity: resolveDatabaseAgentIdentity("microsoft.sql_server", row),
1418
+ namespace: "sql",
1419
+ });
1420
+
1421
+ expect(manifest).toContain(
1422
+ `- name: DATABASE_ENDPOINT_PORT\n value: "${INSTANCE_PORT}"`,
1423
+ );
1424
+ expect(manifest).toContain(
1425
+ `- name: DATABASE_SERVER_PORT\n value: "${INSTANCE_PORT}"`,
1426
+ );
1427
+ expect(manifest).toContain(
1428
+ '- name: DATABASE_ENDPOINT_HOST\n value: "sql1.corp.example.com"',
1429
+ );
1430
+ expect(manifest).not.toContain("1433");
1431
+ expect(manifest).not.toContain("\\");
1432
+ });
1433
+
1434
+ test("an instance whose port the row knows gets that port, and no instance note", () => {
1435
+ for (const target of [
1436
+ {
1437
+ id: DATABASE_ID,
1438
+ dbSystem: "microsoft.sql_server",
1439
+ endpoints: ["sql1.corp.example.com\\inst01,14330"],
1440
+ },
1441
+ {
1442
+ id: DATABASE_ID,
1443
+ dbSystem: "microsoft.sql_server",
1444
+ serverAddress: "sql1.corp.example.com\\inst01",
1445
+ serverPort: 14330,
1446
+ },
1447
+ ] as Array<DatabaseDocumentationTarget>) {
1448
+ const markdown: string = guideFor(target);
1449
+ const env: Map<string, string> = envBlockOf(markdown);
1450
+
1451
+ expect(installCommandOf(markdown)).toBe(
1452
+ `DATABASE_SYSTEM=microsoft.sql_server DATABASE_SERVER_ADDRESS=sql1.corp.example.com DATABASE_SERVER_PORT=14330 DATABASE_SERVER_ID=${DATABASE_ID} bash install.sh`,
1453
+ );
1454
+ expect(env.get("DATABASE_ENDPOINT_PORT")).toBe("14330");
1455
+ expect(env.get("DATABASE_SERVER_PORT")).toBe("14330");
1456
+ expect(markdown).not.toContain("named instance `inst01`");
1457
+ expect(markdown).not.toContain(INSTANCE_PORT);
1458
+ }
1459
+ });
1460
+ });
1461
+
1462
+ describe("the install command line", () => {
1463
+ /*
1464
+ * Every NAME=value on it is one shell word whose value reaches install.sh
1465
+ * unchanged: bare only when made of characters the shell leaves alone,
1466
+ * otherwise single-quoted.
1467
+ */
1468
+ test.each([
1469
+ [
1470
+ {
1471
+ id: DATABASE_ID,
1472
+ dbSystem: "postgresql",
1473
+ serverAddress: "db.prod.internal",
1474
+ serverPort: 5432,
1475
+ },
1476
+ ],
1477
+ [
1478
+ {
1479
+ id: DATABASE_ID,
1480
+ dbSystem: "postgresql",
1481
+ serverAddress: "2001:db8::10",
1482
+ serverPort: 5432,
1483
+ },
1484
+ ],
1485
+ [
1486
+ {
1487
+ id: DATABASE_ID,
1488
+ dbSystem: "microsoft.sql_server",
1489
+ endpoints: ["sql1.corp\\inst01"],
1490
+ },
1491
+ ],
1492
+ [
1493
+ {
1494
+ id: "not a uuid; echo hi",
1495
+ dbSystem: "mysql",
1496
+ serverAddress: "db.example.com",
1497
+ },
1498
+ ],
1499
+ ] as Array<[DatabaseDocumentationTarget]>)(
1500
+ "carries every value through the shell unchanged (%j)",
1501
+ (target: DatabaseDocumentationTarget) => {
1502
+ const words: Array<string> = installCommandOf(guideFor(target))
1503
+ .replace(/ bash install\.sh$/, "")
1504
+ .match(/[A-Z_]+=(?:'[^']*'|\S*)/g)!;
1505
+
1506
+ for (const word of words) {
1507
+ expect(word).toMatch(/^[A-Z_]+=(?:[A-Za-z0-9._:@%+,/=-]+|'[^']*')$/);
1508
+ }
1509
+ expect(words.join(" ")).toBe(
1510
+ installCommandOf(guideFor(target)).replace(/ bash install\.sh$/, ""),
1511
+ );
1512
+ },
1513
+ );
1514
+
1515
+ test("a value with a shell character is single-quoted", () => {
1516
+ expect(
1517
+ installCommandOf(
1518
+ guideFor({
1519
+ id: "it's; odd",
1520
+ dbSystem: "mysql",
1521
+ serverAddress: "db.example.com",
1522
+ }),
1523
+ ),
1524
+ ).toBe(
1525
+ `DATABASE_SYSTEM=mysql DATABASE_SERVER_ADDRESS=db.example.com DATABASE_SERVER_PORT=3306 DATABASE_SERVER_ID='it'\\''s; odd' bash install.sh`,
1526
+ );
1527
+ });
1528
+ });
1529
+
1530
+ /*
1531
+ * Regression: the placeholder was `db.internal`, a `.internal` name — which
1532
+ * only resolves inside one network and never creates a database on its own —
1533
+ * so a sample copied as given never made the database it promised.
1534
+ */
1535
+ describe("the placeholder identity", () => {
1536
+ test("is a name that creates its database", () => {
1537
+ const identity: DatabaseAgentIdentity =
1538
+ resolveDatabaseAgentIdentity("postgresql");
1539
+ const endpoint: DatabaseEndpoint | null = canonicalizeDatabaseEndpoint({
1540
+ system: "postgresql",
1541
+ address: identity.serverAddress,
1542
+ port: identity.serverPort,
1543
+ caller: { isEphemeral: true },
1544
+ purpose: "collector",
1545
+ });
1546
+
1547
+ expect(identity.isPrefilled).toBe(false);
1548
+ expect(endpoint).not.toBeNull();
1549
+ expect(getDatabaseEndpointScope(endpoint as DatabaseEndpoint)).toBe(
1550
+ "global",
1551
+ );
1552
+ expect(MARKDOWN_SOURCE).not.toContain("db.internal");
1553
+ });
1554
+ });
1555
+
1556
+ describe("getDatabaseAgentKubernetesManifest", () => {
1557
+ const manifest: string = getDatabaseAgentKubernetesManifest({
1558
+ oneuptimeUrl: URL,
1559
+ engine: "redis",
1560
+ system: "valkey",
1561
+ identity: resolveDatabaseAgentIdentity("valkey", {
1562
+ id: DATABASE_ID,
1563
+ serverAddress: "redis-master.cache.svc.cluster.local",
1564
+ }),
1565
+ namespace: "cache",
1566
+ });
1567
+
1568
+ test("runs the pinned collector image with the row's values", () => {
1569
+ expect(manifest).toContain(`image: ${DATABASE_AGENT_COLLECTOR_IMAGE}`);
1570
+ expect(manifest).toContain("namespace: cache");
1571
+ expect(manifest).toContain(`value: "${URL}"`);
1572
+ expect(manifest).toContain('value: "valkey"');
1573
+ expect(manifest).toContain(
1574
+ 'value: "redis-master.cache.svc.cluster.local:6379"',
1575
+ );
1576
+ expect(manifest).toContain('value: "6379"');
1577
+ expect(manifest).toContain(`value: "${DATABASE_ID}"`);
1578
+ expect(manifest).toContain("mountPath: /etc/otelcol-contrib");
1579
+ });
1580
+
1581
+ test("sets every variable the compose file passes", () => {
1582
+ const names: Array<string> = Array.from(
1583
+ manifest.matchAll(/^\s*- name: ([A-Z][A-Z0-9_]+)$/gm),
1584
+ )
1585
+ .map((match: RegExpMatchArray): string => {
1586
+ return match[1]!;
1587
+ })
1588
+ .sort();
1589
+
1590
+ expect(names).toEqual([...composeVariables()].sort());
1591
+ });
1592
+
1593
+ test("keeps secrets in a Secret and never stamps k8s.cluster.name", () => {
1594
+ expect(manifest).toContain("secretKeyRef:");
1595
+ expect(manifest).not.toContain(KEY);
1596
+ expect(manifest).not.toContain("k8s.cluster.name");
1597
+ });
1598
+
1599
+ test("an empty namespace falls back to default", () => {
1600
+ expect(
1601
+ getDatabaseAgentKubernetesManifest({
1602
+ oneuptimeUrl: URL,
1603
+ engine: "redis",
1604
+ identity: resolveDatabaseAgentIdentity("redis"),
1605
+ namespace: " ",
1606
+ }),
1607
+ ).toContain("namespace: default");
1608
+ });
1609
+ });
1610
+
1611
+ /*
1612
+ * Every engine the catalog knows gets a guide on its Documentation tab.
1613
+ * Regression: engines with a contrib receiver (SAP HANA, Cloud Spanner) or
1614
+ * their own Prometheus endpoint (ClickHouse, CockroachDB, Neo4j, …) were
1615
+ * told "there is no OpenTelemetry Collector receiver … no engine metrics",
1616
+ * and SQL Server, Oracle, Elasticsearch and Memcached rows got only a
1617
+ * processor and an exporter, with no receiver and no pipeline.
1618
+ */
1619
+ describe("every catalog engine's Documentation tab", () => {
1620
+ test.each(
1621
+ DATABASE_SYSTEMS.map((descriptor: DatabaseSystemDescriptor): string => {
1622
+ return descriptor.system;
1623
+ }),
1624
+ )(
1625
+ "%s gets a guide whose YAML parses and whose pipeline is wired",
1626
+ (system: string) => {
1627
+ const descriptor: DatabaseSystemDescriptor = DATABASE_SYSTEMS.find(
1628
+ (candidate: DatabaseSystemDescriptor): boolean => {
1629
+ return candidate.system === system;
1630
+ },
1631
+ )!;
1632
+ const markdown: string = guideFor(rowFor(descriptor));
1633
+
1634
+ for (const block of codeBlocks(markdown)) {
1635
+ if (block.language !== "yaml") {
1636
+ continue;
1637
+ }
1638
+ const parsed: unknown = yaml.load(block.body);
1639
+ expect(typeof parsed).toBe("object");
1640
+
1641
+ const config: {
1642
+ receivers?: Record<string, unknown>;
1643
+ processors?: Record<string, unknown>;
1644
+ exporters?: Record<string, unknown>;
1645
+ service?: {
1646
+ pipelines?: Record<
1647
+ string,
1648
+ {
1649
+ receivers: Array<string>;
1650
+ processors: Array<string>;
1651
+ exporters: Array<string>;
1652
+ }
1653
+ >;
1654
+ };
1655
+ } = parsed as never;
1656
+
1657
+ // Every component a pipeline names is defined in the same block.
1658
+ for (const pipeline of Object.values(config.service?.pipelines || {})) {
1659
+ for (const receiver of pipeline.receivers) {
1660
+ expect(Object.keys(config.receivers || {})).toContain(receiver);
1661
+ }
1662
+ for (const processor of pipeline.processors) {
1663
+ expect(Object.keys(config.processors || {})).toContain(processor);
1664
+ }
1665
+ for (const exporter of pipeline.exporters) {
1666
+ expect(Object.keys(config.exporters || {})).toContain(exporter);
1667
+ }
1668
+ }
1669
+ }
1670
+
1671
+ if (descriptor.engineMetrics.kind === "embedded") {
1672
+ expect(markdown).toContain("runs inside your application's process");
1673
+ expect(markdown).not.toContain("resource/database");
1674
+ return;
1675
+ }
1676
+
1677
+ // Never "no receiver" for an engine that has a way to its metrics.
1678
+ expect(markdown).not.toContain(
1679
+ "There is no OpenTelemetry Collector receiver",
1680
+ );
1681
+ expect(markdown).toContain(DATABASE_ID);
1682
+ },
1683
+ );
1684
+
1685
+ test("an engine with a contrib receiver but no agent config gets the complete receiver config", () => {
1686
+ for (const system of [
1687
+ "sap.hana",
1688
+ "snowflake",
1689
+ "couchdb",
1690
+ "riak",
1691
+ "aerospike",
1692
+ "gcp.spanner",
1693
+ ]) {
1694
+ const descriptor: DatabaseSystemDescriptor = DATABASE_SYSTEMS.find(
1695
+ (candidate: DatabaseSystemDescriptor): boolean => {
1696
+ return candidate.system === system;
1697
+ },
1698
+ )!;
1699
+ const component: string = getCollectorReceiverComponentName(
1700
+ descriptor.receiverTypes[0]!,
1701
+ );
1702
+ const markdown: string = guideFor(rowFor(descriptor));
1703
+ const config: string = codeBlocks(markdown).find(
1704
+ (block: { language: string; body: string }): boolean => {
1705
+ return block.language === "yaml";
1706
+ },
1707
+ )!.body;
1708
+
1709
+ expect({
1710
+ system,
1711
+ receiver: config.includes(`\n ${component}:\n`),
1712
+ }).toEqual({ system, receiver: true });
1713
+ expect(config).toContain(`receivers: [${component}]`);
1714
+ expect(config).toContain(`value: ${system}`);
1715
+ expect(config).toContain('value: "db.prod.example.com"');
1716
+ expect(config).toContain(`value: "${DATABASE_ID}"`);
1717
+ expect(config).toContain(`endpoint: "${URL}/otlp"`);
1718
+ expect(config).toContain(`x-oneuptime-token: "${KEY}"`);
1719
+ expect(config).toContain("key: service.name\n action: delete");
1720
+ }
1721
+ });
1722
+
1723
+ test("an engine with its own Prometheus endpoint gets the scrape, with service.name and service.instance.id deleted", () => {
1724
+ const markdown: string = guideFor({
1725
+ id: DATABASE_ID,
1726
+ dbSystem: "clickhouse",
1727
+ serverAddress: "ch.prod.example.com",
1728
+ });
1729
+ const config: string = codeBlocks(markdown).find(
1730
+ (block: { language: string; body: string }): boolean => {
1731
+ return block.language === "yaml";
1732
+ },
1733
+ )!.body;
1734
+
1735
+ expect(markdown).toContain("serves Prometheus metrics itself");
1736
+ expect(markdown).toContain("Enable the `<prometheus>` section");
1737
+ expect(config).toContain('metrics_path: "/metrics"');
1738
+ expect(config).toContain('targets: ["ch.prod.example.com:9363"]');
1739
+ expect(config).toContain("receivers: [prometheus/database]");
1740
+ expect(config).toContain(
1741
+ "key: service.instance.id\n action: delete",
1742
+ );
1743
+ expect(config).toContain("value: 9000");
1744
+ });
1745
+
1746
+ test("a managed cloud database is pointed at its provider's monitoring API", () => {
1747
+ const markdown: string = guideFor({
1748
+ id: DATABASE_ID,
1749
+ dbSystem: "dynamodb",
1750
+ serverAddress: "dynamodb.us-east-1.amazonaws.com",
1751
+ });
1752
+
1753
+ expect(markdown).toContain("is a managed service");
1754
+ expect(markdown).toContain("`awsfirehose` receiver");
1755
+ expect(markdown).toContain("resource/database");
1756
+ });
1757
+
1758
+ test("an engine with no built-in endpoint says what to do, and still gives the identity stamp", () => {
1759
+ const markdown: string = guideFor({
1760
+ id: DATABASE_ID,
1761
+ dbSystem: "ibm.db2",
1762
+ serverAddress: "db2.prod.example.com",
1763
+ });
1764
+
1765
+ expect(markdown).toContain("The collector has no Db2 receiver");
1766
+ expect(markdown).toContain("resource/database");
1767
+ expect(markdown).toContain("value: ibm.db2");
1768
+ });
1769
+
1770
+ test("an unknown engine gets the identity stamp under its raw name", () => {
1771
+ const markdown: string = guideFor({
1772
+ id: DATABASE_ID,
1773
+ dbSystem: "AcmeDB",
1774
+ serverAddress: "acme.prod.example.com",
1775
+ });
1776
+
1777
+ expect(markdown).toContain("OneUptime does not know AcmeDB's engine yet");
1778
+ expect(markdown).toContain("value: acmedb");
1779
+ });
1780
+
1781
+ test("an in-process engine explains what the page shows instead", () => {
1782
+ const markdown: string = guideFor({ id: DATABASE_ID, dbSystem: "sqlite" });
1783
+
1784
+ expect(markdown).toContain(
1785
+ "SQLite runs inside your application's process, so there is no server to collect engine metrics from",
1786
+ );
1787
+ expect(markdown).toContain(DATABASE_ID);
1788
+ expect(markdown).not.toContain("resource/database");
1789
+ expect(markdown).not.toContain("Database Health monitor");
1790
+ });
1791
+
1792
+ test("an own-collector guide with environment credentials says to double $ in them", () => {
1793
+ expect(
1794
+ guideFor({
1795
+ id: DATABASE_ID,
1796
+ dbSystem: "sap.hana",
1797
+ serverAddress: "hana.prod.example.com",
1798
+ }),
1799
+ ).toContain("write every `$` in a password as `$$`");
1800
+ });
1801
+ });