@oneuptime/common 14.0.3 → 14.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (617) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
  3. package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
  4. package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
  5. package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
  6. package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
  7. package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
  8. package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
  9. package/Models/DatabaseModels/Incident.ts +55 -0
  10. package/Models/DatabaseModels/Index.ts +16 -0
  11. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
  12. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  13. package/Models/DatabaseModels/TelemetryException.ts +2 -0
  14. package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  18. package/Server/Middleware/MultipartFormData.ts +1 -1
  19. package/Server/Middleware/TelemetryIngest.ts +15 -8
  20. package/Server/Services/AlertReminderRuleService.ts +100 -24
  21. package/Server/Services/DatabaseServerEndpointService.ts +895 -0
  22. package/Server/Services/DatabaseServerFeedService.ts +128 -0
  23. package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
  24. package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
  25. package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
  26. package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
  27. package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
  28. package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
  29. package/Server/Services/DatabaseServerService.ts +4546 -0
  30. package/Server/Services/DatabaseService.ts +14 -8
  31. package/Server/Services/DockerResourceService.ts +43 -2
  32. package/Server/Services/IncidentReminderRuleService.ts +103 -23
  33. package/Server/Services/Index.ts +16 -0
  34. package/Server/Services/InventoryItemService.ts +113 -5
  35. package/Server/Services/IoTDeviceService.ts +145 -37
  36. package/Server/Services/OpenTelemetryIngestService.ts +19 -0
  37. package/Server/Services/PodmanResourceService.ts +43 -2
  38. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
  39. package/Server/Services/ServiceService.ts +1 -1
  40. package/Server/Services/TelemetryUsageBillingService.ts +29 -2
  41. package/Server/Services/UserProjectSsoConsentService.ts +74 -0
  42. package/Server/Services/UserService.ts +14 -1
  43. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
  44. package/Server/Types/Database/QueryHelper.ts +30 -1
  45. package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
  46. package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
  47. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
  48. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  49. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
  50. package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
  51. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
  52. package/Server/Utils/Monitor/MonitorResource.ts +47 -11
  53. package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
  54. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
  55. package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
  56. package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
  57. package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
  58. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  59. package/Server/Utils/StartServer.ts +26 -6
  60. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  61. package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
  62. package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
  63. package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
  64. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
  65. package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
  66. package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
  67. package/Server/Utils/Telemetry/Telemetry.ts +16 -0
  68. package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
  69. package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
  70. package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
  71. package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
  72. package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
  73. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
  74. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
  75. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
  76. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
  77. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
  78. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
  79. package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
  80. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
  81. package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
  82. package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
  83. package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
  84. package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
  85. package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
  86. package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
  87. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
  88. package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
  89. package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
  90. package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
  91. package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
  92. package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
  93. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
  94. package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
  95. package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
  96. package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
  97. package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
  98. package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
  99. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
  100. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
  101. package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
  102. package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
  103. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
  104. package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
  105. package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
  106. package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
  107. package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
  108. package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
  109. package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
  110. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
  111. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
  112. package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
  113. package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
  114. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
  115. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  116. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
  117. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
  118. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
  119. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
  120. package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
  121. package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
  122. package/Tests/ClassStrings.ts +178 -0
  123. package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
  124. package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
  125. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
  126. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
  127. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  128. package/Tests/RenderedMarkup.ts +518 -0
  129. package/Tests/ResponsiveVisibility.test.ts +490 -4
  130. package/Tests/ResponsiveVisibility.ts +505 -52
  131. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  132. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
  133. package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
  134. package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
  135. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
  136. package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
  137. package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
  138. package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
  139. package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
  140. package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
  141. package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
  142. package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
  143. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
  144. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
  145. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
  146. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
  147. package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
  148. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
  149. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
  150. package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
  151. package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
  152. package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
  153. package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
  154. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
  155. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
  156. package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
  157. package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
  158. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
  159. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  160. package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
  161. package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
  162. package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
  163. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
  164. package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
  165. package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
  166. package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
  167. package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
  168. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
  169. package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
  170. package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
  171. package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
  172. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
  173. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
  174. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
  175. package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
  176. package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
  177. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
  178. package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
  179. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
  180. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
  181. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
  182. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
  183. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
  184. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
  185. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
  186. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
  187. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
  188. package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
  189. package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
  190. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
  191. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
  192. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
  193. package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
  194. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
  195. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
  196. package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
  197. package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
  198. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
  199. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
  200. package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
  201. package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
  202. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
  203. package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
  204. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
  205. package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
  206. package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
  207. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
  208. package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
  209. package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
  210. package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
  211. package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
  212. package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
  213. package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
  214. package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
  215. package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
  216. package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
  217. package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
  218. package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
  219. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
  220. package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
  221. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
  222. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
  223. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
  224. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
  225. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
  226. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
  227. package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
  228. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
  229. package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
  230. package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
  231. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
  232. package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
  233. package/Tests/UI/Components/Card.test.tsx +93 -2
  234. package/Tests/UI/Components/FeedOptions.test.ts +6 -0
  235. package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
  236. package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
  237. package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
  238. package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
  239. package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
  240. package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
  241. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  242. package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
  243. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
  244. package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
  245. package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
  246. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
  247. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
  248. package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
  249. package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
  250. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +70 -0
  251. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +21 -0
  252. package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
  253. package/Tests/UI/Utils/Navigation.test.ts +102 -0
  254. package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
  255. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  256. package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
  257. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
  258. package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
  259. package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
  260. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
  261. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
  262. package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
  263. package/Tests/Utils/ValueFormatter.test.ts +50 -0
  264. package/Tests/tsconfig.json +16 -0
  265. package/Types/AI/AIResourceContext.ts +7 -0
  266. package/Types/Dashboard/DashboardTemplates.ts +2 -1
  267. package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
  268. package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
  269. package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
  270. package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
  271. package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
  272. package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
  273. package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
  274. package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
  275. package/Types/Email/EmailTemplateType.ts +1 -0
  276. package/Types/Monitor/CephAlertTemplates.ts +18 -0
  277. package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
  278. package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
  279. package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
  280. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
  281. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
  282. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
  283. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
  284. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
  285. package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
  286. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
  287. package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
  288. package/Types/Permission.ts +267 -0
  289. package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
  290. package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
  291. package/Types/Rules/RuleRun.ts +12 -0
  292. package/Types/Telemetry/EntityType.ts +23 -0
  293. package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
  294. package/Types/Telemetry/ServiceType.ts +8 -0
  295. package/UI/Components/Banner/Banner.tsx +1 -1
  296. package/UI/Components/Card/Card.tsx +1 -1
  297. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
  298. package/UI/Components/Charts/Line/LineChart.tsx +7 -0
  299. package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
  300. package/UI/Components/Date/StartAndEndDate.tsx +5 -5
  301. package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
  302. package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
  303. package/UI/Components/Forms/BasicForm.tsx +1 -1
  304. package/UI/Components/Header/Header.tsx +1 -1
  305. package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
  306. package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
  307. package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
  308. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
  309. package/UI/Components/Modal/ModalFooter.tsx +1 -1
  310. package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
  311. package/UI/Components/ModelTable/BaseModelTable.tsx +4 -4
  312. package/UI/Components/Navbar/NavBar.tsx +9 -2
  313. package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
  314. package/UI/Components/Page/Page.tsx +1 -1
  315. package/UI/Components/Pagination/Pagination.tsx +3 -3
  316. package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
  317. package/UI/Components/SideMenu/SideMenu.tsx +4 -1
  318. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
  319. package/UI/Components/StatusPage/ResourceList.tsx +6 -6
  320. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
  321. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
  322. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
  323. package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
  324. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  325. package/UI/Styles/Theme.css +8 -1
  326. package/UI/Utils/Navigation.ts +89 -42
  327. package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
  328. package/UI/Utils/TelemetryService.ts +4 -3
  329. package/Utils/LabelRuleImportExport.ts +2 -0
  330. package/Utils/MetricUnitUtil.ts +103 -11
  331. package/Utils/Monitor/MetricValueFormatter.ts +66 -13
  332. package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
  333. package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
  334. package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
  335. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
  336. package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
  337. package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
  338. package/Utils/Telemetry/EntityKey.ts +176 -0
  339. package/Utils/ValueFormatter.ts +16 -0
  340. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  341. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  342. package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
  343. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
  344. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
  345. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
  346. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
  347. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
  348. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
  349. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
  350. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
  351. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
  352. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
  353. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
  354. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
  355. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
  356. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  357. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  358. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  359. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  360. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  361. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  362. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  363. package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
  364. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  365. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
  366. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
  367. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
  368. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
  369. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
  370. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js.map +1 -0
  371. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  372. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  373. package/build/dist/Server/Middleware/MultipartFormData.js +1 -1
  374. package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
  375. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  376. package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
  377. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
  378. package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
  379. package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
  380. package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
  381. package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
  382. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
  383. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
  384. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
  385. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
  386. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
  387. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
  388. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
  389. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
  390. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
  391. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
  392. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
  393. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
  394. package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
  395. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
  396. package/build/dist/Server/Services/DatabaseService.js +8 -4
  397. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  398. package/build/dist/Server/Services/DockerResourceService.js +30 -3
  399. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  400. package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
  401. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
  402. package/build/dist/Server/Services/Index.js +16 -0
  403. package/build/dist/Server/Services/Index.js.map +1 -1
  404. package/build/dist/Server/Services/InventoryItemService.js +88 -5
  405. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  406. package/build/dist/Server/Services/IoTDeviceService.js +111 -24
  407. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  408. package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
  409. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  410. package/build/dist/Server/Services/PodmanResourceService.js +30 -3
  411. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  412. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
  413. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
  414. package/build/dist/Server/Services/ServiceService.js +1 -1
  415. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  416. package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
  417. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  418. package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
  419. package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
  420. package/build/dist/Server/Services/UserService.js +13 -1
  421. package/build/dist/Server/Services/UserService.js.map +1 -1
  422. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
  423. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  424. package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
  425. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  426. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
  427. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  428. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
  429. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
  430. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
  431. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  432. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
  433. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  434. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
  435. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  436. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
  437. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  438. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
  439. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  440. package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
  441. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  442. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
  443. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
  444. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
  445. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
  446. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
  447. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
  448. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
  449. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  450. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
  451. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  452. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  453. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  454. package/build/dist/Server/Utils/StartServer.js +20 -4
  455. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  456. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  457. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  458. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
  459. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
  460. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
  461. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  462. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
  463. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
  464. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
  465. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  466. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
  467. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
  468. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
  469. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
  470. package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
  471. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  472. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
  473. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  474. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
  475. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
  476. package/build/dist/Types/AI/AIResourceContext.js +7 -0
  477. package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
  478. package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
  479. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  480. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
  481. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
  482. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
  483. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
  484. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
  485. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
  486. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
  487. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
  488. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
  489. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
  490. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
  491. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
  492. package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
  493. package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
  494. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
  495. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
  496. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  497. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  498. package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
  499. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
  500. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
  501. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
  502. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
  503. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  504. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
  505. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
  506. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
  507. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  508. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
  509. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  510. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
  511. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  512. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
  513. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
  514. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
  515. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
  516. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
  517. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
  518. package/build/dist/Types/Permission.js +234 -0
  519. package/build/dist/Types/Permission.js.map +1 -1
  520. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
  521. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  522. package/build/dist/Types/Rules/RuleRun.js +4 -0
  523. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  524. package/build/dist/Types/Telemetry/EntityType.js +23 -0
  525. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  526. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
  527. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
  528. package/build/dist/Types/Telemetry/ServiceType.js +8 -0
  529. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  530. package/build/dist/UI/Components/Banner/Banner.js +1 -1
  531. package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
  532. package/build/dist/UI/Components/Card/Card.js +1 -1
  533. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  534. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  535. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  536. package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
  537. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  538. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
  539. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
  540. package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
  541. package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
  542. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
  543. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
  544. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
  545. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
  546. package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
  547. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  548. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
  549. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  550. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
  551. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
  552. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
  553. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
  554. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
  555. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  556. package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
  557. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  558. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  559. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  560. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +4 -4
  561. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  562. package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
  563. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  564. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
  565. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  566. package/build/dist/UI/Components/Page/Page.js +1 -1
  567. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  568. package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
  569. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  570. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
  571. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
  572. package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
  573. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  574. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
  575. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
  576. package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
  577. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  578. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
  579. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  580. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
  581. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
  582. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
  583. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
  584. package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
  585. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  586. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  587. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  588. package/build/dist/UI/Utils/Navigation.js +66 -33
  589. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  590. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
  591. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
  592. package/build/dist/UI/Utils/TelemetryService.js +3 -2
  593. package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
  594. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  595. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  596. package/build/dist/Utils/MetricUnitUtil.js +77 -13
  597. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  598. package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
  599. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
  600. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
  601. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
  602. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
  603. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
  604. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
  605. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
  606. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
  607. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  608. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
  609. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
  610. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
  611. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
  612. package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
  613. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  614. package/build/dist/Utils/ValueFormatter.js +15 -0
  615. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  616. package/package.json +1 -1
  617. package/tsconfig.tests.json +7 -5
@@ -0,0 +1,1447 @@
1
+ /*
2
+ * MonitorCriteriaEvaluator reaches the template renderer, which loads the
3
+ * native isolated-vm addon. Nothing here uses the sandbox and the
4
+ * prebuilt binary cannot always dlopen in the test environment.
5
+ */
6
+ jest.mock("isolated-vm", () => {
7
+ return {};
8
+ });
9
+
10
+ import Monitor from "../../../../Models/DatabaseModels/Monitor";
11
+ import MonitorCriteriaEvaluator from "../../../../Server/Utils/Monitor/MonitorCriteriaEvaluator";
12
+ import AggregateModel from "../../../../Types/BaseDatabase/AggregatedModel";
13
+ import AggregatedResult from "../../../../Types/BaseDatabase/AggregatedResult";
14
+ import Dictionary from "../../../../Types/Dictionary";
15
+ import { JSONObject } from "../../../../Types/JSON";
16
+ import MetricQueryConfigData from "../../../../Types/Metrics/MetricQueryConfigData";
17
+ import MetricsAggregationType from "../../../../Types/Metrics/MetricsAggregationType";
18
+ import MetricsViewConfig from "../../../../Types/Metrics/MetricsViewConfig";
19
+ import { FilterType } from "../../../../Types/Monitor/CriteriaFilter";
20
+ import {
21
+ buildKubernetesMonitorConfig,
22
+ buildKubernetesMonitorStep,
23
+ buildOfflineCriteriaInstance,
24
+ buildOnlineCriteriaInstance,
25
+ getKubernetesAlertTemplateById,
26
+ KubernetesAlertTemplate,
27
+ KubernetesAlertTemplateArgs,
28
+ } from "../../../../Types/Monitor/KubernetesAlertTemplates";
29
+ import { getKubernetesMetricByMetricName } from "../../../../Types/Monitor/KubernetesMetricCatalog";
30
+ import MetricCriteriaContext from "../../../../Types/Monitor/MetricMonitor/MetricCriteriaContext";
31
+ import MetricMonitorResponse, {
32
+ KubernetesAffectedResource,
33
+ KubernetesResourceBreakdown,
34
+ } from "../../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
35
+ import MetricSeriesResult from "../../../../Types/Monitor/MetricMonitor/MetricSeriesResult";
36
+ import MonitorCriteriaInstance from "../../../../Types/Monitor/MonitorCriteriaInstance";
37
+ import MonitorEvaluationSummary from "../../../../Types/Monitor/MonitorEvaluationSummary";
38
+ import MonitorStep from "../../../../Types/Monitor/MonitorStep";
39
+ import { KubernetesResourceScope } from "../../../../Types/Monitor/MonitorStepKubernetesMonitor";
40
+ import MonitorType from "../../../../Types/Monitor/MonitorType";
41
+ import { buildUnhealthyCriteriaInstance } from "../../../../Types/Monitor/Recommendation/RecommendationCriteriaBuilder";
42
+ import ObjectID from "../../../../Types/ObjectID";
43
+ import ProbeApiIngestResponse, {
44
+ MatchedCriteriaResult,
45
+ PerSeriesCriteriaMatch,
46
+ } from "../../../../Types/Probe/ProbeApiIngestResponse";
47
+ import RollingTime from "../../../../Types/RollingTime/RollingTime";
48
+ import PlatformMetricUnitUtil from "../../../../Utils/Monitor/PlatformMetricUnitUtil";
49
+ import PlatformResourceIdentity from "../../../../Utils/Monitor/PlatformResourceIdentity";
50
+ import { describe, expect, test } from "@jest/globals";
51
+
52
+ /*
53
+ * The "Affected Resources" list of a GROUPED Kubernetes monitor is built
54
+ * from the series the matched criteria actually breached, not from the
55
+ * worker's raw datapoint scan. Before that, every ratio template listed
56
+ * the scan of whichever query ran last — the DENOMINATOR, in raw bytes or
57
+ * cores — and every resource in the cluster, breaching or not:
58
+ *
59
+ * Affected Resources (4 total)
60
+ * 1. Node `gke-...-1nd7` — 257760964608
61
+ *
62
+ * These tests drive every shipped grouped Kubernetes template through the
63
+ * public evaluator entry point with the response shape the worker builds
64
+ * (per-series query rows carrying the raw datapoint's attributes, formula
65
+ * rows carrying only the group labels, one tagged raw scan per query, and
66
+ * catalog-overlaid native units) and pin what the email says: which
67
+ * resources are listed, how each is titled, what value — in which unit —
68
+ * it carries, in which order, and what the analysis says about the worst
69
+ * one.
70
+ */
71
+
72
+ const CLUSTER: string = "prod-east";
73
+
74
+ const CLUSTER_KEY: string = "resource.k8s.cluster.name";
75
+ const NODE_KEY: string = "resource.k8s.node.name";
76
+ const NAMESPACE_KEY: string = "resource.k8s.namespace.name";
77
+ const POD_KEY: string = "resource.k8s.pod.name";
78
+ const CONTAINER_KEY: string = "resource.k8s.container.name";
79
+ const DEPLOYMENT_KEY: string = "resource.k8s.deployment.name";
80
+ const HPA_KEY: string = "resource.k8s.hpa.name";
81
+
82
+ const MINUTES: Array<Date> = [0, 1, 2, 3, 4].map((minute: number): Date => {
83
+ return new Date(Date.UTC(2026, 8, 25, 10, minute, 0));
84
+ });
85
+
86
+ /*
87
+ * The units the OpenTelemetry exporters declare for these metrics
88
+ * (MetricType.unit). The worker overlays the Kubernetes catalog on top of
89
+ * them — see PlatformMetricUnitUtil — and so do these tests, by calling
90
+ * the same helper.
91
+ */
92
+ const DECLARED_UNITS: Map<string, string> = new Map<string, string>([
93
+ ["k8s.node.cpu.usage", "{cpu}"],
94
+ ["k8s.node.allocatable_cpu", "{cpu}"],
95
+ ["k8s.node.memory.usage", "By"],
96
+ ["k8s.node.allocatable_memory", "By"],
97
+ ["k8s.node.filesystem.usage", "By"],
98
+ ["k8s.node.filesystem.available", "By"],
99
+ ["k8s.container.cpu_request", "{cpu}"],
100
+ ["k8s.container.memory_request", "By"],
101
+ ["k8s.hpa.current_replicas", "{pod}"],
102
+ ["k8s.hpa.max_replicas", "{pod}"],
103
+ ["k8s.container.restarts", "{restart}"],
104
+ ["k8s.pod.memory_limit_utilization", "1"],
105
+ ["k8s.node.condition_ready", "1"],
106
+ ]);
107
+
108
+ /*
109
+ * ---------------------------------------------------------------------------
110
+ * Monitor step helpers
111
+ * ---------------------------------------------------------------------------
112
+ */
113
+
114
+ function templateArgs(): KubernetesAlertTemplateArgs {
115
+ return {
116
+ clusterIdentifier: CLUSTER,
117
+ onlineMonitorStatusId: ObjectID.generate(),
118
+ offlineMonitorStatusId: ObjectID.generate(),
119
+ defaultIncidentSeverityId: ObjectID.generate(),
120
+ defaultAlertSeverityId: ObjectID.generate(),
121
+ monitorName: "prod-east - Kubernetes",
122
+ };
123
+ }
124
+
125
+ function templateStep(templateId: string): MonitorStep {
126
+ const template: KubernetesAlertTemplate | undefined =
127
+ getKubernetesAlertTemplateById(templateId);
128
+
129
+ if (!template) {
130
+ throw new Error(`${templateId} template missing`);
131
+ }
132
+
133
+ return template.getMonitorStep(templateArgs());
134
+ }
135
+
136
+ function viewConfigOf(step: MonitorStep): MetricsViewConfig {
137
+ const config: MetricsViewConfig | undefined =
138
+ MonitorStep.getMetricsViewConfig(step);
139
+
140
+ if (!config) {
141
+ throw new Error("monitor step has no metric view config");
142
+ }
143
+
144
+ return config;
145
+ }
146
+
147
+ function metricNameOf(query: MetricQueryConfigData): string {
148
+ return (query.metricQueryData?.filterData?.metricName as string) || "";
149
+ }
150
+
151
+ // [firing, recovery] — the order every Kubernetes template builds them in.
152
+ function criteriaOf(step: MonitorStep): {
153
+ firing: MonitorCriteriaInstance;
154
+ recovery: MonitorCriteriaInstance;
155
+ } {
156
+ const instances: Array<MonitorCriteriaInstance> =
157
+ step.data?.monitorCriteria?.data?.monitorCriteriaInstanceArray || [];
158
+
159
+ return { firing: instances[0]!, recovery: instances[1]! };
160
+ }
161
+
162
+ /*
163
+ * A hand-built grouped Kubernetes monitor over one query: what a user gets
164
+ * from the monitor form, rather than from a template card.
165
+ */
166
+ function customGroupedStep(input: {
167
+ metricName: string;
168
+ metricAlias: string;
169
+ aggregationType: MetricsAggregationType;
170
+ groupByAttributeKeys: Array<string>;
171
+ firing: MonitorCriteriaInstance;
172
+ }): MonitorStep {
173
+ const args: KubernetesAlertTemplateArgs = templateArgs();
174
+
175
+ return buildKubernetesMonitorStep({
176
+ kubernetesMonitor: buildKubernetesMonitorConfig({
177
+ clusterIdentifier: CLUSTER,
178
+ metricName: input.metricName,
179
+ metricAlias: input.metricAlias,
180
+ resourceScope: KubernetesResourceScope.Node,
181
+ rollingTime: RollingTime.Past5Minutes,
182
+ aggregationType: input.aggregationType,
183
+ groupByAttributeKeys: input.groupByAttributeKeys,
184
+ }),
185
+ offlineCriteriaInstance: input.firing,
186
+ onlineCriteriaInstance: buildOnlineCriteriaInstance({
187
+ onlineMonitorStatusId: args.onlineMonitorStatusId,
188
+ metricAlias: input.metricAlias,
189
+ filterType: FilterType.LessThanOrEqualTo,
190
+ value: 1,
191
+ }),
192
+ });
193
+ }
194
+
195
+ /*
196
+ * ---------------------------------------------------------------------------
197
+ * Worker response helpers
198
+ * ---------------------------------------------------------------------------
199
+ */
200
+
201
+ /*
202
+ * One series as the worker hands it over: its group-by labels, the
203
+ * attributes every raw datapoint behind it carries on top of those, and a
204
+ * value per minute for each query (and the formula, when there is one) —
205
+ * already in each query's display unit.
206
+ */
207
+ interface SeriesSpec {
208
+ labels: JSONObject;
209
+ datapointAttributes?: JSONObject | undefined;
210
+ queryValues: Array<Array<number>>;
211
+ formulaValues?: Array<number> | undefined;
212
+ }
213
+
214
+ function row(input: {
215
+ timestamp: Date;
216
+ value: number;
217
+ attributes: JSONObject;
218
+ }): AggregateModel {
219
+ return {
220
+ timestamp: input.timestamp,
221
+ value: input.value,
222
+ attributes: input.attributes,
223
+ } as unknown as AggregateModel;
224
+ }
225
+
226
+ function rawAttributesOf(spec: SeriesSpec): JSONObject {
227
+ return {
228
+ [CLUSTER_KEY]: CLUSTER,
229
+ ...(spec.datapointAttributes || {}),
230
+ ...spec.labels,
231
+ };
232
+ }
233
+
234
+ function unitsByMetricNameOf(
235
+ queries: Array<MetricQueryConfigData>,
236
+ ): Dictionary<string> {
237
+ return PlatformMetricUnitUtil.buildUnitsByMetricName({
238
+ platform: "kubernetes",
239
+ metricNames: queries.map(metricNameOf),
240
+ declaredUnitsByMetricName: DECLARED_UNITS,
241
+ });
242
+ }
243
+
244
+ function workerResponse(input: {
245
+ step: MonitorStep;
246
+ series: Array<SeriesSpec>;
247
+ }): MetricMonitorResponse {
248
+ const config: MetricsViewConfig = viewConfigOf(input.step);
249
+ const queries: Array<MetricQueryConfigData> = config.queryConfigs;
250
+ const hasFormula: boolean = config.formulaConfigs.length > 0;
251
+
252
+ /*
253
+ * Aligned [queries..., formulas...]: query rows keep the raw datapoint's
254
+ * full attribute map, formula rows only the group labels.
255
+ */
256
+ const seriesBreakdown: Array<MetricSeriesResult> = input.series.map(
257
+ (spec: SeriesSpec, index: number): MetricSeriesResult => {
258
+ const queryResults: Array<AggregatedResult> = queries.map(
259
+ (_query: MetricQueryConfigData, queryIndex: number) => {
260
+ return {
261
+ data: (spec.queryValues[queryIndex] || []).map(
262
+ (value: number, minute: number): AggregateModel => {
263
+ return row({
264
+ timestamp: MINUTES[minute]!,
265
+ value: value,
266
+ attributes: rawAttributesOf(spec),
267
+ });
268
+ },
269
+ ),
270
+ };
271
+ },
272
+ );
273
+
274
+ const formulaResults: Array<AggregatedResult> = hasFormula
275
+ ? [
276
+ {
277
+ data: (spec.formulaValues || []).map(
278
+ (value: number, minute: number): AggregateModel => {
279
+ return row({
280
+ timestamp: MINUTES[minute]!,
281
+ value: value,
282
+ attributes: { ...spec.labels },
283
+ });
284
+ },
285
+ ),
286
+ },
287
+ ]
288
+ : [];
289
+
290
+ return {
291
+ fingerprint: `fp-${index}`,
292
+ labels: spec.labels,
293
+ aggregatedResults: [...queryResults, ...formulaResults],
294
+ };
295
+ },
296
+ );
297
+
298
+ const unitsByMetricName: Dictionary<string> = unitsByMetricNameOf(queries);
299
+
300
+ /*
301
+ * One raw scan per query, tagged with its alias, in the metric's NATIVE
302
+ * unit (the scan never goes through the display-unit converter) — so a
303
+ * list built from it would show bytes / cores / fractions.
304
+ */
305
+ const breakdowns: Array<KubernetesResourceBreakdown> = queries.map(
306
+ (
307
+ query: MetricQueryConfigData,
308
+ queryIndex: number,
309
+ ): KubernetesResourceBreakdown => {
310
+ const metricName: string = metricNameOf(query);
311
+ const toNative: (value: number) => number =
312
+ query.metricAliasData?.legendUnit === "%"
313
+ ? (value: number): number => {
314
+ return value / 100;
315
+ }
316
+ : (value: number): number => {
317
+ return value;
318
+ };
319
+
320
+ return {
321
+ clusterName: CLUSTER,
322
+ metricName: metricName,
323
+ metricFriendlyName:
324
+ getKubernetesMetricByMetricName(metricName)?.friendlyName ||
325
+ metricName,
326
+ metricAlias: query.metricAliasData?.metricVariable,
327
+ metricUnit: unitsByMetricName[metricName.toLowerCase()],
328
+ attributes: { [CLUSTER_KEY]: CLUSTER },
329
+ affectedResources: input.series
330
+ .filter((spec: SeriesSpec): boolean => {
331
+ return (spec.queryValues[queryIndex] || []).length > 0;
332
+ })
333
+ .map((spec: SeriesSpec): KubernetesAffectedResource => {
334
+ const values: Array<number> =
335
+ spec.queryValues[queryIndex]!.map(toNative);
336
+
337
+ return {
338
+ ...PlatformResourceIdentity.kubernetes(rawAttributesOf(spec)),
339
+ metricValue: Math.max(...values),
340
+ lowestMetricValue: Math.min(...values),
341
+ };
342
+ }),
343
+ };
344
+ },
345
+ );
346
+
347
+ return {
348
+ projectId: ObjectID.generate(),
349
+ monitorId: ObjectID.generate(),
350
+ metricResult: [],
351
+ metricViewConfig: config,
352
+ seriesBreakdown: seriesBreakdown,
353
+ kubernetesResourceBreakdowns: breakdowns,
354
+ nativeUnitsByMetricName: unitsByMetricName,
355
+ };
356
+ }
357
+
358
+ type PercentOf = (numerator: number, denominator: number) => number;
359
+
360
+ const plainRatio: PercentOf = (numerator: number, denominator: number) => {
361
+ return (numerator / denominator) * 100;
362
+ };
363
+
364
+ // k8s-high-disk-usage: used / (used + available) * 100, as `df` reports it.
365
+ const diskUsage: PercentOf = (used: number, available: number) => {
366
+ return (used / (used + available)) * 100;
367
+ };
368
+
369
+ function perMinute(value: number | Array<number>): Array<number> {
370
+ return Array.isArray(value)
371
+ ? value
372
+ : MINUTES.map((): number => {
373
+ return value;
374
+ });
375
+ }
376
+
377
+ /*
378
+ * One series of a two-query ratio template, with the formula row the
379
+ * worker computes from the two query rows.
380
+ */
381
+ function ratioSeries(input: {
382
+ labels: JSONObject;
383
+ numerator: number | Array<number>;
384
+ denominator: number | Array<number>;
385
+ percentOf: PercentOf;
386
+ }): SeriesSpec {
387
+ const numerator: Array<number> = perMinute(input.numerator);
388
+ const denominator: Array<number> = perMinute(input.denominator);
389
+
390
+ return {
391
+ labels: input.labels,
392
+ queryValues: [numerator, denominator],
393
+ formulaValues: numerator.map((value: number, minute: number): number => {
394
+ return input.percentOf(value, denominator[minute]!);
395
+ }),
396
+ };
397
+ }
398
+
399
+ function node(name: string): JSONObject {
400
+ return { [NODE_KEY]: name };
401
+ }
402
+
403
+ /*
404
+ * ---------------------------------------------------------------------------
405
+ * Evaluation + rendered-markdown helpers
406
+ * ---------------------------------------------------------------------------
407
+ */
408
+
409
+ async function evaluate(input: {
410
+ step: MonitorStep;
411
+ response: MetricMonitorResponse;
412
+ }): Promise<ProbeApiIngestResponse> {
413
+ const monitor: Monitor = new Monitor();
414
+ monitor._id = ObjectID.generate().toString();
415
+ monitor.monitorType = MonitorType.Kubernetes;
416
+ monitor.name = "prod-east - Kubernetes";
417
+
418
+ return MonitorCriteriaEvaluator.processMonitorStep({
419
+ dataToProcess: input.response,
420
+ monitorStep: input.step,
421
+ monitor: monitor,
422
+ probeApiIngestResponse: {
423
+ monitorId: monitor.id!,
424
+ rootCause: null,
425
+ },
426
+ evaluationSummary: {
427
+ criteriaResults: [],
428
+ events: [],
429
+ } as unknown as MonitorEvaluationSummary,
430
+ });
431
+ }
432
+
433
+ // The rendered root cause of one matched criteria, or "" when it did not match.
434
+ function rootCauseOf(
435
+ response: ProbeApiIngestResponse,
436
+ criteria: MonitorCriteriaInstance,
437
+ ): string {
438
+ const matched: MatchedCriteriaResult | undefined = (
439
+ response.matchedCriteria || []
440
+ ).find((result: MatchedCriteriaResult): boolean => {
441
+ return result.criteriaId === criteria.data?.id?.toString();
442
+ });
443
+
444
+ return matched?.rootCause || "";
445
+ }
446
+
447
+ async function evaluateFiring(input: {
448
+ step: MonitorStep;
449
+ series: Array<SeriesSpec>;
450
+ }): Promise<string> {
451
+ const response: ProbeApiIngestResponse = await evaluate({
452
+ step: input.step,
453
+ response: workerResponse(input),
454
+ });
455
+
456
+ return rootCauseOf(response, criteriaOf(input.step).firing);
457
+ }
458
+
459
+ // From the "Affected Resources" heading up to the analysis that follows it.
460
+ function affectedResourcesSection(rootCause: string): string {
461
+ const start: number = rootCause.indexOf("**Affected Resources**");
462
+
463
+ if (start < 0) {
464
+ return "";
465
+ }
466
+
467
+ const end: number = rootCause.indexOf("**Root Cause Analysis**", start);
468
+
469
+ return (
470
+ end < 0 ? rootCause.substring(start) : rootCause.substring(start, end)
471
+ ).trim();
472
+ }
473
+
474
+ const ENTRY_HEAD: RegExp = /^\d+\. /;
475
+
476
+ // The first line of every numbered entry in the list.
477
+ function entryHeads(section: string): Array<string> {
478
+ return section.split("\n").filter((line: string): boolean => {
479
+ return ENTRY_HEAD.test(line);
480
+ });
481
+ }
482
+
483
+ /*
484
+ * "Friendly (`metric`)", or just "`metric`" when the catalog has no
485
+ * friendlier name — looked up rather than hard-coded, since the catalog
486
+ * is still growing.
487
+ */
488
+ function describedMetric(metricName: string): string {
489
+ const friendlyName: string = (
490
+ getKubernetesMetricByMetricName(metricName)?.friendlyName || ""
491
+ ).trim();
492
+
493
+ if (!friendlyName || friendlyName === metricName) {
494
+ return `\`${metricName}\``;
495
+ }
496
+
497
+ return `${friendlyName} (\`${metricName}\`)`;
498
+ }
499
+
500
+ const PERCENT_ENTRY: RegExp = / — \*\*\d+\.\d{2}%\*\*$/;
501
+ const LONG_RAW_NUMBER: RegExp = /\b\d{10,}\b/;
502
+ const BOLD_BYTES: RegExp = /\*\*[\d.]+ (B|KB|MB|GB|TB|PB)\*\*/;
503
+
504
+ /*
505
+ * ---------------------------------------------------------------------------
506
+ * 1. Every ratio template
507
+ * ---------------------------------------------------------------------------
508
+ */
509
+
510
+ interface RatioTemplateCase {
511
+ templateId: string;
512
+ resourceNoun: string;
513
+ legend: string;
514
+ expression: string;
515
+ series: Array<SeriesSpec>;
516
+ // Each entry exactly as rendered: its head line, then its detail bullets.
517
+ expectedEntries: Array<string>;
518
+ notListed: Array<string>;
519
+ analysis: Array<string>;
520
+ }
521
+
522
+ const RATIO_TEMPLATE_CASES: Array<RatioTemplateCase> = [
523
+ {
524
+ templateId: "k8s-high-cpu",
525
+ resourceNoun: "nodes",
526
+ legend: "Node CPU Utilization (%)",
527
+ expression: "(used_cpu / alloc_cpu) * 100",
528
+ series: [
529
+ ratioSeries({
530
+ labels: node("node-cpu-a"),
531
+ numerator: [3.64, 3.68, 3.72, 3.76, 3.8],
532
+ denominator: 4,
533
+ percentOf: plainRatio,
534
+ }),
535
+ // One sample at 97.5%, the rest at 50%: not sustained, not affected.
536
+ ratioSeries({
537
+ labels: node("node-cpu-spiky"),
538
+ numerator: [2, 2, 3.9, 2, 2],
539
+ denominator: 4,
540
+ percentOf: plainRatio,
541
+ }),
542
+ ratioSeries({
543
+ labels: node("node-cpu-c"),
544
+ numerator: 1.84,
545
+ denominator: 2,
546
+ percentOf: plainRatio,
547
+ }),
548
+ ratioSeries({
549
+ labels: node("node-cpu-b"),
550
+ numerator: [7.6, 7.68, 7.76, 7.84, 7.92],
551
+ denominator: 8,
552
+ percentOf: plainRatio,
553
+ }),
554
+ ratioSeries({
555
+ labels: node("node-cpu-idle"),
556
+ numerator: 1,
557
+ denominator: 4,
558
+ percentOf: plainRatio,
559
+ }),
560
+ ],
561
+ expectedEntries: [
562
+ "1. **Node** `node-cpu-b` — **99.00%**",
563
+ "2. **Node** `node-cpu-a` — **95.00%**",
564
+ "3. **Node** `node-cpu-c` — **92.00%**",
565
+ ],
566
+ notListed: ["node-cpu-spiky", "node-cpu-idle"],
567
+ analysis: [
568
+ "Node CPU utilization has exceeded the configured threshold.",
569
+ "Node `node-cpu-b` is at **99.00%** CPU utilization.",
570
+ ],
571
+ },
572
+ {
573
+ templateId: "k8s-high-memory",
574
+ resourceNoun: "nodes",
575
+ legend: "Node Memory Utilization (%)",
576
+ expression: "(used_mem / alloc_mem) * 100",
577
+ series: [
578
+ ratioSeries({
579
+ labels: node("node-mem-a"),
580
+ numerator: [
581
+ 13760000000, 14000000000, 14400000000, 14560000000, 14720000000,
582
+ ],
583
+ denominator: 16000000000,
584
+ percentOf: plainRatio,
585
+ }),
586
+ ratioSeries({
587
+ labels: node("node-mem-ok"),
588
+ numerator: 16000000000,
589
+ denominator: 32000000000,
590
+ percentOf: plainRatio,
591
+ }),
592
+ ratioSeries({
593
+ labels: node("node-mem-b"),
594
+ numerator: 56000000000,
595
+ denominator: 64000000000,
596
+ percentOf: plainRatio,
597
+ }),
598
+ ],
599
+ expectedEntries: [
600
+ "1. **Node** `node-mem-a` — **92.00%**",
601
+ "2. **Node** `node-mem-b` — **87.50%**",
602
+ ],
603
+ notListed: ["node-mem-ok"],
604
+ analysis: [
605
+ "Node memory utilization has exceeded the configured threshold.",
606
+ "Node `node-mem-a` memory usage is at **92.00%**.",
607
+ ],
608
+ },
609
+ {
610
+ templateId: "k8s-high-disk-usage",
611
+ resourceNoun: "nodes",
612
+ legend: "Node Disk Usage (%)",
613
+ expression: "(used_disk / (used_disk + avail_disk)) * 100",
614
+ series: [
615
+ ratioSeries({
616
+ labels: node("node-disk-a"),
617
+ numerator: [
618
+ 92000000000, 93000000000, 94000000000, 95000000000, 96000000000,
619
+ ],
620
+ denominator: [
621
+ 8000000000, 7000000000, 6000000000, 5000000000, 4000000000,
622
+ ],
623
+ percentOf: diskUsage,
624
+ }),
625
+ ratioSeries({
626
+ labels: node("node-disk-ok"),
627
+ numerator: 50000000000,
628
+ denominator: 150000000000,
629
+ percentOf: diskUsage,
630
+ }),
631
+ ratioSeries({
632
+ labels: node("node-disk-b"),
633
+ numerator: 182000000000,
634
+ denominator: 18000000000,
635
+ percentOf: diskUsage,
636
+ }),
637
+ ],
638
+ expectedEntries: [
639
+ "1. **Node** `node-disk-a` — **96.00%**",
640
+ "2. **Node** `node-disk-b` — **91.00%**",
641
+ ],
642
+ notListed: ["node-disk-ok"],
643
+ analysis: [
644
+ "Node disk/filesystem usage has exceeded the configured threshold.",
645
+ "Node `node-disk-a` filesystem usage is at **96.00%**.",
646
+ ],
647
+ },
648
+ {
649
+ templateId: "k8s-node-cpu-request-utilization",
650
+ resourceNoun: "nodes",
651
+ legend: "Node CPU Request Utilization (%)",
652
+ expression: "(req_cpu / alloc_cpu) * 100",
653
+ series: [
654
+ ratioSeries({
655
+ labels: node("node-req-a"),
656
+ numerator: 7.44,
657
+ denominator: 8,
658
+ percentOf: plainRatio,
659
+ }),
660
+ ratioSeries({
661
+ labels: node("node-req-ok"),
662
+ numerator: 4,
663
+ denominator: 8,
664
+ percentOf: plainRatio,
665
+ }),
666
+ ratioSeries({
667
+ labels: node("node-req-b"),
668
+ numerator: [15.2, 15.36, 15.52, 15.68, 15.84],
669
+ denominator: 16,
670
+ percentOf: plainRatio,
671
+ }),
672
+ ],
673
+ expectedEntries: [
674
+ "1. **Node** `node-req-b` — **99.00%**",
675
+ "2. **Node** `node-req-a` — **93.00%**",
676
+ ],
677
+ notListed: ["node-req-ok"],
678
+ analysis: [
679
+ "Node CPU Request Utilization (%) (`(req_cpu / alloc_cpu) * 100`) has breached the configured threshold.",
680
+ "Most affected node: `node-req-b` (**99.00%**)",
681
+ ],
682
+ },
683
+ {
684
+ templateId: "k8s-node-memory-request-utilization",
685
+ resourceNoun: "nodes",
686
+ legend: "Node Memory Request Utilization (%)",
687
+ expression: "(req_mem / alloc_mem) * 100",
688
+ series: [
689
+ ratioSeries({
690
+ labels: node("node-mreq-b"),
691
+ numerator: 58240000000,
692
+ denominator: 64000000000,
693
+ percentOf: plainRatio,
694
+ }),
695
+ ratioSeries({
696
+ labels: node("node-mreq-ok"),
697
+ numerator: 16000000000,
698
+ denominator: 32000000000,
699
+ percentOf: plainRatio,
700
+ }),
701
+ ratioSeries({
702
+ labels: node("node-mreq-a"),
703
+ numerator: 30400000000,
704
+ denominator: 32000000000,
705
+ percentOf: plainRatio,
706
+ }),
707
+ ],
708
+ expectedEntries: [
709
+ "1. **Node** `node-mreq-a` — **95.00%**",
710
+ "2. **Node** `node-mreq-b` — **91.00%**",
711
+ ],
712
+ notListed: ["node-mreq-ok"],
713
+ analysis: [
714
+ "Node Memory Request Utilization (%) (`(req_mem / alloc_mem) * 100`) has breached the configured threshold.",
715
+ "Most affected node: `node-mreq-a` (**95.00%**)",
716
+ ],
717
+ },
718
+ {
719
+ templateId: "k8s-hpa-at-max-replicas",
720
+ resourceNoun: "HPAs",
721
+ legend: "HPA Replica Saturation (%)",
722
+ expression: "(current_replicas / max_replicas) * 100",
723
+ series: [
724
+ ratioSeries({
725
+ labels: { [NAMESPACE_KEY]: "search", [HPA_KEY]: "search-api" },
726
+ numerator: 9,
727
+ denominator: 10,
728
+ percentOf: plainRatio,
729
+ }),
730
+ ratioSeries({
731
+ labels: { [NAMESPACE_KEY]: "batch", [HPA_KEY]: "worker" },
732
+ numerator: 5,
733
+ denominator: 20,
734
+ percentOf: plainRatio,
735
+ }),
736
+ ratioSeries({
737
+ labels: { [NAMESPACE_KEY]: "checkout", [HPA_KEY]: "checkout-api" },
738
+ numerator: 10,
739
+ denominator: 10,
740
+ percentOf: plainRatio,
741
+ }),
742
+ // At the ceiling for one sample only.
743
+ ratioSeries({
744
+ labels: { [NAMESPACE_KEY]: "reports", [HPA_KEY]: "reports-api" },
745
+ numerator: [5, 5, 20, 5, 5],
746
+ denominator: 20,
747
+ percentOf: plainRatio,
748
+ }),
749
+ ratioSeries({
750
+ labels: { [NAMESPACE_KEY]: "orders", [HPA_KEY]: "orders-api" },
751
+ numerator: 19,
752
+ denominator: 20,
753
+ percentOf: plainRatio,
754
+ }),
755
+ ],
756
+ expectedEntries: [
757
+ "1. **HorizontalPodAutoscaler** `checkout-api` — **100.00%**\n - Namespace: `checkout`",
758
+ "2. **HorizontalPodAutoscaler** `orders-api` — **95.00%**\n - Namespace: `orders`",
759
+ "3. **HorizontalPodAutoscaler** `search-api` — **90.00%**\n - Namespace: `search`",
760
+ ],
761
+ notListed: ["`worker`", "reports-api"],
762
+ analysis: [
763
+ "HPA Replica Saturation (%) (`(current_replicas / max_replicas) * 100`) has breached the configured threshold.",
764
+ "Most affected HorizontalPodAutoscaler: `checkout-api` (**100.00%**)",
765
+ ],
766
+ },
767
+ ];
768
+
769
+ for (const testCase of RATIO_TEMPLATE_CASES) {
770
+ describe(`Kubernetes grouped ratio template ${testCase.templateId}`, () => {
771
+ const run: () => Promise<{
772
+ step: MonitorStep;
773
+ rootCause: string;
774
+ }> = async (): Promise<{ step: MonitorStep; rootCause: string }> => {
775
+ const step: MonitorStep = templateStep(testCase.templateId);
776
+
777
+ return {
778
+ step: step,
779
+ rootCause: await evaluateFiring({
780
+ step: step,
781
+ series: testCase.series,
782
+ }),
783
+ };
784
+ };
785
+
786
+ test(`lists only the breaching ${testCase.resourceNoun}, worst first, valued in %`, async () => {
787
+ const { rootCause } = await run();
788
+ const section: string = affectedResourcesSection(rootCause);
789
+
790
+ expect(section).toContain(
791
+ `**Affected Resources** (${testCase.expectedEntries.length} total)`,
792
+ );
793
+
794
+ expect(entryHeads(section)).toEqual(
795
+ testCase.expectedEntries.map((entry: string): string => {
796
+ return entry.split("\n")[0]!;
797
+ }),
798
+ );
799
+
800
+ for (const entry of testCase.expectedEntries) {
801
+ expect(section).toContain(entry);
802
+ }
803
+
804
+ for (const head of entryHeads(section)) {
805
+ expect(head).toMatch(PERCENT_ENTRY);
806
+ }
807
+
808
+ for (const name of testCase.notListed) {
809
+ expect(section).not.toContain(name);
810
+ }
811
+
812
+ // No raw operand anywhere — bytes, cores, replica counts.
813
+ expect(rootCause).not.toMatch(LONG_RAW_NUMBER);
814
+ expect(rootCause).not.toMatch(BOLD_BYTES);
815
+ expect(rootCause).not.toContain(" cores**");
816
+ expect(section).not.toContain("overflow");
817
+ expect(section).not.toContain("more affected resources");
818
+ });
819
+
820
+ test("names the formula, its expression and the metric behind each operand", async () => {
821
+ const { step, rootCause } = await run();
822
+ const queries: Array<MetricQueryConfigData> =
823
+ viewConfigOf(step).queryConfigs;
824
+
825
+ const componentLines: Array<string> = queries.map(
826
+ (query: MetricQueryConfigData): string => {
827
+ return ` - \`${query.metricAliasData?.metricVariable}\` = ${describedMetric(metricNameOf(query))}`;
828
+ },
829
+ );
830
+
831
+ expect(rootCause).toContain(
832
+ [
833
+ "**Kubernetes Cluster Details**",
834
+ `- Cluster: ${CLUSTER}`,
835
+ `- Metric: ${testCase.legend}`,
836
+ `- Formula: \`${testCase.expression}\``,
837
+ ...componentLines,
838
+ ].join("\n"),
839
+ );
840
+
841
+ // The numerator or denominator is never presented as THE metric.
842
+ for (const query of queries) {
843
+ expect(rootCause).not.toContain(
844
+ `- Metric: ${describedMetric(metricNameOf(query))}`,
845
+ );
846
+ }
847
+ });
848
+
849
+ test("the analysis names the worst resource at its percentage", async () => {
850
+ const { rootCause } = await run();
851
+
852
+ expect(rootCause).toContain("**Root Cause Analysis**");
853
+
854
+ for (const line of testCase.analysis) {
855
+ expect(rootCause).toContain(line);
856
+ }
857
+
858
+ // Never the generic "Kubernetes metric `k8s....`" line for a formula.
859
+ expect(rootCause).not.toContain("Kubernetes metric `");
860
+ });
861
+ });
862
+ }
863
+
864
+ /*
865
+ * ---------------------------------------------------------------------------
866
+ * 2. Grouped single-query templates
867
+ * ---------------------------------------------------------------------------
868
+ */
869
+
870
+ describe("Kubernetes grouped template k8s-crashloopbackoff", () => {
871
+ const series: Array<SeriesSpec> = [
872
+ {
873
+ labels: {
874
+ [NAMESPACE_KEY]: "search",
875
+ [POD_KEY]: "search-5c4b-abc",
876
+ [CONTAINER_KEY]: "indexer",
877
+ },
878
+ datapointAttributes: {
879
+ [DEPLOYMENT_KEY]: "search",
880
+ [NODE_KEY]: "gke-node-2",
881
+ },
882
+ queryValues: [[6, 6, 6, 6, 6]],
883
+ },
884
+ {
885
+ labels: {
886
+ [NAMESPACE_KEY]: "payments",
887
+ [POD_KEY]: "checkout-7d9f-2xk",
888
+ [CONTAINER_KEY]: "api",
889
+ },
890
+ datapointAttributes: {
891
+ [DEPLOYMENT_KEY]: "checkout",
892
+ [NODE_KEY]: "gke-node-1",
893
+ },
894
+ queryValues: [[6, 6, 7, 7, 7]],
895
+ },
896
+ {
897
+ labels: {
898
+ [NAMESPACE_KEY]: "payments",
899
+ [POD_KEY]: "ledger-66f8-q9z",
900
+ [CONTAINER_KEY]: "ledger",
901
+ },
902
+ datapointAttributes: {
903
+ [DEPLOYMENT_KEY]: "ledger",
904
+ [NODE_KEY]: "gke-node-3",
905
+ },
906
+ queryValues: [[0, 0, 0, 0, 0]],
907
+ },
908
+ ];
909
+
910
+ test("titles each container with its pod and borrows namespace, deployment and node from the datapoint", async () => {
911
+ const rootCause: string = await evaluateFiring({
912
+ step: templateStep("k8s-crashloopbackoff"),
913
+ series: series,
914
+ });
915
+ const section: string = affectedResourcesSection(rootCause);
916
+
917
+ expect(section).toContain("**Affected Resources** (2 total)");
918
+ expect(entryHeads(section)).toEqual([
919
+ "1. **Container** `api` in pod `checkout-7d9f-2xk` — **7**",
920
+ "2. **Container** `indexer` in pod `search-5c4b-abc` — **6**",
921
+ ]);
922
+ expect(section).toContain(
923
+ [
924
+ "1. **Container** `api` in pod `checkout-7d9f-2xk` — **7**",
925
+ " - Namespace: `payments`",
926
+ " - Deployment: `checkout`",
927
+ " - Node: `gke-node-1`",
928
+ ].join("\n"),
929
+ );
930
+ expect(section).toContain(
931
+ [
932
+ "2. **Container** `indexer` in pod `search-5c4b-abc` — **6**",
933
+ " - Namespace: `search`",
934
+ " - Deployment: `search`",
935
+ " - Node: `gke-node-2`",
936
+ ].join("\n"),
937
+ );
938
+
939
+ // The healthy container is not "affected".
940
+ expect(section).not.toContain("ledger");
941
+ });
942
+
943
+ test("names the metric and says how many times the worst container restarted", async () => {
944
+ const rootCause: string = await evaluateFiring({
945
+ step: templateStep("k8s-crashloopbackoff"),
946
+ series: series,
947
+ });
948
+
949
+ expect(rootCause).toContain(
950
+ `- Metric: ${describedMetric("k8s.container.restarts")}`,
951
+ );
952
+ expect(rootCause).not.toContain("- Formula:");
953
+ expect(rootCause).toContain(
954
+ "The container `api` in pod `checkout-7d9f-2xk` has restarted **7** times.",
955
+ );
956
+ expect(rootCause).not.toMatch(LONG_RAW_NUMBER);
957
+ });
958
+ });
959
+
960
+ describe("Kubernetes grouped template k8s-pod-memory-limit-saturation", () => {
961
+ /*
962
+ * The query declares legendUnit "%", so the worker has already converted
963
+ * kubeletstats' 0–1 fraction to 0–100 before the evaluator sees it.
964
+ */
965
+ const series: Array<SeriesSpec> = [
966
+ {
967
+ labels: { [NAMESPACE_KEY]: "payments", [POD_KEY]: "checkout-7d9f-2xk" },
968
+ datapointAttributes: {
969
+ [DEPLOYMENT_KEY]: "checkout",
970
+ [NODE_KEY]: "gke-node-1",
971
+ },
972
+ queryValues: [[91, 92, 93, 92.5, 91.5]],
973
+ },
974
+ {
975
+ labels: { [NAMESPACE_KEY]: "batch", [POD_KEY]: "worker-0" },
976
+ datapointAttributes: { [NODE_KEY]: "gke-node-3" },
977
+ queryValues: [[40, 41, 42, 43, 44]],
978
+ },
979
+ {
980
+ labels: { [NAMESPACE_KEY]: "search", [POD_KEY]: "search-5c4b-abc" },
981
+ datapointAttributes: {
982
+ [DEPLOYMENT_KEY]: "search",
983
+ [NODE_KEY]: "gke-node-2",
984
+ },
985
+ queryValues: [[95.5, 95.5, 95.5, 95.5, 95.5]],
986
+ },
987
+ ];
988
+
989
+ test("lists the saturated pods in % with their namespace, workload and node", async () => {
990
+ const rootCause: string = await evaluateFiring({
991
+ step: templateStep("k8s-pod-memory-limit-saturation"),
992
+ series: series,
993
+ });
994
+ const section: string = affectedResourcesSection(rootCause);
995
+
996
+ expect(section).toContain("**Affected Resources** (2 total)");
997
+ expect(entryHeads(section)).toEqual([
998
+ "1. **Pod** `search-5c4b-abc` — **95.50%**",
999
+ "2. **Pod** `checkout-7d9f-2xk` — **93.00%**",
1000
+ ]);
1001
+ expect(section).toContain(
1002
+ [
1003
+ "2. **Pod** `checkout-7d9f-2xk` — **93.00%**",
1004
+ " - Namespace: `payments`",
1005
+ " - Deployment: `checkout`",
1006
+ " - Node: `gke-node-1`",
1007
+ ].join("\n"),
1008
+ );
1009
+ expect(section).not.toContain("worker-0");
1010
+
1011
+ // Never re-scaled as a fraction ("9300.00%") nor shown as one ("0.93").
1012
+ expect(rootCause).not.toMatch(/\d{4,}\.\d{2}%/);
1013
+ expect(section).not.toContain("**0.9");
1014
+ });
1015
+
1016
+ test("the analysis names the worst pod at its saturation", async () => {
1017
+ const rootCause: string = await evaluateFiring({
1018
+ step: templateStep("k8s-pod-memory-limit-saturation"),
1019
+ series: series,
1020
+ });
1021
+
1022
+ expect(rootCause).toContain(
1023
+ `- Metric: ${describedMetric("k8s.pod.memory_limit_utilization")}`,
1024
+ );
1025
+ expect(rootCause).toContain(
1026
+ "Most affected pod: `search-5c4b-abc` (**95.50%**)",
1027
+ );
1028
+ // Pod-scoped: never answered with the node CPU / memory sentences.
1029
+ expect(rootCause).not.toContain("Node memory utilization");
1030
+ expect(rootCause).not.toContain("CPU utilization.");
1031
+ });
1032
+ });
1033
+
1034
+ describe("Kubernetes grouped template k8s-node-not-ready", () => {
1035
+ const series: Array<SeriesSpec> = [
1036
+ { labels: node("node-up"), queryValues: [[1, 1, 1, 1, 1]] },
1037
+ { labels: node("node-down"), queryValues: [[0, 0, 0, 0, 0]] },
1038
+ ];
1039
+
1040
+ test("lists only the NotReady node, at 0", async () => {
1041
+ const rootCause: string = await evaluateFiring({
1042
+ step: templateStep("k8s-node-not-ready"),
1043
+ series: series,
1044
+ });
1045
+ const section: string = affectedResourcesSection(rootCause);
1046
+
1047
+ expect(section).toBe(
1048
+ "**Affected Resources** (1 total)\n\n1. **Node** `node-down` — **0**",
1049
+ );
1050
+ expect(rootCause).not.toContain("node-up");
1051
+ });
1052
+
1053
+ test("the analysis names the NotReady node, not its Ready peer", async () => {
1054
+ const rootCause: string = await evaluateFiring({
1055
+ step: templateStep("k8s-node-not-ready"),
1056
+ series: series,
1057
+ });
1058
+
1059
+ expect(rootCause).toContain(
1060
+ `- Metric: ${describedMetric("k8s.node.condition_ready")}`,
1061
+ );
1062
+ expect(rootCause).toContain(
1063
+ "Node `node-down` is reporting NotReady (value: 0).",
1064
+ );
1065
+ });
1066
+ });
1067
+
1068
+ /*
1069
+ * ---------------------------------------------------------------------------
1070
+ * 3. Enrichment safety
1071
+ * ---------------------------------------------------------------------------
1072
+ */
1073
+
1074
+ describe("Kubernetes grouped monitor: a node-level sum of container rows", () => {
1075
+ /*
1076
+ * Container CPU requests summed per NODE. Each raw datapoint behind the
1077
+ * sum belongs to some tenant's container, so the breaching sample's
1078
+ * attributes name a pod, container, namespace and deployment that are
1079
+ * not the node's — borrowing them would title the node with an
1080
+ * arbitrary tenant.
1081
+ */
1082
+ function step(): MonitorStep {
1083
+ const args: KubernetesAlertTemplateArgs = templateArgs();
1084
+
1085
+ return customGroupedStep({
1086
+ metricName: "k8s.container.cpu_request",
1087
+ metricAlias: "node_cpu_requests",
1088
+ aggregationType: MetricsAggregationType.Sum,
1089
+ groupByAttributeKeys: [NODE_KEY],
1090
+ firing: buildOfflineCriteriaInstance({
1091
+ offlineMonitorStatusId: args.offlineMonitorStatusId,
1092
+ incidentSeverityId: args.defaultIncidentSeverityId,
1093
+ alertSeverityId: args.defaultAlertSeverityId,
1094
+ monitorName: args.monitorName,
1095
+ metricAlias: "node_cpu_requests",
1096
+ filterType: FilterType.GreaterThan,
1097
+ value: 3,
1098
+ }),
1099
+ });
1100
+ }
1101
+
1102
+ const series: Array<SeriesSpec> = [
1103
+ {
1104
+ labels: node("gke-node-7"),
1105
+ datapointAttributes: {
1106
+ [NAMESPACE_KEY]: "tenant-a",
1107
+ [POD_KEY]: "tenant-a-api-0",
1108
+ [CONTAINER_KEY]: "api",
1109
+ [DEPLOYMENT_KEY]: "tenant-a-api",
1110
+ },
1111
+ queryValues: [[3.5, 3.6, 3.7, 3.8, 3.9]],
1112
+ },
1113
+ {
1114
+ labels: node("gke-node-8"),
1115
+ datapointAttributes: {
1116
+ [NAMESPACE_KEY]: "tenant-b",
1117
+ [POD_KEY]: "tenant-b-web-0",
1118
+ [CONTAINER_KEY]: "web",
1119
+ },
1120
+ queryValues: [[1, 1, 1, 1, 1]],
1121
+ },
1122
+ ];
1123
+
1124
+ test("titles the entry as the node and borrows nothing from the tenant datapoint", async () => {
1125
+ const rootCause: string = await evaluateFiring({
1126
+ step: step(),
1127
+ series: series,
1128
+ });
1129
+ const section: string = affectedResourcesSection(rootCause);
1130
+
1131
+ expect(section).toBe(
1132
+ "**Affected Resources** (1 total)\n\n1. **Node** `gke-node-7` — **3.9 cores**",
1133
+ );
1134
+
1135
+ expect(rootCause).not.toContain("Namespace:");
1136
+ expect(rootCause).not.toContain("Deployment:");
1137
+ expect(rootCause).not.toContain("tenant-a");
1138
+ expect(rootCause).not.toContain("**Container**");
1139
+ expect(rootCause).not.toContain("**Pod**");
1140
+ });
1141
+
1142
+ test("the analysis names the node, not a tenant pod", async () => {
1143
+ const rootCause: string = await evaluateFiring({
1144
+ step: step(),
1145
+ series: series,
1146
+ });
1147
+
1148
+ expect(rootCause).toContain(
1149
+ `- Metric: ${describedMetric("k8s.container.cpu_request")}`,
1150
+ );
1151
+ expect(rootCause).toContain(
1152
+ "Most affected node: `gke-node-7` (**3.9 cores**)",
1153
+ );
1154
+ expect(rootCause).not.toContain("Most affected pod");
1155
+ });
1156
+ });
1157
+
1158
+ /*
1159
+ * ---------------------------------------------------------------------------
1160
+ * 4. More breaching series than the list shows
1161
+ * ---------------------------------------------------------------------------
1162
+ */
1163
+
1164
+ describe("Kubernetes grouped monitor: more than ten breaching series", () => {
1165
+ // 86% .. 97%, in scrambled order, plus one healthy node.
1166
+ const percents: Array<number> = [
1167
+ 90, 86, 97, 88, 93, 91, 87, 95, 89, 96, 92, 94,
1168
+ ];
1169
+ const allocatable: number = 16000000000;
1170
+
1171
+ const series: Array<SeriesSpec> = [
1172
+ ...percents.map((percent: number): SeriesSpec => {
1173
+ return ratioSeries({
1174
+ labels: node(`node-${percent}`),
1175
+ numerator: (allocatable * percent) / 100,
1176
+ denominator: allocatable,
1177
+ percentOf: plainRatio,
1178
+ });
1179
+ }),
1180
+ ratioSeries({
1181
+ labels: node("node-healthy"),
1182
+ numerator: allocatable / 2,
1183
+ denominator: allocatable,
1184
+ percentOf: plainRatio,
1185
+ }),
1186
+ ];
1187
+
1188
+ test("shows the ten worst, counts all twelve, and summarises the rest", async () => {
1189
+ const rootCause: string = await evaluateFiring({
1190
+ step: templateStep("k8s-high-memory"),
1191
+ series: series,
1192
+ });
1193
+ const section: string = affectedResourcesSection(rootCause);
1194
+
1195
+ expect(section).toContain("**Affected Resources** (12 total)");
1196
+
1197
+ const expectedHeads: Array<string> = [
1198
+ 97, 96, 95, 94, 93, 92, 91, 90, 89, 88,
1199
+ ].map((percent: number, index: number): string => {
1200
+ return `${index + 1}. **Node** \`node-${percent}\` — **${percent}.00%**`;
1201
+ });
1202
+
1203
+ expect(entryHeads(section)).toEqual(expectedHeads);
1204
+ expect(section).toContain("*... and 2 more affected resources*");
1205
+
1206
+ // The two least-bad breaching nodes are counted but not shown.
1207
+ expect(section).not.toContain("`node-87`");
1208
+ expect(section).not.toContain("`node-86`");
1209
+ expect(section).not.toContain("node-healthy");
1210
+ expect(rootCause).not.toMatch(LONG_RAW_NUMBER);
1211
+
1212
+ // The analysis still names the worst node.
1213
+ expect(rootCause).toContain(
1214
+ "Node `node-97` memory usage is at **97.00%**.",
1215
+ );
1216
+ });
1217
+ });
1218
+
1219
+ /*
1220
+ * ---------------------------------------------------------------------------
1221
+ * 5. A series that matched with no data
1222
+ * ---------------------------------------------------------------------------
1223
+ */
1224
+
1225
+ type EvaluatorPrivate = {
1226
+ buildKubernetesRootCauseContext: (input: {
1227
+ dataToProcess: unknown;
1228
+ monitorStep: MonitorStep;
1229
+ monitor: Monitor;
1230
+ criteriaInstance?: MonitorCriteriaInstance | undefined;
1231
+ perSeriesMatches?: Array<PerSeriesCriteriaMatch> | undefined;
1232
+ }) => Promise<string | null>;
1233
+ };
1234
+
1235
+ const Evaluator: EvaluatorPrivate =
1236
+ MonitorCriteriaEvaluator as unknown as EvaluatorPrivate;
1237
+
1238
+ describe("Kubernetes grouped monitor: a series that matched with no data", () => {
1239
+ function kubernetesMonitor(): Monitor {
1240
+ const monitor: Monitor = new Monitor();
1241
+ monitor._id = ObjectID.generate().toString();
1242
+ monitor.monitorType = MonitorType.Kubernetes;
1243
+ monitor.name = "prod-east - Kubernetes";
1244
+ return monitor;
1245
+ }
1246
+
1247
+ function formulaContext(
1248
+ breachingValues: Array<number>,
1249
+ ): MetricCriteriaContext {
1250
+ return {
1251
+ metricName: "(used_mem / alloc_mem) * 100",
1252
+ alias: "node_memory_utilization",
1253
+ displayName: "Node Memory Utilization (%)",
1254
+ unit: "%",
1255
+ aggregationType: null,
1256
+ isFormula: true,
1257
+ formulaExpression: "(used_mem / alloc_mem) * 100",
1258
+ filterAttributes: {},
1259
+ groupBy: [],
1260
+ ...(breachingValues.length > 0
1261
+ ? {
1262
+ breachingSamples: breachingValues.map(
1263
+ (value: number, minute: number) => {
1264
+ return {
1265
+ value: value,
1266
+ timestamp: MINUTES[minute]!,
1267
+ attributes: { attributes: {} },
1268
+ };
1269
+ },
1270
+ ),
1271
+ sampleValueRange: {
1272
+ min: Math.min(...breachingValues),
1273
+ max: Math.max(...breachingValues),
1274
+ },
1275
+ }
1276
+ : {}),
1277
+ };
1278
+ }
1279
+
1280
+ function match(input: {
1281
+ nodeName: string;
1282
+ criteriaId: string;
1283
+ breachingValues: Array<number>;
1284
+ }): PerSeriesCriteriaMatch {
1285
+ return {
1286
+ criteriaMetId: input.criteriaId,
1287
+ fingerprint: `fp-${input.nodeName}`,
1288
+ labels: node(input.nodeName),
1289
+ rootCause: "- matched",
1290
+ metricContext: formulaContext(input.breachingValues),
1291
+ };
1292
+ }
1293
+
1294
+ test("renders **no data** for the silent series and sorts it last", async () => {
1295
+ const step: MonitorStep = templateStep("k8s-high-memory");
1296
+ const firing: MonitorCriteriaInstance = criteriaOf(step).firing;
1297
+ const criteriaId: string = firing.data!.id!.toString();
1298
+
1299
+ const context: string | null =
1300
+ await Evaluator.buildKubernetesRootCauseContext({
1301
+ dataToProcess: workerResponse({ step: step, series: [] }),
1302
+ monitorStep: step,
1303
+ monitor: kubernetesMonitor(),
1304
+ criteriaInstance: firing,
1305
+ perSeriesMatches: [
1306
+ match({ nodeName: "node-silent", criteriaId, breachingValues: [] }),
1307
+ match({
1308
+ nodeName: "node-warm",
1309
+ criteriaId,
1310
+ breachingValues: [86.1, 85.5],
1311
+ }),
1312
+ match({
1313
+ nodeName: "node-hot",
1314
+ criteriaId,
1315
+ breachingValues: [88.5, 91.25, 90],
1316
+ }),
1317
+ ],
1318
+ });
1319
+
1320
+ const section: string = affectedResourcesSection(context || "");
1321
+
1322
+ expect(section).toContain("**Affected Resources** (3 total)");
1323
+ expect(entryHeads(section)).toEqual([
1324
+ "1. **Node** `node-hot` — **91.25%**",
1325
+ "2. **Node** `node-warm` — **86.10%**",
1326
+ "3. **Node** `node-silent` — **no data**",
1327
+ ]);
1328
+ });
1329
+
1330
+ test("end to end: a NoDataPolicy-trigger criteria lists the silent node after the breaching one", async () => {
1331
+ const args: KubernetesAlertTemplateArgs = templateArgs();
1332
+
1333
+ const firing: MonitorCriteriaInstance = buildUnhealthyCriteriaInstance({
1334
+ offlineMonitorStatusId: args.offlineMonitorStatusId,
1335
+ incidentSeverityId: args.defaultIncidentSeverityId,
1336
+ alertSeverityId: args.defaultAlertSeverityId,
1337
+ monitorName: args.monitorName,
1338
+ metricAlias: "node_memory",
1339
+ filterType: FilterType.GreaterThan,
1340
+ value: 12000000000,
1341
+ triggerOnNoData: true,
1342
+ });
1343
+
1344
+ const step: MonitorStep = customGroupedStep({
1345
+ metricName: "k8s.node.memory.usage",
1346
+ metricAlias: "node_memory",
1347
+ aggregationType: MetricsAggregationType.Avg,
1348
+ groupByAttributeKeys: [NODE_KEY],
1349
+ firing: firing,
1350
+ });
1351
+
1352
+ const rootCause: string = await evaluateFiring({
1353
+ step: step,
1354
+ series: [
1355
+ { labels: node("node-silent"), queryValues: [[]] },
1356
+ {
1357
+ labels: node("node-full"),
1358
+ queryValues: [
1359
+ [12500000000, 12600000000, 12800000000, 12900000000, 13000000000],
1360
+ ],
1361
+ },
1362
+ {
1363
+ labels: node("node-fine"),
1364
+ queryValues: [
1365
+ [4000000000, 4000000000, 4000000000, 4000000000, 4000000000],
1366
+ ],
1367
+ },
1368
+ ],
1369
+ });
1370
+ const section: string = affectedResourcesSection(rootCause);
1371
+
1372
+ expect(section).toBe(
1373
+ [
1374
+ "**Affected Resources** (2 total)",
1375
+ "",
1376
+ "1. **Node** `node-full` — **13 GB**",
1377
+ "2. **Node** `node-silent` — **no data**",
1378
+ ].join("\n"),
1379
+ );
1380
+ expect(rootCause).not.toMatch(LONG_RAW_NUMBER);
1381
+ });
1382
+ });
1383
+
1384
+ /*
1385
+ * ---------------------------------------------------------------------------
1386
+ * 6. The recovery criteria, evaluated on the same data
1387
+ * ---------------------------------------------------------------------------
1388
+ */
1389
+
1390
+ describe("Kubernetes grouped ratio template k8s-high-memory: recovery criteria", () => {
1391
+ const series: Array<SeriesSpec> = [
1392
+ ratioSeries({
1393
+ labels: node("node-mem-hot"),
1394
+ numerator: 14720000000,
1395
+ denominator: 16000000000,
1396
+ percentOf: plainRatio,
1397
+ }),
1398
+ ratioSeries({
1399
+ labels: node("node-mem-ok"),
1400
+ numerator: 16000000000,
1401
+ denominator: 32000000000,
1402
+ percentOf: plainRatio,
1403
+ }),
1404
+ ratioSeries({
1405
+ labels: node("node-mem-low"),
1406
+ numerator: 16000000000,
1407
+ denominator: 64000000000,
1408
+ percentOf: plainRatio,
1409
+ }),
1410
+ ];
1411
+
1412
+ test("whatever the recovery list shows is valued in % — never bytes", async () => {
1413
+ const step: MonitorStep = templateStep("k8s-high-memory");
1414
+ const { firing, recovery } = criteriaOf(step);
1415
+
1416
+ const response: ProbeApiIngestResponse = await evaluate({
1417
+ step: step,
1418
+ response: workerResponse({ step: step, series: series }),
1419
+ });
1420
+
1421
+ // Both bands fire on a grouped monitor: the hot node, and the two healthy ones.
1422
+ expect(rootCauseOf(response, firing)).toContain(
1423
+ "1. **Node** `node-mem-hot` — **92.00%**",
1424
+ );
1425
+
1426
+ const recoveryRootCause: string = rootCauseOf(response, recovery);
1427
+ const section: string = affectedResourcesSection(recoveryRootCause);
1428
+
1429
+ expect(recoveryRootCause).not.toBe("");
1430
+ expect(section).toContain("**Affected Resources** (2 total)");
1431
+ expect(section).toContain("**Node** `node-mem-ok` — **50.00%**");
1432
+ expect(section).toContain("**Node** `node-mem-low` — **25.00%**");
1433
+ expect(section).not.toContain("node-mem-hot");
1434
+
1435
+ expect(entryHeads(section)).toHaveLength(2);
1436
+ for (const head of entryHeads(section)) {
1437
+ expect(head).toMatch(PERCENT_ENTRY);
1438
+ }
1439
+
1440
+ expect(recoveryRootCause).not.toMatch(LONG_RAW_NUMBER);
1441
+ expect(recoveryRootCause).not.toMatch(BOLD_BYTES);
1442
+ expect(recoveryRootCause).not.toMatch(/memory usage is at \*\*[\d.]+ GB/);
1443
+ expect(recoveryRootCause).toContain(
1444
+ "- Metric: Node Memory Utilization (%)",
1445
+ );
1446
+ });
1447
+ });