@oneuptime/common 13.0.5 → 13.0.6

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 (1091) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +81 -1
  2. package/Models/AnalyticsModels/SecurityEvent.ts +4 -4
  3. package/Models/DatabaseModels/Alert.ts +62 -0
  4. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  5. package/Models/DatabaseModels/Incident.ts +66 -0
  6. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +74 -0
  7. package/Models/DatabaseModels/IncidentPublicNote.ts +74 -0
  8. package/Models/DatabaseModels/Index.ts +6 -4
  9. package/Models/DatabaseModels/NetworkDevice.ts +67 -0
  10. package/Models/DatabaseModels/NetworkDeviceDiagnostic.ts +746 -0
  11. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +151 -0
  12. package/Models/DatabaseModels/NetworkSiteAssignmentRule.ts +1 -1
  13. package/Models/DatabaseModels/Project.ts +41 -0
  14. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +74 -0
  15. package/Models/DatabaseModels/SecurityEventConnection.ts +11 -11
  16. package/Models/DatabaseModels/ServiceLevelObjective.ts +185 -8
  17. package/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.ts +746 -43
  18. package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +575 -0
  19. package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +582 -0
  20. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.ts +16 -0
  21. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.ts +16 -0
  22. package/Models/DatabaseModels/StatusPageAnnouncement.ts +75 -0
  23. package/Models/DatabaseModels/ThreatIntelFeed.ts +1 -1
  24. package/Server/API/BaseAPI.ts +17 -0
  25. package/Server/API/BillingInvoiceAPI.ts +343 -30
  26. package/Server/API/BillingPaymentMethodAPI.ts +126 -5
  27. package/Server/API/DashboardAPI.ts +96 -2
  28. package/Server/API/SecurityEventConnectionAPI.ts +141 -11
  29. package/Server/API/StatusPageAPI.ts +308 -199
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1792800000000-AddNetworkDeviceDiagnostic.ts +83 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1792900000000-AddNetworkDeviceDnsNameAndShortDeviceNames.ts +36 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1793000000000-AddNetbiosLookupToNetworkDeviceDiscoveryScan.ts +29 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1793100000000-SloProductOverhaul.ts +516 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1793200000000-BackfillSloMonitorRulesAndAffectedResources.ts +98 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1793300000000-AddDataResidencyToProject.ts +27 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1793400000000-AddSubscriberUpdateNotificationStatus.ts +97 -0
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  38. package/Server/Services/AlertEpisodeLabelRuleEngineService.ts +138 -67
  39. package/Server/Services/AlertEpisodeOwnerRuleEngineService.ts +213 -84
  40. package/Server/Services/AlertEpisodePrivacyRuleEngineService.ts +112 -35
  41. package/Server/Services/AlertLabelRuleEngineService.ts +299 -217
  42. package/Server/Services/AlertOwnerRuleEngineService.ts +572 -396
  43. package/Server/Services/AlertPrivacyRuleEngineService.ts +116 -40
  44. package/Server/Services/AlertService.ts +111 -4
  45. package/Server/Services/AuditLogService.ts +612 -59
  46. package/Server/Services/BillingInvoiceService.ts +112 -102
  47. package/Server/Services/BillingService.ts +539 -11
  48. package/Server/Services/CephClusterLabelRuleEngineService.ts +170 -109
  49. package/Server/Services/CephClusterOwnerRuleEngineService.ts +233 -115
  50. package/Server/Services/CloudResourceLabelRuleEngineService.ts +163 -100
  51. package/Server/Services/CloudResourceOwnerRuleEngineService.ts +236 -115
  52. package/Server/Services/DashboardLabelRuleEngineService.ts +145 -84
  53. package/Server/Services/DashboardOwnerRuleEngineService.ts +213 -95
  54. package/Server/Services/DatabaseService.ts +171 -21
  55. package/Server/Services/DockerHostLabelRuleEngineService.ts +171 -110
  56. package/Server/Services/DockerHostOwnerRuleEngineService.ts +233 -115
  57. package/Server/Services/DockerSwarmClusterLabelRuleEngineService.ts +181 -112
  58. package/Server/Services/DockerSwarmClusterOwnerRuleEngineService.ts +243 -118
  59. package/Server/Services/HostLabelRuleEngineService.ts +167 -106
  60. package/Server/Services/HostOwnerRuleEngineService.ts +229 -111
  61. package/Server/Services/IncidentEpisodeLabelRuleEngineService.ts +141 -67
  62. package/Server/Services/IncidentEpisodeOwnerRuleEngineService.ts +272 -72
  63. package/Server/Services/IncidentEpisodePrivacyRuleEngineService.ts +112 -35
  64. package/Server/Services/IncidentEpisodePublicNoteService.ts +24 -0
  65. package/Server/Services/IncidentLabelRuleEngineService.ts +279 -204
  66. package/Server/Services/IncidentOwnerRuleEngineService.ts +595 -425
  67. package/Server/Services/IncidentPrivacyRuleEngineService.ts +117 -44
  68. package/Server/Services/IncidentPublicNoteService.ts +24 -0
  69. package/Server/Services/IncidentService.ts +114 -3
  70. package/Server/Services/IncomingCallPolicyLabelRuleEngineService.ts +147 -83
  71. package/Server/Services/IncomingCallPolicyOwnerRuleEngineService.ts +221 -100
  72. package/Server/Services/Index.ts +6 -4
  73. package/Server/Services/IoTFleetLabelRuleEngineService.ts +145 -84
  74. package/Server/Services/IoTFleetOwnerRuleEngineService.ts +213 -95
  75. package/Server/Services/KubernetesClusterLabelRuleEngineService.ts +181 -112
  76. package/Server/Services/KubernetesClusterOwnerRuleEngineService.ts +243 -118
  77. package/Server/Services/LabelService.ts +23 -28
  78. package/Server/Services/MonitorLabelRuleEngineService.ts +152 -92
  79. package/Server/Services/MonitorOwnerRuleEngineService.ts +220 -104
  80. package/Server/Services/MonitorProbeService.ts +20 -2
  81. package/Server/Services/MonitorService.ts +11 -9
  82. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +376 -5
  83. package/Server/Services/NetworkDeviceDiagnosticService.ts +417 -0
  84. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +147 -83
  85. package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +218 -97
  86. package/Server/Services/NetworkDeviceService.ts +39 -2
  87. package/Server/Services/OnCallDutyPolicyLabelRuleEngineService.ts +151 -80
  88. package/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.ts +222 -97
  89. package/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.ts +154 -83
  90. package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.ts +225 -100
  91. package/Server/Services/PodmanHostLabelRuleEngineService.ts +171 -110
  92. package/Server/Services/PodmanHostOwnerRuleEngineService.ts +235 -115
  93. package/Server/Services/ProjectService.ts +111 -10
  94. package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +174 -110
  95. package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +239 -116
  96. package/Server/Services/RumApplicationLabelRuleEngineService.ts +137 -73
  97. package/Server/Services/RumApplicationOwnerRuleEngineService.ts +200 -83
  98. package/Server/Services/RunbookLabelRuleEngineService.ts +146 -84
  99. package/Server/Services/RunbookOwnerRuleEngineService.ts +216 -95
  100. package/Server/Services/ScheduledMaintenanceLabelRuleEngineService.ts +324 -230
  101. package/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.ts +613 -428
  102. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +24 -0
  103. package/Server/Services/ScheduledMaintenanceService.ts +43 -14
  104. package/Server/Services/SecurityEventConnectionService.ts +56 -4
  105. package/Server/Services/ServerlessFunctionLabelRuleEngineService.ts +143 -74
  106. package/Server/Services/ServerlessFunctionOwnerRuleEngineService.ts +206 -85
  107. package/Server/Services/ServiceLabelRuleEngineService.ts +172 -110
  108. package/Server/Services/ServiceLevelObjectiveBurnRateRuleService.ts +1057 -18
  109. package/Server/Services/ServiceLevelObjectiveFeedService.ts +108 -0
  110. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +793 -97
  111. package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +1069 -0
  112. package/Server/Services/ServiceLevelObjectiveOwnerTeamService.ts +240 -1
  113. package/Server/Services/ServiceLevelObjectiveOwnerUserService.ts +257 -1
  114. package/Server/Services/ServiceLevelObjectiveService.ts +1356 -56
  115. package/Server/Services/ServiceOwnerRuleEngineService.ts +238 -116
  116. package/Server/Services/StatusPageAnnouncementService.ts +13 -0
  117. package/Server/Services/StatusPageLabelRuleEngineService.ts +146 -85
  118. package/Server/Services/StatusPageOwnerRuleEngineService.ts +210 -99
  119. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +28 -232
  120. package/Server/Services/StatusPageSubscriberService.ts +6 -0
  121. package/Server/Services/TelemetryUsageBillingService.ts +38 -7
  122. package/Server/Services/ThreatIntelFeedService.ts +1 -1
  123. package/Server/Services/VMwareVCenterLabelRuleEngineService.ts +173 -110
  124. package/Server/Services/VMwareVCenterOwnerRuleEngineService.ts +238 -116
  125. package/Server/Services/WorkflowLabelRuleEngineService.ts +145 -84
  126. package/Server/Services/WorkflowOwnerRuleEngineService.ts +215 -95
  127. package/Server/Types/Database/UpdateByID.ts +3 -0
  128. package/Server/Types/Database/UpdateOneBy.ts +7 -0
  129. package/Server/Utils/AI/AlertAIContextBuilder.ts +19 -0
  130. package/Server/Utils/AI/IncidentAIContextBuilder.ts +39 -0
  131. package/Server/Utils/AI/Toolbox/AlertTools.ts +37 -1
  132. package/Server/Utils/AI/Toolbox/IncidentTools.ts +22 -1
  133. package/Server/Utils/AI/Toolbox/SloTools.ts +416 -51
  134. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +151 -13
  135. package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +17 -4
  136. package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +155 -8
  137. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +16 -0
  138. package/Server/Utils/Database/RelationValueUtil.ts +106 -0
  139. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +6 -1
  140. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +22 -27
  141. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +3 -1
  142. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +50 -20
  143. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +13 -1
  144. package/Server/Utils/Monitor/NetworkDeviceMetricUtil.ts +6 -11
  145. package/Server/Utils/Rules/MonitorRulePatternValidator.ts +51 -0
  146. package/Server/Utils/Rules/OwnerRuleAssignment.ts +110 -0
  147. package/Server/Utils/Rules/RuleRun/RuleApplication.ts +81 -0
  148. package/Server/Utils/Rules/RuleRun/RuleRunPermission.ts +188 -0
  149. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +803 -0
  150. package/Server/Utils/Rules/RuleRun/RuleRunner.ts +341 -0
  151. package/Server/Utils/SecurityEvent/ConnectorErrorMessage.ts +3 -2
  152. package/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.ts +6 -4
  153. package/Server/Utils/SecurityEvent/{GoogleSecOps → Connectors/GoogleSecOps}/GoogleSecOpsClient.ts +263 -16
  154. package/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnector.ts +2101 -0
  155. package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.ts +194 -14
  156. package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.ts +1 -2
  157. package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.ts +38 -7
  158. package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.ts +2 -0
  159. package/Server/Utils/SecurityEvent/Connectors/Types.ts +242 -2
  160. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +1 -1
  161. package/Server/Utils/SecurityEvent/SecurityEventDedupe.ts +3 -3
  162. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +2 -2
  163. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +217 -56
  164. package/Server/Utils/Slo/SloFeedUtil.ts +200 -0
  165. package/Server/Utils/Slo/SloLegacyMonitorLabelAdoption.ts +173 -0
  166. package/Server/Utils/Slo/SloMetricUtil.ts +327 -0
  167. package/Server/Utils/Slo/SloOwnerReferenceValidator.ts +208 -0
  168. package/Server/Utils/Slo/SloRecordReferenceValidator.ts +169 -0
  169. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +6 -43
  170. package/Server/Utils/StatusPageResource.ts +50 -5
  171. package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +664 -15
  172. package/Tests/App/Dashboard/AddWidgetModal.test.tsx +4 -1
  173. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +307 -0
  174. package/Tests/App/Dashboard/ArchiveResourceCard.test.tsx +289 -0
  175. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +598 -0
  176. package/Tests/App/Dashboard/BillingDefaultPaymentMethod.test.tsx +1046 -0
  177. package/Tests/App/Dashboard/BillingPaidUsageFlow.test.tsx +3 -0
  178. package/Tests/App/Dashboard/BillingPaymentMethodForm.test.tsx +201 -7
  179. package/Tests/App/Dashboard/BooleanSeriesCriteriaFilterText.test.ts +82 -0
  180. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +16 -1
  181. package/Tests/App/Dashboard/ChangeAlertStateAIHeader.test.tsx +116 -8
  182. package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +168 -1
  183. package/Tests/App/Dashboard/ConnectionTestModal.test.tsx +1 -1
  184. package/Tests/App/Dashboard/ConnectorTestReportView.test.tsx +20 -3
  185. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +1095 -1
  186. package/Tests/App/Dashboard/CorrelateFilterChips.test.tsx +910 -0
  187. package/Tests/App/Dashboard/CorrelateGraph.test.tsx +3747 -25
  188. package/Tests/App/Dashboard/CorrelateGraphHelpers.test.ts +1085 -0
  189. package/Tests/App/Dashboard/CorrelateInspector.test.tsx +1534 -0
  190. package/Tests/App/Dashboard/CorrelateNodeCard.test.tsx +965 -0
  191. package/Tests/App/Dashboard/CorrelateResultSummary.test.tsx +1525 -0
  192. package/Tests/App/Dashboard/CorrelationGraph.test.ts +1030 -0
  193. package/Tests/App/Dashboard/CorrelationGraphLayout.test.ts +526 -0
  194. package/Tests/App/Dashboard/DeviceDiagnostics.test.tsx +787 -0
  195. package/Tests/App/Dashboard/DeviceLatencyTrend.test.tsx +278 -0
  196. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +279 -0
  197. package/Tests/App/Dashboard/DiscoveryScanEditForm.test.tsx +87 -0
  198. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +443 -8
  199. package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +340 -0
  200. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +98 -2
  201. package/Tests/App/Dashboard/ExceptionSegmentedControl.test.tsx +1020 -0
  202. package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +268 -47
  203. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1114 -76
  204. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +124 -2
  205. package/Tests/App/Dashboard/InvestigationReportView.test.tsx +279 -176
  206. package/Tests/App/Dashboard/InvestigationRunDetails.test.tsx +1166 -0
  207. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +437 -0
  208. package/Tests/App/Dashboard/NetworkDeviceBulkDelete.test.tsx +65 -0
  209. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +5 -0
  210. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +74 -0
  211. package/Tests/App/Dashboard/ResourceFeed.test.tsx +125 -5
  212. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  213. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +1764 -0
  214. package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +1000 -64
  215. package/Tests/App/Dashboard/SecurityEventConnectionsEmptyState.test.tsx +448 -0
  216. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +739 -189
  217. package/Tests/App/Dashboard/SecurityEventSeverityPill.test.tsx +233 -0
  218. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +282 -0
  219. package/Tests/App/Dashboard/SloActiveBurnEventsCard.test.tsx +538 -0
  220. package/Tests/App/Dashboard/SloBulkActionsArchive.test.tsx +252 -0
  221. package/Tests/App/Dashboard/SloDashboardTemplateWidgets.test.tsx +322 -518
  222. package/Tests/App/Dashboard/SloElement.test.tsx +194 -0
  223. package/Tests/App/Dashboard/SloFeed.test.tsx +289 -0
  224. package/Tests/App/Dashboard/SloHistoryCharts.test.tsx +471 -0
  225. package/Tests/App/Dashboard/SloHistorySeries.test.tsx +607 -0
  226. package/Tests/App/Dashboard/SloListWidget.test.tsx +783 -0
  227. package/Tests/App/Dashboard/SloMetricsPage.test.tsx +346 -0
  228. package/Tests/App/Dashboard/SloMonitorsPage.test.tsx +661 -0
  229. package/Tests/App/Dashboard/SloMonitorsSummaryCard.test.tsx +489 -0
  230. package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +437 -0
  231. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +305 -0
  232. package/Tests/App/Dashboard/SloOverviewData.test.tsx +563 -0
  233. package/Tests/App/Dashboard/SloOverviewHero.test.tsx +311 -0
  234. package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +426 -0
  235. package/Tests/App/Dashboard/SloOverviewSidebarCards.test.tsx +493 -0
  236. package/Tests/App/Dashboard/SloSettings.test.tsx +633 -0
  237. package/Tests/App/Dashboard/SloStatusSummaryCards.test.tsx +403 -0
  238. package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +373 -0
  239. package/Tests/App/Dashboard/SloWidgetVariableBinding.test.tsx +689 -0
  240. package/Tests/App/Dashboard/TopologyConnectivitySection.test.tsx +206 -0
  241. package/Tests/App/Dashboard/TracesEntityNames.test.tsx +47 -15
  242. package/Tests/App/Dashboard/UseBulkShortenDeviceNames.test.tsx +1312 -0
  243. package/Tests/App/Dashboard/WidgetCatalog.test.ts +9 -1
  244. package/Tests/Models/AnalyticsModels/RumSessionReplayColumns.test.ts +14 -16
  245. package/Tests/Models/DatabaseModels/AuditLogModelConfiguration.test.ts +442 -0
  246. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +9 -10
  247. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +48 -1
  248. package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +835 -0
  249. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +3 -2
  250. package/Tests/Models/DiscoveryScanNetbiosLookupColumn.test.ts +328 -0
  251. package/Tests/Models/DiscoveryScanShortDeviceNamesColumn.test.ts +335 -0
  252. package/Tests/Models/NetworkDeviceDiagnosticContract.test.ts +194 -0
  253. package/Tests/Models/NetworkDeviceDnsNameColumn.test.ts +321 -0
  254. package/Tests/Models/ProjectDataResidencyColumn.test.ts +154 -0
  255. package/Tests/Models/SecurityDomainAccessControl.test.ts +181 -50
  256. package/Tests/Server/API/BaseAPI.test.ts +6 -0
  257. package/Tests/Server/API/BaseAPIUpdateMiscDataProps.test.ts +217 -0
  258. package/Tests/Server/API/BillingInvoiceAPI.test.ts +7 -0
  259. package/Tests/Server/API/BillingInvoiceAPIPaymentStates.test.ts +990 -0
  260. package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +498 -0
  261. package/Tests/Server/API/DashboardPublicSloAPI.test.ts +405 -0
  262. package/Tests/Server/API/SecurityEventConnectionAPI.test.ts +901 -7
  263. package/Tests/Server/API/SessionReplayAPI.test.ts +116 -0
  264. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +857 -0
  265. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +1084 -0
  266. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +33 -3
  267. package/Tests/Server/Infrastructure/Postgres/AddDataResidencyToProjectMigration.test.ts +202 -0
  268. package/Tests/Server/Infrastructure/Postgres/AddNetbiosLookupToNetworkDeviceDiscoveryScanMigration.test.ts +330 -0
  269. package/Tests/Server/Infrastructure/Postgres/AddNetworkDeviceDnsNameAndShortDeviceNamesMigration.test.ts +340 -0
  270. package/Tests/Server/Infrastructure/Postgres/BackfillSloMonitorRulesAndAffectedResourcesMigration.test.ts +343 -0
  271. package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +801 -0
  272. package/Tests/Server/Services/AddSubscriberUpdateNotificationStatusMigration.test.ts +307 -0
  273. package/Tests/Server/Services/AuditLogService.test.ts +1124 -0
  274. package/Tests/Server/Services/AutoImportScanCredentialSelect.test.ts +200 -1
  275. package/Tests/Server/Services/BillingInvoiceServiceMutexRelease.test.ts +254 -0
  276. package/Tests/Server/Services/BillingService.test.ts +70 -0
  277. package/Tests/Server/Services/BillingServicePayInvoiceStates.test.ts +723 -0
  278. package/Tests/Server/Services/BillingServicePaymentMethodReads.test.ts +14 -2
  279. package/Tests/Server/Services/BillingServiceSubscriptionPaymentMethodSync.test.ts +1634 -0
  280. package/Tests/Server/Services/DatabaseServiceAuditRelationDiff.test.ts +492 -0
  281. package/Tests/Server/Services/DatabaseServicePasswordFieldSecrets.test.ts +629 -0
  282. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +431 -0
  283. package/Tests/Server/Services/IncidentAlertSloReferenceGuard.test.ts +358 -0
  284. package/Tests/Server/Services/MonitorProbeServicePastDueMonitoring.test.ts +362 -0
  285. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1460 -1
  286. package/Tests/Server/Services/NetworkDeviceDiagnosticService.test.ts +962 -0
  287. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +106 -0
  288. package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +10 -1
  289. package/Tests/Server/Services/NetworkDeviceServicePastDueMonitoring.test.ts +335 -0
  290. package/Tests/Server/Services/NetworkDeviceSiteRuleDnsName.test.ts +568 -0
  291. package/Tests/Server/Services/ProjectDataResidencyPostgres.test.ts +344 -0
  292. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +187 -0
  293. package/Tests/Server/Services/ProjectServiceDataResidency.test.ts +369 -0
  294. package/Tests/Server/Services/ProjectServiceDataResidencySelfHosted.test.ts +176 -0
  295. package/Tests/Server/Services/ProjectServicePastDueMonitoring.test.ts +289 -0
  296. package/Tests/Server/Services/ResourceFeedServices.test.ts +24 -8
  297. package/Tests/Server/Services/RuleRun/IncidentAlertMaintenanceRuleEnginesRun.test.ts +1396 -0
  298. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupARun.test.ts +917 -0
  299. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +1262 -0
  300. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupARun.test.ts +902 -0
  301. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +1163 -0
  302. package/Tests/Server/Services/RuleRun/PrivacyRuleEnginesRun.test.ts +843 -0
  303. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +465 -0
  304. package/Tests/Server/Services/SecurityEventConnectionService.test.ts +525 -4
  305. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +77 -23
  306. package/Tests/Server/Services/ServiceLevelObjectiveBurnRateRuleFeedWrites.test.ts +777 -0
  307. package/Tests/Server/Services/ServiceLevelObjectiveBurnRateRuleOptions.test.ts +1399 -0
  308. package/Tests/Server/Services/ServiceLevelObjectiveBurnRateRuleService.test.ts +67 -74
  309. package/Tests/Server/Services/ServiceLevelObjectiveChildRowTenancy.test.ts +906 -0
  310. package/Tests/Server/Services/ServiceLevelObjectiveDeleteTenancy.test.ts +1029 -0
  311. package/Tests/Server/Services/ServiceLevelObjectiveFeedService.test.ts +313 -0
  312. package/Tests/Server/Services/ServiceLevelObjectiveFeedWrites.test.ts +1214 -0
  313. package/Tests/Server/Services/ServiceLevelObjectiveMarkdownLink.test.ts +289 -0
  314. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +1455 -430
  315. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleLegacyLabelAdoption.test.ts +634 -0
  316. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +1193 -0
  317. package/Tests/Server/Services/{SloMonitorLabelRuleHooks.test.ts → ServiceLevelObjectiveMonitorRuleTriggers.test.ts} +135 -41
  318. package/Tests/Server/Services/ServiceLevelObjectiveOwnerFeedWrites.test.ts +671 -0
  319. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +180 -85
  320. package/Tests/Server/Services/ServiceLevelObjectiveServiceArchive.test.ts +537 -0
  321. package/Tests/Server/Services/ServiceLevelObjectiveServiceLegacyMonitorLabelWrite.test.ts +870 -0
  322. package/Tests/Server/Services/ServiceLevelObjectiveServiceMonitorRuleGuard.test.ts +343 -0
  323. package/Tests/Server/Services/ServiceLevelObjectiveServiceReEvaluation.test.ts +246 -0
  324. package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +501 -0
  325. package/Tests/Server/Services/SloAffectedResourceLinkContract.test.ts +86 -0
  326. package/Tests/Server/Services/SloAffectedResourceMetricAttributes.test.ts +547 -0
  327. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +10 -4
  328. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +7 -2
  329. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +174 -0
  330. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateServiceVariables.test.ts +40 -0
  331. package/Tests/Server/Services/SubscriberUpdateNotificationHooks.test.ts +541 -0
  332. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +288 -0
  333. package/Tests/Server/TestingUtils/PastDueMonitoringClaimSql.ts +177 -0
  334. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +51 -10
  335. package/Tests/Server/TestingUtils/__mocks__/Stripe.mock.ts +16 -1
  336. package/Tests/Server/Types/Database/Permissions/ProjectDataResidencyColumn.test.ts +386 -0
  337. package/Tests/Server/Types/Database/Permissions/SecurityRoleAccess.test.ts +26 -36
  338. package/Tests/Server/Utils/AI/SloAffectedResourceAIContext.test.ts +445 -0
  339. package/Tests/Server/Utils/AI/SloTools.test.ts +423 -1
  340. package/Tests/Server/Utils/AI/SloToolsArchive.test.ts +294 -0
  341. package/Tests/Server/Utils/AI/SloToolsRuleReadPermissions.test.ts +297 -0
  342. package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +255 -10
  343. package/Tests/Server/Utils/Dashboard/SloTemplatePublicDashboard.test.ts +404 -750
  344. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +45 -0
  345. package/Tests/Server/Utils/Database/RelationValueUtil.test.ts +157 -0
  346. package/Tests/Server/Utils/Monitor/Criteria/BooleanSeriesEvaluateOverTime.test.ts +260 -0
  347. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +181 -0
  348. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +147 -30
  349. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.test.ts +17 -0
  350. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +136 -0
  351. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidatorLoadOrder.test.ts +472 -0
  352. package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +48 -2
  353. package/Tests/Server/Utils/Monitor/SloTemplateMetricEmitterContract.test.ts +435 -475
  354. package/Tests/Server/Utils/Rules/MonitorRulePatternValidator.test.ts +77 -0
  355. package/Tests/Server/Utils/Rules/OwnerRuleAssignment.test.ts +177 -0
  356. package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +365 -0
  357. package/Tests/Server/Utils/Rules/RuleRun/RuleRunRegistry.test.ts +173 -0
  358. package/Tests/Server/Utils/Rules/RuleRun/RuleRunner.test.ts +675 -0
  359. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +8 -8
  360. package/Tests/Server/Utils/SecurityEvent/Connectors/ConnectorFetchSummary.test.ts +261 -0
  361. package/Tests/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.test.ts +68 -13
  362. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsAuth.test.ts → Connectors/GoogleSecOps/GoogleSecOpsAuth.test.ts} +5 -5
  363. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsClient.test.ts → Connectors/GoogleSecOps/GoogleSecOpsClient.test.ts} +4 -4
  364. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnector.test.ts +483 -0
  365. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorCuratedAndLateAlerts.test.ts +676 -0
  366. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorFetchEvents.test.ts +1994 -0
  367. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorFixtures.ts +332 -0
  368. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorHttpIntegration.test.ts +680 -0
  369. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorRequestContract.test.ts +1206 -0
  370. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorTestConnection.test.ts +1136 -0
  371. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsCuratedRuleCounts.test.ts +407 -0
  372. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsErrorHandling.test.ts → Connectors/GoogleSecOps/GoogleSecOpsErrorHandling.test.ts} +5 -5
  373. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingAdaptiveCatchUp.test.ts +730 -0
  374. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsClickhouseIntegration.test.ts → Connectors/GoogleSecOps/GoogleSecOpsPollingClickhouseIntegration.test.ts} +130 -35
  375. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingFailureTaxonomy.test.ts +772 -0
  376. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingFixtures.ts +648 -0
  377. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsHttpIntegration.test.ts → Connectors/GoogleSecOps/GoogleSecOpsPollingHttpIntegration.test.ts} +230 -225
  378. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingIntegration.test.ts +1390 -0
  379. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingLoop.test.ts +981 -0
  380. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingNothingImportedRegression.test.ts +290 -0
  381. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsRequestContract.test.ts → Connectors/GoogleSecOps/GoogleSecOpsRequestContract.test.ts} +2 -2
  382. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsResponseParsing.test.ts → Connectors/GoogleSecOps/GoogleSecOpsResponseParsing.test.ts} +5 -5
  383. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsSearchDetections.test.ts → Connectors/GoogleSecOps/GoogleSecOpsSearchDetections.test.ts} +38 -5
  384. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsTesterIntegration.test.ts +421 -0
  385. package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.test.ts +1445 -3
  386. package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.test.ts +14 -4
  387. package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.test.ts +313 -4
  388. package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +352 -0
  389. package/Tests/Server/Utils/Slo/SloFeedUtil.test.ts +326 -0
  390. package/Tests/Server/Utils/Slo/SloLegacyMonitorLabelAdoption.test.ts +260 -0
  391. package/Tests/Server/Utils/Slo/SloMetricUtil.test.ts +592 -0
  392. package/Tests/Server/Utils/Slo/SloRecordReferenceValidator.test.ts +311 -0
  393. package/Tests/Server/Utils/StatusPageResource.test.ts +91 -3
  394. package/Tests/Types/Billing/SubscriptionStatus.test.ts +176 -0
  395. package/Tests/Types/Billing/SubscriptionStatusPastDueMonitoring.test.ts +187 -0
  396. package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +53 -7
  397. package/Tests/Types/Dashboard/SloDashboardTemplate.test.ts +816 -792
  398. package/Tests/Types/Monitor/CriteriaFilter.test.ts +216 -24
  399. package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +14 -1
  400. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +14 -1
  401. package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +14 -1
  402. package/Tests/Types/Monitor/MonitorStepRetriesParsing.test.ts +201 -0
  403. package/Tests/Types/Rules/RuleRun.test.ts +315 -0
  404. package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +60 -0
  405. package/Tests/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.test.ts +29 -4
  406. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +217 -0
  407. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +200 -0
  408. package/Tests/UI/Components/ActiveFilterChipsLockedFilters.test.tsx +141 -87
  409. package/Tests/UI/Components/AlertBanner.test.tsx +319 -0
  410. package/Tests/UI/Components/BulkArchiveActionsCopy.test.tsx +310 -0
  411. package/Tests/UI/Components/Detail/DetailIdIsUnique.test.tsx +87 -0
  412. package/Tests/UI/Components/Dropdown/DropdownClearable.test.tsx +393 -0
  413. package/Tests/UI/Components/Dropdown/DropdownMultiSelectChipAccessibleName.test.tsx +64 -0
  414. package/Tests/UI/Components/EmptyState/EmptyState.test.tsx +335 -4
  415. package/Tests/UI/Components/EmptyState/EmptyStateGuideLinks.test.tsx +337 -0
  416. package/Tests/UI/Components/Forms/ModelFormJsonColumns.test.tsx +21 -20
  417. package/Tests/UI/Components/LabelRuleImportExport.test.tsx +15 -2
  418. package/Tests/UI/Components/LockedFilterChip.test.tsx +281 -144
  419. package/Tests/UI/Components/MoreMenu.test.tsx +133 -0
  420. package/Tests/UI/Components/MoreMenuMotion.test.tsx +84 -3
  421. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  422. package/Tests/UI/Components/RuleRun/RuleDetailFields.test.tsx +336 -0
  423. package/Tests/UI/Components/RuleRun/RuleTable.test.tsx +304 -0
  424. package/Tests/UI/Components/RuleRun/RunRuleNowModal.test.tsx +231 -0
  425. package/Tests/UI/Components/RuleRun/RunRulesBulkAction.test.ts +213 -0
  426. package/Tests/UI/Components/TelemetryActiveFilterChipsLockedFilters.test.tsx +124 -61
  427. package/Tests/UI/Rum/ReplayEngine.test.ts +154 -0
  428. package/Tests/UI/Rum/ReplayHeader.test.tsx +234 -123
  429. package/Tests/UI/Rum/ReplayPinControl.test.tsx +122 -0
  430. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +245 -0
  431. package/Tests/UI/Rum/ReplayRail.test.tsx +294 -0
  432. package/Tests/UI/Rum/ReplayScrubber.test.tsx +213 -1
  433. package/Tests/UI/Rum/ReplayStage.test.tsx +565 -172
  434. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +260 -3
  435. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +1391 -0
  436. package/Tests/UI/Rum/ReplayTimeline.test.tsx +222 -0
  437. package/Tests/UI/Utils/DashboardStackingLayers.test.ts +8 -1
  438. package/Tests/UI/Utils/RuleRunClient.test.ts +303 -0
  439. package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +1 -0
  440. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +2 -0
  441. package/Tests/Utils/Dashboard/Components/DashboardSloListComponent.test.ts +206 -0
  442. package/Tests/Utils/Dashboard/DashboardSloComponent.test.ts +36 -2
  443. package/Tests/Utils/Dashboard/SloTemplateVariableScoping.test.ts +409 -588
  444. package/Tests/Utils/Dashboard/SloWidgetSource.test.ts +351 -0
  445. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +361 -0
  446. package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +1425 -118
  447. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +188 -3
  448. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +661 -13
  449. package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +349 -0
  450. package/Tests/Utils/NetworkDiscovery/RunImportedDeviceNaming.test.ts +656 -0
  451. package/Tests/Utils/NetworkDiscovery/ShortDeviceNamePlanner.test.ts +1020 -0
  452. package/Tests/Utils/NetworkDiscovery/ShortHostnameUtil.test.ts +619 -0
  453. package/Tests/Utils/NetworkSite/CidrMatchUtil.test.ts +317 -0
  454. package/Tests/Utils/Project/DataResidency.test.ts +198 -0
  455. package/Tests/Utils/Rules/RuleRunSummary.test.ts +394 -0
  456. package/Tests/Utils/Slo/SloAffectedResourceMarkdown.test.ts +302 -0
  457. package/Tests/Utils/Slo/SloBurnRateTemplate.test.ts +468 -0
  458. package/Tests/Utils/Slo/SloEvaluation.test.ts +20 -4
  459. package/Tests/Utils/Slo/SloFeedMarkdown.test.ts +1185 -0
  460. package/Tests/Utils/Slo/SloHealth.test.ts +262 -0
  461. package/Tests/Utils/Slo/SloHistoryMetricName.test.ts +102 -0
  462. package/Tests/Utils/Slo/SloListWidgetFormat.test.ts +522 -0
  463. package/Tests/Utils/Slo/SloMetricType.test.ts +355 -0
  464. package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +387 -0
  465. package/Tests/Utils/Slo/SloMonitorStatusSummary.test.ts +210 -0
  466. package/Tests/Utils/Slo/SloOverviewText.test.ts +226 -0
  467. package/Tests/Utils/Slo/SloProjection.test.ts +1039 -0
  468. package/Tests/Utils/Slo/SloWidgetFormat.test.ts +47 -0
  469. package/Tests/Utils/Telemetry/LockedFilterSearch.test.ts +1 -80
  470. package/Types/BaseDatabase/EnableAuditLogOn.ts +41 -0
  471. package/Types/Billing/SubscriptionStatus.ts +39 -4
  472. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  473. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +5 -0
  474. package/Types/Dashboard/DashboardComponents/DashboardSloComponent.ts +10 -0
  475. package/Types/Dashboard/DashboardComponents/DashboardSloListComponent.ts +29 -0
  476. package/Types/Dashboard/DashboardTemplates.ts +290 -297
  477. package/Types/Database/EnableAuditLog.ts +23 -1
  478. package/Types/Email/EmailTemplateType.ts +4 -0
  479. package/Types/Monitor/CriteriaFilter.ts +55 -8
  480. package/Types/Monitor/MonitorStep.ts +5 -2
  481. package/Types/Monitor/MonitorStepDnsMonitor.ts +3 -1
  482. package/Types/Monitor/MonitorStepDnssecMonitor.ts +3 -1
  483. package/Types/Monitor/MonitorStepDomainMonitor.ts +3 -1
  484. package/Types/Monitor/MonitorStepExternalStatusPageMonitor.ts +3 -1
  485. package/Types/Monitor/MonitorStepRetries.ts +59 -0
  486. package/Types/Monitor/MonitorStepSnmpMonitor.ts +9 -2
  487. package/Types/NetworkDevice/NetworkDeviceDiagnosticResult.ts +104 -0
  488. package/Types/NetworkDevice/NetworkDeviceDiagnosticStatus.ts +47 -0
  489. package/Types/NetworkDevice/NetworkDeviceDiagnosticType.ts +47 -0
  490. package/Types/NetworkDevice/NetworkDevicePingMetricNames.ts +20 -0
  491. package/Types/Permission.ts +127 -0
  492. package/Types/Rules/RuleCriteriaFieldRegistry.ts +5 -0
  493. package/Types/Rules/RuleRun.ts +518 -0
  494. package/Types/Rum/SessionReplayApi.ts +18 -0
  495. package/Types/SecurityEvent/Connectors/ConnectorDiagnostics.ts +6 -0
  496. package/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.ts +30 -4
  497. package/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.ts +108 -1
  498. package/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.ts +6 -2
  499. package/Types/ServiceLevelObjective/SloHistoryMetricName.ts +25 -0
  500. package/Types/ServiceLevelObjective/SloMetricType.ts +49 -0
  501. package/Types/StatusPage/StatusPageSubscriberNotificationEventType.ts +4 -0
  502. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +331 -0
  503. package/Types/StatusPage/SubscriberNotificationTrigger.ts +12 -0
  504. package/Types/StatusPage/SubscriberUpdateNotification.ts +89 -0
  505. package/Types/Telemetry/LockedFilterDetail.ts +7 -28
  506. package/Types/Telemetry/ServiceType.ts +7 -0
  507. package/Typings/Index.d.ts +53 -3
  508. package/UI/Components/AlertBanner/AlertBanner.tsx +85 -27
  509. package/UI/Components/BulkUpdate/BulkArchiveActions.tsx +21 -2
  510. package/UI/Components/Detail/Detail.tsx +9 -2
  511. package/UI/Components/Dropdown/Dropdown.tsx +64 -1
  512. package/UI/Components/EmptyState/EmptyState.tsx +27 -3
  513. package/UI/Components/EmptyState/EmptyStateGuideLinks.tsx +124 -0
  514. package/UI/Components/Forms/ModelForm.tsx +6 -5
  515. package/UI/Components/LabelRule/LabelRuleTable.tsx +8 -2
  516. package/UI/Components/LogsViewer/LogsViewer.tsx +2 -6
  517. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +2 -13
  518. package/UI/Components/LogsViewer/types.ts +2 -2
  519. package/UI/Components/MoreMenu/MoreMenu.tsx +14 -3
  520. package/UI/Components/RuleRun/RuleDetailFields.tsx +359 -0
  521. package/UI/Components/RuleRun/RuleTable.tsx +183 -0
  522. package/UI/Components/RuleRun/RuleView.tsx +170 -0
  523. package/UI/Components/RuleRun/RunRuleNowModal.tsx +147 -0
  524. package/UI/Components/RuleRun/RunRulesBulkAction.ts +103 -0
  525. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +2 -6
  526. package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +38 -104
  527. package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +2 -18
  528. package/UI/Components/TelemetryViewer/types.ts +2 -2
  529. package/UI/Utils/Rules/RuleRunClient.ts +178 -0
  530. package/UI/Utils/Telemetry/TelemetryEntityNames.ts +8 -0
  531. package/Utils/Dashboard/Components/DashboardSloComponent.ts +19 -2
  532. package/Utils/Dashboard/Components/DashboardSloListComponent.ts +126 -0
  533. package/Utils/Dashboard/Components/Index.ts +7 -0
  534. package/Utils/Dashboard/SloWidgetSource.ts +188 -0
  535. package/Utils/Markdown/MarkdownEscape.ts +63 -0
  536. package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +161 -36
  537. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +25 -0
  538. package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +146 -0
  539. package/Utils/NetworkDiscovery/RunImportedDeviceNaming.ts +390 -0
  540. package/Utils/NetworkDiscovery/ShortDeviceNamePlanner.ts +291 -0
  541. package/Utils/NetworkDiscovery/ShortHostnameUtil.ts +91 -0
  542. package/Utils/NetworkSite/CidrMatchUtil.ts +37 -12
  543. package/Utils/Project/DataResidency.ts +63 -0
  544. package/Utils/Rules/RuleRunSummary.ts +323 -0
  545. package/Utils/Slo/SloAffectedResourceMarkdown.ts +123 -0
  546. package/Utils/Slo/SloBurnRateTemplate.ts +373 -0
  547. package/Utils/Slo/SloFeedMarkdown.ts +1507 -0
  548. package/Utils/Slo/SloHealth.ts +96 -9
  549. package/Utils/Slo/SloListWidgetFormat.ts +370 -0
  550. package/Utils/Slo/SloMetricType.ts +237 -0
  551. package/Utils/Slo/SloMonitorRuleCriteria.ts +364 -0
  552. package/Utils/Slo/SloMonitorStatusSummary.ts +210 -0
  553. package/Utils/Slo/SloOverviewText.ts +265 -0
  554. package/Utils/Slo/SloProjection.ts +967 -0
  555. package/Utils/Slo/SloWidgetFormat.ts +12 -2
  556. package/Utils/Telemetry/LockedFilterSearch.ts +3 -49
  557. package/build/dist/Models/AnalyticsModels/AuditLog.js +70 -1
  558. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  559. package/build/dist/Models/AnalyticsModels/SecurityEvent.js +4 -4
  560. package/build/dist/Models/DatabaseModels/Alert.js +60 -0
  561. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  562. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  563. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  564. package/build/dist/Models/DatabaseModels/Incident.js +64 -0
  565. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  566. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +75 -0
  567. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  568. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +75 -0
  569. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  570. package/build/dist/Models/DatabaseModels/Index.js +6 -4
  571. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  572. package/build/dist/Models/DatabaseModels/NetworkDevice.js +68 -0
  573. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  574. package/build/dist/Models/DatabaseModels/NetworkDeviceDiagnostic.js +772 -0
  575. package/build/dist/Models/DatabaseModels/NetworkDeviceDiagnostic.js.map +1 -0
  576. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +140 -0
  577. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  578. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js +1 -1
  579. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js.map +1 -1
  580. package/build/dist/Models/DatabaseModels/Project.js +42 -0
  581. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  582. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +75 -0
  583. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  584. package/build/dist/Models/DatabaseModels/SecurityEventConnection.js +11 -11
  585. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +192 -10
  586. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  587. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.js +798 -96
  588. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.js.map +1 -1
  589. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +596 -0
  590. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -0
  591. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +590 -0
  592. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -0
  593. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.js +16 -0
  594. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.js.map +1 -1
  595. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.js +16 -0
  596. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.js.map +1 -1
  597. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +75 -0
  598. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  599. package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js +1 -1
  600. package/build/dist/Server/API/BaseAPI.js +14 -0
  601. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  602. package/build/dist/Server/API/BillingInvoiceAPI.js +233 -19
  603. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  604. package/build/dist/Server/API/BillingPaymentMethodAPI.js +86 -5
  605. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  606. package/build/dist/Server/API/DashboardAPI.js +72 -2
  607. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  608. package/build/dist/Server/API/SecurityEventConnectionAPI.js +102 -10
  609. package/build/dist/Server/API/SecurityEventConnectionAPI.js.map +1 -1
  610. package/build/dist/Server/API/StatusPageAPI.js +236 -154
  611. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  612. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792800000000-AddNetworkDeviceDiagnostic.js +34 -0
  613. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792800000000-AddNetworkDeviceDiagnostic.js.map +1 -0
  614. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792900000000-AddNetworkDeviceDnsNameAndShortDeviceNames.js +24 -0
  615. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792900000000-AddNetworkDeviceDnsNameAndShortDeviceNames.js.map +1 -0
  616. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793000000000-AddNetbiosLookupToNetworkDeviceDiscoveryScan.js +21 -0
  617. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793000000000-AddNetbiosLookupToNetworkDeviceDiscoveryScan.js.map +1 -0
  618. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793100000000-SloProductOverhaul.js +207 -0
  619. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793100000000-SloProductOverhaul.js.map +1 -0
  620. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793200000000-BackfillSloMonitorRulesAndAffectedResources.js +72 -0
  621. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793200000000-BackfillSloMonitorRulesAndAffectedResources.js.map +1 -0
  622. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793300000000-AddDataResidencyToProject.js +20 -0
  623. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793300000000-AddDataResidencyToProject.js.map +1 -0
  624. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793400000000-AddSubscriberUpdateNotificationStatus.js +46 -0
  625. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793400000000-AddSubscriberUpdateNotificationStatus.js.map +1 -0
  626. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  627. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  628. package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js +112 -56
  629. package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js.map +1 -1
  630. package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js +167 -68
  631. package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js.map +1 -1
  632. package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js +87 -26
  633. package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js.map +1 -1
  634. package/build/dist/Server/Services/AlertLabelRuleEngineService.js +270 -203
  635. package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
  636. package/build/dist/Server/Services/AlertOwnerRuleEngineService.js +498 -354
  637. package/build/dist/Server/Services/AlertOwnerRuleEngineService.js.map +1 -1
  638. package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js +97 -34
  639. package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js.map +1 -1
  640. package/build/dist/Server/Services/AlertService.js +91 -8
  641. package/build/dist/Server/Services/AlertService.js.map +1 -1
  642. package/build/dist/Server/Services/AuditLogService.js +408 -44
  643. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  644. package/build/dist/Server/Services/BillingInvoiceService.js +94 -85
  645. package/build/dist/Server/Services/BillingInvoiceService.js.map +1 -1
  646. package/build/dist/Server/Services/BillingService.js +406 -14
  647. package/build/dist/Server/Services/BillingService.js.map +1 -1
  648. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +141 -92
  649. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -1
  650. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +189 -101
  651. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -1
  652. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js +135 -86
  653. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js.map +1 -1
  654. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +189 -100
  655. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
  656. package/build/dist/Server/Services/DashboardLabelRuleEngineService.js +117 -69
  657. package/build/dist/Server/Services/DashboardLabelRuleEngineService.js.map +1 -1
  658. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js +171 -83
  659. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js.map +1 -1
  660. package/build/dist/Server/Services/DatabaseService.js +125 -18
  661. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  662. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js +141 -92
  663. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js.map +1 -1
  664. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js +189 -101
  665. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js.map +1 -1
  666. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js +145 -91
  667. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js.map +1 -1
  668. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js +191 -101
  669. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js.map +1 -1
  670. package/build/dist/Server/Services/HostLabelRuleEngineService.js +141 -92
  671. package/build/dist/Server/Services/HostLabelRuleEngineService.js.map +1 -1
  672. package/build/dist/Server/Services/HostOwnerRuleEngineService.js +190 -102
  673. package/build/dist/Server/Services/HostOwnerRuleEngineService.js.map +1 -1
  674. package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js +112 -56
  675. package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js.map +1 -1
  676. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js +220 -63
  677. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js.map +1 -1
  678. package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js +87 -26
  679. package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js.map +1 -1
  680. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  681. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  682. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +258 -192
  683. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
  684. package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js +519 -376
  685. package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js.map +1 -1
  686. package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js +95 -32
  687. package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js.map +1 -1
  688. package/build/dist/Server/Services/IncidentPublicNoteService.js +24 -0
  689. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  690. package/build/dist/Server/Services/IncidentService.js +90 -5
  691. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  692. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js +113 -65
  693. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js.map +1 -1
  694. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js +171 -83
  695. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js.map +1 -1
  696. package/build/dist/Server/Services/Index.js +6 -4
  697. package/build/dist/Server/Services/Index.js.map +1 -1
  698. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js +117 -69
  699. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js.map +1 -1
  700. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +171 -83
  701. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -1
  702. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js +145 -91
  703. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js.map +1 -1
  704. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js +191 -101
  705. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js.map +1 -1
  706. package/build/dist/Server/Services/LabelService.js +20 -25
  707. package/build/dist/Server/Services/LabelService.js.map +1 -1
  708. package/build/dist/Server/Services/MonitorLabelRuleEngineService.js +123 -77
  709. package/build/dist/Server/Services/MonitorLabelRuleEngineService.js.map +1 -1
  710. package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js +167 -82
  711. package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js.map +1 -1
  712. package/build/dist/Server/Services/MonitorProbeService.js +18 -2
  713. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  714. package/build/dist/Server/Services/MonitorService.js +11 -9
  715. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  716. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +287 -5
  717. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  718. package/build/dist/Server/Services/NetworkDeviceDiagnosticService.js +333 -0
  719. package/build/dist/Server/Services/NetworkDeviceDiagnosticService.js.map +1 -0
  720. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +117 -69
  721. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  722. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +171 -83
  723. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
  724. package/build/dist/Server/Services/NetworkDeviceService.js +36 -2
  725. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  726. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js +117 -64
  727. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js.map +1 -1
  728. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js +173 -83
  729. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js.map +1 -1
  730. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js +116 -65
  731. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js.map +1 -1
  732. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js +171 -83
  733. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js.map +1 -1
  734. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js +141 -92
  735. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js.map +1 -1
  736. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js +189 -101
  737. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js.map +1 -1
  738. package/build/dist/Server/Services/ProjectService.js +82 -10
  739. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  740. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +141 -92
  741. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -1
  742. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +189 -101
  743. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -1
  744. package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js +111 -63
  745. package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js.map +1 -1
  746. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js +161 -77
  747. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js.map +1 -1
  748. package/build/dist/Server/Services/RunbookLabelRuleEngineService.js +117 -69
  749. package/build/dist/Server/Services/RunbookLabelRuleEngineService.js.map +1 -1
  750. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js +171 -83
  751. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js.map +1 -1
  752. package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js +280 -209
  753. package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js.map +1 -1
  754. package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js +521 -376
  755. package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js.map +1 -1
  756. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +24 -0
  757. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  758. package/build/dist/Server/Services/ScheduledMaintenanceService.js +24 -8
  759. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  760. package/build/dist/Server/Services/SecurityEventConnectionService.js +43 -3
  761. package/build/dist/Server/Services/SecurityEventConnectionService.js.map +1 -1
  762. package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js +115 -62
  763. package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js.map +1 -1
  764. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js +163 -77
  765. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js.map +1 -1
  766. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js +141 -92
  767. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js.map +1 -1
  768. package/build/dist/Server/Services/ServiceLevelObjectiveBurnRateRuleService.js +769 -18
  769. package/build/dist/Server/Services/ServiceLevelObjectiveBurnRateRuleService.js.map +1 -1
  770. package/build/dist/Server/Services/ServiceLevelObjectiveFeedService.js +96 -0
  771. package/build/dist/Server/Services/ServiceLevelObjectiveFeedService.js.map +1 -0
  772. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +614 -87
  773. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
  774. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +844 -0
  775. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -0
  776. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerTeamService.js +207 -1
  777. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerTeamService.js.map +1 -1
  778. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerUserService.js +221 -1
  779. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerUserService.js.map +1 -1
  780. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +997 -50
  781. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  782. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js +191 -102
  783. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js.map +1 -1
  784. package/build/dist/Server/Services/StatusPageAnnouncementService.js +12 -0
  785. package/build/dist/Server/Services/StatusPageAnnouncementService.js.map +1 -1
  786. package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js +118 -70
  787. package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js.map +1 -1
  788. package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js +158 -76
  789. package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js.map +1 -1
  790. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +19 -215
  791. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  792. package/build/dist/Server/Services/StatusPageSubscriberService.js +6 -0
  793. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  794. package/build/dist/Server/Services/TelemetryUsageBillingService.js +27 -5
  795. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  796. package/build/dist/Server/Services/ThreatIntelFeedService.js +1 -1
  797. package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js +141 -92
  798. package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js.map +1 -1
  799. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js +189 -101
  800. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js.map +1 -1
  801. package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js +117 -69
  802. package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js.map +1 -1
  803. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js +171 -83
  804. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js.map +1 -1
  805. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +17 -0
  806. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
  807. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +31 -0
  808. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  809. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +24 -1
  810. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
  811. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +21 -1
  812. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  813. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +337 -53
  814. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  815. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +108 -15
  816. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  817. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +2 -2
  818. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -1
  819. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +115 -6
  820. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -1
  821. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +13 -0
  822. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  823. package/build/dist/Server/Utils/Database/RelationValueUtil.js +82 -0
  824. package/build/dist/Server/Utils/Database/RelationValueUtil.js.map +1 -0
  825. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +10 -6
  826. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  827. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +17 -21
  828. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  829. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +5 -4
  830. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  831. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +50 -10
  832. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  833. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +13 -1
  834. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
  835. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js +6 -10
  836. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js.map +1 -1
  837. package/build/dist/Server/Utils/Rules/MonitorRulePatternValidator.js +34 -0
  838. package/build/dist/Server/Utils/Rules/MonitorRulePatternValidator.js.map +1 -0
  839. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js +69 -0
  840. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js.map +1 -0
  841. package/build/dist/Server/Utils/Rules/RuleRun/RuleApplication.js +25 -0
  842. package/build/dist/Server/Utils/Rules/RuleRun/RuleApplication.js.map +1 -0
  843. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunPermission.js +128 -0
  844. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunPermission.js.map +1 -0
  845. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +746 -0
  846. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -0
  847. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunner.js +221 -0
  848. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunner.js.map +1 -0
  849. package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js +3 -2
  850. package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js.map +1 -1
  851. package/build/dist/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.js +6 -4
  852. package/build/dist/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.js.map +1 -1
  853. package/build/dist/Server/Utils/SecurityEvent/{GoogleSecOps → Connectors/GoogleSecOps}/GoogleSecOpsClient.js +174 -14
  854. package/build/dist/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsClient.js.map +1 -0
  855. package/build/dist/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnector.js +1417 -0
  856. package/build/dist/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnector.js.map +1 -0
  857. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.js +138 -18
  858. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.js.map +1 -1
  859. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.js +1 -2
  860. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.js.map +1 -1
  861. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.js +23 -18
  862. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.js.map +1 -1
  863. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.js +2 -0
  864. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.js.map +1 -1
  865. package/build/dist/Server/Utils/SecurityEvent/Connectors/Types.js +95 -0
  866. package/build/dist/Server/Utils/SecurityEvent/Connectors/Types.js.map +1 -1
  867. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +1 -1
  868. package/build/dist/Server/Utils/SecurityEvent/SecurityEventDedupe.js +3 -3
  869. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +2 -2
  870. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +118 -40
  871. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  872. package/build/dist/Server/Utils/Slo/SloFeedUtil.js +150 -0
  873. package/build/dist/Server/Utils/Slo/SloFeedUtil.js.map +1 -0
  874. package/build/dist/Server/Utils/Slo/SloLegacyMonitorLabelAdoption.js +136 -0
  875. package/build/dist/Server/Utils/Slo/SloLegacyMonitorLabelAdoption.js.map +1 -0
  876. package/build/dist/Server/Utils/Slo/SloMetricUtil.js +269 -0
  877. package/build/dist/Server/Utils/Slo/SloMetricUtil.js.map +1 -0
  878. package/build/dist/Server/Utils/Slo/SloOwnerReferenceValidator.js +134 -0
  879. package/build/dist/Server/Utils/Slo/SloOwnerReferenceValidator.js.map +1 -0
  880. package/build/dist/Server/Utils/Slo/SloRecordReferenceValidator.js +137 -0
  881. package/build/dist/Server/Utils/Slo/SloRecordReferenceValidator.js.map +1 -0
  882. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +6 -26
  883. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -1
  884. package/build/dist/Server/Utils/StatusPageResource.js +31 -5
  885. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  886. package/build/dist/Types/Billing/SubscriptionStatus.js +37 -3
  887. package/build/dist/Types/Billing/SubscriptionStatus.js.map +1 -1
  888. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  889. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  890. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +5 -0
  891. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  892. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSloListComponent.js +2 -0
  893. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSloListComponent.js.map +1 -0
  894. package/build/dist/Types/Dashboard/DashboardTemplates.js +275 -286
  895. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  896. package/build/dist/Types/Database/EnableAuditLog.js +19 -1
  897. package/build/dist/Types/Database/EnableAuditLog.js.map +1 -1
  898. package/build/dist/Types/Email/EmailTemplateType.js +4 -0
  899. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  900. package/build/dist/Types/Monitor/CriteriaFilter.js +45 -6
  901. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  902. package/build/dist/Types/Monitor/MonitorStep.js +2 -0
  903. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  904. package/build/dist/Types/Monitor/MonitorStepDnsMonitor.js +3 -1
  905. package/build/dist/Types/Monitor/MonitorStepDnsMonitor.js.map +1 -1
  906. package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js +3 -1
  907. package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js.map +1 -1
  908. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +3 -1
  909. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
  910. package/build/dist/Types/Monitor/MonitorStepExternalStatusPageMonitor.js +3 -1
  911. package/build/dist/Types/Monitor/MonitorStepExternalStatusPageMonitor.js.map +1 -1
  912. package/build/dist/Types/Monitor/MonitorStepRetries.js +47 -0
  913. package/build/dist/Types/Monitor/MonitorStepRetries.js.map +1 -0
  914. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js +3 -1
  915. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js.map +1 -1
  916. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticResult.js +29 -0
  917. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticResult.js.map +1 -0
  918. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticStatus.js +37 -0
  919. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticStatus.js.map +1 -0
  920. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticType.js +42 -0
  921. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticType.js.map +1 -0
  922. package/build/dist/Types/NetworkDevice/NetworkDevicePingMetricNames.js +20 -0
  923. package/build/dist/Types/NetworkDevice/NetworkDevicePingMetricNames.js.map +1 -0
  924. package/build/dist/Types/Permission.js +114 -0
  925. package/build/dist/Types/Permission.js.map +1 -1
  926. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +5 -0
  927. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  928. package/build/dist/Types/Rules/RuleRun.js +267 -0
  929. package/build/dist/Types/Rules/RuleRun.js.map +1 -0
  930. package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
  931. package/build/dist/Types/SecurityEvent/Connectors/ConnectorDiagnostics.js.map +1 -1
  932. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.js +16 -1
  933. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.js.map +1 -1
  934. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js +65 -0
  935. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js.map +1 -1
  936. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.js +6 -2
  937. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.js.map +1 -1
  938. package/build/dist/Types/ServiceLevelObjective/SloHistoryMetricName.js +26 -0
  939. package/build/dist/Types/ServiceLevelObjective/SloHistoryMetricName.js.map +1 -0
  940. package/build/dist/Types/ServiceLevelObjective/SloMetricType.js +45 -0
  941. package/build/dist/Types/ServiceLevelObjective/SloMetricType.js.map +1 -0
  942. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js +4 -0
  943. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js.map +1 -1
  944. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +295 -0
  945. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -0
  946. package/build/dist/Types/StatusPage/SubscriberNotificationTrigger.js +13 -0
  947. package/build/dist/Types/StatusPage/SubscriberNotificationTrigger.js.map +1 -0
  948. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +67 -0
  949. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -0
  950. package/build/dist/Types/Telemetry/LockedFilterDetail.js +7 -7
  951. package/build/dist/Types/Telemetry/ServiceType.js +7 -0
  952. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  953. package/build/dist/UI/Components/AlertBanner/AlertBanner.js +48 -19
  954. package/build/dist/UI/Components/AlertBanner/AlertBanner.js.map +1 -1
  955. package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js +10 -2
  956. package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js.map +1 -1
  957. package/build/dist/UI/Components/Detail/Detail.js +12 -3
  958. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  959. package/build/dist/UI/Components/Dropdown/Dropdown.js +33 -5
  960. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  961. package/build/dist/UI/Components/EmptyState/EmptyState.js +3 -3
  962. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  963. package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js +47 -0
  964. package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js.map +1 -0
  965. package/build/dist/UI/Components/Forms/ModelForm.js +6 -5
  966. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  967. package/build/dist/UI/Components/LabelRule/LabelRuleTable.js +6 -2
  968. package/build/dist/UI/Components/LabelRule/LabelRuleTable.js.map +1 -1
  969. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +1 -1
  970. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  971. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +0 -2
  972. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  973. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +3 -1
  974. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  975. package/build/dist/UI/Components/RuleRun/RuleDetailFields.js +226 -0
  976. package/build/dist/UI/Components/RuleRun/RuleDetailFields.js.map +1 -0
  977. package/build/dist/UI/Components/RuleRun/RuleTable.js +97 -0
  978. package/build/dist/UI/Components/RuleRun/RuleTable.js.map +1 -0
  979. package/build/dist/UI/Components/RuleRun/RuleView.js +83 -0
  980. package/build/dist/UI/Components/RuleRun/RuleView.js.map +1 -0
  981. package/build/dist/UI/Components/RuleRun/RunRuleNowModal.js +74 -0
  982. package/build/dist/UI/Components/RuleRun/RunRuleNowModal.js.map +1 -0
  983. package/build/dist/UI/Components/RuleRun/RunRulesBulkAction.js +78 -0
  984. package/build/dist/UI/Components/RuleRun/RunRulesBulkAction.js.map +1 -0
  985. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
  986. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  987. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +24 -50
  988. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
  989. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +0 -4
  990. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -1
  991. package/build/dist/UI/Utils/Rules/RuleRunClient.js +98 -0
  992. package/build/dist/UI/Utils/Rules/RuleRunClient.js.map +1 -0
  993. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +8 -0
  994. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
  995. package/build/dist/Utils/Dashboard/Components/DashboardSloComponent.js +16 -2
  996. package/build/dist/Utils/Dashboard/Components/DashboardSloComponent.js.map +1 -1
  997. package/build/dist/Utils/Dashboard/Components/DashboardSloListComponent.js +94 -0
  998. package/build/dist/Utils/Dashboard/Components/DashboardSloListComponent.js.map +1 -0
  999. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  1000. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  1001. package/build/dist/Utils/Dashboard/SloWidgetSource.js +125 -0
  1002. package/build/dist/Utils/Dashboard/SloWidgetSource.js.map +1 -0
  1003. package/build/dist/Utils/Markdown/MarkdownEscape.js +49 -0
  1004. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -0
  1005. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +112 -34
  1006. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
  1007. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +22 -0
  1008. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  1009. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +129 -0
  1010. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -0
  1011. package/build/dist/Utils/NetworkDiscovery/RunImportedDeviceNaming.js +255 -0
  1012. package/build/dist/Utils/NetworkDiscovery/RunImportedDeviceNaming.js.map +1 -0
  1013. package/build/dist/Utils/NetworkDiscovery/ShortDeviceNamePlanner.js +184 -0
  1014. package/build/dist/Utils/NetworkDiscovery/ShortDeviceNamePlanner.js.map +1 -0
  1015. package/build/dist/Utils/NetworkDiscovery/ShortHostnameUtil.js +79 -0
  1016. package/build/dist/Utils/NetworkDiscovery/ShortHostnameUtil.js.map +1 -0
  1017. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js +21 -12
  1018. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js.map +1 -1
  1019. package/build/dist/Utils/Project/DataResidency.js +51 -0
  1020. package/build/dist/Utils/Project/DataResidency.js.map +1 -0
  1021. package/build/dist/Utils/Rules/RuleRunSummary.js +197 -0
  1022. package/build/dist/Utils/Rules/RuleRunSummary.js.map +1 -0
  1023. package/build/dist/Utils/Slo/SloAffectedResourceMarkdown.js +51 -0
  1024. package/build/dist/Utils/Slo/SloAffectedResourceMarkdown.js.map +1 -0
  1025. package/build/dist/Utils/Slo/SloBurnRateTemplate.js +228 -0
  1026. package/build/dist/Utils/Slo/SloBurnRateTemplate.js.map +1 -0
  1027. package/build/dist/Utils/Slo/SloFeedMarkdown.js +934 -0
  1028. package/build/dist/Utils/Slo/SloFeedMarkdown.js.map +1 -0
  1029. package/build/dist/Utils/Slo/SloHealth.js +75 -9
  1030. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  1031. package/build/dist/Utils/Slo/SloListWidgetFormat.js +217 -0
  1032. package/build/dist/Utils/Slo/SloListWidgetFormat.js.map +1 -0
  1033. package/build/dist/Utils/Slo/SloMetricType.js +212 -0
  1034. package/build/dist/Utils/Slo/SloMetricType.js.map +1 -0
  1035. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +216 -0
  1036. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -0
  1037. package/build/dist/Utils/Slo/SloMonitorStatusSummary.js +129 -0
  1038. package/build/dist/Utils/Slo/SloMonitorStatusSummary.js.map +1 -0
  1039. package/build/dist/Utils/Slo/SloOverviewText.js +110 -0
  1040. package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -0
  1041. package/build/dist/Utils/Slo/SloProjection.js +448 -0
  1042. package/build/dist/Utils/Slo/SloProjection.js.map +1 -0
  1043. package/build/dist/Utils/Slo/SloWidgetFormat.js +4 -2
  1044. package/build/dist/Utils/Slo/SloWidgetFormat.js.map +1 -1
  1045. package/build/dist/Utils/Telemetry/LockedFilterSearch.js +1 -26
  1046. package/build/dist/Utils/Telemetry/LockedFilterSearch.js.map +1 -1
  1047. package/package.json +1 -1
  1048. package/Models/DatabaseModels/GoogleSecOpsConnection.ts +0 -447
  1049. package/Models/DatabaseModels/GoogleSecOpsConnectionRun.ts +0 -263
  1050. package/Server/API/GoogleSecOpsConnectionAPI.ts +0 -320
  1051. package/Server/Services/GoogleSecOpsConnectionRunService.ts +0 -10
  1052. package/Server/Services/GoogleSecOpsConnectionService.ts +0 -116
  1053. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.ts +0 -777
  1054. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +0 -1373
  1055. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.ts +0 -512
  1056. package/Tests/App/Dashboard/GoogleSecOpsConnectionForm.test.tsx +0 -838
  1057. package/Tests/App/Dashboard/GoogleSecOpsConnectionsErrors.test.tsx +0 -441
  1058. package/Tests/App/Dashboard/GoogleSecOpsDiagnostics.test.tsx +0 -1001
  1059. package/Tests/Server/API/GoogleSecOpsConnectionAPI.test.ts +0 -657
  1060. package/Tests/Server/Services/GoogleSecOpsDiagnosticsModel.test.ts +0 -153
  1061. package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.test.ts +0 -784
  1062. package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.test.ts +0 -823
  1063. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsDiagnostics.test.ts +0 -918
  1064. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +0 -738
  1065. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +0 -874
  1066. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +0 -1297
  1067. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsThreePassPoller.test.ts +0 -1577
  1068. package/Tests/UI/Components/LockedFilterActions.test.tsx +0 -396
  1069. package/Types/SecurityEvent/GoogleSecOpsDiagnostics.ts +0 -111
  1070. package/UI/Components/TelemetryViewer/components/LockedFilterActions.tsx +0 -205
  1071. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +0 -478
  1072. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +0 -1
  1073. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnectionRun.js +0 -287
  1074. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnectionRun.js.map +0 -1
  1075. package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js +0 -231
  1076. package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js.map +0 -1
  1077. package/build/dist/Server/Services/GoogleSecOpsConnectionRunService.js +0 -9
  1078. package/build/dist/Server/Services/GoogleSecOpsConnectionRunService.js.map +0 -1
  1079. package/build/dist/Server/Services/GoogleSecOpsConnectionService.js +0 -70
  1080. package/build/dist/Server/Services/GoogleSecOpsConnectionService.js.map +0 -1
  1081. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +0 -1
  1082. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.js +0 -537
  1083. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.js.map +0 -1
  1084. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +0 -1034
  1085. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +0 -1
  1086. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js +0 -410
  1087. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js.map +0 -1
  1088. package/build/dist/Types/SecurityEvent/GoogleSecOpsDiagnostics.js +0 -2
  1089. package/build/dist/Types/SecurityEvent/GoogleSecOpsDiagnostics.js.map +0 -1
  1090. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterActions.js +0 -88
  1091. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterActions.js.map +0 -1
@@ -1,32 +1,50 @@
1
1
  import Label from "../../../Models/DatabaseModels/Label";
2
2
  import Monitor from "../../../Models/DatabaseModels/Monitor";
3
3
  import ServiceLevelObjective from "../../../Models/DatabaseModels/ServiceLevelObjective";
4
+ import { ServiceLevelObjectiveFeedEventType } from "../../../Models/DatabaseModels/ServiceLevelObjectiveFeed";
5
+ import ServiceLevelObjectiveMonitorRule from "../../../Models/DatabaseModels/ServiceLevelObjectiveMonitorRule";
4
6
  import MonitorService from "../../../Server/Services/MonitorService";
7
+ import ServiceLevelObjectiveFeedService from "../../../Server/Services/ServiceLevelObjectiveFeedService";
5
8
  import ServiceLevelObjectiveMonitorRuleEngineService, {
6
9
  SloMonitorSyncResult,
7
10
  } from "../../../Server/Services/ServiceLevelObjectiveMonitorRuleEngineService";
11
+ import ServiceLevelObjectiveMonitorRuleService from "../../../Server/Services/ServiceLevelObjectiveMonitorRuleService";
8
12
  import ServiceLevelObjectiveService from "../../../Server/Services/ServiceLevelObjectiveService";
13
+ import logger from "../../../Server/Utils/Logger";
14
+ import SloLegacyMonitorLabelAdoption from "../../../Server/Utils/Slo/SloLegacyMonitorLabelAdoption";
15
+ import FilterCondition from "../../../Types/Filter/FilterCondition";
9
16
  import ObjectID from "../../../Types/ObjectID";
17
+ import RuleCriteria, {
18
+ RULE_CRITERIA_LEGACY_NEVER_MATCH_PATTERN,
19
+ RULE_CRITERIA_SCHEMA_VERSION,
20
+ RuleCriteriaFilter,
21
+ RuleCriteriaOperator,
22
+ } from "../../../Types/Rules/RuleCriteria";
23
+ import { MAX_RULES_EVALUATED_PER_PROJECT } from "../../../Utils/Rules/RuleEngineLimits";
10
24
  import { describe, expect, it, beforeEach, afterEach } from "@jest/globals";
11
25
 
12
26
  /*
13
- * Contract under test - the SLO monitor label rule.
27
+ * Contract under test - SLO monitor rules.
14
28
  *
15
- * An SLO can say "measure every monitor labelled Production" instead of
16
- * naming monitors one by one. That promise has two halves, and both have to
17
- * hold or the SLO quietly measures the wrong thing:
29
+ * An SLO can carry any number of ServiceLevelObjectiveMonitorRule rows
30
+ * ("every monitor labelled Production", "every monitor named api-*") instead
31
+ * of naming monitors one by one. Membership is a UNION: a monitor belongs to
32
+ * the SLO while at least one ENABLED rule of that SLO matches it. That promise
33
+ * has two halves, and both have to hold or the SLO quietly measures the wrong
34
+ * thing:
18
35
  *
19
- * - a monitor that starts matching gets attached (whether the rule moved or
20
- * the monitor's labels did), and a monitor that stops matching gets
36
+ * - a monitor that starts matching gets attached (whether a rule moved or
37
+ * the monitor did), and a monitor no enabled rule matches any more gets
21
38
  * detached again;
22
39
  *
23
40
  * - a monitor a human attached by hand is never touched. It is not adopted
24
- * into the rule when it happens to match, and - the case that would
25
- * actually lose someone's configuration - it is never detached when it
26
- * stops matching, or when the rule is deleted outright.
41
+ * when it happens to match, and - the case that would actually lose
42
+ * someone's configuration - it is never detached when it stops matching,
43
+ * or when every rule is deleted outright.
27
44
  *
28
- * The second half is what ServiceLevelObjective.autoAddedMonitors exists for,
29
- * so most of what follows is about that boundary.
45
+ * The second half is what ServiceLevelObjective.autoAddedMonitors exists for.
46
+ * Every write is also told to the SLO's feed, without a user, because nobody
47
+ * clicked anything - a rule reacted.
30
48
  */
31
49
 
32
50
  const PROJECT_ID: ObjectID = new ObjectID(
@@ -39,6 +57,10 @@ const SLO_ID: ObjectID = new ObjectID("11111111-1111-4111-8111-111111111111");
39
57
  const OTHER_SLO_ID: ObjectID = new ObjectID(
40
58
  "1a1a1a1a-1a1a-4a1a-8a1a-1a1a1a1a1a1a",
41
59
  );
60
+ const RULE_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
61
+ const OTHER_RULE_ID: ObjectID = new ObjectID(
62
+ "44444444-4444-4444-8444-444444444444",
63
+ );
42
64
  const MONITOR_A_ID: ObjectID = new ObjectID(
43
65
  "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
44
66
  );
@@ -58,18 +80,34 @@ const LABEL_STAGING_ID: ObjectID = new ObjectID(
58
80
  "ffffffff-ffff-4fff-8fff-ffffffffffff",
59
81
  );
60
82
 
83
+ const SLO_LINK: string = "[SLO Checkout](https://oneuptime.test/slos/1)";
84
+
61
85
  function fakeLabel(id: ObjectID): Label {
62
86
  return { id: id, _id: id.toString() } as unknown as Label;
63
87
  }
64
88
 
65
- function fakeMonitor(id: ObjectID): Monitor {
66
- return { id: id, _id: id.toString() } as unknown as Monitor;
89
+ function fakeMonitor(
90
+ id: ObjectID,
91
+ fields?: {
92
+ name?: string | undefined;
93
+ description?: string | undefined;
94
+ labelIds?: Array<ObjectID> | undefined;
95
+ projectId?: ObjectID | null | undefined;
96
+ },
97
+ ): Monitor {
98
+ return {
99
+ id: id,
100
+ _id: id.toString(),
101
+ projectId: fields?.projectId === undefined ? PROJECT_ID : fields.projectId,
102
+ name: fields?.name === undefined ? `Monitor ${id.toString()}` : fields.name,
103
+ description: fields?.description,
104
+ labels: (fields?.labelIds || []).map(fakeLabel),
105
+ } as unknown as Monitor;
67
106
  }
68
107
 
69
108
  function fakeSlo(fields: {
70
109
  id?: ObjectID | undefined;
71
- projectId?: ObjectID | undefined;
72
- monitorLabels?: Array<ObjectID> | undefined;
110
+ projectId?: ObjectID | null | undefined;
73
111
  monitors?: Array<ObjectID> | undefined;
74
112
  autoAddedMonitors?: Array<ObjectID> | undefined;
75
113
  }): ServiceLevelObjective {
@@ -78,28 +116,53 @@ function fakeSlo(fields: {
78
116
  return {
79
117
  id: id,
80
118
  _id: id.toString(),
119
+ name: "Checkout",
81
120
  projectId:
82
121
  fields.projectId === undefined ? PROJECT_ID : fields.projectId || null,
83
- monitorLabels: (fields.monitorLabels || []).map(fakeLabel),
84
- monitors: (fields.monitors || []).map(fakeMonitor),
85
- autoAddedMonitors: (fields.autoAddedMonitors || []).map(fakeMonitor),
122
+ monitors: (fields.monitors || []).map((monitorId: ObjectID) => {
123
+ return fakeMonitor(monitorId);
124
+ }),
125
+ autoAddedMonitors: (fields.autoAddedMonitors || []).map(
126
+ (monitorId: ObjectID) => {
127
+ return fakeMonitor(monitorId);
128
+ },
129
+ ),
86
130
  } as unknown as ServiceLevelObjective;
87
131
  }
88
132
 
89
- function fakeMonitorWithLabels(
90
- labelIds: Array<ObjectID>,
91
- overrides?: {
92
- id?: ObjectID | undefined;
93
- projectId?: ObjectID | null | undefined;
94
- },
95
- ): Monitor {
133
+ function fakeRule(fields: {
134
+ id?: ObjectID | undefined;
135
+ serviceLevelObjectiveId?: ObjectID | undefined;
136
+ labelIds?: Array<ObjectID> | undefined;
137
+ monitorNamePattern?: string | undefined;
138
+ monitorDescriptionPattern?: string | undefined;
139
+ criteria?: RuleCriteria | null | undefined;
140
+ isEnabled?: boolean | undefined;
141
+ }): ServiceLevelObjectiveMonitorRule {
142
+ const id: ObjectID = fields.id || RULE_ID;
143
+
96
144
  return {
97
- id: overrides?.id || MONITOR_A_ID,
98
- _id: (overrides?.id || MONITOR_A_ID).toString(),
99
- projectId:
100
- overrides?.projectId === undefined ? PROJECT_ID : overrides.projectId,
101
- labels: labelIds.map(fakeLabel),
102
- } as unknown as Monitor;
145
+ id: id,
146
+ _id: id.toString(),
147
+ projectId: PROJECT_ID,
148
+ serviceLevelObjectiveId: fields.serviceLevelObjectiveId || SLO_ID,
149
+ isEnabled: fields.isEnabled === undefined ? true : fields.isEnabled,
150
+ monitorLabels: (fields.labelIds || []).map(fakeLabel),
151
+ monitorNamePattern: fields.monitorNamePattern,
152
+ monitorDescriptionPattern: fields.monitorDescriptionPattern,
153
+ criteria: fields.criteria,
154
+ } as unknown as ServiceLevelObjectiveMonitorRule;
155
+ }
156
+
157
+ function criteria(
158
+ filterCondition: FilterCondition,
159
+ filters: Array<RuleCriteriaFilter>,
160
+ ): RuleCriteria {
161
+ return {
162
+ schemaVersion: RULE_CRITERIA_SCHEMA_VERSION,
163
+ filterCondition: filterCondition,
164
+ filters: filters,
165
+ };
103
166
  }
104
167
 
105
168
  /**
@@ -123,636 +186,1598 @@ function writtenIds(
123
186
  .sort();
124
187
  }
125
188
 
126
- function writtenSloId(
127
- updateOneByIdSpy: jest.SpyInstance,
128
- callIndex: number = 0,
129
- ): string {
130
- const call: { id: ObjectID } = updateOneByIdSpy.mock.calls[callIndex]![0] as {
131
- id: ObjectID;
189
+ function sortedIds(ids: Array<ObjectID>): Array<string> {
190
+ return ids
191
+ .map((id: ObjectID) => {
192
+ return id.toString();
193
+ })
194
+ .sort();
195
+ }
196
+
197
+ interface FeedCall {
198
+ serviceLevelObjectiveId: ObjectID;
199
+ projectId: ObjectID;
200
+ serviceLevelObjectiveFeedEventType: ServiceLevelObjectiveFeedEventType;
201
+ feedInfoInMarkdown: string;
202
+ moreInformationInMarkdown?: string | undefined;
203
+ userId?: ObjectID | undefined;
204
+ }
205
+
206
+ function feedCalls(feedSpy: jest.SpyInstance): Array<FeedCall> {
207
+ return feedSpy.mock.calls.map((call: Array<unknown>) => {
208
+ return call[0] as FeedCall;
209
+ });
210
+ }
211
+
212
+ interface SyncSpies {
213
+ sloFindOneById: jest.SpyInstance;
214
+ sloFindBy: jest.SpyInstance;
215
+ sloUpdateOneById: jest.SpyInstance;
216
+ ruleFindBy: jest.SpyInstance;
217
+ monitorFindBy: jest.SpyInstance;
218
+ monitorFindOneById: jest.SpyInstance;
219
+ feed: jest.SpyInstance;
220
+ markdownLink: jest.SpyInstance;
221
+ legacyLabelAdoption: jest.SpyInstance;
222
+ }
223
+
224
+ function installSpies(): SyncSpies {
225
+ return {
226
+ /*
227
+ * No SLO carries a legacy label list unless a test says so - and the real
228
+ * adoption would need a live Postgres.
229
+ */
230
+ legacyLabelAdoption: jest
231
+ .spyOn(SloLegacyMonitorLabelAdoption, "adoptLegacyMonitorLabels")
232
+ .mockResolvedValue([]),
233
+ sloFindOneById: jest.spyOn(ServiceLevelObjectiveService, "findOneById"),
234
+ sloFindBy: jest
235
+ .spyOn(ServiceLevelObjectiveService, "findBy")
236
+ .mockResolvedValue([]),
237
+ sloUpdateOneById: jest
238
+ .spyOn(ServiceLevelObjectiveService, "updateOneById")
239
+ .mockResolvedValue(1),
240
+ ruleFindBy: jest
241
+ .spyOn(ServiceLevelObjectiveMonitorRuleService, "findBy")
242
+ .mockResolvedValue([]),
243
+ monitorFindBy: jest.spyOn(MonitorService, "findBy").mockResolvedValue([]),
244
+ monitorFindOneById: jest.spyOn(MonitorService, "findOneById"),
245
+ feed: jest
246
+ .spyOn(
247
+ ServiceLevelObjectiveFeedService,
248
+ "createServiceLevelObjectiveFeedItem",
249
+ )
250
+ .mockResolvedValue(undefined),
251
+ markdownLink: jest
252
+ .spyOn(ServiceLevelObjectiveService, "getSloMarkdownLink")
253
+ .mockResolvedValue(SLO_LINK),
254
+ };
255
+ }
256
+
257
+ type SloFindByImplementation = (args: {
258
+ query: Record<string, unknown>;
259
+ }) => Promise<Array<ServiceLevelObjective>>;
260
+
261
+ /*
262
+ * syncSlosForMonitor asks ServiceLevelObjectiveService.findBy two different
263
+ * questions - "which SLOs still record this monitor as rule-attached" (by
264
+ * relation) and "give me these SLOs" (by id). Answer each separately.
265
+ */
266
+ function answerSloReads(data: {
267
+ holdingAutoAddedMonitor?: Array<ServiceLevelObjective> | undefined;
268
+ byId: Array<ServiceLevelObjective>;
269
+ }): SloFindByImplementation {
270
+ return (args: {
271
+ query: Record<string, unknown>;
272
+ }): Promise<Array<ServiceLevelObjective>> => {
273
+ if (args.query["autoAddedMonitors"] !== undefined) {
274
+ return Promise.resolve(data.holdingAutoAddedMonitor || []);
275
+ }
276
+
277
+ return Promise.resolve(data.byId);
132
278
  };
279
+ }
133
280
 
134
- return call.id.toString();
281
+ async function syncSlo(): Promise<SloMonitorSyncResult> {
282
+ return await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo(
283
+ {
284
+ serviceLevelObjectiveId: SLO_ID,
285
+ },
286
+ );
135
287
  }
136
288
 
137
289
  describe("ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo", () => {
138
- let updateOneByIdSpy: jest.SpyInstance;
139
- let sloFindOneByIdSpy: jest.SpyInstance;
140
- let monitorFindBySpy: jest.SpyInstance;
290
+ let spies: SyncSpies;
141
291
 
142
292
  beforeEach(() => {
143
- updateOneByIdSpy = jest
144
- .spyOn(ServiceLevelObjectiveService, "updateOneById")
145
- .mockResolvedValue(1);
146
- sloFindOneByIdSpy = jest.spyOn(ServiceLevelObjectiveService, "findOneById");
147
- monitorFindBySpy = jest.spyOn(MonitorService, "findBy");
293
+ spies = installSpies();
294
+ spies.sloFindOneById.mockResolvedValue(fakeSlo({}));
148
295
  });
149
296
 
150
297
  afterEach(() => {
151
298
  jest.restoreAllMocks();
152
299
  });
153
300
 
154
- it("attaches every monitor in the project that carries a rule label", async () => {
155
- sloFindOneByIdSpy.mockResolvedValue(
156
- fakeSlo({ monitorLabels: [LABEL_PRODUCTION_ID] }),
157
- );
158
- monitorFindBySpy.mockResolvedValue([
159
- fakeMonitor(MONITOR_A_ID),
160
- fakeMonitor(MONITOR_B_ID),
301
+ it("attaches every monitor an enabled rule's labels match", async () => {
302
+ spies.ruleFindBy.mockResolvedValue([
303
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
304
+ ]);
305
+ spies.monitorFindBy.mockResolvedValue([
306
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
307
+ fakeMonitor(MONITOR_B_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
161
308
  ]);
162
309
 
163
- const result: SloMonitorSyncResult =
164
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
165
- serviceLevelObjectiveId: SLO_ID,
166
- });
310
+ const result: SloMonitorSyncResult = await syncSlo();
167
311
 
168
- expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
169
- expect(writtenSloId(updateOneByIdSpy)).toBe(SLO_ID.toString());
170
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual(
171
- [MONITOR_A_ID.toString(), MONITOR_B_ID.toString()].sort(),
312
+ expect(spies.sloUpdateOneById).toHaveBeenCalledTimes(1);
313
+ expect(
314
+ (spies.sloUpdateOneById.mock.calls[0]![0] as { id: ObjectID }).id,
315
+ ).toBe(SLO_ID);
316
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual(
317
+ sortedIds([MONITOR_A_ID, MONITOR_B_ID]),
172
318
  );
173
319
  expect(result.monitorIdsAdded.sort()).toEqual(
174
- [MONITOR_A_ID.toString(), MONITOR_B_ID.toString()].sort(),
320
+ sortedIds([MONITOR_A_ID, MONITOR_B_ID]),
175
321
  );
176
322
  expect(result.monitorIdsRemoved).toEqual([]);
177
323
  });
178
324
 
179
325
  it("records what it attached in autoAddedMonitors so it can undo it later", async () => {
180
- sloFindOneByIdSpy.mockResolvedValue(
181
- fakeSlo({ monitorLabels: [LABEL_PRODUCTION_ID] }),
182
- );
183
- monitorFindBySpy.mockResolvedValue([fakeMonitor(MONITOR_A_ID)]);
326
+ spies.ruleFindBy.mockResolvedValue([
327
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
328
+ ]);
329
+ spies.monitorFindBy.mockResolvedValue([
330
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
331
+ ]);
184
332
 
185
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
186
- serviceLevelObjectiveId: SLO_ID,
187
- });
333
+ await syncSlo();
188
334
 
189
- expect(writtenIds(updateOneByIdSpy, "autoAddedMonitors")).toEqual([
335
+ expect(writtenIds(spies.sloUpdateOneById, "autoAddedMonitors")).toEqual([
190
336
  MONITOR_A_ID.toString(),
191
337
  ]);
192
338
  });
193
339
 
194
- it("writes as root - the rule is server-side and must not need the caller's permissions", async () => {
195
- sloFindOneByIdSpy.mockResolvedValue(
196
- fakeSlo({ monitorLabels: [LABEL_PRODUCTION_ID] }),
197
- );
198
- monitorFindBySpy.mockResolvedValue([fakeMonitor(MONITOR_A_ID)]);
340
+ it("writes as root - rules are server-side and must never trip the manual-add guard", async () => {
341
+ spies.ruleFindBy.mockResolvedValue([
342
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
343
+ ]);
344
+ spies.monitorFindBy.mockResolvedValue([
345
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
346
+ ]);
199
347
 
200
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
348
+ await syncSlo();
349
+
350
+ expect(
351
+ (spies.sloUpdateOneById.mock.calls[0]![0] as { props: unknown }).props,
352
+ ).toEqual({ isRoot: true });
353
+ });
354
+
355
+ it("reads only the SLO's enabled rules, pinned to its project, with criteria selected", async () => {
356
+ await syncSlo();
357
+
358
+ expect(spies.ruleFindBy).toHaveBeenCalledTimes(1);
359
+
360
+ const call: {
361
+ query: Record<string, unknown>;
362
+ select: Record<string, unknown>;
363
+ limit: number;
364
+ skip: number;
365
+ props: unknown;
366
+ } = spies.ruleFindBy.mock.calls[0]![0] as {
367
+ query: Record<string, unknown>;
368
+ select: Record<string, unknown>;
369
+ limit: number;
370
+ skip: number;
371
+ props: unknown;
372
+ };
373
+
374
+ expect(call.query).toEqual({
375
+ projectId: PROJECT_ID,
201
376
  serviceLevelObjectiveId: SLO_ID,
377
+ isEnabled: true,
202
378
  });
203
-
204
- const call: { props: Record<string, unknown> } = updateOneByIdSpy.mock
205
- .calls[0]![0] as { props: Record<string, unknown> };
379
+ expect(call.select["criteria"]).toBe(true);
380
+ expect(call.select["monitorLabels"]).toEqual({ _id: true });
381
+ expect(call.select["monitorNamePattern"]).toBe(true);
382
+ expect(call.select["monitorDescriptionPattern"]).toBe(true);
383
+ expect(call.limit).toBe(MAX_RULES_EVALUATED_PER_PROJECT);
384
+ expect(call.skip).toBe(0);
206
385
  expect(call.props).toEqual({ isRoot: true });
207
386
  });
208
387
 
209
- it("matches on any of the rule's labels, not all of them", async () => {
210
- sloFindOneByIdSpy.mockResolvedValue(
211
- fakeSlo({ monitorLabels: [LABEL_PRODUCTION_ID, LABEL_TIER1_ID] }),
212
- );
213
- monitorFindBySpy.mockResolvedValue([fakeMonitor(MONITOR_A_ID)]);
388
+ it("keeps membership in sync whatever the SLO's own state - it reads the SLO by id alone", async () => {
389
+ spies.ruleFindBy.mockResolvedValue([
390
+ fakeRule({ monitorNamePattern: ".*" }),
391
+ ]);
392
+ spies.monitorFindBy.mockResolvedValue([fakeMonitor(MONITOR_A_ID)]);
214
393
 
215
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
216
- serviceLevelObjectiveId: SLO_ID,
217
- });
394
+ await syncSlo();
218
395
 
219
- const query: { projectId: ObjectID; labels: Array<ObjectID> } =
220
- monitorFindBySpy.mock.calls[0]![0]!.query;
396
+ const select: Record<string, unknown> = (
397
+ spies.sloFindOneById.mock.calls[0]![0] as {
398
+ select: Record<string, unknown>;
399
+ }
400
+ ).select;
221
401
 
222
- expect(query.projectId).toEqual(PROJECT_ID);
223
- expect(
224
- query.labels.map((id: ObjectID) => {
225
- return id.toString();
226
- }),
227
- ).toEqual([LABEL_PRODUCTION_ID.toString(), LABEL_TIER1_ID.toString()]);
402
+ // Disabled and archived SLOs are skipped by the worker, never here.
403
+ expect(select["isEnabled"]).toBeUndefined();
404
+ expect(select["isArchived"]).toBeUndefined();
405
+ expect(spies.sloUpdateOneById).toHaveBeenCalledTimes(1);
228
406
  });
229
407
 
230
- it("scopes the monitor lookup to the SLO's own project", async () => {
231
- sloFindOneByIdSpy.mockResolvedValue(
232
- fakeSlo({
233
- projectId: OTHER_PROJECT_ID,
234
- monitorLabels: [LABEL_PRODUCTION_ID],
408
+ it("unions the matches of every enabled rule on the SLO", async () => {
409
+ spies.ruleFindBy.mockResolvedValue([
410
+ fakeRule({ id: RULE_ID, labelIds: [LABEL_PRODUCTION_ID] }),
411
+ fakeRule({ id: OTHER_RULE_ID, monitorNamePattern: "^payments-" }),
412
+ ]);
413
+ spies.monitorFindBy.mockResolvedValue([
414
+ fakeMonitor(MONITOR_A_ID, {
415
+ name: "checkout-web",
416
+ labelIds: [LABEL_PRODUCTION_ID],
235
417
  }),
236
- );
237
- monitorFindBySpy.mockResolvedValue([]);
418
+ fakeMonitor(MONITOR_B_ID, { name: "payments-api" }),
419
+ fakeMonitor(MONITOR_C_ID, {
420
+ name: "search",
421
+ labelIds: [LABEL_STAGING_ID],
422
+ }),
423
+ ]);
238
424
 
239
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
240
- serviceLevelObjectiveId: SLO_ID,
241
- });
425
+ await syncSlo();
242
426
 
243
- expect(monitorFindBySpy.mock.calls[0]![0]!.query.projectId).toEqual(
244
- OTHER_PROJECT_ID,
427
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual(
428
+ sortedIds([MONITOR_A_ID, MONITOR_B_ID]),
245
429
  );
246
430
  });
247
431
 
248
- it("leaves a hand-attached monitor out of autoAddedMonitors even when it matches the rule", async () => {
249
- sloFindOneByIdSpy.mockResolvedValue(
250
- fakeSlo({
251
- monitorLabels: [LABEL_PRODUCTION_ID],
252
- monitors: [MONITOR_A_ID],
253
- autoAddedMonitors: [],
432
+ it("attaches a monitor two rules both match exactly once", async () => {
433
+ spies.ruleFindBy.mockResolvedValue([
434
+ fakeRule({ id: RULE_ID, labelIds: [LABEL_PRODUCTION_ID] }),
435
+ fakeRule({ id: OTHER_RULE_ID, monitorNamePattern: "api" }),
436
+ ]);
437
+ spies.monitorFindBy.mockResolvedValue([
438
+ fakeMonitor(MONITOR_A_ID, {
439
+ name: "api",
440
+ labelIds: [LABEL_PRODUCTION_ID],
254
441
  }),
255
- );
256
- // Monitor A matches the rule, but a human put it there first.
257
- monitorFindBySpy.mockResolvedValue([
258
- fakeMonitor(MONITOR_A_ID),
259
- fakeMonitor(MONITOR_B_ID),
260
442
  ]);
261
443
 
262
- const result: SloMonitorSyncResult =
263
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
264
- serviceLevelObjectiveId: SLO_ID,
265
- });
444
+ const result: SloMonitorSyncResult = await syncSlo();
266
445
 
267
- expect(result.monitorIdsAdded).toEqual([MONITOR_B_ID.toString()]);
268
- expect(writtenIds(updateOneByIdSpy, "autoAddedMonitors")).toEqual([
269
- MONITOR_B_ID.toString(),
446
+ expect(result.monitorIdsAdded).toEqual([MONITOR_A_ID.toString()]);
447
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
448
+ MONITOR_A_ID.toString(),
270
449
  ]);
271
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual(
272
- [MONITOR_A_ID.toString(), MONITOR_B_ID.toString()].sort(),
273
- );
274
450
  });
275
451
 
276
- it("detaches a monitor it had attached once the monitor stops matching", async () => {
277
- sloFindOneByIdSpy.mockResolvedValue(
278
- fakeSlo({
279
- monitorLabels: [LABEL_PRODUCTION_ID],
280
- monitors: [MONITOR_A_ID, MONITOR_B_ID],
281
- autoAddedMonitors: [MONITOR_A_ID, MONITOR_B_ID],
282
- }),
283
- );
284
- monitorFindBySpy.mockResolvedValue([fakeMonitor(MONITOR_A_ID)]);
452
+ it("matches a legacy rule's label criterion on ANY of its labels", async () => {
453
+ spies.ruleFindBy.mockResolvedValue([
454
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID, LABEL_TIER1_ID] }),
455
+ ]);
456
+ spies.monitorFindBy.mockResolvedValue([
457
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_TIER1_ID] }),
458
+ ]);
285
459
 
286
- const result: SloMonitorSyncResult =
287
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
288
- serviceLevelObjectiveId: SLO_ID,
289
- });
460
+ await syncSlo();
290
461
 
291
- expect(result.monitorIdsRemoved).toEqual([MONITOR_B_ID.toString()]);
292
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual([
462
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
293
463
  MONITOR_A_ID.toString(),
294
464
  ]);
295
- expect(writtenIds(updateOneByIdSpy, "autoAddedMonitors")).toEqual([
465
+ });
466
+
467
+ it("ANDs a legacy rule's criteria: the label and the name pattern must both hold", async () => {
468
+ spies.ruleFindBy.mockResolvedValue([
469
+ fakeRule({
470
+ labelIds: [LABEL_PRODUCTION_ID],
471
+ monitorNamePattern: "^api-",
472
+ }),
473
+ ]);
474
+ spies.monitorFindBy.mockResolvedValue([
475
+ fakeMonitor(MONITOR_A_ID, {
476
+ name: "api-gateway",
477
+ labelIds: [LABEL_PRODUCTION_ID],
478
+ }),
479
+ fakeMonitor(MONITOR_B_ID, {
480
+ name: "web",
481
+ labelIds: [LABEL_PRODUCTION_ID],
482
+ }),
483
+ ]);
484
+
485
+ await syncSlo();
486
+
487
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
296
488
  MONITOR_A_ID.toString(),
297
489
  ]);
298
490
  });
299
491
 
300
- it("never detaches a hand-attached monitor, however badly it fails the rule", async () => {
301
- sloFindOneByIdSpy.mockResolvedValue(
302
- fakeSlo({
303
- monitorLabels: [LABEL_PRODUCTION_ID],
304
- monitors: [MONITOR_A_ID, MONITOR_C_ID],
305
- autoAddedMonitors: [MONITOR_A_ID],
492
+ it("pushes the union of every rule's labels into the monitor query when all rules are legacy label rules", async () => {
493
+ spies.ruleFindBy.mockResolvedValue([
494
+ fakeRule({ id: RULE_ID, labelIds: [LABEL_PRODUCTION_ID] }),
495
+ fakeRule({
496
+ id: OTHER_RULE_ID,
497
+ labelIds: [LABEL_TIER1_ID, LABEL_PRODUCTION_ID],
498
+ monitorNamePattern: "^api-",
306
499
  }),
500
+ ]);
501
+
502
+ await syncSlo();
503
+
504
+ const query: Record<string, unknown> = (
505
+ spies.monitorFindBy.mock.calls[0]![0] as {
506
+ query: Record<string, unknown>;
507
+ }
508
+ ).query;
509
+
510
+ expect(query["projectId"]).toBe(PROJECT_ID);
511
+ expect(sortedIds(query["labels"] as Array<ObjectID>)).toEqual(
512
+ sortedIds([LABEL_PRODUCTION_ID, LABEL_TIER1_ID]),
307
513
  );
308
- // Neither A nor C matches any more; only A is the rule's to take back.
309
- monitorFindBySpy.mockResolvedValue([]);
514
+ });
310
515
 
311
- const result: SloMonitorSyncResult =
312
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
313
- serviceLevelObjectiveId: SLO_ID,
314
- });
516
+ it("reads every project monitor when a rule could match a monitor with no labels", async () => {
517
+ spies.ruleFindBy.mockResolvedValue([
518
+ fakeRule({ id: RULE_ID, labelIds: [LABEL_PRODUCTION_ID] }),
519
+ fakeRule({ id: OTHER_RULE_ID, monitorNamePattern: "^api-" }),
520
+ ]);
315
521
 
316
- expect(result.monitorIdsRemoved).toEqual([MONITOR_A_ID.toString()]);
317
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual([
318
- MONITOR_C_ID.toString(),
522
+ await syncSlo();
523
+
524
+ const call: {
525
+ query: Record<string, unknown>;
526
+ select: Record<string, unknown>;
527
+ props: unknown;
528
+ } = spies.monitorFindBy.mock.calls[0]![0] as {
529
+ query: Record<string, unknown>;
530
+ select: Record<string, unknown>;
531
+ props: unknown;
532
+ };
533
+
534
+ expect(call.query).toEqual({ projectId: PROJECT_ID });
535
+ expect(call.select).toEqual({
536
+ _id: true,
537
+ name: true,
538
+ description: true,
539
+ labels: { _id: true },
540
+ });
541
+ expect(call.props).toEqual({ isRoot: true });
542
+ });
543
+
544
+ it("never pre-filters by label for a criteria rule, even one about labels", async () => {
545
+ spies.ruleFindBy.mockResolvedValue([
546
+ fakeRule({
547
+ labelIds: [LABEL_PRODUCTION_ID],
548
+ criteria: criteria(FilterCondition.All, [
549
+ {
550
+ field: "monitorLabels",
551
+ operator: RuleCriteriaOperator.HasNoneOf,
552
+ value: [LABEL_STAGING_ID.toString()],
553
+ },
554
+ ]),
555
+ }),
319
556
  ]);
320
- expect(writtenIds(updateOneByIdSpy, "autoAddedMonitors")).toEqual([]);
557
+
558
+ await syncSlo();
559
+
560
+ expect(
561
+ (
562
+ spies.monitorFindBy.mock.calls[0]![0] as {
563
+ query: Record<string, unknown>;
564
+ }
565
+ ).query,
566
+ ).toEqual({ projectId: PROJECT_ID });
321
567
  });
322
568
 
323
- it("gives back every rule-attached monitor when the rule is cleared, and keeps the manual ones", async () => {
324
- sloFindOneByIdSpy.mockResolvedValue(
569
+ it("gives back every rule-attached monitor once no enabled rule is left, and keeps the manual ones", async () => {
570
+ spies.sloFindOneById.mockResolvedValue(
325
571
  fakeSlo({
326
- monitorLabels: [],
327
572
  monitors: [MONITOR_A_ID, MONITOR_B_ID, MONITOR_C_ID],
328
573
  autoAddedMonitors: [MONITOR_A_ID, MONITOR_B_ID],
329
574
  }),
330
575
  );
576
+ spies.ruleFindBy.mockResolvedValue([]);
331
577
 
332
- const result: SloMonitorSyncResult =
333
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
334
- serviceLevelObjectiveId: SLO_ID,
335
- });
578
+ const result: SloMonitorSyncResult = await syncSlo();
336
579
 
337
- // No rule means nothing to look up - the monitor query must not even run.
338
- expect(monitorFindBySpy).not.toHaveBeenCalled();
339
- expect(result.monitorIdsRemoved.sort()).toEqual(
340
- [MONITOR_A_ID.toString(), MONITOR_B_ID.toString()].sort(),
341
- );
342
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual([
580
+ // Nothing can match, so there is no reason to read the project's monitors.
581
+ expect(
582
+ spies.monitorFindBy.mock.calls.filter((call: Array<unknown>) => {
583
+ return (
584
+ (call[0] as { select: Record<string, unknown> }).select[
585
+ "description"
586
+ ] === true
587
+ );
588
+ }),
589
+ ).toHaveLength(0);
590
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
343
591
  MONITOR_C_ID.toString(),
344
592
  ]);
345
- expect(writtenIds(updateOneByIdSpy, "autoAddedMonitors")).toEqual([]);
593
+ expect(writtenIds(spies.sloUpdateOneById, "autoAddedMonitors")).toEqual([]);
594
+ expect(result.monitorIdsRemoved.sort()).toEqual(
595
+ sortedIds([MONITOR_A_ID, MONITOR_B_ID]),
596
+ );
346
597
  });
347
598
 
348
- it("writes nothing when the rule already agrees with the SLO", async () => {
349
- sloFindOneByIdSpy.mockResolvedValue(
599
+ it("matches nothing for an enabled rule with no criteria at all", async () => {
600
+ spies.sloFindOneById.mockResolvedValue(
350
601
  fakeSlo({
351
- monitorLabels: [LABEL_PRODUCTION_ID],
352
- monitors: [MONITOR_A_ID, MONITOR_C_ID],
602
+ monitors: [MONITOR_A_ID],
353
603
  autoAddedMonitors: [MONITOR_A_ID],
354
604
  }),
355
605
  );
356
- monitorFindBySpy.mockResolvedValue([fakeMonitor(MONITOR_A_ID)]);
606
+ spies.ruleFindBy.mockResolvedValue([fakeRule({})]);
357
607
 
358
- const result: SloMonitorSyncResult =
359
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
360
- serviceLevelObjectiveId: SLO_ID,
361
- });
608
+ await syncSlo();
362
609
 
363
- expect(updateOneByIdSpy).not.toHaveBeenCalled();
364
- expect(result).toEqual({ monitorIdsAdded: [], monitorIdsRemoved: [] });
610
+ expect(spies.monitorFindBy).not.toHaveBeenCalledWith(
611
+ expect.objectContaining({ query: { projectId: PROJECT_ID } }),
612
+ );
613
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([]);
365
614
  });
366
615
 
367
- it("writes nothing for an SLO with neither a rule nor anything it attached", async () => {
368
- sloFindOneByIdSpy.mockResolvedValue(
369
- fakeSlo({ monitorLabels: [], monitors: [MONITOR_C_ID] }),
616
+ it("leaves a hand-attached monitor out of autoAddedMonitors even when a rule matches it", async () => {
617
+ spies.sloFindOneById.mockResolvedValue(
618
+ fakeSlo({ monitors: [MONITOR_A_ID] }),
370
619
  );
620
+ spies.ruleFindBy.mockResolvedValue([
621
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
622
+ ]);
623
+ spies.monitorFindBy.mockResolvedValue([
624
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
625
+ fakeMonitor(MONITOR_B_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
626
+ ]);
371
627
 
372
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
373
- serviceLevelObjectiveId: SLO_ID,
374
- });
628
+ const result: SloMonitorSyncResult = await syncSlo();
375
629
 
376
- expect(updateOneByIdSpy).not.toHaveBeenCalled();
630
+ expect(writtenIds(spies.sloUpdateOneById, "autoAddedMonitors")).toEqual([
631
+ MONITOR_B_ID.toString(),
632
+ ]);
633
+ expect(result.monitorIdsAdded).toEqual([MONITOR_B_ID.toString()]);
377
634
  });
378
635
 
379
- it("re-attaches a monitor that is recorded as rule-attached but has gone missing from the list", async () => {
380
- /*
381
- * Bookkeeping drift, e.g. a monitor removed straight out of the Monitors
382
- * picker while the rule still claims it. The sync is authoritative and
383
- * repairs it rather than leaving the two relations disagreeing.
384
- */
385
- sloFindOneByIdSpy.mockResolvedValue(
636
+ it("detaches a monitor a rule attached once no rule matches it", async () => {
637
+ spies.sloFindOneById.mockResolvedValue(
386
638
  fakeSlo({
387
- monitorLabels: [LABEL_PRODUCTION_ID],
388
- monitors: [],
389
- autoAddedMonitors: [MONITOR_A_ID],
639
+ monitors: [MONITOR_A_ID, MONITOR_B_ID],
640
+ autoAddedMonitors: [MONITOR_A_ID, MONITOR_B_ID],
390
641
  }),
391
642
  );
392
- monitorFindBySpy.mockResolvedValue([fakeMonitor(MONITOR_A_ID)]);
643
+ spies.ruleFindBy.mockResolvedValue([
644
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
645
+ ]);
646
+ spies.monitorFindBy.mockResolvedValue([
647
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
648
+ ]);
393
649
 
394
- const result: SloMonitorSyncResult =
395
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
396
- serviceLevelObjectiveId: SLO_ID,
397
- });
650
+ const result: SloMonitorSyncResult = await syncSlo();
398
651
 
399
- expect(result.monitorIdsAdded).toEqual([MONITOR_A_ID.toString()]);
400
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual([
652
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
401
653
  MONITOR_A_ID.toString(),
402
654
  ]);
655
+ expect(result.monitorIdsRemoved).toEqual([MONITOR_B_ID.toString()]);
403
656
  });
404
657
 
405
- it("does nothing for an SLO it cannot read", async () => {
406
- sloFindOneByIdSpy.mockResolvedValue(null);
658
+ it("never detaches a hand-attached monitor, however badly it fails every rule", async () => {
659
+ spies.sloFindOneById.mockResolvedValue(
660
+ fakeSlo({
661
+ monitors: [MONITOR_A_ID, MONITOR_B_ID],
662
+ autoAddedMonitors: [MONITOR_A_ID],
663
+ }),
664
+ );
665
+ spies.ruleFindBy.mockResolvedValue([
666
+ fakeRule({ monitorNamePattern: "^nothing-matches$" }),
667
+ ]);
668
+ spies.monitorFindBy.mockResolvedValue([
669
+ fakeMonitor(MONITOR_A_ID),
670
+ fakeMonitor(MONITOR_B_ID),
671
+ ]);
407
672
 
408
- const result: SloMonitorSyncResult =
409
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
410
- serviceLevelObjectiveId: SLO_ID,
411
- });
673
+ await syncSlo();
674
+
675
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
676
+ MONITOR_B_ID.toString(),
677
+ ]);
678
+ });
679
+
680
+ it("writes nothing when the rules already agree with the SLO", async () => {
681
+ spies.sloFindOneById.mockResolvedValue(
682
+ fakeSlo({
683
+ monitors: [MONITOR_A_ID, MONITOR_B_ID],
684
+ autoAddedMonitors: [MONITOR_A_ID],
685
+ }),
686
+ );
687
+ spies.ruleFindBy.mockResolvedValue([
688
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
689
+ ]);
690
+ spies.monitorFindBy.mockResolvedValue([
691
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
692
+ ]);
412
693
 
413
- expect(monitorFindBySpy).not.toHaveBeenCalled();
414
- expect(updateOneByIdSpy).not.toHaveBeenCalled();
694
+ const result: SloMonitorSyncResult = await syncSlo();
695
+
696
+ expect(spies.sloUpdateOneById).not.toHaveBeenCalled();
697
+ expect(spies.feed).not.toHaveBeenCalled();
415
698
  expect(result).toEqual({ monitorIdsAdded: [], monitorIdsRemoved: [] });
416
699
  });
417
700
 
418
- it("does nothing for an SLO with no project - there is no tenant to scope the lookup to", async () => {
419
- sloFindOneByIdSpy.mockResolvedValue(
420
- fakeSlo({ projectId: null as unknown as ObjectID }),
701
+ it("re-attaches a monitor that is recorded as rule-attached but has gone missing from the list", async () => {
702
+ spies.sloFindOneById.mockResolvedValue(
703
+ fakeSlo({ monitors: [], autoAddedMonitors: [MONITOR_A_ID] }),
421
704
  );
705
+ spies.ruleFindBy.mockResolvedValue([
706
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
707
+ ]);
708
+ spies.monitorFindBy.mockResolvedValue([
709
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
710
+ ]);
422
711
 
423
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
424
- serviceLevelObjectiveId: SLO_ID,
425
- });
712
+ await syncSlo();
426
713
 
427
- expect(monitorFindBySpy).not.toHaveBeenCalled();
428
- expect(updateOneByIdSpy).not.toHaveBeenCalled();
714
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
715
+ MONITOR_A_ID.toString(),
716
+ ]);
429
717
  });
430
718
 
431
- it("reads the relations it needs to decide - the rule, the list, and the bookkeeping", async () => {
432
- sloFindOneByIdSpy.mockResolvedValue(fakeSlo({ monitorLabels: [] }));
719
+ it("does nothing for an SLO it cannot read", async () => {
720
+ spies.sloFindOneById.mockResolvedValue(null);
433
721
 
434
- await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
435
- serviceLevelObjectiveId: SLO_ID,
722
+ await expect(syncSlo()).resolves.toEqual({
723
+ monitorIdsAdded: [],
724
+ monitorIdsRemoved: [],
436
725
  });
726
+ expect(spies.ruleFindBy).not.toHaveBeenCalled();
727
+ expect(spies.sloUpdateOneById).not.toHaveBeenCalled();
728
+ });
437
729
 
438
- const select: Record<string, unknown> =
439
- sloFindOneByIdSpy.mock.calls[0]![0]!.select;
730
+ it("does nothing for an SLO with no project - there is no tenant to scope the lookups to", async () => {
731
+ spies.sloFindOneById.mockResolvedValue(fakeSlo({ projectId: null }));
440
732
 
441
- expect(Object.keys(select).sort()).toEqual([
442
- "_id",
443
- "autoAddedMonitors",
444
- "monitorLabels",
445
- "monitors",
446
- "projectId",
447
- ]);
733
+ await syncSlo();
734
+
735
+ expect(spies.ruleFindBy).not.toHaveBeenCalled();
736
+ expect(spies.monitorFindBy).not.toHaveBeenCalled();
737
+ });
738
+
739
+ it("says so when the rule read hits the per-project ceiling", async () => {
740
+ const errorSpy: jest.SpyInstance = jest
741
+ .spyOn(logger, "error")
742
+ .mockImplementation(() => {
743
+ return undefined as never;
744
+ });
745
+
746
+ spies.ruleFindBy.mockResolvedValue(
747
+ Array.from({ length: MAX_RULES_EVALUATED_PER_PROJECT }, () => {
748
+ return fakeRule({});
749
+ }),
750
+ );
751
+
752
+ await syncSlo();
753
+
754
+ const messages: Array<string> = errorSpy.mock.calls.map(
755
+ (call: Array<unknown>) => {
756
+ return String(call[0]);
757
+ },
758
+ );
759
+
760
+ expect(
761
+ messages.some((message: string) => {
762
+ return message.includes("ServiceLevelObjectiveMonitorRule");
763
+ }),
764
+ ).toBe(true);
448
765
  });
449
766
  });
450
767
 
451
- describe("ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor", () => {
452
- let updateOneByIdSpy: jest.SpyInstance;
453
- let sloFindBySpy: jest.SpyInstance;
454
- let monitorFindOneByIdSpy: jest.SpyInstance;
768
+ describe("ServiceLevelObjectiveMonitorRuleEngineService - membership feed items", () => {
769
+ let spies: SyncSpies;
455
770
 
456
771
  beforeEach(() => {
457
- updateOneByIdSpy = jest
458
- .spyOn(ServiceLevelObjectiveService, "updateOneById")
459
- .mockResolvedValue(1);
460
- sloFindBySpy = jest.spyOn(ServiceLevelObjectiveService, "findBy");
461
- monitorFindOneByIdSpy = jest.spyOn(MonitorService, "findOneById");
772
+ spies = installSpies();
462
773
  });
463
774
 
464
775
  afterEach(() => {
465
776
  jest.restoreAllMocks();
466
777
  });
467
778
 
468
- it("attaches the monitor to an SLO whose rule its new labels satisfy", async () => {
469
- monitorFindOneByIdSpy.mockResolvedValue(
470
- fakeMonitorWithLabels([LABEL_PRODUCTION_ID]),
471
- );
472
- sloFindBySpy.mockResolvedValue([
473
- fakeSlo({ monitorLabels: [LABEL_PRODUCTION_ID] }),
779
+ it("posts MonitorsAttached naming the monitor, with no acting user", async () => {
780
+ spies.sloFindOneById.mockResolvedValue(fakeSlo({}));
781
+ spies.ruleFindBy.mockResolvedValue([
782
+ fakeRule({ monitorNamePattern: "api" }),
783
+ ]);
784
+ spies.monitorFindBy.mockResolvedValue([
785
+ fakeMonitor(MONITOR_A_ID, { name: "API Gateway" }),
474
786
  ]);
475
787
 
476
- const results: Array<SloMonitorSyncResult> =
477
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
478
- monitorId: MONITOR_A_ID,
479
- });
788
+ await syncSlo();
480
789
 
481
- expect(updateOneByIdSpy).toHaveBeenCalledTimes(1);
482
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual([
483
- MONITOR_A_ID.toString(),
484
- ]);
485
- expect(writtenIds(updateOneByIdSpy, "autoAddedMonitors")).toEqual([
486
- MONITOR_A_ID.toString(),
487
- ]);
488
- expect(results).toHaveLength(1);
489
- expect(results[0]!.monitorIdsAdded).toEqual([MONITOR_A_ID.toString()]);
490
- });
790
+ const calls: Array<FeedCall> = feedCalls(spies.feed);
491
791
 
492
- it("detaches the monitor once its last matching label is taken away", async () => {
493
- monitorFindOneByIdSpy.mockResolvedValue(
494
- fakeMonitorWithLabels([LABEL_STAGING_ID]),
792
+ expect(calls).toHaveLength(1);
793
+ expect(calls[0]!.serviceLevelObjectiveFeedEventType).toBe(
794
+ ServiceLevelObjectiveFeedEventType.MonitorsAttached,
495
795
  );
496
- sloFindBySpy.mockResolvedValue([
796
+ expect(calls[0]!.serviceLevelObjectiveId).toBe(SLO_ID);
797
+ expect(calls[0]!.projectId).toBe(PROJECT_ID);
798
+ expect(calls[0]!.userId).toBeUndefined();
799
+ expect(calls[0]!.feedInfoInMarkdown).toContain("**API Gateway**");
800
+ expect(calls[0]!.feedInfoInMarkdown).toContain(SLO_LINK);
801
+ // A single monitor is fully described by the sentence.
802
+ expect(calls[0]!.moreInformationInMarkdown).toBeUndefined();
803
+ expect(spies.markdownLink).toHaveBeenCalledWith({
804
+ projectId: PROJECT_ID,
805
+ sloId: SLO_ID,
806
+ sloName: "Checkout",
807
+ });
808
+ });
809
+
810
+ it("posts MonitorsDetached for monitors the rules let go, looking up names it does not hold", async () => {
811
+ spies.sloFindOneById.mockResolvedValue(
497
812
  fakeSlo({
498
- monitorLabels: [LABEL_PRODUCTION_ID],
499
- monitors: [MONITOR_A_ID],
500
- autoAddedMonitors: [MONITOR_A_ID],
813
+ monitors: [MONITOR_A_ID, MONITOR_B_ID],
814
+ autoAddedMonitors: [MONITOR_A_ID, MONITOR_B_ID],
501
815
  }),
816
+ );
817
+ spies.ruleFindBy.mockResolvedValue([]);
818
+ spies.monitorFindBy.mockResolvedValue([
819
+ fakeMonitor(MONITOR_A_ID, { name: "Checkout API" }),
820
+ fakeMonitor(MONITOR_B_ID, { name: "Cart API" }),
502
821
  ]);
503
822
 
504
- const results: Array<SloMonitorSyncResult> =
505
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
506
- monitorId: MONITOR_A_ID,
507
- });
823
+ await syncSlo();
824
+
825
+ const calls: Array<FeedCall> = feedCalls(spies.feed);
826
+
827
+ expect(calls).toHaveLength(1);
828
+ expect(calls[0]!.serviceLevelObjectiveFeedEventType).toBe(
829
+ ServiceLevelObjectiveFeedEventType.MonitorsDetached,
830
+ );
831
+ expect(calls[0]!.feedInfoInMarkdown).toContain("detached 2 monitors");
832
+ // Sorted by name so the same set always reads the same way.
833
+ expect(calls[0]!.feedInfoInMarkdown).toContain(
834
+ "**Cart API** and **Checkout API**",
835
+ );
508
836
 
509
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual([]);
510
- expect(writtenIds(updateOneByIdSpy, "autoAddedMonitors")).toEqual([]);
511
- expect(results[0]!.monitorIdsRemoved).toEqual([MONITOR_A_ID.toString()]);
837
+ const nameLookup: { query: Record<string, unknown>; props: unknown } = spies
838
+ .monitorFindBy.mock.calls[0]![0] as {
839
+ query: Record<string, unknown>;
840
+ props: unknown;
841
+ };
842
+
843
+ expect(JSON.stringify(nameLookup.query["_id"])).toContain(
844
+ MONITOR_A_ID.toString(),
845
+ );
846
+ expect(nameLookup.props).toEqual({ isRoot: true });
512
847
  });
513
848
 
514
- it("detaches the monitor when every label is cleared at once", async () => {
515
- monitorFindOneByIdSpy.mockResolvedValue(fakeMonitorWithLabels([]));
516
- sloFindBySpy.mockResolvedValue([
849
+ it("posts both items when one sync attaches and detaches", async () => {
850
+ spies.sloFindOneById.mockResolvedValue(
517
851
  fakeSlo({
518
- monitorLabels: [LABEL_PRODUCTION_ID],
519
852
  monitors: [MONITOR_A_ID],
520
853
  autoAddedMonitors: [MONITOR_A_ID],
521
854
  }),
855
+ );
856
+ spies.ruleFindBy.mockResolvedValue([
857
+ fakeRule({ monitorNamePattern: "^new-" }),
522
858
  ]);
859
+ spies.monitorFindBy.mockImplementation(((args: {
860
+ select: Record<string, unknown>;
861
+ }): Promise<Array<Monitor>> => {
862
+ if (args.select["description"]) {
863
+ return Promise.resolve([
864
+ fakeMonitor(MONITOR_A_ID, { name: "old-api" }),
865
+ fakeMonitor(MONITOR_B_ID, { name: "new-api" }),
866
+ ]);
867
+ }
523
868
 
524
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
525
- monitorId: MONITOR_A_ID,
526
- });
869
+ return Promise.resolve([fakeMonitor(MONITOR_A_ID, { name: "old-api" })]);
870
+ }) as never);
527
871
 
528
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual([]);
529
- });
872
+ await syncSlo();
530
873
 
531
- it("leaves the other monitors an SLO's rule attached exactly where they are", async () => {
532
- monitorFindOneByIdSpy.mockResolvedValue(
533
- fakeMonitorWithLabels([LABEL_STAGING_ID]),
534
- );
535
- sloFindBySpy.mockResolvedValue([
536
- fakeSlo({
537
- monitorLabels: [LABEL_PRODUCTION_ID],
538
- monitors: [MONITOR_A_ID, MONITOR_B_ID, MONITOR_C_ID],
539
- autoAddedMonitors: [MONITOR_A_ID, MONITOR_B_ID],
874
+ expect(
875
+ feedCalls(spies.feed).map((call: FeedCall) => {
876
+ return call.serviceLevelObjectiveFeedEventType;
540
877
  }),
878
+ ).toEqual([
879
+ ServiceLevelObjectiveFeedEventType.MonitorsAttached,
880
+ ServiceLevelObjectiveFeedEventType.MonitorsDetached,
541
881
  ]);
882
+ });
542
883
 
543
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
544
- monitorId: MONITOR_A_ID,
545
- });
884
+ it("escapes monitor names so a name cannot rewrite the markdown around it", async () => {
885
+ spies.sloFindOneById.mockResolvedValue(fakeSlo({}));
886
+ spies.ruleFindBy.mockResolvedValue([
887
+ fakeRule({ monitorNamePattern: ".*" }),
888
+ ]);
889
+ spies.monitorFindBy.mockResolvedValue([
890
+ fakeMonitor(MONITOR_A_ID, { name: "x](https://evil.test) ![p](y)" }),
891
+ ]);
546
892
 
547
- /*
548
- * Only monitor A's membership was in question. B keeps its rule-attached
549
- * status without the engine re-checking B's labels, and the manual C is
550
- * untouched.
551
- */
552
- expect(writtenIds(updateOneByIdSpy, "monitors")).toEqual(
553
- [MONITOR_B_ID.toString(), MONITOR_C_ID.toString()].sort(),
893
+ await syncSlo();
894
+
895
+ const text: string = feedCalls(spies.feed)[0]!.feedInfoInMarkdown;
896
+
897
+ expect(text).toContain("x\\]\\(https://evil.test\\) \\!\\[p\\]\\(y\\)");
898
+ expect(text).not.toContain("](https://evil.test)");
899
+ });
900
+
901
+ it("names a handful in the sentence and lists a bounded page in more information", async () => {
902
+ const monitors: Array<Monitor> = Array.from(
903
+ { length: 60 },
904
+ (_value: unknown, index: number) => {
905
+ const suffix: string = index.toString().padStart(12, "0");
906
+
907
+ return fakeMonitor(new ObjectID(`abababab-abab-4bab-8bab-${suffix}`), {
908
+ name: `monitor-${index.toString().padStart(2, "0")}`,
909
+ });
910
+ },
554
911
  );
555
- expect(writtenIds(updateOneByIdSpy, "autoAddedMonitors")).toEqual([
556
- MONITOR_B_ID.toString(),
912
+
913
+ spies.sloFindOneById.mockResolvedValue(fakeSlo({}));
914
+ spies.ruleFindBy.mockResolvedValue([
915
+ fakeRule({ monitorNamePattern: "^monitor-" }),
557
916
  ]);
917
+ spies.monitorFindBy.mockResolvedValue(monitors);
918
+
919
+ await syncSlo();
920
+
921
+ const call: FeedCall = feedCalls(spies.feed)[0]!;
922
+
923
+ expect(call.feedInfoInMarkdown).toContain("attached 60 monitors");
924
+ expect(call.feedInfoInMarkdown).toContain("and 55 more");
925
+ expect(call.feedInfoInMarkdown).toContain("**monitor\\-00**");
926
+
927
+ const bullets: Array<string> = (call.moreInformationInMarkdown || "")
928
+ .split("\n")
929
+ .filter((line: string) => {
930
+ return line.startsWith("- ");
931
+ });
932
+
933
+ expect(call.moreInformationInMarkdown).toContain(
934
+ "**Monitors attached (60)**",
935
+ );
936
+ // 50 named, then one line counting the rest.
937
+ expect(bullets).toHaveLength(51);
938
+ expect(bullets[bullets.length - 1]).toBe("- …and 10 more not listed here.");
558
939
  });
559
940
 
560
- it("never touches an SLO with no label rule - that list is curated by hand", async () => {
561
- monitorFindOneByIdSpy.mockResolvedValue(
562
- fakeMonitorWithLabels([LABEL_PRODUCTION_ID]),
941
+ it("does not report a stale bookkeeping entry as a detached monitor", async () => {
942
+ spies.sloFindOneById.mockResolvedValue(
943
+ fakeSlo({ monitors: [], autoAddedMonitors: [MONITOR_A_ID] }),
563
944
  );
564
- sloFindBySpy.mockResolvedValue([
565
- fakeSlo({ monitorLabels: [], monitors: [] }),
945
+ spies.ruleFindBy.mockResolvedValue([]);
946
+
947
+ await syncSlo();
948
+
949
+ // The bookkeeping is repaired...
950
+ expect(spies.sloUpdateOneById).toHaveBeenCalledTimes(1);
951
+ // ...but nothing a reader could see was detached.
952
+ expect(spies.feed).not.toHaveBeenCalled();
953
+ });
954
+
955
+ it("never lets a failed feed post fail the sync that already saved", async () => {
956
+ spies.sloFindOneById.mockResolvedValue(fakeSlo({}));
957
+ spies.ruleFindBy.mockResolvedValue([
958
+ fakeRule({ monitorNamePattern: ".*" }),
566
959
  ]);
960
+ spies.monitorFindBy.mockResolvedValue([fakeMonitor(MONITOR_A_ID)]);
961
+ spies.markdownLink.mockRejectedValue(new Error("config down"));
962
+ jest.spyOn(logger, "error").mockImplementation(() => {
963
+ return undefined as never;
964
+ });
965
+
966
+ await expect(syncSlo()).resolves.toEqual({
967
+ monitorIdsAdded: [MONITOR_A_ID.toString()],
968
+ monitorIdsRemoved: [],
969
+ });
970
+ });
971
+ });
972
+
973
+ describe("ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor", () => {
974
+ let spies: SyncSpies;
567
975
 
568
- const results: Array<SloMonitorSyncResult> =
569
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
976
+ beforeEach(() => {
977
+ spies = installSpies();
978
+ spies.monitorFindOneById.mockResolvedValue(
979
+ fakeMonitor(MONITOR_A_ID, {
980
+ name: "api-gateway",
981
+ labelIds: [LABEL_PRODUCTION_ID],
982
+ }),
983
+ );
984
+ });
985
+
986
+ afterEach(() => {
987
+ jest.restoreAllMocks();
988
+ });
989
+
990
+ async function syncMonitor(
991
+ projectId?: ObjectID | undefined,
992
+ ): Promise<Array<SloMonitorSyncResult>> {
993
+ return await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor(
994
+ {
570
995
  monitorId: MONITOR_A_ID,
571
- });
996
+ projectId: projectId,
997
+ },
998
+ );
999
+ }
572
1000
 
573
- expect(updateOneByIdSpy).not.toHaveBeenCalled();
574
- expect(results).toEqual([]);
1001
+ it("attaches the monitor to an SLO whose enabled rule its labels satisfy", async () => {
1002
+ spies.ruleFindBy.mockResolvedValue([
1003
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
1004
+ ]);
1005
+ spies.sloFindBy.mockImplementation(
1006
+ answerSloReads({ byId: [fakeSlo({})] }) as never,
1007
+ );
1008
+
1009
+ const results: Array<SloMonitorSyncResult> = await syncMonitor();
1010
+
1011
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
1012
+ MONITOR_A_ID.toString(),
1013
+ ]);
1014
+ expect(writtenIds(spies.sloUpdateOneById, "autoAddedMonitors")).toEqual([
1015
+ MONITOR_A_ID.toString(),
1016
+ ]);
1017
+ expect(results).toHaveLength(1);
575
1018
  });
576
1019
 
577
- it("does not detach a monitor a human attached to a rule-driven SLO", async () => {
578
- monitorFindOneByIdSpy.mockResolvedValue(
579
- fakeMonitorWithLabels([LABEL_STAGING_ID]),
1020
+ it("attaches on a name match too - a rename can pull a monitor in", async () => {
1021
+ spies.ruleFindBy.mockResolvedValue([
1022
+ fakeRule({ monitorNamePattern: "*gateway*" }),
1023
+ ]);
1024
+ spies.sloFindBy.mockImplementation(
1025
+ answerSloReads({ byId: [fakeSlo({})] }) as never,
580
1026
  );
581
- sloFindBySpy.mockResolvedValue([
582
- fakeSlo({
583
- monitorLabels: [LABEL_PRODUCTION_ID],
584
- monitors: [MONITOR_A_ID],
585
- autoAddedMonitors: [],
586
- }),
1027
+
1028
+ await syncMonitor();
1029
+
1030
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
1031
+ MONITOR_A_ID.toString(),
587
1032
  ]);
1033
+ });
588
1034
 
589
- const results: Array<SloMonitorSyncResult> =
590
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
591
- monitorId: MONITOR_A_ID,
592
- });
1035
+ it("detaches the monitor once no enabled rule of the SLO matches it", async () => {
1036
+ spies.monitorFindOneById.mockResolvedValue(
1037
+ fakeMonitor(MONITOR_A_ID, { name: "renamed", labelIds: [] }),
1038
+ );
1039
+ spies.ruleFindBy.mockResolvedValue([
1040
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
1041
+ ]);
1042
+ spies.sloFindBy.mockImplementation(
1043
+ answerSloReads({
1044
+ byId: [
1045
+ fakeSlo({
1046
+ monitors: [MONITOR_A_ID],
1047
+ autoAddedMonitors: [MONITOR_A_ID],
1048
+ }),
1049
+ ],
1050
+ }) as never,
1051
+ );
1052
+
1053
+ await syncMonitor();
593
1054
 
594
- expect(updateOneByIdSpy).not.toHaveBeenCalled();
1055
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([]);
1056
+ expect(writtenIds(spies.sloUpdateOneById, "autoAddedMonitors")).toEqual([]);
1057
+ });
1058
+
1059
+ it("keeps the monitor while another rule of the same SLO still matches it", async () => {
1060
+ spies.ruleFindBy.mockResolvedValue([
1061
+ fakeRule({ id: RULE_ID, labelIds: [LABEL_STAGING_ID] }),
1062
+ fakeRule({ id: OTHER_RULE_ID, monitorNamePattern: "^api-" }),
1063
+ ]);
1064
+ spies.sloFindBy.mockImplementation(
1065
+ answerSloReads({
1066
+ byId: [
1067
+ fakeSlo({
1068
+ monitors: [MONITOR_A_ID],
1069
+ autoAddedMonitors: [MONITOR_A_ID],
1070
+ }),
1071
+ ],
1072
+ }) as never,
1073
+ );
1074
+
1075
+ const results: Array<SloMonitorSyncResult> = await syncMonitor();
1076
+
1077
+ expect(spies.sloUpdateOneById).not.toHaveBeenCalled();
595
1078
  expect(results).toEqual([]);
596
1079
  });
597
1080
 
598
- it("does not adopt a hand-attached monitor into the rule when it starts matching", async () => {
599
- monitorFindOneByIdSpy.mockResolvedValue(
600
- fakeMonitorWithLabels([LABEL_PRODUCTION_ID]),
1081
+ it("leaves the other monitors an SLO's rules attached exactly where they are", async () => {
1082
+ spies.monitorFindOneById.mockResolvedValue(
1083
+ fakeMonitor(MONITOR_A_ID, { name: "renamed", labelIds: [] }),
601
1084
  );
602
- sloFindBySpy.mockResolvedValue([
603
- fakeSlo({
604
- monitorLabels: [LABEL_PRODUCTION_ID],
605
- monitors: [MONITOR_A_ID],
606
- autoAddedMonitors: [],
1085
+ spies.ruleFindBy.mockResolvedValue([
1086
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
1087
+ ]);
1088
+ spies.sloFindBy.mockImplementation(
1089
+ answerSloReads({
1090
+ byId: [
1091
+ fakeSlo({
1092
+ monitors: [MONITOR_A_ID, MONITOR_B_ID],
1093
+ autoAddedMonitors: [MONITOR_A_ID, MONITOR_B_ID],
1094
+ }),
1095
+ ],
1096
+ }) as never,
1097
+ );
1098
+
1099
+ await syncMonitor();
1100
+
1101
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([
1102
+ MONITOR_B_ID.toString(),
1103
+ ]);
1104
+ expect(writtenIds(spies.sloUpdateOneById, "autoAddedMonitors")).toEqual([
1105
+ MONITOR_B_ID.toString(),
1106
+ ]);
1107
+ });
1108
+
1109
+ it("releases the monitor from an SLO with no enabled rule left that still records it", async () => {
1110
+ spies.ruleFindBy.mockResolvedValue([]);
1111
+ spies.sloFindBy.mockImplementation(
1112
+ answerSloReads({
1113
+ holdingAutoAddedMonitor: [fakeSlo({ id: OTHER_SLO_ID })],
1114
+ byId: [
1115
+ fakeSlo({
1116
+ id: OTHER_SLO_ID,
1117
+ monitors: [MONITOR_A_ID],
1118
+ autoAddedMonitors: [MONITOR_A_ID],
1119
+ }),
1120
+ ],
1121
+ }) as never,
1122
+ );
1123
+
1124
+ await syncMonitor();
1125
+
1126
+ const holderLookup: { query: Record<string, unknown>; select: unknown } =
1127
+ spies.sloFindBy.mock.calls[0]![0] as {
1128
+ query: Record<string, unknown>;
1129
+ select: unknown;
1130
+ };
1131
+
1132
+ expect(holderLookup.query["projectId"]).toBe(PROJECT_ID);
1133
+ expect(holderLookup.query["autoAddedMonitors"]).toEqual([MONITOR_A_ID]);
1134
+ // Ids only: a relation filter can narrow the relation rows it returns.
1135
+ expect(holderLookup.select).toEqual({ _id: true });
1136
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([]);
1137
+ });
1138
+
1139
+ it("never touches an SLO with no enabled rule that does not hold the monitor", async () => {
1140
+ spies.ruleFindBy.mockResolvedValue([]);
1141
+ spies.sloFindBy.mockImplementation(answerSloReads({ byId: [] }) as never);
1142
+
1143
+ const results: Array<SloMonitorSyncResult> = await syncMonitor();
1144
+
1145
+ // Only the holder lookup - with no candidates, no SLO is read at all.
1146
+ expect(spies.sloFindBy).toHaveBeenCalledTimes(1);
1147
+ expect(spies.sloUpdateOneById).not.toHaveBeenCalled();
1148
+ expect(results).toEqual([]);
1149
+ });
1150
+
1151
+ it("reads the candidate SLOs by id, pinned to the monitor's project", async () => {
1152
+ spies.ruleFindBy.mockResolvedValue([
1153
+ fakeRule({ serviceLevelObjectiveId: SLO_ID, labelIds: [LABEL_TIER1_ID] }),
1154
+ fakeRule({
1155
+ id: OTHER_RULE_ID,
1156
+ serviceLevelObjectiveId: OTHER_SLO_ID,
1157
+ labelIds: [LABEL_TIER1_ID],
607
1158
  }),
608
1159
  ]);
1160
+ spies.sloFindBy.mockImplementation(answerSloReads({ byId: [] }) as never);
609
1161
 
610
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
611
- monitorId: MONITOR_A_ID,
612
- });
1162
+ await syncMonitor();
613
1163
 
614
- // Already attached and still manual: nothing to write, nothing to adopt.
615
- expect(updateOneByIdSpy).not.toHaveBeenCalled();
1164
+ const read: { query: Record<string, unknown>; props: unknown } = spies
1165
+ .sloFindBy.mock.calls[1]![0] as {
1166
+ query: Record<string, unknown>;
1167
+ props: unknown;
1168
+ };
1169
+
1170
+ expect(read.query["projectId"]).toBe(PROJECT_ID);
1171
+ expect(JSON.stringify(read.query["_id"])).toContain(SLO_ID.toString());
1172
+ expect(JSON.stringify(read.query["_id"])).toContain(
1173
+ OTHER_SLO_ID.toString(),
1174
+ );
1175
+ expect(read.props).toEqual({ isRoot: true });
616
1176
  });
617
1177
 
618
- it("applies every rule in the project, not just the first one that matches", async () => {
619
- monitorFindOneByIdSpy.mockResolvedValue(
620
- fakeMonitorWithLabels([LABEL_PRODUCTION_ID]),
1178
+ it("does not detach a monitor a human attached to a rule-driven SLO", async () => {
1179
+ spies.monitorFindOneById.mockResolvedValue(
1180
+ fakeMonitor(MONITOR_A_ID, { name: "renamed", labelIds: [] }),
621
1181
  );
622
- sloFindBySpy.mockResolvedValue([
623
- fakeSlo({ id: SLO_ID, monitorLabels: [LABEL_PRODUCTION_ID] }),
624
- fakeSlo({ id: OTHER_SLO_ID, monitorLabels: [LABEL_PRODUCTION_ID] }),
1182
+ spies.ruleFindBy.mockResolvedValue([
1183
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
625
1184
  ]);
1185
+ spies.sloFindBy.mockImplementation(
1186
+ answerSloReads({
1187
+ byId: [fakeSlo({ monitors: [MONITOR_A_ID], autoAddedMonitors: [] })],
1188
+ }) as never,
1189
+ );
626
1190
 
627
- const results: Array<SloMonitorSyncResult> =
628
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
629
- monitorId: MONITOR_A_ID,
630
- });
1191
+ await syncMonitor();
1192
+
1193
+ expect(spies.sloUpdateOneById).not.toHaveBeenCalled();
1194
+ });
1195
+
1196
+ it("does not adopt a hand-attached monitor into the rules when it starts matching", async () => {
1197
+ spies.ruleFindBy.mockResolvedValue([
1198
+ fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
1199
+ ]);
1200
+ spies.sloFindBy.mockImplementation(
1201
+ answerSloReads({
1202
+ byId: [fakeSlo({ monitors: [MONITOR_A_ID], autoAddedMonitors: [] })],
1203
+ }) as never,
1204
+ );
1205
+
1206
+ await syncMonitor();
1207
+
1208
+ expect(spies.sloUpdateOneById).not.toHaveBeenCalled();
1209
+ });
631
1210
 
632
- expect(updateOneByIdSpy).toHaveBeenCalledTimes(2);
633
- expect(writtenSloId(updateOneByIdSpy, 0)).toBe(SLO_ID.toString());
634
- expect(writtenSloId(updateOneByIdSpy, 1)).toBe(OTHER_SLO_ID.toString());
1211
+ it("applies the rules of every SLO in the project, not just the first that matches", async () => {
1212
+ spies.ruleFindBy.mockResolvedValue([
1213
+ fakeRule({
1214
+ serviceLevelObjectiveId: SLO_ID,
1215
+ labelIds: [LABEL_PRODUCTION_ID],
1216
+ }),
1217
+ fakeRule({
1218
+ id: OTHER_RULE_ID,
1219
+ serviceLevelObjectiveId: OTHER_SLO_ID,
1220
+ monitorNamePattern: "^api-",
1221
+ }),
1222
+ ]);
1223
+ spies.sloFindBy.mockImplementation(
1224
+ answerSloReads({
1225
+ byId: [fakeSlo({ id: SLO_ID }), fakeSlo({ id: OTHER_SLO_ID })],
1226
+ }) as never,
1227
+ );
1228
+
1229
+ const results: Array<SloMonitorSyncResult> = await syncMonitor();
1230
+
1231
+ expect(spies.sloUpdateOneById).toHaveBeenCalledTimes(2);
635
1232
  expect(results).toHaveLength(2);
636
1233
  });
637
1234
 
638
- it("carries on with the remaining SLOs when one of them fails to write", async () => {
639
- monitorFindOneByIdSpy.mockResolvedValue(
640
- fakeMonitorWithLabels([LABEL_PRODUCTION_ID]),
1235
+ it("an SLO's rules decide only that SLO - another SLO's rule never attaches to it", async () => {
1236
+ spies.ruleFindBy.mockResolvedValue([
1237
+ fakeRule({
1238
+ serviceLevelObjectiveId: OTHER_SLO_ID,
1239
+ labelIds: [LABEL_PRODUCTION_ID],
1240
+ }),
1241
+ ]);
1242
+ spies.sloFindBy.mockImplementation(
1243
+ answerSloReads({
1244
+ byId: [fakeSlo({ id: SLO_ID }), fakeSlo({ id: OTHER_SLO_ID })],
1245
+ }) as never,
641
1246
  );
642
- sloFindBySpy.mockResolvedValue([
643
- fakeSlo({ id: SLO_ID, monitorLabels: [LABEL_PRODUCTION_ID] }),
644
- fakeSlo({ id: OTHER_SLO_ID, monitorLabels: [LABEL_PRODUCTION_ID] }),
1247
+
1248
+ await syncMonitor();
1249
+
1250
+ expect(spies.sloUpdateOneById).toHaveBeenCalledTimes(1);
1251
+ expect(
1252
+ (spies.sloUpdateOneById.mock.calls[0]![0] as { id: ObjectID }).id,
1253
+ ).toBe(OTHER_SLO_ID);
1254
+ });
1255
+
1256
+ it("carries on with the remaining SLOs when one of them fails to write", async () => {
1257
+ jest.spyOn(logger, "error").mockImplementation(() => {
1258
+ return undefined as never;
1259
+ });
1260
+ spies.ruleFindBy.mockResolvedValue([
1261
+ fakeRule({
1262
+ serviceLevelObjectiveId: SLO_ID,
1263
+ labelIds: [LABEL_PRODUCTION_ID],
1264
+ }),
1265
+ fakeRule({
1266
+ id: OTHER_RULE_ID,
1267
+ serviceLevelObjectiveId: OTHER_SLO_ID,
1268
+ labelIds: [LABEL_PRODUCTION_ID],
1269
+ }),
645
1270
  ]);
646
- updateOneByIdSpy
1271
+ spies.sloFindBy.mockImplementation(
1272
+ answerSloReads({
1273
+ byId: [fakeSlo({ id: SLO_ID }), fakeSlo({ id: OTHER_SLO_ID })],
1274
+ }) as never,
1275
+ );
1276
+ spies.sloUpdateOneById
647
1277
  .mockRejectedValueOnce(new Error("db down"))
648
1278
  .mockResolvedValueOnce(1);
649
1279
 
650
- const results: Array<SloMonitorSyncResult> =
651
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
652
- monitorId: MONITOR_A_ID,
653
- });
1280
+ const results: Array<SloMonitorSyncResult> = await syncMonitor();
654
1281
 
655
- expect(updateOneByIdSpy).toHaveBeenCalledTimes(2);
1282
+ expect(spies.sloUpdateOneById).toHaveBeenCalledTimes(2);
656
1283
  expect(results).toHaveLength(1);
657
1284
  });
658
1285
 
659
- it("looks up the SLOs of the monitor's own project", async () => {
660
- monitorFindOneByIdSpy.mockResolvedValue(
661
- fakeMonitorWithLabels([LABEL_PRODUCTION_ID], {
662
- projectId: OTHER_PROJECT_ID,
1286
+ it("reads the project's enabled rules once, as root, with criteria selected", async () => {
1287
+ await syncMonitor();
1288
+
1289
+ expect(spies.ruleFindBy).toHaveBeenCalledTimes(1);
1290
+
1291
+ const call: {
1292
+ query: Record<string, unknown>;
1293
+ select: Record<string, unknown>;
1294
+ limit: number;
1295
+ props: unknown;
1296
+ } = spies.ruleFindBy.mock.calls[0]![0] as {
1297
+ query: Record<string, unknown>;
1298
+ select: Record<string, unknown>;
1299
+ limit: number;
1300
+ props: unknown;
1301
+ };
1302
+
1303
+ expect(call.query).toEqual({ projectId: PROJECT_ID, isEnabled: true });
1304
+ expect(call.select["criteria"]).toBe(true);
1305
+ expect(call.limit).toBe(MAX_RULES_EVALUATED_PER_PROJECT);
1306
+ expect(call.props).toEqual({ isRoot: true });
1307
+ });
1308
+
1309
+ it("reads the monitor's name and description, not only its labels", async () => {
1310
+ await syncMonitor();
1311
+
1312
+ expect(
1313
+ (
1314
+ spies.monitorFindOneById.mock.calls[0]![0] as {
1315
+ select: Record<string, unknown>;
1316
+ }
1317
+ ).select,
1318
+ ).toEqual({
1319
+ _id: true,
1320
+ projectId: true,
1321
+ name: true,
1322
+ description: true,
1323
+ labels: { _id: true },
1324
+ });
1325
+ });
1326
+
1327
+ it("falls back to the caller's project when the monitor row does not carry one", async () => {
1328
+ spies.monitorFindOneById.mockResolvedValue(
1329
+ fakeMonitor(MONITOR_A_ID, { projectId: null }),
1330
+ );
1331
+
1332
+ await syncMonitor(OTHER_PROJECT_ID);
1333
+
1334
+ expect(
1335
+ (
1336
+ spies.ruleFindBy.mock.calls[0]![0] as {
1337
+ query: Record<string, unknown>;
1338
+ }
1339
+ ).query["projectId"],
1340
+ ).toBe(OTHER_PROJECT_ID);
1341
+ });
1342
+
1343
+ it("does nothing for a monitor that no longer exists", async () => {
1344
+ spies.monitorFindOneById.mockResolvedValue(null);
1345
+
1346
+ await expect(syncMonitor()).resolves.toEqual([]);
1347
+ expect(spies.ruleFindBy).not.toHaveBeenCalled();
1348
+ });
1349
+
1350
+ it("does nothing when no project can be determined at all", async () => {
1351
+ spies.monitorFindOneById.mockResolvedValue(
1352
+ fakeMonitor(MONITOR_A_ID, { projectId: null }),
1353
+ );
1354
+
1355
+ await expect(syncMonitor()).resolves.toEqual([]);
1356
+ expect(spies.ruleFindBy).not.toHaveBeenCalled();
1357
+ });
1358
+ });
1359
+
1360
+ /*
1361
+ * Rolling-deploy gap: previous-release API pods keep writing the deprecated
1362
+ * SLO label list after the one-shot backfill ran, and their engine attaches
1363
+ * the matching monitors as rule-attached. The new engine reads only rules, so
1364
+ * without adoption the first edit to one of those monitors released it from an
1365
+ * SLO with no rule - silently, with no rule in the UI to explain why.
1366
+ */
1367
+ describe("ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor - legacy label lists", () => {
1368
+ let spies: SyncSpies;
1369
+
1370
+ beforeEach(() => {
1371
+ spies = installSpies();
1372
+ spies.monitorFindOneById.mockResolvedValue(
1373
+ fakeMonitor(MONITOR_A_ID, {
1374
+ name: "api-gateway",
1375
+ labelIds: [LABEL_PRODUCTION_ID],
663
1376
  }),
664
1377
  );
665
- sloFindBySpy.mockResolvedValue([]);
1378
+ });
1379
+
1380
+ afterEach(() => {
1381
+ jest.restoreAllMocks();
1382
+ });
1383
+
1384
+ async function syncMonitor(): Promise<Array<SloMonitorSyncResult>> {
1385
+ return await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor(
1386
+ {
1387
+ monitorId: MONITOR_A_ID,
1388
+ },
1389
+ );
1390
+ }
666
1391
 
667
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
668
- monitorId: MONITOR_A_ID,
1392
+ function updatedSloIds(): Array<string> {
1393
+ return spies.sloUpdateOneById.mock.calls.map((call: Array<unknown>) => {
1394
+ return String((call[0] as { id: ObjectID }).id);
1395
+ });
1396
+ }
1397
+
1398
+ it("adopts the label list of an SLO holding the monitor with no rule, and keeps the monitor instead of releasing it", async () => {
1399
+ // The project has no enabled rule; after adoption the SLO has its new one.
1400
+ spies.ruleFindBy.mockImplementation(((args: {
1401
+ query: Record<string, unknown>;
1402
+ }): Promise<Array<ServiceLevelObjectiveMonitorRule>> => {
1403
+ if (args.query["serviceLevelObjectiveId"] !== undefined) {
1404
+ return Promise.resolve([
1405
+ fakeRule({
1406
+ serviceLevelObjectiveId: OTHER_SLO_ID,
1407
+ labelIds: [LABEL_PRODUCTION_ID],
1408
+ }),
1409
+ ]);
1410
+ }
1411
+
1412
+ return Promise.resolve([]);
1413
+ }) as never);
1414
+ spies.sloFindBy.mockImplementation(
1415
+ answerSloReads({
1416
+ holdingAutoAddedMonitor: [fakeSlo({ id: OTHER_SLO_ID })],
1417
+ byId: [
1418
+ fakeSlo({
1419
+ id: OTHER_SLO_ID,
1420
+ monitors: [MONITOR_A_ID, MONITOR_B_ID],
1421
+ autoAddedMonitors: [MONITOR_A_ID, MONITOR_B_ID],
1422
+ }),
1423
+ ],
1424
+ }) as never,
1425
+ );
1426
+ spies.legacyLabelAdoption.mockResolvedValue([OTHER_SLO_ID.toString()]);
1427
+ spies.sloFindOneById.mockResolvedValue(
1428
+ fakeSlo({
1429
+ id: OTHER_SLO_ID,
1430
+ monitors: [MONITOR_A_ID, MONITOR_B_ID],
1431
+ autoAddedMonitors: [MONITOR_A_ID, MONITOR_B_ID],
1432
+ }),
1433
+ );
1434
+ // What the previous-release engine would have attached by now, plus one more.
1435
+ spies.monitorFindBy.mockResolvedValue([
1436
+ fakeMonitor(MONITOR_A_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
1437
+ fakeMonitor(MONITOR_B_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
1438
+ fakeMonitor(MONITOR_C_ID, { labelIds: [LABEL_PRODUCTION_ID] }),
1439
+ ]);
1440
+
1441
+ const results: Array<SloMonitorSyncResult> = await syncMonitor();
1442
+
1443
+ expect(spies.legacyLabelAdoption).toHaveBeenCalledTimes(1);
1444
+ expect(spies.legacyLabelAdoption).toHaveBeenCalledWith({
669
1445
  projectId: PROJECT_ID,
1446
+ serviceLevelObjectiveIds: [OTHER_SLO_ID.toString()],
670
1447
  });
671
1448
 
672
- // The monitor row wins over the caller's hint - it cannot be stale.
673
- expect(sloFindBySpy.mock.calls[0]![0]!.query.projectId).toEqual(
674
- OTHER_PROJECT_ID,
1449
+ // Synced with its adopted rule: A and B stay, C (also labelled) joins.
1450
+ expect(updatedSloIds()).toEqual([OTHER_SLO_ID.toString()]);
1451
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual(
1452
+ sortedIds([MONITOR_A_ID, MONITOR_B_ID, MONITOR_C_ID]),
675
1453
  );
1454
+ expect(writtenIds(spies.sloUpdateOneById, "autoAddedMonitors")).toEqual(
1455
+ sortedIds([MONITOR_A_ID, MONITOR_B_ID, MONITOR_C_ID]),
1456
+ );
1457
+ expect(results).toHaveLength(1);
1458
+ expect(results[0]!.monitorIdsRemoved).toEqual([]);
676
1459
  });
677
1460
 
678
- it("falls back to the caller's project when the monitor row does not carry one", async () => {
679
- monitorFindOneByIdSpy.mockResolvedValue(
680
- fakeMonitorWithLabels([LABEL_PRODUCTION_ID], { projectId: null }),
1461
+ it("an SLO with no legacy label list is released exactly as before", async () => {
1462
+ spies.ruleFindBy.mockResolvedValue([]);
1463
+ spies.sloFindBy.mockImplementation(
1464
+ answerSloReads({
1465
+ holdingAutoAddedMonitor: [fakeSlo({ id: OTHER_SLO_ID })],
1466
+ byId: [
1467
+ fakeSlo({
1468
+ id: OTHER_SLO_ID,
1469
+ monitors: [MONITOR_A_ID],
1470
+ autoAddedMonitors: [MONITOR_A_ID],
1471
+ }),
1472
+ ],
1473
+ }) as never,
681
1474
  );
682
- sloFindBySpy.mockResolvedValue([]);
1475
+ spies.legacyLabelAdoption.mockResolvedValue([]);
1476
+
1477
+ await syncMonitor();
1478
+
1479
+ expect(spies.legacyLabelAdoption).toHaveBeenCalledTimes(1);
1480
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([]);
1481
+ expect(spies.sloFindOneById).not.toHaveBeenCalled();
1482
+ });
683
1483
 
684
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
685
- monitorId: MONITOR_A_ID,
1484
+ it("only SLOs without an enabled rule are offered for adoption, and none at all when every candidate has one", async () => {
1485
+ spies.ruleFindBy.mockResolvedValue([
1486
+ fakeRule({ serviceLevelObjectiveId: SLO_ID, labelIds: [LABEL_TIER1_ID] }),
1487
+ ]);
1488
+ spies.sloFindBy.mockImplementation(
1489
+ answerSloReads({
1490
+ holdingAutoAddedMonitor: [fakeSlo({ id: OTHER_SLO_ID })],
1491
+ byId: [fakeSlo({ id: SLO_ID }), fakeSlo({ id: OTHER_SLO_ID })],
1492
+ }) as never,
1493
+ );
1494
+
1495
+ await syncMonitor();
1496
+
1497
+ expect(spies.legacyLabelAdoption).toHaveBeenCalledWith({
686
1498
  projectId: PROJECT_ID,
1499
+ serviceLevelObjectiveIds: [OTHER_SLO_ID.toString()],
687
1500
  });
688
1501
 
689
- expect(sloFindBySpy.mock.calls[0]![0]!.query.projectId).toEqual(PROJECT_ID);
1502
+ spies.legacyLabelAdoption.mockClear();
1503
+ spies.sloFindBy.mockImplementation(
1504
+ answerSloReads({ byId: [fakeSlo({ id: SLO_ID })] }) as never,
1505
+ );
1506
+
1507
+ await syncMonitor();
1508
+
1509
+ // The normal path pays nothing for the rolling-deploy guard.
1510
+ expect(spies.legacyLabelAdoption).not.toHaveBeenCalled();
690
1511
  });
691
1512
 
692
- it("does nothing for a monitor that no longer exists", async () => {
693
- monitorFindOneByIdSpy.mockResolvedValue(null);
1513
+ it("a failed adoption releases nothing from SLOs without a rule, and still syncs the SLOs that have one", async () => {
1514
+ const errorSpy: jest.SpyInstance = jest
1515
+ .spyOn(logger, "error")
1516
+ .mockImplementation((() => {
1517
+ return undefined;
1518
+ }) as never);
694
1519
 
695
- const results: Array<SloMonitorSyncResult> =
696
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
697
- monitorId: MONITOR_A_ID,
698
- projectId: PROJECT_ID,
699
- });
1520
+ spies.ruleFindBy.mockResolvedValue([
1521
+ fakeRule({
1522
+ serviceLevelObjectiveId: SLO_ID,
1523
+ labelIds: [LABEL_PRODUCTION_ID],
1524
+ }),
1525
+ ]);
1526
+ // The by-id read answers what its _id filter asks for (asserted below).
1527
+ spies.sloFindBy.mockImplementation(
1528
+ answerSloReads({
1529
+ holdingAutoAddedMonitor: [fakeSlo({ id: OTHER_SLO_ID })],
1530
+ byId: [fakeSlo({ id: SLO_ID })],
1531
+ }) as never,
1532
+ );
1533
+ spies.legacyLabelAdoption.mockRejectedValue(
1534
+ new Error("connection terminated"),
1535
+ );
700
1536
 
701
- expect(sloFindBySpy).not.toHaveBeenCalled();
702
- expect(updateOneByIdSpy).not.toHaveBeenCalled();
703
- expect(results).toEqual([]);
1537
+ await syncMonitor();
1538
+
1539
+ const byIdRead: { query: Record<string, unknown> } = spies.sloFindBy.mock
1540
+ .calls[1]![0] as { query: Record<string, unknown> };
1541
+
1542
+ // OTHER_SLO_ID is dropped from the candidates, so it is not even read.
1543
+ expect(JSON.stringify(byIdRead.query["_id"])).toContain(SLO_ID.toString());
1544
+ expect(JSON.stringify(byIdRead.query["_id"])).not.toContain(
1545
+ OTHER_SLO_ID.toString(),
1546
+ );
1547
+
1548
+ // SLO_ID still gets the monitor; OTHER_SLO_ID is not written at all.
1549
+ expect(updatedSloIds()).toEqual([SLO_ID.toString()]);
1550
+ expect(errorSpy).toHaveBeenCalledWith(
1551
+ expect.stringContaining("Error adopting legacy SLO monitor labels"),
1552
+ expect.anything(),
1553
+ );
704
1554
  });
705
1555
 
706
- it("does nothing when no project can be determined at all", async () => {
707
- monitorFindOneByIdSpy.mockResolvedValue(
708
- fakeMonitorWithLabels([LABEL_PRODUCTION_ID], { projectId: null }),
1556
+ it("the rule-side sync never adopts: after a rule delete, 'no rule, monitors still attached' is what a deliberate delete looks like", async () => {
1557
+ spies.sloFindOneById.mockResolvedValue(
1558
+ fakeSlo({
1559
+ monitors: [MONITOR_A_ID],
1560
+ autoAddedMonitors: [MONITOR_A_ID],
1561
+ }),
709
1562
  );
1563
+ spies.ruleFindBy.mockResolvedValue([]);
710
1564
 
711
- const results: Array<SloMonitorSyncResult> =
712
- await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor({
713
- monitorId: MONITOR_A_ID,
714
- });
1565
+ await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
1566
+ serviceLevelObjectiveId: SLO_ID,
1567
+ });
715
1568
 
716
- expect(sloFindBySpy).not.toHaveBeenCalled();
717
- expect(results).toEqual([]);
1569
+ expect(spies.legacyLabelAdoption).not.toHaveBeenCalled();
1570
+ expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual([]);
718
1571
  });
719
1572
  });
720
1573
 
721
- describe("ServiceLevelObjectiveMonitorRuleEngineService.doesLabelSetMatchRule", () => {
1574
+ describe("ServiceLevelObjectiveMonitorRuleEngineService.doesMonitorMatchRule", () => {
1575
+ const monitor: Monitor = fakeMonitor(MONITOR_A_ID, {
1576
+ name: "API-Gateway-Prod",
1577
+ description: "Customer facing tier-1 edge",
1578
+ labelIds: [LABEL_PRODUCTION_ID, LABEL_TIER1_ID],
1579
+ });
1580
+
722
1581
  const cases: Array<{
723
1582
  label: string;
724
- labelIds: Array<string>;
725
- ruleLabelIds: Array<string>;
1583
+ rule: ServiceLevelObjectiveMonitorRule;
726
1584
  expected: boolean;
727
1585
  }> = [
728
1586
  {
729
- label: "one shared label is enough",
730
- labelIds: ["a", "b"],
731
- ruleLabelIds: ["b", "c"],
1587
+ label: "a rule with no criteria matches nothing",
1588
+ rule: fakeRule({}),
1589
+ expected: false,
1590
+ },
1591
+ {
1592
+ label: "a name regex matches case-insensitively",
1593
+ rule: fakeRule({ monitorNamePattern: "^api-gateway" }),
1594
+ expected: true,
1595
+ },
1596
+ {
1597
+ label: "a * glob matches (#2940), it is not silently an invalid regex",
1598
+ rule: fakeRule({ monitorNamePattern: "*gateway*" }),
1599
+ expected: true,
1600
+ },
1601
+ {
1602
+ label: "a description pattern matches the description",
1603
+ rule: fakeRule({ monitorDescriptionPattern: "tier-1" }),
1604
+ expected: true,
1605
+ },
1606
+ {
1607
+ label: "a description pattern that does not match fails the rule",
1608
+ rule: fakeRule({ monitorDescriptionPattern: "internal only" }),
1609
+ expected: false,
1610
+ },
1611
+ {
1612
+ label: "labels match on any of the rule's labels",
1613
+ rule: fakeRule({ labelIds: [LABEL_STAGING_ID, LABEL_TIER1_ID] }),
1614
+ expected: true,
1615
+ },
1616
+ {
1617
+ label: "labels the monitor does not carry fail the rule",
1618
+ rule: fakeRule({ labelIds: [LABEL_STAGING_ID] }),
1619
+ expected: false,
1620
+ },
1621
+ {
1622
+ label: "legacy criteria AND together",
1623
+ rule: fakeRule({
1624
+ labelIds: [LABEL_PRODUCTION_ID],
1625
+ monitorNamePattern: "^web-",
1626
+ }),
1627
+ expected: false,
1628
+ },
1629
+ {
1630
+ label: "criteria All needs every filter",
1631
+ rule: fakeRule({
1632
+ criteria: criteria(FilterCondition.All, [
1633
+ {
1634
+ field: "monitorNamePattern",
1635
+ operator: RuleCriteriaOperator.StartsWith,
1636
+ value: "api",
1637
+ },
1638
+ {
1639
+ field: "monitorDescriptionPattern",
1640
+ operator: RuleCriteriaOperator.Contains,
1641
+ value: "internal",
1642
+ },
1643
+ ]),
1644
+ }),
1645
+ expected: false,
1646
+ },
1647
+ {
1648
+ label: "criteria Any needs one filter",
1649
+ rule: fakeRule({
1650
+ criteria: criteria(FilterCondition.Any, [
1651
+ {
1652
+ field: "monitorNamePattern",
1653
+ operator: RuleCriteriaOperator.StartsWith,
1654
+ value: "web",
1655
+ },
1656
+ {
1657
+ field: "monitorDescriptionPattern",
1658
+ operator: RuleCriteriaOperator.Contains,
1659
+ value: "customer facing",
1660
+ },
1661
+ ]),
1662
+ }),
732
1663
  expected: true,
733
1664
  },
734
1665
  {
735
- label: "no overlap does not match",
736
- labelIds: ["a"],
737
- ruleLabelIds: ["b"],
1666
+ label: "criteria HasAnyOf matches a carried label",
1667
+ rule: fakeRule({
1668
+ criteria: criteria(FilterCondition.All, [
1669
+ {
1670
+ field: "monitorLabels",
1671
+ operator: RuleCriteriaOperator.HasAnyOf,
1672
+ value: [LABEL_STAGING_ID.toString(), LABEL_TIER1_ID.toString()],
1673
+ },
1674
+ ]),
1675
+ }),
1676
+ expected: true,
1677
+ },
1678
+ {
1679
+ label: "criteria HasAllOf needs every label",
1680
+ rule: fakeRule({
1681
+ criteria: criteria(FilterCondition.All, [
1682
+ {
1683
+ field: "monitorLabels",
1684
+ operator: RuleCriteriaOperator.HasAllOf,
1685
+ value: [
1686
+ LABEL_PRODUCTION_ID.toString(),
1687
+ LABEL_STAGING_ID.toString(),
1688
+ ],
1689
+ },
1690
+ ]),
1691
+ }),
738
1692
  expected: false,
739
1693
  },
740
1694
  {
741
- label: "a monitor with no labels never matches",
742
- labelIds: [],
743
- ruleLabelIds: ["a"],
1695
+ label: "criteria HasNoneOf excludes a labelled monitor",
1696
+ rule: fakeRule({
1697
+ criteria: criteria(FilterCondition.All, [
1698
+ {
1699
+ field: "monitorLabels",
1700
+ operator: RuleCriteriaOperator.HasNoneOf,
1701
+ value: [LABEL_TIER1_ID.toString()],
1702
+ },
1703
+ ]),
1704
+ }),
744
1705
  expected: false,
745
1706
  },
746
1707
  {
747
- label: "an empty rule matches nothing, not everything",
748
- labelIds: ["a"],
749
- ruleLabelIds: [],
1708
+ label: "criteria HasNoneOf keeps a monitor without those labels",
1709
+ rule: fakeRule({
1710
+ criteria: criteria(FilterCondition.All, [
1711
+ {
1712
+ field: "monitorLabels",
1713
+ operator: RuleCriteriaOperator.HasNoneOf,
1714
+ value: [LABEL_STAGING_ID.toString()],
1715
+ },
1716
+ ]),
1717
+ }),
1718
+ expected: true,
1719
+ },
1720
+ {
1721
+ label: "criteria DoesNotMatchPattern inverts the pattern",
1722
+ rule: fakeRule({
1723
+ criteria: criteria(FilterCondition.All, [
1724
+ {
1725
+ field: "monitorNamePattern",
1726
+ operator: RuleCriteriaOperator.DoesNotMatchPattern,
1727
+ value: "-prod$",
1728
+ },
1729
+ ]),
1730
+ }),
1731
+ expected: false,
1732
+ },
1733
+ {
1734
+ label:
1735
+ "criteria win over the legacy shadow a criteria save leaves behind",
1736
+ rule: fakeRule({
1737
+ monitorNamePattern: RULE_CRITERIA_LEGACY_NEVER_MATCH_PATTERN,
1738
+ criteria: criteria(FilterCondition.All, [
1739
+ {
1740
+ field: "monitorNamePattern",
1741
+ operator: RuleCriteriaOperator.EndsWith,
1742
+ value: "prod",
1743
+ },
1744
+ ]),
1745
+ }),
1746
+ expected: true,
1747
+ },
1748
+ {
1749
+ label: "invalid criteria fail closed",
1750
+ rule: fakeRule({
1751
+ criteria: {
1752
+ schemaVersion: RULE_CRITERIA_SCHEMA_VERSION,
1753
+ filterCondition: FilterCondition.All,
1754
+ filters: [
1755
+ {
1756
+ field: "monitorLabels",
1757
+ operator: RuleCriteriaOperator.HasAnyOf,
1758
+ value: "not-an-array",
1759
+ },
1760
+ ],
1761
+ },
1762
+ }),
1763
+ expected: false,
1764
+ },
1765
+ {
1766
+ label: "a filter on a field the rule does not own matches nothing",
1767
+ rule: fakeRule({
1768
+ criteria: criteria(FilterCondition.All, [
1769
+ {
1770
+ field: "monitorType",
1771
+ operator: RuleCriteriaOperator.Equals,
1772
+ value: "API",
1773
+ },
1774
+ ]),
1775
+ }),
750
1776
  expected: false,
751
1777
  },
752
1778
  {
753
- label: "both empty is still no match",
754
- labelIds: [],
755
- ruleLabelIds: [],
1779
+ label: "criteria with no filters match nothing",
1780
+ rule: fakeRule({ criteria: criteria(FilterCondition.All, []) }),
756
1781
  expected: false,
757
1782
  },
758
1783
  ];
@@ -760,9 +1785,9 @@ describe("ServiceLevelObjectiveMonitorRuleEngineService.doesLabelSetMatchRule",
760
1785
  for (const testCase of cases) {
761
1786
  it(testCase.label, () => {
762
1787
  expect(
763
- ServiceLevelObjectiveMonitorRuleEngineService.doesLabelSetMatchRule({
764
- labelIds: new Set(testCase.labelIds),
765
- ruleLabelIds: new Set(testCase.ruleLabelIds),
1788
+ ServiceLevelObjectiveMonitorRuleEngineService.doesMonitorMatchRule({
1789
+ monitor: monitor,
1790
+ rule: testCase.rule,
766
1791
  }),
767
1792
  ).toBe(testCase.expected);
768
1793
  });