@oneuptime/common 14.0.2 → 14.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (765) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/AlertFeed.ts +2 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
  4. package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
  5. package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
  6. package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
  7. package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
  8. package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
  9. package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
  10. package/Models/DatabaseModels/Incident.ts +55 -0
  11. package/Models/DatabaseModels/IncidentAlert.ts +540 -0
  12. package/Models/DatabaseModels/IncidentFeed.ts +2 -0
  13. package/Models/DatabaseModels/Index.ts +18 -0
  14. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
  15. package/Models/DatabaseModels/Project.ts +60 -0
  16. package/Models/DatabaseModels/RumApplication.ts +52 -1
  17. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  18. package/Models/DatabaseModels/TelemetryException.ts +2 -0
  19. package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  25. package/Server/Middleware/MultipartFormData.ts +1 -1
  26. package/Server/Middleware/TelemetryIngest.ts +15 -8
  27. package/Server/Services/AlertReminderRuleService.ts +100 -24
  28. package/Server/Services/DatabaseServerEndpointService.ts +895 -0
  29. package/Server/Services/DatabaseServerFeedService.ts +128 -0
  30. package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
  31. package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
  32. package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
  33. package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
  34. package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
  35. package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
  36. package/Server/Services/DatabaseServerService.ts +4546 -0
  37. package/Server/Services/DatabaseService.ts +14 -8
  38. package/Server/Services/DockerResourceService.ts +43 -2
  39. package/Server/Services/IncidentAlertService.ts +1811 -0
  40. package/Server/Services/IncidentReminderRuleService.ts +103 -23
  41. package/Server/Services/IncidentService.ts +210 -10
  42. package/Server/Services/IncidentStateTimelineService.ts +24 -0
  43. package/Server/Services/Index.ts +18 -0
  44. package/Server/Services/InventoryItemService.ts +113 -5
  45. package/Server/Services/IoTDeviceService.ts +145 -37
  46. package/Server/Services/OpenTelemetryIngestService.ts +19 -0
  47. package/Server/Services/PodmanResourceService.ts +43 -2
  48. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
  49. package/Server/Services/ServiceService.ts +1 -1
  50. package/Server/Services/TelemetryUsageBillingService.ts +29 -2
  51. package/Server/Services/UserProjectSsoConsentService.ts +74 -0
  52. package/Server/Services/UserService.ts +14 -1
  53. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
  54. package/Server/Types/Database/QueryHelper.ts +30 -1
  55. package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
  56. package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
  57. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
  58. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  59. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
  60. package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
  61. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
  62. package/Server/Utils/Monitor/MonitorResource.ts +47 -11
  63. package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
  64. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
  65. package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
  66. package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
  67. package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
  68. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  69. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
  70. package/Server/Utils/StartServer.ts +26 -6
  71. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  72. package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
  73. package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
  74. package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
  75. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
  76. package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
  77. package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
  78. package/Server/Utils/Telemetry/Telemetry.ts +16 -0
  79. package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
  80. package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
  81. package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
  82. package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
  83. package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
  84. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
  85. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
  86. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
  87. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
  88. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
  89. package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
  90. package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
  91. package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
  92. package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
  93. package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
  94. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
  95. package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
  96. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
  97. package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
  98. package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
  99. package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
  100. package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
  101. package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
  102. package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
  103. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
  104. package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
  105. package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
  106. package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
  107. package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
  108. package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
  109. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
  110. package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
  111. package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
  112. package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
  113. package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
  114. package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
  115. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
  116. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
  117. package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
  118. package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
  119. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
  120. package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
  121. package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
  122. package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
  123. package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
  124. package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
  125. package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
  126. package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
  127. package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
  128. package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
  129. package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
  130. package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
  131. package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
  132. package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
  133. package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
  134. package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
  135. package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
  136. package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
  137. package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
  138. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
  139. package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
  140. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
  141. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
  142. package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
  143. package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
  144. package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
  145. package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
  146. package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
  147. package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
  148. package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
  149. package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
  150. package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
  151. package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
  152. package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
  153. package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
  154. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
  155. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
  156. package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
  157. package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
  158. package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
  159. package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
  160. package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
  161. package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
  162. package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
  163. package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
  164. package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
  165. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
  166. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  167. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
  168. package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
  169. package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
  170. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
  171. package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
  172. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
  173. package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
  174. package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
  175. package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
  176. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
  177. package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
  178. package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
  179. package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
  180. package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
  181. package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
  182. package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
  183. package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
  184. package/Tests/ClassStrings.ts +178 -0
  185. package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
  186. package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
  187. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
  188. package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
  189. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
  190. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
  191. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  192. package/Tests/RenderedMarkup.ts +518 -0
  193. package/Tests/ResponsiveVisibility.test.ts +490 -4
  194. package/Tests/ResponsiveVisibility.ts +505 -52
  195. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  196. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
  197. package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
  198. package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
  199. package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
  200. package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
  201. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
  202. package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
  203. package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
  204. package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
  205. package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
  206. package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
  207. package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
  208. package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
  209. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
  210. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
  211. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
  212. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
  213. package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
  214. package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
  215. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
  216. package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
  217. package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
  218. package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
  219. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
  220. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
  221. package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
  222. package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
  223. package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
  224. package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
  225. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
  226. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
  227. package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
  228. package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
  229. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
  230. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  231. package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
  232. package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
  233. package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
  234. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
  235. package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
  236. package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
  237. package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
  238. package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
  239. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
  240. package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
  241. package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
  242. package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
  243. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
  244. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
  245. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
  246. package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
  247. package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
  248. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
  249. package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
  250. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
  251. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
  252. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
  253. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
  254. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
  255. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
  256. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
  257. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
  258. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
  259. package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
  260. package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
  261. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
  262. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
  263. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
  264. package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
  265. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
  266. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
  267. package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
  268. package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
  269. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
  270. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
  271. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
  272. package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
  273. package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
  274. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
  275. package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
  276. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
  277. package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
  278. package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
  279. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
  280. package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
  281. package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
  282. package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
  283. package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
  284. package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
  285. package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
  286. package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
  287. package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
  288. package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
  289. package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
  290. package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
  291. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
  292. package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
  293. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
  294. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
  295. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
  296. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
  297. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
  298. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
  299. package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
  300. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
  301. package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
  302. package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
  303. package/Tests/Types/Rum/WebVitals.test.ts +173 -0
  304. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
  305. package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
  306. package/Tests/UI/Components/Card.test.tsx +93 -2
  307. package/Tests/UI/Components/FeedOptions.test.ts +6 -0
  308. package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
  309. package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
  310. package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
  311. package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
  312. package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
  313. package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
  314. package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
  315. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
  316. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
  317. package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
  318. package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
  319. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  320. package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
  321. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
  322. package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
  323. package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
  324. package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
  325. package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
  326. package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
  327. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
  328. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
  329. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
  330. package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
  331. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
  332. package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
  333. package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
  334. package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
  335. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
  336. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
  337. package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
  338. package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
  339. package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
  340. package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
  341. package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
  342. package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
  343. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +80 -0
  344. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +23 -0
  345. package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
  346. package/Tests/UI/Utils/Navigation.test.ts +102 -0
  347. package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
  348. package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
  349. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
  350. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  351. package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
  352. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
  353. package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
  354. package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
  355. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
  356. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
  357. package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
  358. package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
  359. package/Tests/Utils/ValueFormatter.test.ts +50 -0
  360. package/Tests/tsconfig.json +16 -0
  361. package/Types/AI/AIResourceContext.ts +7 -0
  362. package/Types/Dashboard/DashboardTemplates.ts +2 -1
  363. package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
  364. package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
  365. package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
  366. package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
  367. package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
  368. package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
  369. package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
  370. package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
  371. package/Types/Email/EmailTemplateType.ts +1 -0
  372. package/Types/Incident/IncidentAlertLink.ts +38 -0
  373. package/Types/Monitor/CephAlertTemplates.ts +18 -0
  374. package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
  375. package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
  376. package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
  377. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
  378. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
  379. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
  380. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
  381. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
  382. package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
  383. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
  384. package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
  385. package/Types/Permission.ts +315 -0
  386. package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
  387. package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
  388. package/Types/Rules/RuleRun.ts +12 -0
  389. package/Types/Rum/SessionReplay.ts +25 -6
  390. package/Types/Rum/WebVitals.ts +52 -0
  391. package/Types/Telemetry/EntityType.ts +23 -0
  392. package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
  393. package/Types/Telemetry/ServiceType.ts +8 -0
  394. package/UI/Components/Banner/Banner.tsx +1 -1
  395. package/UI/Components/Card/Card.tsx +1 -1
  396. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
  397. package/UI/Components/Charts/Line/LineChart.tsx +7 -0
  398. package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
  399. package/UI/Components/Date/StartAndEndDate.tsx +5 -5
  400. package/UI/Components/Detail/FieldLabel.tsx +8 -0
  401. package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
  402. package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
  403. package/UI/Components/Forms/BasicForm.tsx +1 -1
  404. package/UI/Components/Header/Header.tsx +1 -1
  405. package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
  406. package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
  407. package/UI/Components/InfoCard/InfoCard.tsx +47 -1
  408. package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
  409. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
  410. package/UI/Components/Modal/ModalFooter.tsx +1 -1
  411. package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
  412. package/UI/Components/ModelTable/BaseModelTable.tsx +99 -21
  413. package/UI/Components/ModelTable/Column.ts +5 -0
  414. package/UI/Components/Navbar/NavBar.tsx +9 -2
  415. package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
  416. package/UI/Components/Page/Page.tsx +1 -1
  417. package/UI/Components/Pagination/Pagination.tsx +3 -3
  418. package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
  419. package/UI/Components/SideMenu/SideMenu.tsx +4 -1
  420. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
  421. package/UI/Components/StatusPage/ResourceList.tsx +6 -6
  422. package/UI/Components/Table/TableHeader.tsx +47 -2
  423. package/UI/Components/Table/Types/Column.ts +7 -0
  424. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
  425. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
  426. package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
  427. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
  428. package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
  429. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  430. package/UI/Styles/Theme.css +8 -1
  431. package/UI/Utils/Navigation.ts +89 -42
  432. package/UI/Utils/PermissionGate.ts +11 -2
  433. package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
  434. package/UI/Utils/TelemetryService.ts +4 -3
  435. package/Utils/Incident/IncidentFromAlerts.ts +516 -0
  436. package/Utils/LabelRuleImportExport.ts +2 -0
  437. package/Utils/MetricUnitUtil.ts +103 -11
  438. package/Utils/Monitor/MetricValueFormatter.ts +66 -13
  439. package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
  440. package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
  441. package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
  442. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
  443. package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
  444. package/Utils/Rum/SessionTraceState.ts +212 -0
  445. package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
  446. package/Utils/Telemetry/EntityKey.ts +176 -0
  447. package/Utils/ValueFormatter.ts +16 -0
  448. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  449. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  450. package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
  451. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  452. package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
  453. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
  454. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
  455. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
  456. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
  457. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
  458. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
  459. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
  460. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
  461. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
  462. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
  463. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
  464. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
  465. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
  466. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  467. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  468. package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
  469. package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
  470. package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
  471. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  472. package/build/dist/Models/DatabaseModels/Index.js +18 -0
  473. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  474. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  475. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  476. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  477. package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
  478. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  479. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  480. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  481. package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
  482. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  483. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
  484. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
  485. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
  486. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
  487. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
  488. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
  489. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
  490. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
  491. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
  492. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js.map +1 -0
  493. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  494. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  495. package/build/dist/Server/Middleware/MultipartFormData.js +1 -1
  496. package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
  497. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  498. package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
  499. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
  500. package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
  501. package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
  502. package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
  503. package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
  504. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
  505. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
  506. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
  507. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
  508. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
  509. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
  510. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
  511. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
  512. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
  513. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
  514. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
  515. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
  516. package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
  517. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
  518. package/build/dist/Server/Services/DatabaseService.js +8 -4
  519. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  520. package/build/dist/Server/Services/DockerResourceService.js +30 -3
  521. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  522. package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
  523. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
  524. package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
  525. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
  526. package/build/dist/Server/Services/IncidentService.js +164 -12
  527. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  528. package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
  529. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  530. package/build/dist/Server/Services/Index.js +18 -0
  531. package/build/dist/Server/Services/Index.js.map +1 -1
  532. package/build/dist/Server/Services/InventoryItemService.js +88 -5
  533. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  534. package/build/dist/Server/Services/IoTDeviceService.js +111 -24
  535. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  536. package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
  537. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  538. package/build/dist/Server/Services/PodmanResourceService.js +30 -3
  539. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  540. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
  541. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
  542. package/build/dist/Server/Services/ServiceService.js +1 -1
  543. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  544. package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
  545. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  546. package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
  547. package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
  548. package/build/dist/Server/Services/UserService.js +13 -1
  549. package/build/dist/Server/Services/UserService.js.map +1 -1
  550. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
  551. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  552. package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
  553. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  554. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
  555. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  556. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
  557. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
  558. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
  559. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  560. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
  561. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  562. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
  563. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  564. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
  565. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  566. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
  567. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  568. package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
  569. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  570. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
  571. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
  572. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
  573. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
  574. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
  575. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
  576. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
  577. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  578. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
  579. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  580. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  581. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  582. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
  583. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  584. package/build/dist/Server/Utils/StartServer.js +20 -4
  585. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  586. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  587. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  588. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
  589. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
  590. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
  591. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  592. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
  593. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
  594. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
  595. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  596. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
  597. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
  598. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
  599. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
  600. package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
  601. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  602. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
  603. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  604. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
  605. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
  606. package/build/dist/Types/AI/AIResourceContext.js +7 -0
  607. package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
  608. package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
  609. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  610. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
  611. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
  612. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
  613. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
  614. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
  615. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
  616. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
  617. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
  618. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
  619. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
  620. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
  621. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
  622. package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
  623. package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
  624. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
  625. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
  626. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  627. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  628. package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
  629. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
  630. package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
  631. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
  632. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
  633. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
  634. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
  635. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  636. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
  637. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
  638. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
  639. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  640. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
  641. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  642. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
  643. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  644. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
  645. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
  646. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
  647. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
  648. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
  649. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
  650. package/build/dist/Types/Permission.js +276 -0
  651. package/build/dist/Types/Permission.js.map +1 -1
  652. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
  653. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  654. package/build/dist/Types/Rules/RuleRun.js +4 -0
  655. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  656. package/build/dist/Types/Rum/WebVitals.js +25 -0
  657. package/build/dist/Types/Rum/WebVitals.js.map +1 -1
  658. package/build/dist/Types/Telemetry/EntityType.js +23 -0
  659. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  660. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
  661. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
  662. package/build/dist/Types/Telemetry/ServiceType.js +8 -0
  663. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  664. package/build/dist/UI/Components/Banner/Banner.js +1 -1
  665. package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
  666. package/build/dist/UI/Components/Card/Card.js +1 -1
  667. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  668. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  669. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  670. package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
  671. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  672. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
  673. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
  674. package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
  675. package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
  676. package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
  677. package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
  678. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
  679. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
  680. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
  681. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
  682. package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
  683. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  684. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
  685. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  686. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
  687. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
  688. package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
  689. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  690. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
  691. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
  692. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
  693. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  694. package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
  695. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  696. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  697. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  698. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +70 -18
  699. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  700. package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
  701. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  702. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
  703. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  704. package/build/dist/UI/Components/Page/Page.js +1 -1
  705. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  706. package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
  707. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  708. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
  709. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
  710. package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
  711. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  712. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
  713. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
  714. package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
  715. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  716. package/build/dist/UI/Components/Table/TableHeader.js +18 -2
  717. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  718. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
  719. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  720. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
  721. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
  722. package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
  723. package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
  724. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
  725. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
  726. package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
  727. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  728. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  729. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  730. package/build/dist/UI/Utils/Navigation.js +66 -33
  731. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  732. package/build/dist/UI/Utils/PermissionGate.js +4 -2
  733. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  734. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
  735. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
  736. package/build/dist/UI/Utils/TelemetryService.js +3 -2
  737. package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
  738. package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
  739. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
  740. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  741. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  742. package/build/dist/Utils/MetricUnitUtil.js +77 -13
  743. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  744. package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
  745. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
  746. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
  747. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
  748. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
  749. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
  750. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
  751. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
  752. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
  753. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  754. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
  755. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
  756. package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
  757. package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
  758. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
  759. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
  760. package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
  761. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  762. package/build/dist/Utils/ValueFormatter.js +15 -0
  763. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  764. package/package.json +1 -1
  765. package/tsconfig.tests.json +7 -5
@@ -0,0 +1,1719 @@
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 FilterCondition from "../../../../Types/Filter/FilterCondition";
16
+ import { JSONObject } from "../../../../Types/JSON";
17
+ import MetricFormulaConfigData from "../../../../Types/Metrics/MetricFormulaConfigData";
18
+ import MetricQueryConfigData from "../../../../Types/Metrics/MetricQueryConfigData";
19
+ import MetricsAggregationType from "../../../../Types/Metrics/MetricsAggregationType";
20
+ import MetricsViewConfig from "../../../../Types/Metrics/MetricsViewConfig";
21
+ import {
22
+ CephAlertTemplate,
23
+ getCephAlertTemplateById,
24
+ } from "../../../../Types/Monitor/CephAlertTemplates";
25
+ import { getCephMetricByMetricName } from "../../../../Types/Monitor/CephMetricCatalog";
26
+ import {
27
+ CheckOn,
28
+ CriteriaFilter,
29
+ FilterType,
30
+ } from "../../../../Types/Monitor/CriteriaFilter";
31
+ import {
32
+ DockerSwarmAlertTemplate,
33
+ getDockerSwarmAlertTemplateById,
34
+ } from "../../../../Types/Monitor/DockerSwarmAlertTemplates";
35
+ import { getDockerSwarmMetricByMetricName } from "../../../../Types/Monitor/DockerSwarmMetricCatalog";
36
+ import {
37
+ buildKubernetesMonitorConfig,
38
+ buildKubernetesMonitorStep,
39
+ buildOfflineCriteriaInstance,
40
+ buildOnlineCriteriaInstance,
41
+ getKubernetesAlertTemplateById,
42
+ KubernetesAlertTemplate,
43
+ } from "../../../../Types/Monitor/KubernetesAlertTemplates";
44
+ import { getKubernetesMetricByMetricName } from "../../../../Types/Monitor/KubernetesMetricCatalog";
45
+ import MetricMonitorResponse from "../../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
46
+ import MetricSeriesResult from "../../../../Types/Monitor/MetricMonitor/MetricSeriesResult";
47
+ import MonitorCriteriaInstance from "../../../../Types/Monitor/MonitorCriteriaInstance";
48
+ import MonitorEvaluationSummary from "../../../../Types/Monitor/MonitorEvaluationSummary";
49
+ import MonitorStep from "../../../../Types/Monitor/MonitorStep";
50
+ import { KubernetesResourceScope } from "../../../../Types/Monitor/MonitorStepKubernetesMonitor";
51
+ import MonitorType from "../../../../Types/Monitor/MonitorType";
52
+ import {
53
+ getProxmoxAlertTemplateById,
54
+ ProxmoxAlertTemplate,
55
+ } from "../../../../Types/Monitor/ProxmoxAlertTemplates";
56
+ import { getProxmoxMetricByMetricName } from "../../../../Types/Monitor/ProxmoxMetricCatalog";
57
+ import ObjectID from "../../../../Types/ObjectID";
58
+ import ProbeApiIngestResponse, {
59
+ MatchedCriteriaResult,
60
+ } from "../../../../Types/Probe/ProbeApiIngestResponse";
61
+ import RollingTime from "../../../../Types/RollingTime/RollingTime";
62
+ import MetricFormulaEvaluator from "../../../../Utils/Metrics/MetricFormulaEvaluator";
63
+ import MetricSeriesFingerprint from "../../../../Utils/Metrics/MetricSeriesFingerprint";
64
+ import PlatformMetricUnitUtil, {
65
+ MetricCatalogPlatform,
66
+ } from "../../../../Utils/Monitor/PlatformMetricUnitUtil";
67
+ import PlatformResourceIdentity, {
68
+ CephResourceIdentity,
69
+ DockerSwarmResourceIdentity,
70
+ KubernetesResourceIdentity,
71
+ ProxmoxResourceIdentity,
72
+ } from "../../../../Utils/Monitor/PlatformResourceIdentity";
73
+ import { describe, expect, test } from "@jest/globals";
74
+
75
+ /*
76
+ * Why this suite exists.
77
+ *
78
+ * An adversarial review of the "Affected Resources" list found five inputs
79
+ * the per-platform suites did not cover, each of which produced a wrong or
80
+ * missing list in the incident email:
81
+ *
82
+ * 1. An "Any" criteria (ceph-pg-damaged: PG_DAMAGED > 0 OR
83
+ * OSD_SCRUB_ERRORS > 0) was always described by its FIRST filter. With
84
+ * only the scrub-error check active there is no PG_DAMAGED scan, so the
85
+ * list the scrub errors had was dropped.
86
+ * 2. A grouped "Any" criteria over two aliases ranked a node that matched
87
+ * on "used bytes > 200 GB" against nodes valued in "% utilization" —
88
+ * one list, two units — and the analysis could say "memory usage is at
89
+ * **210 GB**" under a percentage threshold.
90
+ * 3. The dashboard's criteria form saves a threshold as a STRING ("60").
91
+ * The list's breach predicate only read numeric thresholds, so every
92
+ * edited criteria fell back to "> 0, highest first": pod-pending named
93
+ * the Unknown pod, task-down ranked the healthiest task first, and
94
+ * node-offline listed the nodes that were up.
95
+ * 4. The raw scan is in the metric's native unit, the threshold in the
96
+ * filter's thresholdUnit: "uptime < 1 min" tested 30 seconds against 1
97
+ * and listed nothing.
98
+ * 5. A monitor grouped by a key that names no platform object (a PVC
99
+ * name) rendered every series as the same anonymous "**Cluster**" row,
100
+ * and Proxmox dropped the list outright.
101
+ *
102
+ * Each case below drives the REAL template (or the form-built monitor the
103
+ * review reproduced) through MonitorCriteriaEvaluator.processMonitorStep
104
+ * with a worker-shaped response, so the evaluator sets each filter's
105
+ * context itself, and pins the rendered markdown.
106
+ */
107
+
108
+ const MINUTES: Array<Date> = [0, 1, 2, 3, 4].map((minute: number): Date => {
109
+ return new Date(Date.UTC(2026, 8, 25, 10, minute, 0));
110
+ });
111
+
112
+ const K8S_CLUSTER: string = "prod-east";
113
+ const PVE_CLUSTER: string = "pve-lab";
114
+ const CEPH_CLUSTER: string = "ceph-prod";
115
+ const SWARM_CLUSTER: string = "swarm-prod";
116
+
117
+ const K8S_CLUSTER_KEY: string = "resource.k8s.cluster.name";
118
+ const NODE_KEY: string = "resource.k8s.node.name";
119
+ const NAMESPACE_KEY: string = "resource.k8s.namespace.name";
120
+ const POD_KEY: string = "resource.k8s.pod.name";
121
+ const PVC_KEY: string = "resource.k8s.persistentvolumeclaim.name";
122
+
123
+ const GB: number = 1_000_000_000;
124
+
125
+ // No rendered value may be a raw, unscaled 10+ digit number (bytes, epochs).
126
+ const RAW_NUMBER: RegExp = /\b\d{10,}\b/;
127
+
128
+ /*
129
+ * ---------------------------------------------------------------------------
130
+ * Platform fixtures
131
+ * ---------------------------------------------------------------------------
132
+ */
133
+
134
+ type ScannedResource<I> = I & {
135
+ metricValue: number;
136
+ lowestMetricValue?: number | undefined;
137
+ };
138
+
139
+ // A platform raw-scan breakdown without its cluster name.
140
+ interface ScannedBreakdown<I> {
141
+ metricName: string;
142
+ metricFriendlyName: string;
143
+ metricAlias: string | undefined;
144
+ metricUnit: string | undefined;
145
+ attributes: Dictionary<string>;
146
+ affectedResources: Array<ScannedResource<I>>;
147
+ }
148
+
149
+ interface PlatformFixture<I> {
150
+ monitorType: MonitorType;
151
+ platform: MetricCatalogPlatform;
152
+ friendlyName: (metricName: string) => string | undefined;
153
+ toIdentity: (attributes: JSONObject) => I;
154
+ identityKey: (identity: I) => string;
155
+ attach: (
156
+ response: MetricMonitorResponse,
157
+ breakdowns: Array<ScannedBreakdown<I>>,
158
+ ) => void;
159
+ }
160
+
161
+ const KUBERNETES: PlatformFixture<KubernetesResourceIdentity> = {
162
+ monitorType: MonitorType.Kubernetes,
163
+ platform: "kubernetes",
164
+ friendlyName: (metricName: string): string | undefined => {
165
+ return getKubernetesMetricByMetricName(metricName)?.friendlyName;
166
+ },
167
+ toIdentity: PlatformResourceIdentity.kubernetes,
168
+ identityKey: PlatformResourceIdentity.kubernetesKey,
169
+ attach: (
170
+ response: MetricMonitorResponse,
171
+ breakdowns: Array<ScannedBreakdown<KubernetesResourceIdentity>>,
172
+ ): void => {
173
+ response.kubernetesResourceBreakdowns = breakdowns.map(
174
+ (breakdown: ScannedBreakdown<KubernetesResourceIdentity>) => {
175
+ return { ...breakdown, clusterName: K8S_CLUSTER };
176
+ },
177
+ );
178
+ },
179
+ };
180
+
181
+ const PROXMOX: PlatformFixture<ProxmoxResourceIdentity> = {
182
+ monitorType: MonitorType.Proxmox,
183
+ platform: "proxmox",
184
+ friendlyName: (metricName: string): string | undefined => {
185
+ return getProxmoxMetricByMetricName(metricName)?.friendlyName;
186
+ },
187
+ toIdentity: PlatformResourceIdentity.proxmox,
188
+ identityKey: PlatformResourceIdentity.proxmoxKey,
189
+ attach: (
190
+ response: MetricMonitorResponse,
191
+ breakdowns: Array<ScannedBreakdown<ProxmoxResourceIdentity>>,
192
+ ): void => {
193
+ response.proxmoxResourceBreakdowns = breakdowns.map(
194
+ (breakdown: ScannedBreakdown<ProxmoxResourceIdentity>) => {
195
+ return { ...breakdown, clusterName: PVE_CLUSTER };
196
+ },
197
+ );
198
+ },
199
+ };
200
+
201
+ const CEPH: PlatformFixture<CephResourceIdentity> = {
202
+ monitorType: MonitorType.Ceph,
203
+ platform: "ceph",
204
+ friendlyName: (metricName: string): string | undefined => {
205
+ return getCephMetricByMetricName(metricName)?.friendlyName;
206
+ },
207
+ toIdentity: PlatformResourceIdentity.ceph,
208
+ identityKey: PlatformResourceIdentity.cephKey,
209
+ attach: (
210
+ response: MetricMonitorResponse,
211
+ breakdowns: Array<ScannedBreakdown<CephResourceIdentity>>,
212
+ ): void => {
213
+ response.cephResourceBreakdowns = breakdowns.map(
214
+ (breakdown: ScannedBreakdown<CephResourceIdentity>) => {
215
+ return { ...breakdown, clusterName: CEPH_CLUSTER };
216
+ },
217
+ );
218
+ },
219
+ };
220
+
221
+ const DOCKER_SWARM: PlatformFixture<DockerSwarmResourceIdentity> = {
222
+ monitorType: MonitorType.DockerSwarm,
223
+ platform: "dockerSwarm",
224
+ friendlyName: (metricName: string): string | undefined => {
225
+ return getDockerSwarmMetricByMetricName(metricName)?.friendlyName;
226
+ },
227
+ toIdentity: PlatformResourceIdentity.dockerSwarm,
228
+ identityKey: PlatformResourceIdentity.dockerSwarmKey,
229
+ attach: (
230
+ response: MetricMonitorResponse,
231
+ breakdowns: Array<ScannedBreakdown<DockerSwarmResourceIdentity>>,
232
+ ): void => {
233
+ response.dockerSwarmResourceBreakdowns = breakdowns.map(
234
+ (breakdown: ScannedBreakdown<DockerSwarmResourceIdentity>) => {
235
+ return { ...breakdown, clusterName: SWARM_CLUSTER };
236
+ },
237
+ );
238
+ },
239
+ };
240
+
241
+ /*
242
+ * ---------------------------------------------------------------------------
243
+ * Monitor step helpers
244
+ * ---------------------------------------------------------------------------
245
+ */
246
+
247
+ interface CommonTemplateArgs {
248
+ onlineMonitorStatusId: ObjectID;
249
+ offlineMonitorStatusId: ObjectID;
250
+ defaultIncidentSeverityId: ObjectID;
251
+ defaultAlertSeverityId: ObjectID;
252
+ monitorName: string;
253
+ }
254
+
255
+ function commonArgs(monitorName: string): CommonTemplateArgs {
256
+ return {
257
+ onlineMonitorStatusId: ObjectID.generate(),
258
+ offlineMonitorStatusId: ObjectID.generate(),
259
+ defaultIncidentSeverityId: ObjectID.generate(),
260
+ defaultAlertSeverityId: ObjectID.generate(),
261
+ monitorName: monitorName,
262
+ };
263
+ }
264
+
265
+ function requireTemplate<T>(template: T | undefined, id: string): T {
266
+ if (!template) {
267
+ throw new Error(`${id} template missing`);
268
+ }
269
+
270
+ return template;
271
+ }
272
+
273
+ function kubernetesStep(templateId: string): MonitorStep {
274
+ const template: KubernetesAlertTemplate = requireTemplate(
275
+ getKubernetesAlertTemplateById(templateId),
276
+ templateId,
277
+ );
278
+
279
+ return template.getMonitorStep({
280
+ clusterIdentifier: K8S_CLUSTER,
281
+ ...commonArgs(`${K8S_CLUSTER} - ${template.name}`),
282
+ });
283
+ }
284
+
285
+ function proxmoxStep(templateId: string): MonitorStep {
286
+ const template: ProxmoxAlertTemplate = requireTemplate(
287
+ getProxmoxAlertTemplateById(templateId),
288
+ templateId,
289
+ );
290
+
291
+ return template.getMonitorStep({
292
+ clusterIdentifier: PVE_CLUSTER,
293
+ ...commonArgs(`${PVE_CLUSTER} - ${template.name}`),
294
+ });
295
+ }
296
+
297
+ function cephStep(templateId: string): MonitorStep {
298
+ const template: CephAlertTemplate = requireTemplate(
299
+ getCephAlertTemplateById(templateId),
300
+ templateId,
301
+ );
302
+
303
+ return template.getMonitorStep({
304
+ clusterIdentifier: CEPH_CLUSTER,
305
+ ...commonArgs(`${CEPH_CLUSTER} - ${template.name}`),
306
+ });
307
+ }
308
+
309
+ function dockerSwarmStep(templateId: string): MonitorStep {
310
+ const template: DockerSwarmAlertTemplate = requireTemplate(
311
+ getDockerSwarmAlertTemplateById(templateId),
312
+ templateId,
313
+ );
314
+
315
+ return template.getMonitorStep({
316
+ clusterIdentifier: SWARM_CLUSTER,
317
+ ...commonArgs(`${SWARM_CLUSTER} - ${template.name}`),
318
+ });
319
+ }
320
+
321
+ function viewConfigOf(monitorStep: MonitorStep): MetricsViewConfig {
322
+ const config: MetricsViewConfig | undefined =
323
+ MonitorStep.getMetricsViewConfig(monitorStep);
324
+
325
+ if (!config) {
326
+ throw new Error("monitor step has no metric view config");
327
+ }
328
+
329
+ return config;
330
+ }
331
+
332
+ function aliasOf(config: {
333
+ metricAliasData?: { metricVariable?: string | undefined } | undefined;
334
+ }): string {
335
+ return config.metricAliasData?.metricVariable || "";
336
+ }
337
+
338
+ function metricNameOf(queryConfig: MetricQueryConfigData): string {
339
+ return (queryConfig.metricQueryData.filterData.metricName as string) || "";
340
+ }
341
+
342
+ function queryOf(
343
+ monitorStep: MonitorStep,
344
+ alias: string,
345
+ ): MetricQueryConfigData {
346
+ const query: MetricQueryConfigData | undefined = viewConfigOf(
347
+ monitorStep,
348
+ ).queryConfigs.find((queryConfig: MetricQueryConfigData): boolean => {
349
+ return aliasOf(queryConfig) === alias;
350
+ });
351
+
352
+ if (!query) {
353
+ throw new Error(`no query aliased ${alias}`);
354
+ }
355
+
356
+ return query;
357
+ }
358
+
359
+ // The template's FIRING criteria — every template builds it first.
360
+ function firingOf(monitorStep: MonitorStep): MonitorCriteriaInstance {
361
+ const instance: MonitorCriteriaInstance | undefined =
362
+ monitorStep.data?.monitorCriteria?.data?.monitorCriteriaInstanceArray[0];
363
+
364
+ if (!instance) {
365
+ throw new Error("firing criteria instance missing");
366
+ }
367
+
368
+ return instance;
369
+ }
370
+
371
+ function metricFiltersOf(
372
+ criteria: MonitorCriteriaInstance,
373
+ ): Array<CriteriaFilter> {
374
+ return (criteria.data?.filters || []).filter(
375
+ (filter: CriteriaFilter): boolean => {
376
+ return filter.checkOn === CheckOn.MetricValue;
377
+ },
378
+ );
379
+ }
380
+
381
+ // Drop every query's group-by: the monitor as a user saves it ungrouped.
382
+ function ungroup(monitorStep: MonitorStep): MonitorStep {
383
+ for (const queryConfig of viewConfigOf(monitorStep).queryConfigs) {
384
+ delete queryConfig.metricQueryData.groupByAttributeKeys;
385
+ }
386
+
387
+ if (MonitorStep.getGroupByAttributeKeys(monitorStep).length > 0) {
388
+ throw new Error("monitor step is still grouped");
389
+ }
390
+
391
+ return monitorStep;
392
+ }
393
+
394
+ /*
395
+ * Every metric threshold of every criteria as the dashboard's criteria
396
+ * form saves it: a string ("60", not 60).
397
+ */
398
+ function stringifyThresholds(monitorStep: MonitorStep): MonitorStep {
399
+ for (const instance of monitorStep.data?.monitorCriteria?.data
400
+ ?.monitorCriteriaInstanceArray || []) {
401
+ for (const filter of instance.data?.filters || []) {
402
+ if (typeof filter.value === "number") {
403
+ filter.value = String(filter.value);
404
+ }
405
+ }
406
+ }
407
+
408
+ return monitorStep;
409
+ }
410
+
411
+ /*
412
+ * ---------------------------------------------------------------------------
413
+ * Worker responses
414
+ * ---------------------------------------------------------------------------
415
+ */
416
+
417
+ function unitsByMetricNameOf<I>(input: {
418
+ monitorStep: MonitorStep;
419
+ fixture: PlatformFixture<I>;
420
+ declaredUnits?: Dictionary<string> | undefined;
421
+ }): Dictionary<string> {
422
+ return PlatformMetricUnitUtil.buildUnitsByMetricName({
423
+ platform: input.fixture.platform,
424
+ metricNames: viewConfigOf(input.monitorStep).queryConfigs.map(metricNameOf),
425
+ declaredUnitsByMetricName: new Map<string, string>(
426
+ Object.entries(input.declaredUnits || {}).map(
427
+ ([metricName, unit]: [string, string]): [string, string] => {
428
+ return [metricName.toLowerCase(), unit];
429
+ },
430
+ ),
431
+ ),
432
+ });
433
+ }
434
+
435
+ function rowsOf(input: {
436
+ values: Array<number>;
437
+ attributes: JSONObject;
438
+ }): Array<AggregateModel> {
439
+ return input.values.map((value: number, minute: number): AggregateModel => {
440
+ return {
441
+ timestamp: MINUTES[minute]!,
442
+ value: value,
443
+ attributes: input.attributes,
444
+ } as unknown as AggregateModel;
445
+ });
446
+ }
447
+
448
+ /*
449
+ * One resource as the worker's raw rows see it: the datapoint attributes
450
+ * every row of it carries, and one value per minute for each query alias.
451
+ */
452
+ interface RawSeries {
453
+ attributes: JSONObject;
454
+ valuesByAlias: Dictionary<Array<number>>;
455
+ }
456
+
457
+ /*
458
+ * What the telemetry worker hands the evaluator for a GROUPED monitor:
459
+ * per-series rows aligned [queries..., formulas...] (query rows carry the
460
+ * raw datapoint's attributes, formula slots are evaluated by the worker's
461
+ * own MetricFormulaEvaluator), labels / fingerprints from the step's
462
+ * group-by keys, one alias-tagged raw scan per query, and the
463
+ * catalog-overlaid unit map.
464
+ */
465
+ function groupedResponse<I>(input: {
466
+ monitorStep: MonitorStep;
467
+ fixture: PlatformFixture<I>;
468
+ series: Array<RawSeries>;
469
+ declaredUnits?: Dictionary<string> | undefined;
470
+ }): MetricMonitorResponse {
471
+ const viewConfig: MetricsViewConfig = viewConfigOf(input.monitorStep);
472
+ const queryConfigs: Array<MetricQueryConfigData> = viewConfig.queryConfigs;
473
+ const formulaConfigs: Array<MetricFormulaConfigData> =
474
+ viewConfig.formulaConfigs || [];
475
+ const groupByKeys: Array<string> = MonitorStep.getGroupByAttributeKeys(
476
+ input.monitorStep,
477
+ );
478
+
479
+ if (groupByKeys.length === 0) {
480
+ throw new Error("groupedResponse is for GROUPED monitors");
481
+ }
482
+
483
+ const unitsByMetricName: Dictionary<string> = unitsByMetricNameOf(input);
484
+
485
+ const seriesBreakdown: Array<MetricSeriesResult> = input.series.map(
486
+ (series: RawSeries): MetricSeriesResult => {
487
+ const results: Array<AggregatedResult> = queryConfigs.map(
488
+ (queryConfig: MetricQueryConfigData): AggregatedResult => {
489
+ return {
490
+ data: rowsOf({
491
+ values: series.valuesByAlias[aliasOf(queryConfig)] || [],
492
+ attributes: series.attributes,
493
+ }),
494
+ };
495
+ },
496
+ );
497
+
498
+ formulaConfigs.forEach(
499
+ (formulaConfig: MetricFormulaConfigData, index: number): void => {
500
+ results.push(
501
+ MetricFormulaEvaluator.evaluateFormula({
502
+ formula: formulaConfig.metricFormulaData.metricFormula,
503
+ queryConfigs: queryConfigs,
504
+ formulaConfigs: formulaConfigs.slice(0, index),
505
+ results: [...results],
506
+ }),
507
+ );
508
+ },
509
+ );
510
+
511
+ const labels: JSONObject = MetricSeriesFingerprint.extractSeriesLabels({
512
+ sample: { attributes: series.attributes } as unknown as AggregateModel,
513
+ attributeKeys: groupByKeys,
514
+ });
515
+
516
+ return {
517
+ fingerprint: MetricSeriesFingerprint.computeFingerprint(labels),
518
+ labels: labels,
519
+ aggregatedResults: results,
520
+ };
521
+ },
522
+ );
523
+
524
+ const breakdowns: Array<ScannedBreakdown<I>> = queryConfigs.map(
525
+ (queryConfig: MetricQueryConfigData): ScannedBreakdown<I> => {
526
+ const alias: string = aliasOf(queryConfig);
527
+ const metricName: string = metricNameOf(queryConfig);
528
+ const byKey: Map<string, ScannedResource<I>> = new Map<
529
+ string,
530
+ ScannedResource<I>
531
+ >();
532
+
533
+ for (const series of input.series) {
534
+ for (const value of series.valuesByAlias[alias] || []) {
535
+ const identity: I = input.fixture.toIdentity(series.attributes);
536
+ const key: string = input.fixture.identityKey(identity);
537
+ const existing: ScannedResource<I> | undefined = byKey.get(key);
538
+
539
+ if (!existing) {
540
+ byKey.set(key, {
541
+ ...identity,
542
+ metricValue: value,
543
+ lowestMetricValue: value,
544
+ });
545
+ continue;
546
+ }
547
+
548
+ existing.metricValue = Math.max(existing.metricValue, value);
549
+ existing.lowestMetricValue = Math.min(
550
+ existing.lowestMetricValue ?? value,
551
+ value,
552
+ );
553
+ }
554
+ }
555
+
556
+ return {
557
+ metricName: metricName,
558
+ metricFriendlyName:
559
+ input.fixture.friendlyName(metricName) || metricName,
560
+ metricAlias: alias,
561
+ metricUnit: unitsByMetricName[metricName.toLowerCase()],
562
+ attributes: {},
563
+ affectedResources: Array.from(byKey.values()),
564
+ };
565
+ },
566
+ );
567
+
568
+ const response: MetricMonitorResponse = {
569
+ projectId: ObjectID.generate(),
570
+ monitorId: ObjectID.generate(),
571
+ metricResult: [],
572
+ metricViewConfig: viewConfig,
573
+ seriesBreakdown: seriesBreakdown,
574
+ nativeUnitsByMetricName: unitsByMetricName,
575
+ };
576
+
577
+ input.fixture.attach(
578
+ response,
579
+ breakdowns.filter((breakdown: ScannedBreakdown<I>): boolean => {
580
+ return breakdown.affectedResources.length > 0;
581
+ }),
582
+ );
583
+
584
+ return response;
585
+ }
586
+
587
+ // One query's raw scan, as the worker tags it.
588
+ interface ScanSpec<I> {
589
+ alias: string;
590
+ metricUnit?: string | undefined;
591
+ resources: Array<ScannedResource<I>>;
592
+ }
593
+
594
+ /*
595
+ * What the worker hands the evaluator for an UNGROUPED monitor: one
596
+ * AggregatedResult per query (aligned with the step's queries — the
597
+ * criteria read their own alias's slot), and a raw scan for each query
598
+ * that returned datapoints, tagged with that query's alias.
599
+ */
600
+ function ungroupedResponse<I>(input: {
601
+ monitorStep: MonitorStep;
602
+ fixture: PlatformFixture<I>;
603
+ valuesByAlias: Dictionary<Array<number>>;
604
+ scans: Array<ScanSpec<I>>;
605
+ declaredUnits?: Dictionary<string> | undefined;
606
+ }): MetricMonitorResponse {
607
+ const viewConfig: MetricsViewConfig = viewConfigOf(input.monitorStep);
608
+
609
+ if (MonitorStep.getGroupByAttributeKeys(input.monitorStep).length > 0) {
610
+ throw new Error("ungroupedResponse is for UNGROUPED monitors");
611
+ }
612
+
613
+ const unitsByMetricName: Dictionary<string> = unitsByMetricNameOf(input);
614
+
615
+ const response: MetricMonitorResponse = {
616
+ projectId: ObjectID.generate(),
617
+ monitorId: ObjectID.generate(),
618
+ metricResult: viewConfig.queryConfigs.map(
619
+ (queryConfig: MetricQueryConfigData): AggregatedResult => {
620
+ return {
621
+ data: rowsOf({
622
+ values: input.valuesByAlias[aliasOf(queryConfig)] || [],
623
+ attributes: {},
624
+ }),
625
+ };
626
+ },
627
+ ),
628
+ metricViewConfig: viewConfig,
629
+ nativeUnitsByMetricName: unitsByMetricName,
630
+ };
631
+
632
+ input.fixture.attach(
633
+ response,
634
+ input.scans.map((scan: ScanSpec<I>): ScannedBreakdown<I> => {
635
+ const metricName: string = metricNameOf(
636
+ queryOf(input.monitorStep, scan.alias),
637
+ );
638
+
639
+ return {
640
+ metricName: metricName,
641
+ metricFriendlyName:
642
+ input.fixture.friendlyName(metricName) || metricName,
643
+ metricAlias: scan.alias,
644
+ metricUnit: scan.metricUnit,
645
+ attributes: {},
646
+ affectedResources: scan.resources,
647
+ };
648
+ }),
649
+ );
650
+
651
+ return response;
652
+ }
653
+
654
+ /*
655
+ * ---------------------------------------------------------------------------
656
+ * Evaluation + rendered-markdown helpers
657
+ * ---------------------------------------------------------------------------
658
+ */
659
+
660
+ async function evaluate<I>(input: {
661
+ monitorStep: MonitorStep;
662
+ fixture: PlatformFixture<I>;
663
+ response: MetricMonitorResponse;
664
+ }): Promise<ProbeApiIngestResponse> {
665
+ const monitor: Monitor = new Monitor();
666
+ monitor._id = ObjectID.generate().toString();
667
+ monitor.monitorType = input.fixture.monitorType;
668
+ monitor.name = "edge-case monitor";
669
+
670
+ return MonitorCriteriaEvaluator.processMonitorStep({
671
+ dataToProcess: input.response,
672
+ monitorStep: input.monitorStep,
673
+ monitor: monitor,
674
+ probeApiIngestResponse: {
675
+ monitorId: monitor.id!,
676
+ rootCause: null,
677
+ },
678
+ evaluationSummary: {
679
+ criteriaResults: [],
680
+ events: [],
681
+ } as unknown as MonitorEvaluationSummary,
682
+ });
683
+ }
684
+
685
+ /*
686
+ * The rendered root cause of one criteria, or "" when it did not match: a
687
+ * grouped monitor reports every matched criteria, an ungrouped one only
688
+ * its first.
689
+ */
690
+ function rootCauseFor(
691
+ response: ProbeApiIngestResponse,
692
+ criteria: MonitorCriteriaInstance,
693
+ ): string {
694
+ const criteriaId: string = criteria.data?.id?.toString() || "";
695
+
696
+ const matched: MatchedCriteriaResult | undefined = (
697
+ response.matchedCriteria || []
698
+ ).find((result: MatchedCriteriaResult): boolean => {
699
+ return result.criteriaId === criteriaId;
700
+ });
701
+
702
+ if (matched) {
703
+ return matched.rootCause;
704
+ }
705
+
706
+ return response.criteriaMetId?.toString() === criteriaId
707
+ ? response.rootCause || ""
708
+ : "";
709
+ }
710
+
711
+ async function firingRootCause<I>(input: {
712
+ monitorStep: MonitorStep;
713
+ fixture: PlatformFixture<I>;
714
+ response: MetricMonitorResponse;
715
+ }): Promise<string> {
716
+ return rootCauseFor(await evaluate(input), firingOf(input.monitorStep));
717
+ }
718
+
719
+ // From a list heading up to the analysis that follows it, trimmed.
720
+ function listSection(rootCause: string, heading: string): string {
721
+ const start: number = rootCause.indexOf(`**${heading}**`);
722
+
723
+ if (start < 0) {
724
+ return "";
725
+ }
726
+
727
+ const end: number = rootCause.indexOf("**Root Cause Analysis**", start);
728
+
729
+ return (
730
+ end < 0 ? rootCause.substring(start) : rootCause.substring(start, end)
731
+ ).trim();
732
+ }
733
+
734
+ const ENTRY_HEAD: RegExp = /^\d+\. /;
735
+
736
+ // The first line of every numbered entry in a list.
737
+ function entryHeads(section: string): Array<string> {
738
+ return section.split("\n").filter((line: string): boolean => {
739
+ return ENTRY_HEAD.test(line);
740
+ });
741
+ }
742
+
743
+ function linesOf(rootCause: string): Array<string> {
744
+ return rootCause.split("\n");
745
+ }
746
+
747
+ /*
748
+ * The "- Metric:" line for one query, with the catalog's name looked up
749
+ * rather than hard-coded (catalogs change).
750
+ */
751
+ function metricLine<I>(
752
+ fixture: PlatformFixture<I>,
753
+ metricName: string,
754
+ ): string {
755
+ const friendlyName: string | undefined = fixture.friendlyName(metricName);
756
+
757
+ return friendlyName && friendlyName !== metricName
758
+ ? `- Metric: ${friendlyName} (\`${metricName}\`)`
759
+ : `- Metric: \`${metricName}\``;
760
+ }
761
+
762
+ function metricLinesOf(rootCause: string): Array<string> {
763
+ return linesOf(rootCause).filter((line: string): boolean => {
764
+ return line.startsWith("- Metric:");
765
+ });
766
+ }
767
+
768
+ /*
769
+ * ---------------------------------------------------------------------------
770
+ * 1. An "Any" criteria is described by the filter that fired
771
+ * ---------------------------------------------------------------------------
772
+ */
773
+
774
+ interface CephAnyCase {
775
+ templateId: string;
776
+ // The one health check that is active, and the query that watches it.
777
+ activeAlias: string;
778
+ activeCheck: string;
779
+ // The check that is NOT active.
780
+ inactiveCheck: string;
781
+ }
782
+
783
+ const CEPH_ANY_CASES: Array<CephAnyCase> = [
784
+ {
785
+ templateId: "ceph-pg-damaged",
786
+ activeAlias: "scrub_errors",
787
+ activeCheck: "OSD_SCRUB_ERRORS",
788
+ inactiveCheck: "PG_DAMAGED",
789
+ },
790
+ {
791
+ templateId: "ceph-pg-damaged",
792
+ activeAlias: "pg_damaged",
793
+ activeCheck: "PG_DAMAGED",
794
+ inactiveCheck: "OSD_SCRUB_ERRORS",
795
+ },
796
+ {
797
+ templateId: "ceph-osd-slow-heartbeats",
798
+ activeAlias: "slow_ping_back",
799
+ activeCheck: "OSD_SLOW_PING_TIME_BACK",
800
+ inactiveCheck: "OSD_SLOW_PING_TIME_FRONT",
801
+ },
802
+ ];
803
+
804
+ describe("An Any criteria follows the filter that fired", () => {
805
+ /*
806
+ * ceph_health_detail reports a check only while it is active (an absent
807
+ * series is healthy), so the inactive query returns no datapoints and
808
+ * the worker has no scan for it: metricResult is [[], [rows]] and the
809
+ * one breakdown is the active query's.
810
+ */
811
+ function cephAnyResponse(input: {
812
+ monitorStep: MonitorStep;
813
+ testCase: CephAnyCase;
814
+ }): MetricMonitorResponse {
815
+ return ungroupedResponse({
816
+ monitorStep: input.monitorStep,
817
+ fixture: CEPH,
818
+ valuesByAlias: { [input.testCase.activeAlias]: [1, 1, 1, 1, 1] },
819
+ scans: [
820
+ {
821
+ alias: input.testCase.activeAlias,
822
+ resources: [{ poolName: input.testCase.activeCheck, metricValue: 1 }],
823
+ },
824
+ ],
825
+ });
826
+ }
827
+
828
+ for (const testCase of CEPH_ANY_CASES) {
829
+ test(`${testCase.templateId}: only ${testCase.activeCheck} active — it is listed and its query named`, async () => {
830
+ const monitorStep: MonitorStep = cephStep(testCase.templateId);
831
+ const firing: MonitorCriteriaInstance = firingOf(monitorStep);
832
+
833
+ // Fixture guard: an ungrouped "Any" over two ceph_health_detail queries.
834
+ expect(firing.data?.filterCondition).toBe(FilterCondition.Any);
835
+ expect(
836
+ metricFiltersOf(firing).map((filter: CriteriaFilter): string => {
837
+ return filter.metricMonitorOptions?.metricAlias || "";
838
+ }),
839
+ ).toContain(testCase.activeAlias);
840
+ expect(metricFiltersOf(firing)).toHaveLength(2);
841
+ expect(MonitorStep.getGroupByAttributeKeys(monitorStep)).toEqual([]);
842
+
843
+ const rootCause: string = await firingRootCause({
844
+ monitorStep: monitorStep,
845
+ fixture: CEPH,
846
+ response: cephAnyResponse({
847
+ monitorStep: monitorStep,
848
+ testCase: testCase,
849
+ }),
850
+ });
851
+
852
+ // The evaluator marked exactly the active query's filter as breaching.
853
+ const breached: Array<string> = metricFiltersOf(firing)
854
+ .filter((filter: CriteriaFilter): boolean => {
855
+ return (
856
+ (filter.metricCriteriaContext?.breachingSamples || []).length > 0
857
+ );
858
+ })
859
+ .map((filter: CriteriaFilter): string => {
860
+ return filter.metricMonitorOptions?.metricAlias || "";
861
+ });
862
+ expect(breached).toEqual([testCase.activeAlias]);
863
+
864
+ expect(listSection(rootCause, "Affected Resources")).toBe(
865
+ [
866
+ "**Affected Resources** (1 total)",
867
+ "",
868
+ `1. **Health Check** \`${testCase.activeCheck}\` — **1**`,
869
+ ].join("\n"),
870
+ );
871
+
872
+ /*
873
+ * Both queries read ceph_health_detail, so the fired query's metric
874
+ * line is the only one — never a second, and never the metric-count
875
+ * fallback that stood in for the dropped list.
876
+ */
877
+ expect(metricLinesOf(rootCause)).toEqual([
878
+ metricLine(
879
+ CEPH,
880
+ metricNameOf(queryOf(monitorStep, testCase.activeAlias)),
881
+ ),
882
+ ]);
883
+ expect(rootCause).toContain(`- Cluster: ${CEPH_CLUSTER}`);
884
+ expect(rootCause).not.toContain("**Metric Summary**");
885
+ expect(rootCause).not.toContain(testCase.inactiveCheck);
886
+ });
887
+ }
888
+ });
889
+
890
+ /*
891
+ * ---------------------------------------------------------------------------
892
+ * 2. A grouped Any criteria over two aliases
893
+ * ---------------------------------------------------------------------------
894
+ */
895
+
896
+ /*
897
+ * k8s-high-memory with its firing criteria widened, as a user would in the
898
+ * form, to "utilization > 85% OR used memory > 200 GB": node_memory_
899
+ * utilization is the formula (%), used_mem its numerator (bytes).
900
+ */
901
+ function highMemoryAnyStep(): MonitorStep {
902
+ const monitorStep: MonitorStep = kubernetesStep("k8s-high-memory");
903
+ const firing: MonitorCriteriaInstance = firingOf(monitorStep);
904
+ const utilizationFilter: CriteriaFilter = metricFiltersOf(firing)[0]!;
905
+
906
+ expect(utilizationFilter.metricMonitorOptions?.metricAlias).toBe(
907
+ "node_memory_utilization",
908
+ );
909
+
910
+ firing.data!.filterCondition = FilterCondition.Any;
911
+ firing.data!.filters.push({
912
+ ...utilizationFilter,
913
+ metricMonitorOptions: {
914
+ ...utilizationFilter.metricMonitorOptions!,
915
+ metricAlias: "used_mem",
916
+ },
917
+ value: 200 * GB,
918
+ });
919
+
920
+ return monitorStep;
921
+ }
922
+
923
+ function k8sNode(input: {
924
+ name: string;
925
+ used: number | Array<number>;
926
+ allocatable: number;
927
+ }): RawSeries {
928
+ return {
929
+ attributes: { [K8S_CLUSTER_KEY]: K8S_CLUSTER, [NODE_KEY]: input.name },
930
+ valuesByAlias: {
931
+ used_mem: Array.isArray(input.used)
932
+ ? input.used
933
+ : MINUTES.map((): number => {
934
+ return input.used as number;
935
+ }),
936
+ alloc_mem: MINUTES.map((): number => {
937
+ return input.allocatable;
938
+ }),
939
+ },
940
+ };
941
+ }
942
+
943
+ const K8S_DECLARED_UNITS: Dictionary<string> = {
944
+ "k8s.node.memory.usage": "By",
945
+ "k8s.node.allocatable_memory": "By",
946
+ "k8s.volume.available": "By",
947
+ "k8s.pod.phase": "1",
948
+ };
949
+
950
+ // 86.26% .. 88.82% of 257.76 GB, and over 200 GB: matches BOTH filters.
951
+ const HOT_NODE: RawSeries = k8sNode({
952
+ name: "node-hot",
953
+ used: [222344608010, 224 * GB, 226 * GB, 228 * GB, 228943289000],
954
+ allocatable: 257760964608,
955
+ });
956
+
957
+ // 210 GB of 400 GB = 52.5%: matches only "used memory > 200 GB".
958
+ const BIG_NODE: RawSeries = k8sNode({
959
+ name: "node-big",
960
+ used: 210 * GB,
961
+ allocatable: 400 * GB,
962
+ });
963
+
964
+ // 100 GB of 400 GB: matches neither.
965
+ const CALM_NODE: RawSeries = k8sNode({
966
+ name: "node-calm",
967
+ used: 100 * GB,
968
+ allocatable: 400 * GB,
969
+ });
970
+
971
+ describe("A grouped Any criteria over two aliases keeps one unit per rank", () => {
972
+ async function run(series: Array<RawSeries>): Promise<string> {
973
+ const monitorStep: MonitorStep = highMemoryAnyStep();
974
+
975
+ return firingRootCause({
976
+ monitorStep: monitorStep,
977
+ fixture: KUBERNETES,
978
+ response: groupedResponse({
979
+ monitorStep: monitorStep,
980
+ fixture: KUBERNETES,
981
+ series: series,
982
+ declaredUnits: K8S_DECLARED_UNITS,
983
+ }),
984
+ });
985
+ }
986
+
987
+ test("the node over 85% leads in %; the node that matched only on bytes follows, its metric named", async () => {
988
+ // The off-filter node arrives FIRST: its position is the sort's doing.
989
+ const rootCause: string = await run([BIG_NODE, HOT_NODE, CALM_NODE]);
990
+
991
+ expect(listSection(rootCause, "Affected Resources")).toBe(
992
+ [
993
+ "**Affected Resources** (2 total)",
994
+ "",
995
+ "1. **Node** `node-hot` — **88.82%**",
996
+ "2. **Node** `node-big` — **210 GB** (`used_mem`)",
997
+ ].join("\n"),
998
+ );
999
+
1000
+ expect(rootCause).not.toContain("node-calm");
1001
+ expect(rootCause).not.toMatch(RAW_NUMBER);
1002
+ // Never the bytes node ranked as if 210 were a percentage.
1003
+ expect(rootCause).not.toContain("**210.00%**");
1004
+ });
1005
+
1006
+ test("the list and the analysis are about the filter that fired — the % one", async () => {
1007
+ const rootCause: string = await run([BIG_NODE, HOT_NODE, CALM_NODE]);
1008
+
1009
+ expect(rootCause).toContain("- Metric: Node Memory Utilization (%)");
1010
+ expect(rootCause).toContain("**Root Cause Analysis**");
1011
+ expect(rootCause).toContain(
1012
+ "Node `node-hot` memory usage is at **88.82%**.",
1013
+ );
1014
+ expect(rootCause).not.toContain("memory usage is at **210 GB**");
1015
+ expect(rootCause).not.toMatch(/memory usage is at \*\*[\d.]+ GB/);
1016
+ });
1017
+
1018
+ test("off-filter rows never interleave with on-filter rows, and are never ranked", async () => {
1019
+ /*
1020
+ * Input interleaves the two kinds. The % rows are ranked worst first;
1021
+ * the bytes rows follow in the order they came — 230 GB after 210 GB,
1022
+ * because their numbers are not comparable with the % rows and are
1023
+ * not re-ranked among themselves either.
1024
+ */
1025
+ const rootCause: string = await run([
1026
+ BIG_NODE,
1027
+ HOT_NODE,
1028
+ k8sNode({ name: "node-big-2", used: 230 * GB, allocatable: 512 * GB }),
1029
+ CALM_NODE,
1030
+ k8sNode({ name: "node-hot-2", used: 58.88 * GB, allocatable: 64 * GB }),
1031
+ ]);
1032
+
1033
+ expect(entryHeads(listSection(rootCause, "Affected Resources"))).toEqual([
1034
+ "1. **Node** `node-hot-2` — **92.00%**",
1035
+ "2. **Node** `node-hot` — **88.82%**",
1036
+ "3. **Node** `node-big` — **210 GB** (`used_mem`)",
1037
+ "4. **Node** `node-big-2` — **230 GB** (`used_mem`)",
1038
+ ]);
1039
+ expect(listSection(rootCause, "Affected Resources")).toContain(
1040
+ "**Affected Resources** (4 total)",
1041
+ );
1042
+ expect(rootCause).toContain(
1043
+ "Node `node-hot-2` memory usage is at **92.00%**.",
1044
+ );
1045
+ });
1046
+ });
1047
+
1048
+ /*
1049
+ * The ordering and value-cell rules on their own, through the typed shim:
1050
+ * a fall criteria ranks lowest first, rows valued by another filter follow
1051
+ * in arrival order, a no-data row comes last, and only an off-filter
1052
+ * value carries a note.
1053
+ */
1054
+ interface ShimRow {
1055
+ identity: { name: string };
1056
+ value: number | null;
1057
+ formattedValue: string;
1058
+ valueNote?: string | undefined;
1059
+ }
1060
+
1061
+ type EvaluatorPrivate = {
1062
+ sortAffectedRows: (input: {
1063
+ rows: Array<ShimRow>;
1064
+ worstIsLowest: boolean;
1065
+ }) => Array<ShimRow>;
1066
+ renderAffectedRowValue: (row: ShimRow) => string;
1067
+ };
1068
+
1069
+ const Evaluator: EvaluatorPrivate =
1070
+ MonitorCriteriaEvaluator as unknown as EvaluatorPrivate;
1071
+
1072
+ describe("Affected-row ordering and value cells", () => {
1073
+ function shimRow(
1074
+ name: string,
1075
+ value: number | null,
1076
+ valueNote?: string | undefined,
1077
+ ): ShimRow {
1078
+ return {
1079
+ identity: { name: name },
1080
+ value: value,
1081
+ formattedValue: value === null ? "no data" : `${value}`,
1082
+ ...(valueNote ? { valueNote: valueNote } : {}),
1083
+ };
1084
+ }
1085
+
1086
+ function rendered(rows: Array<ShimRow>): Array<string> {
1087
+ return rows.map((row: ShimRow): string => {
1088
+ return `${row.identity.name} ${Evaluator.renderAffectedRowValue(row)}`;
1089
+ });
1090
+ }
1091
+
1092
+ const ROWS: Array<ShimRow> = [
1093
+ shimRow("off-a", 5, "`other_alias`"),
1094
+ shimRow("silent", null),
1095
+ shimRow("on-40", 40),
1096
+ shimRow("off-b", 1, "Other Metric"),
1097
+ shimRow("on-10", 10),
1098
+ shimRow("on-25", 25),
1099
+ ];
1100
+
1101
+ test("a rise criteria: on-filter highest first, then off-filter as they came, then no data", () => {
1102
+ expect(
1103
+ rendered(
1104
+ Evaluator.sortAffectedRows({ rows: ROWS, worstIsLowest: false }),
1105
+ ),
1106
+ ).toEqual([
1107
+ "on-40 **40**",
1108
+ "on-25 **25**",
1109
+ "on-10 **10**",
1110
+ "off-a **5** (`other_alias`)",
1111
+ "off-b **1** (Other Metric)",
1112
+ "silent **no data**",
1113
+ ]);
1114
+ });
1115
+
1116
+ test("a fall criteria: on-filter lowest first, then off-filter as they came, then no data", () => {
1117
+ expect(
1118
+ rendered(Evaluator.sortAffectedRows({ rows: ROWS, worstIsLowest: true })),
1119
+ ).toEqual([
1120
+ "on-10 **10**",
1121
+ "on-25 **25**",
1122
+ "on-40 **40**",
1123
+ "off-a **5** (`other_alias`)",
1124
+ "off-b **1** (Other Metric)",
1125
+ "silent **no data**",
1126
+ ]);
1127
+ });
1128
+ });
1129
+
1130
+ /*
1131
+ * ---------------------------------------------------------------------------
1132
+ * 3. Thresholds saved as strings
1133
+ * ---------------------------------------------------------------------------
1134
+ */
1135
+
1136
+ describe("A threshold saved as a string reads like the number", () => {
1137
+ test('k8s-pod-pending with "1": only the Pending pod is listed, and the analysis sends the reader to it', async () => {
1138
+ const monitorStep: MonitorStep = stringifyThresholds(
1139
+ kubernetesStep("k8s-pod-pending"),
1140
+ );
1141
+ const firing: MonitorCriteriaInstance = firingOf(monitorStep);
1142
+
1143
+ // Fixture guard: an ungrouped, incident-opening `= "1"`.
1144
+ expect(metricFiltersOf(firing)[0]?.filterType).toBe(FilterType.EqualTo);
1145
+ expect(metricFiltersOf(firing)[0]?.value).toBe("1");
1146
+ expect(MonitorStep.getGroupByAttributeKeys(monitorStep)).toEqual([]);
1147
+
1148
+ const alias: string = aliasOf(viewConfigOf(monitorStep).queryConfigs[0]!);
1149
+
1150
+ const rootCause: string = await firingRootCause({
1151
+ monitorStep: monitorStep,
1152
+ fixture: KUBERNETES,
1153
+ response: ungroupedResponse({
1154
+ monitorStep: monitorStep,
1155
+ fixture: KUBERNETES,
1156
+ declaredUnits: K8S_DECLARED_UNITS,
1157
+ // The cluster-wide Min phase: a pod has been Pending all window.
1158
+ valuesByAlias: { [alias]: [1, 1, 1, 1, 1] },
1159
+ scans: [
1160
+ {
1161
+ alias: alias,
1162
+ metricUnit: "1",
1163
+ resources: [
1164
+ {
1165
+ podName: "checkout-7d9f-running",
1166
+ namespace: "shop",
1167
+ metricValue: 2,
1168
+ lowestMetricValue: 2,
1169
+ },
1170
+ {
1171
+ podName: "checkout-7d9f-pending",
1172
+ namespace: "shop",
1173
+ metricValue: 1,
1174
+ lowestMetricValue: 1,
1175
+ },
1176
+ {
1177
+ podName: "report-job-failed",
1178
+ namespace: "batch",
1179
+ metricValue: 4,
1180
+ lowestMetricValue: 4,
1181
+ },
1182
+ {
1183
+ podName: "lost-pod",
1184
+ namespace: "shop",
1185
+ metricValue: 5,
1186
+ lowestMetricValue: 5,
1187
+ },
1188
+ ],
1189
+ },
1190
+ ],
1191
+ }),
1192
+ });
1193
+
1194
+ expect(listSection(rootCause, "Affected Resources")).toBe(
1195
+ [
1196
+ "**Affected Resources** (1 total)",
1197
+ "",
1198
+ "1. **Pod** `checkout-7d9f-pending` — **1**",
1199
+ " - Namespace: `shop`",
1200
+ ].join("\n"),
1201
+ );
1202
+ expect(rootCause).toContain("kubectl describe pod checkout-7d9f-pending");
1203
+ expect(rootCause).not.toContain("checkout-7d9f-running");
1204
+ expect(rootCause).not.toContain("report-job-failed");
1205
+ expect(rootCause).not.toContain("lost-pod");
1206
+ });
1207
+
1208
+ describe('grouped docker-swarm-task-down with "60"', () => {
1209
+ function swarmTask(input: {
1210
+ container: string;
1211
+ service: string;
1212
+ node: string;
1213
+ uptimes: Array<number>;
1214
+ }): RawSeries {
1215
+ return {
1216
+ attributes: {
1217
+ "resource.container.name": input.container,
1218
+ "resource.docker.swarm.cluster.name": SWARM_CLUSTER,
1219
+ "docker.swarm.service.name": input.service,
1220
+ "docker.swarm.node.name": input.node,
1221
+ },
1222
+ valuesByAlias: { container_uptime: input.uptimes },
1223
+ };
1224
+ }
1225
+
1226
+ const TASKS: Array<RawSeries> = [
1227
+ swarmTask({
1228
+ container: "web.2.bbb",
1229
+ service: "web",
1230
+ node: "n2",
1231
+ uptimes: [40, 55, 59],
1232
+ }),
1233
+ swarmTask({
1234
+ container: "db.1.ccc",
1235
+ service: "db",
1236
+ node: "n3",
1237
+ uptimes: [3000, 3060, 3120],
1238
+ }),
1239
+ swarmTask({
1240
+ container: "web.1.aaa",
1241
+ service: "web",
1242
+ node: "n1",
1243
+ uptimes: [5, 35, 58],
1244
+ }),
1245
+ ];
1246
+
1247
+ function run(monitorStep: MonitorStep): Promise<string> {
1248
+ return firingRootCause({
1249
+ monitorStep: monitorStep,
1250
+ fixture: DOCKER_SWARM,
1251
+ response: groupedResponse({
1252
+ monitorStep: monitorStep,
1253
+ fixture: DOCKER_SWARM,
1254
+ series: TASKS,
1255
+ declaredUnits: { "container.uptime": "s" },
1256
+ }),
1257
+ });
1258
+ }
1259
+
1260
+ test("ranks the lowest-uptime task first, each at its LOWEST breaching sample", async () => {
1261
+ const monitorStep: MonitorStep = stringifyThresholds(
1262
+ dockerSwarmStep("docker-swarm-task-down"),
1263
+ );
1264
+
1265
+ expect(metricFiltersOf(firingOf(monitorStep))[0]?.value).toBe("60");
1266
+ expect(metricFiltersOf(firingOf(monitorStep))[0]?.filterType).toBe(
1267
+ FilterType.LessThan,
1268
+ );
1269
+
1270
+ const rootCause: string = await run(monitorStep);
1271
+
1272
+ expect(listSection(rootCause, "Affected Tasks")).toBe(
1273
+ [
1274
+ "**Affected Tasks** (2 total)",
1275
+ "",
1276
+ "1. **Task** `web.1.aaa` — **5 sec**",
1277
+ " - Service: `web`",
1278
+ " - Node: `n1`",
1279
+ "2. **Task** `web.2.bbb` — **40 sec**",
1280
+ " - Service: `web`",
1281
+ " - Node: `n2`",
1282
+ ].join("\n"),
1283
+ );
1284
+ expect(rootCause).not.toContain("db.1.ccc");
1285
+ });
1286
+
1287
+ test("renders exactly what the numeric threshold 60 renders", async () => {
1288
+ const numeric: string = await run(
1289
+ dockerSwarmStep("docker-swarm-task-down"),
1290
+ );
1291
+ const text: string = await run(
1292
+ stringifyThresholds(dockerSwarmStep("docker-swarm-task-down")),
1293
+ );
1294
+
1295
+ expect(listSection(numeric, "Affected Tasks")).not.toBe("");
1296
+ expect(text).toBe(numeric);
1297
+ });
1298
+ });
1299
+
1300
+ test('pve-node-offline ungrouped with pve_up < "1": the raw scan lists the down node, not the up ones', async () => {
1301
+ const monitorStep: MonitorStep = stringifyThresholds(
1302
+ ungroup(proxmoxStep("pve-node-offline")),
1303
+ );
1304
+
1305
+ expect(metricFiltersOf(firingOf(monitorStep))[0]?.filterType).toBe(
1306
+ FilterType.LessThan,
1307
+ );
1308
+ expect(metricFiltersOf(firingOf(monitorStep))[0]?.value).toBe("1");
1309
+
1310
+ const rootCause: string = await firingRootCause({
1311
+ monitorStep: monitorStep,
1312
+ fixture: PROXMOX,
1313
+ response: ungroupedResponse({
1314
+ monitorStep: monitorStep,
1315
+ fixture: PROXMOX,
1316
+ valuesByAlias: { node_up: [0, 0, 0, 0, 0] },
1317
+ scans: [
1318
+ {
1319
+ alias: "node_up",
1320
+ resources: [
1321
+ {
1322
+ resourceId: "node/pve1",
1323
+ resourceType: "node",
1324
+ scope: "node",
1325
+ metricValue: 1,
1326
+ lowestMetricValue: 1,
1327
+ },
1328
+ {
1329
+ resourceId: "node/pve2",
1330
+ resourceType: "node",
1331
+ scope: "node",
1332
+ metricValue: 0,
1333
+ lowestMetricValue: 0,
1334
+ },
1335
+ {
1336
+ resourceId: "node/pve3",
1337
+ resourceType: "node",
1338
+ scope: "node",
1339
+ metricValue: 1,
1340
+ lowestMetricValue: 1,
1341
+ },
1342
+ ],
1343
+ },
1344
+ ],
1345
+ }),
1346
+ });
1347
+
1348
+ expect(listSection(rootCause, "Affected Resources")).toBe(
1349
+ [
1350
+ "**Affected Resources** (1 total)",
1351
+ "",
1352
+ "1. **Node** `node/pve2` — **0**",
1353
+ ].join("\n"),
1354
+ );
1355
+ expect(rootCause).not.toContain("node/pve1");
1356
+ expect(rootCause).not.toContain("node/pve3");
1357
+ });
1358
+ });
1359
+
1360
+ /*
1361
+ * ---------------------------------------------------------------------------
1362
+ * 4. The raw scan is judged in the threshold's unit
1363
+ * ---------------------------------------------------------------------------
1364
+ */
1365
+
1366
+ describe("The raw scan is judged in the unit the threshold was written in", () => {
1367
+ test('ungrouped docker-swarm-task-down, "uptime < 1 min": the 30 s task is listed in seconds, the 50000 s one is not', async () => {
1368
+ const monitorStep: MonitorStep = ungroup(
1369
+ dockerSwarmStep("docker-swarm-task-down"),
1370
+ );
1371
+ const filter: CriteriaFilter = metricFiltersOf(firingOf(monitorStep))[0]!;
1372
+
1373
+ expect(filter.filterType).toBe(FilterType.LessThan);
1374
+ filter.value = 1;
1375
+ filter.metricMonitorOptions = {
1376
+ ...filter.metricMonitorOptions!,
1377
+ thresholdUnit: "min",
1378
+ };
1379
+
1380
+ const rootCause: string = await firingRootCause({
1381
+ monitorStep: monitorStep,
1382
+ fixture: DOCKER_SWARM,
1383
+ response: ungroupedResponse({
1384
+ monitorStep: monitorStep,
1385
+ fixture: DOCKER_SWARM,
1386
+ declaredUnits: { "container.uptime": "s" },
1387
+ // The youngest scrape in the window: 30 s, under a minute.
1388
+ valuesByAlias: { container_uptime: [30] },
1389
+ scans: [
1390
+ {
1391
+ alias: "container_uptime",
1392
+ metricUnit: "s",
1393
+ resources: [
1394
+ {
1395
+ containerName: "web.2.old",
1396
+ serviceName: "web",
1397
+ nodeName: "n1",
1398
+ metricValue: 50000,
1399
+ lowestMetricValue: 50000,
1400
+ },
1401
+ {
1402
+ containerName: "web.1.restarted",
1403
+ serviceName: "web",
1404
+ nodeName: "n1",
1405
+ metricValue: 30,
1406
+ lowestMetricValue: 30,
1407
+ },
1408
+ ],
1409
+ },
1410
+ ],
1411
+ }),
1412
+ });
1413
+
1414
+ expect(listSection(rootCause, "Affected Tasks")).toBe(
1415
+ [
1416
+ "**Affected Tasks** (1 total)",
1417
+ "",
1418
+ "1. **Task** `web.1.restarted` — **30 sec**",
1419
+ " - Service: `web`",
1420
+ " - Node: `n1`",
1421
+ ].join("\n"),
1422
+ );
1423
+ expect(rootCause).not.toContain("web.2.old");
1424
+ expect(rootCause).toContain(metricLine(DOCKER_SWARM, "container.uptime"));
1425
+ });
1426
+
1427
+ test('ungrouped pve-node-high-cpu, "> 90 %" over a 0–1 ratio: only the node at 0.95 is listed, as 95.00%', async () => {
1428
+ const monitorStep: MonitorStep = ungroup(proxmoxStep("pve-node-high-cpu"));
1429
+ const filter: CriteriaFilter = metricFiltersOf(firingOf(monitorStep))[0]!;
1430
+
1431
+ expect(filter.filterType).toBe(FilterType.GreaterThan);
1432
+ filter.value = 90;
1433
+ filter.metricMonitorOptions = {
1434
+ ...filter.metricMonitorOptions!,
1435
+ thresholdUnit: "%",
1436
+ };
1437
+
1438
+ const rootCause: string = await firingRootCause({
1439
+ monitorStep: monitorStep,
1440
+ fixture: PROXMOX,
1441
+ response: ungroupedResponse({
1442
+ monitorStep: monitorStep,
1443
+ fixture: PROXMOX,
1444
+ declaredUnits: { pve_cpu_usage_ratio: "1" },
1445
+ valuesByAlias: { node_cpu: [0.95, 0.95, 0.95, 0.95, 0.95] },
1446
+ scans: [
1447
+ {
1448
+ alias: "node_cpu",
1449
+ metricUnit: "1",
1450
+ resources: [
1451
+ {
1452
+ resourceId: "node/pve2",
1453
+ resourceType: "node",
1454
+ scope: "node",
1455
+ metricValue: 0.5,
1456
+ lowestMetricValue: 0.5,
1457
+ },
1458
+ {
1459
+ resourceId: "node/pve1",
1460
+ resourceType: "node",
1461
+ scope: "node",
1462
+ metricValue: 0.95,
1463
+ lowestMetricValue: 0.95,
1464
+ },
1465
+ ],
1466
+ },
1467
+ ],
1468
+ }),
1469
+ });
1470
+
1471
+ expect(listSection(rootCause, "Affected Resources")).toBe(
1472
+ [
1473
+ "**Affected Resources** (1 total)",
1474
+ "",
1475
+ "1. **Node** `node/pve1` — **95.00%**",
1476
+ ].join("\n"),
1477
+ );
1478
+ // The node at 50% is healthy under a "> 90%" criteria.
1479
+ expect(rootCause).not.toContain("node/pve2");
1480
+ expect(rootCause).not.toContain("**0.95**");
1481
+ });
1482
+ });
1483
+
1484
+ /*
1485
+ * ---------------------------------------------------------------------------
1486
+ * 5. Grouped by a key that names no platform object
1487
+ * ---------------------------------------------------------------------------
1488
+ */
1489
+
1490
+ describe("A monitor grouped by a key that names no platform object titles each series by its labels", () => {
1491
+ test("Kubernetes k8s.volume.available grouped by PVC name: one distinct **Series** row per claim", async () => {
1492
+ const args: CommonTemplateArgs = commonArgs(`${K8S_CLUSTER} - PVC space`);
1493
+
1494
+ // What the monitor form builds: one query, grouped by the claim name.
1495
+ const monitorStep: MonitorStep = buildKubernetesMonitorStep({
1496
+ kubernetesMonitor: buildKubernetesMonitorConfig({
1497
+ clusterIdentifier: K8S_CLUSTER,
1498
+ metricName: "k8s.volume.available",
1499
+ metricAlias: "vol_avail",
1500
+ resourceScope: KubernetesResourceScope.Cluster,
1501
+ rollingTime: RollingTime.Past5Minutes,
1502
+ aggregationType: MetricsAggregationType.Min,
1503
+ groupByAttributeKeys: [PVC_KEY],
1504
+ }),
1505
+ offlineCriteriaInstance: buildOfflineCriteriaInstance({
1506
+ offlineMonitorStatusId: args.offlineMonitorStatusId,
1507
+ incidentSeverityId: args.defaultIncidentSeverityId,
1508
+ alertSeverityId: args.defaultAlertSeverityId,
1509
+ monitorName: args.monitorName,
1510
+ metricAlias: "vol_avail",
1511
+ filterType: FilterType.LessThan,
1512
+ value: GB,
1513
+ }),
1514
+ onlineCriteriaInstance: buildOnlineCriteriaInstance({
1515
+ onlineMonitorStatusId: args.onlineMonitorStatusId,
1516
+ metricAlias: "vol_avail",
1517
+ filterType: FilterType.GreaterThanOrEqualTo,
1518
+ value: GB,
1519
+ }),
1520
+ });
1521
+
1522
+ /*
1523
+ * Each claim's datapoints also carry the pod that mounts it — which a
1524
+ * claim-level series must not be titled by.
1525
+ */
1526
+ const claim: (input: {
1527
+ name: string;
1528
+ namespace: string;
1529
+ pod: string;
1530
+ node: string;
1531
+ available: number;
1532
+ }) => RawSeries = (input: {
1533
+ name: string;
1534
+ namespace: string;
1535
+ pod: string;
1536
+ node: string;
1537
+ available: number;
1538
+ }): RawSeries => {
1539
+ return {
1540
+ attributes: {
1541
+ [K8S_CLUSTER_KEY]: K8S_CLUSTER,
1542
+ [PVC_KEY]: input.name,
1543
+ [NAMESPACE_KEY]: input.namespace,
1544
+ [POD_KEY]: input.pod,
1545
+ [NODE_KEY]: input.node,
1546
+ },
1547
+ valuesByAlias: {
1548
+ vol_avail: MINUTES.map((): number => {
1549
+ return input.available;
1550
+ }),
1551
+ },
1552
+ };
1553
+ };
1554
+
1555
+ const rootCause: string = await firingRootCause({
1556
+ monitorStep: monitorStep,
1557
+ fixture: KUBERNETES,
1558
+ response: groupedResponse({
1559
+ monitorStep: monitorStep,
1560
+ fixture: KUBERNETES,
1561
+ declaredUnits: K8S_DECLARED_UNITS,
1562
+ series: [
1563
+ claim({
1564
+ name: "data-postgres-0",
1565
+ namespace: "db",
1566
+ pod: "postgres-0",
1567
+ node: "node-a",
1568
+ available: 0.5 * GB,
1569
+ }),
1570
+ claim({
1571
+ name: "data-redis-0",
1572
+ namespace: "cache",
1573
+ pod: "redis-0",
1574
+ node: "node-b",
1575
+ available: 0.3 * GB,
1576
+ }),
1577
+ claim({
1578
+ name: "data-kafka-0",
1579
+ namespace: "stream",
1580
+ pod: "kafka-0",
1581
+ node: "node-c",
1582
+ available: 40 * GB,
1583
+ }),
1584
+ ],
1585
+ }),
1586
+ });
1587
+
1588
+ expect(listSection(rootCause, "Affected Resources")).toBe(
1589
+ [
1590
+ "**Affected Resources** (2 total)",
1591
+ "",
1592
+ "1. **Series** `k8s.persistentvolumeclaim.name=data-redis-0` — **300 MB**",
1593
+ "2. **Series** `k8s.persistentvolumeclaim.name=data-postgres-0` — **500 MB**",
1594
+ ].join("\n"),
1595
+ );
1596
+
1597
+ expect(rootCause).not.toContain("**Cluster**");
1598
+ expect(rootCause).not.toContain("resource.k8s.persistentvolumeclaim.name=");
1599
+ expect(rootCause).not.toContain("data-kafka-0");
1600
+ // Never titled by the pod that happens to mount the claim.
1601
+ expect(rootCause).not.toContain("**Pod**");
1602
+ expect(rootCause).not.toMatch(RAW_NUMBER);
1603
+ });
1604
+
1605
+ test("Proxmox pve-node-high-cpu grouped by the exporter instance: the list renders, one **Series** row per instance", async () => {
1606
+ const monitorStep: MonitorStep = proxmoxStep("pve-node-high-cpu");
1607
+
1608
+ for (const queryConfig of viewConfigOf(monitorStep).queryConfigs) {
1609
+ queryConfig.metricQueryData.groupByAttributeKeys = ["instance"];
1610
+ }
1611
+
1612
+ const exporter: (instance: string, ratio: number) => RawSeries = (
1613
+ instance: string,
1614
+ ratio: number,
1615
+ ): RawSeries => {
1616
+ return {
1617
+ /*
1618
+ * pve-exporter's datapoint names the node by `id`, but the series
1619
+ * is keyed by the scrape target only — so its labels name no
1620
+ * Proxmox object.
1621
+ */
1622
+ attributes: {
1623
+ instance: instance,
1624
+ "resource.proxmox.cluster.name": PVE_CLUSTER,
1625
+ },
1626
+ valuesByAlias: {
1627
+ node_cpu: MINUTES.map((): number => {
1628
+ return ratio;
1629
+ }),
1630
+ },
1631
+ };
1632
+ };
1633
+
1634
+ const rootCause: string = await firingRootCause({
1635
+ monitorStep: monitorStep,
1636
+ fixture: PROXMOX,
1637
+ response: groupedResponse({
1638
+ monitorStep: monitorStep,
1639
+ fixture: PROXMOX,
1640
+ declaredUnits: { pve_cpu_usage_ratio: "1" },
1641
+ series: [
1642
+ exporter("10.0.0.11:9221", 0.93),
1643
+ exporter("10.0.0.12:9221", 0.2),
1644
+ exporter("10.0.0.13:9221", 0.97),
1645
+ ],
1646
+ }),
1647
+ });
1648
+
1649
+ expect(listSection(rootCause, "Affected Resources")).toBe(
1650
+ [
1651
+ "**Affected Resources** (2 total)",
1652
+ "",
1653
+ "1. **Series** `instance=10.0.0.13:9221` — **97.00%**",
1654
+ "2. **Series** `instance=10.0.0.11:9221` — **93.00%**",
1655
+ ].join("\n"),
1656
+ );
1657
+ expect(rootCause).not.toContain("10.0.0.12");
1658
+ expect(rootCause).not.toContain("**Cluster**");
1659
+ expect(rootCause).not.toContain("**Metric Summary**");
1660
+ });
1661
+
1662
+ test("Proxmox pve-guest-high-cpu grouped by guest type (pve.type): the list renders, one **Series** row per type", async () => {
1663
+ /*
1664
+ * "One alert per guest type": each series aggregates every VM (or
1665
+ * every container), so its only label is the type — a kind, not an
1666
+ * object. No row can be titled by a guest id, so each is titled by
1667
+ * its labels rather than the list being dropped.
1668
+ */
1669
+ const monitorStep: MonitorStep = proxmoxStep("pve-guest-high-cpu");
1670
+
1671
+ for (const queryConfig of viewConfigOf(monitorStep).queryConfigs) {
1672
+ queryConfig.metricQueryData.groupByAttributeKeys = ["pve.type"];
1673
+ }
1674
+
1675
+ const guest: (id: string, ratio: number) => RawSeries = (
1676
+ id: string,
1677
+ ratio: number,
1678
+ ): RawSeries => {
1679
+ return {
1680
+ attributes: {
1681
+ id: id,
1682
+ "pve.type": id.split("/")[0]!,
1683
+ "pve.scope": "guest",
1684
+ "pve.id": id.split("/")[1]!,
1685
+ "resource.proxmox.cluster.name": PVE_CLUSTER,
1686
+ },
1687
+ valuesByAlias: {
1688
+ guest_cpu: MINUTES.map((): number => {
1689
+ return ratio;
1690
+ }),
1691
+ },
1692
+ };
1693
+ };
1694
+
1695
+ const rootCause: string = await firingRootCause({
1696
+ monitorStep: monitorStep,
1697
+ fixture: PROXMOX,
1698
+ response: groupedResponse({
1699
+ monitorStep: monitorStep,
1700
+ fixture: PROXMOX,
1701
+ declaredUnits: { pve_cpu_usage_ratio: "1" },
1702
+ series: [guest("qemu/100", 0.97), guest("lxc/101", 0.98)],
1703
+ }),
1704
+ });
1705
+
1706
+ // Fixture guard: the criteria fired on both types.
1707
+ expect(rootCause).toContain("**Proxmox Cluster Details**");
1708
+
1709
+ expect(listSection(rootCause, "Affected Resources")).toBe(
1710
+ [
1711
+ "**Affected Resources** (2 total)",
1712
+ "",
1713
+ "1. **Series** `pve.type=lxc` — **98.00%**",
1714
+ "2. **Series** `pve.type=qemu` — **97.00%**",
1715
+ ].join("\n"),
1716
+ );
1717
+ expect(rootCause).not.toContain("**Cluster**");
1718
+ });
1719
+ });