@oneuptime/common 12.0.23 → 12.0.25

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 (323) hide show
  1. package/Models/AnalyticsModels/Span.ts +101 -0
  2. package/Models/DatabaseModels/DetectionRule.ts +55 -0
  3. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
  4. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
  5. package/Models/DatabaseModels/Project.ts +68 -0
  6. package/Models/DatabaseModels/User.ts +68 -0
  7. package/Server/API/BaseAPI.ts +0 -24
  8. package/Server/API/SlackAPI.ts +0 -2
  9. package/Server/API/TelemetryAPI.ts +29 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  14. package/Server/Middleware/GzipRequestBody.ts +311 -0
  15. package/Server/Middleware/MultipartFormData.ts +87 -3
  16. package/Server/Middleware/SlackAuthorization.ts +96 -18
  17. package/Server/Services/DetectionRuleService.ts +73 -6
  18. package/Server/Services/Index.ts +205 -0
  19. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
  20. package/Server/Services/NetworkDeviceService.ts +148 -0
  21. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  22. package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
  23. package/Server/Services/ProjectService.ts +29 -19
  24. package/Server/Services/SecurityEventService.ts +90 -13
  25. package/Server/Services/StatusPageResourceService.ts +223 -0
  26. package/Server/Services/TelemetryAttributeService.ts +17 -0
  27. package/Server/Services/UserService.ts +4 -6
  28. package/Server/Types/Markdown.ts +161 -67
  29. package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
  30. package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
  31. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
  32. package/Server/Utils/Attribution.ts +4 -0
  33. package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
  34. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
  35. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
  36. package/Server/Utils/StartServer.ts +12 -32
  37. package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
  38. package/Server/Utils/Telemetry/LlmMetricSpend.ts +181 -0
  39. package/Server/Utils/Telemetry/LlmSpan.ts +46 -0
  40. package/Server/Utils/Workspace/Slack/Actions/Auth.ts +0 -12
  41. package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
  42. package/Server/Views/Partials/Head.ejs +2 -1
  43. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
  44. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
  45. package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
  46. package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
  47. package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
  48. package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
  49. package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
  50. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
  51. package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
  52. package/Tests/App/Dashboard/LlmCallsTableIdentity.test.tsx +322 -0
  53. package/Tests/App/Dashboard/LlmOverview.test.tsx +335 -0
  54. package/Tests/App/Dashboard/LlmSpanDisplay.test.ts +391 -0
  55. package/Tests/App/Dashboard/LlmUsageBreakdown.test.tsx +1007 -0
  56. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
  57. package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
  58. package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
  59. package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
  60. package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
  61. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
  62. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
  63. package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
  64. package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
  65. package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
  66. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
  67. package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
  68. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
  69. package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
  70. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
  71. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
  72. package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
  73. package/Tests/Server/API/BaseAPI.test.ts +41 -0
  74. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +9 -16
  75. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
  76. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
  77. package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
  78. package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
  79. package/Tests/Server/Middleware/SlackAuthorization.test.ts +262 -5
  80. package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
  81. package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
  82. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
  83. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
  84. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
  85. package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
  86. package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
  87. package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
  88. package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
  89. package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
  90. package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
  91. package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
  92. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
  93. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
  94. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
  95. package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
  96. package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +387 -4
  97. package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +386 -0
  98. package/Tests/Server/Utils/Telemetry/LlmSpan.test.ts +804 -0
  99. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
  100. package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
  101. package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
  102. package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
  103. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
  104. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
  105. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
  106. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
  107. package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +584 -0
  108. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
  109. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
  110. package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
  111. package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
  112. package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
  113. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
  114. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
  115. package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
  116. package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
  117. package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
  118. package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
  119. package/Tests/UI/Components/SideOver.test.tsx +1 -1
  120. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
  121. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
  122. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
  123. package/Tests/UI/Components/Toggle.test.tsx +25 -0
  124. package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
  125. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
  126. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
  127. package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
  128. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
  129. package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +770 -0
  130. package/Types/Code/YamlSyntax.ts +396 -0
  131. package/Types/Exception/ExceptionCode.ts +1 -0
  132. package/Types/Exception/PayloadTooLargeException.ts +8 -0
  133. package/Types/Marketing/Attribution.ts +10 -0
  134. package/Types/Marketing/MarketingEvent.ts +4 -0
  135. package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
  136. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
  137. package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
  138. package/Types/Telemetry/LlmConventions.ts +255 -0
  139. package/Types/Telemetry/LlmMetricConventions.ts +329 -0
  140. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
  141. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
  142. package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
  143. package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
  144. package/UI/Components/Detail/Detail.tsx +10 -0
  145. package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
  146. package/UI/Components/Forms/Fields/FormField.tsx +31 -0
  147. package/UI/Components/Forms/Types/Field.ts +8 -0
  148. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
  149. package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
  150. package/UI/Components/Forms/Validation.ts +46 -0
  151. package/UI/Components/Input/Input.tsx +3 -0
  152. package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
  153. package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
  154. package/UI/Components/ModelTable/Column.ts +8 -0
  155. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
  156. package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
  157. package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
  158. package/UI/Components/SideOver/SideOver.tsx +5 -5
  159. package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
  160. package/UI/Components/StatusPage/ResourceList.tsx +197 -38
  161. package/UI/Components/Toggle/Toggle.tsx +9 -1
  162. package/UI/Components/Types/FieldType.ts +1 -0
  163. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
  164. package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
  165. package/Utils/StatusPage/ResourceExplorer.ts +258 -0
  166. package/Utils/Telemetry/LlmMetricQuery.ts +287 -0
  167. package/build/dist/Models/AnalyticsModels/Span.js +89 -0
  168. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  169. package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
  170. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
  171. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
  172. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
  173. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
  174. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  175. package/build/dist/Models/DatabaseModels/Project.js +76 -0
  176. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  177. package/build/dist/Models/DatabaseModels/User.js +76 -0
  178. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  179. package/build/dist/Server/API/BaseAPI.js +3 -19
  180. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  181. package/build/dist/Server/API/SlackAPI.js +0 -2
  182. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  183. package/build/dist/Server/API/TelemetryAPI.js +17 -0
  184. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  185. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
  190. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
  191. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  192. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  193. package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
  194. package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
  195. package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
  196. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
  197. package/build/dist/Server/Middleware/SlackAuthorization.js +58 -7
  198. package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
  199. package/build/dist/Server/Services/DetectionRuleService.js +49 -4
  200. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  201. package/build/dist/Server/Services/Index.js +204 -0
  202. package/build/dist/Server/Services/Index.js.map +1 -1
  203. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
  204. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  205. package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
  206. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  207. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
  208. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  209. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
  210. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
  211. package/build/dist/Server/Services/ProjectService.js +25 -45
  212. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  213. package/build/dist/Server/Services/SecurityEventService.js +57 -5
  214. package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
  215. package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
  216. package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
  217. package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
  218. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  219. package/build/dist/Server/Services/UserService.js +2 -12
  220. package/build/dist/Server/Services/UserService.js.map +1 -1
  221. package/build/dist/Server/Types/Markdown.js +147 -61
  222. package/build/dist/Server/Types/Markdown.js.map +1 -1
  223. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
  224. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
  225. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
  226. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  227. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
  228. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  229. package/build/dist/Server/Utils/Attribution.js.map +1 -1
  230. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
  231. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
  232. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
  233. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  234. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
  235. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  236. package/build/dist/Server/Utils/StartServer.js +12 -21
  237. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  238. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
  239. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
  240. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +151 -0
  241. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
  242. package/build/dist/Server/Utils/Telemetry/LlmSpan.js +24 -1
  243. package/build/dist/Server/Utils/Telemetry/LlmSpan.js.map +1 -1
  244. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +0 -10
  245. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
  246. package/build/dist/Types/Code/YamlSyntax.js +212 -0
  247. package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
  248. package/build/dist/Types/Exception/ExceptionCode.js +1 -0
  249. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  250. package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
  251. package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
  252. package/build/dist/Types/Marketing/Attribution.js +10 -0
  253. package/build/dist/Types/Marketing/Attribution.js.map +1 -1
  254. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
  255. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  256. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
  257. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
  258. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
  259. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  260. package/build/dist/Types/Telemetry/LlmConventions.js +236 -0
  261. package/build/dist/Types/Telemetry/LlmConventions.js.map +1 -1
  262. package/build/dist/Types/Telemetry/LlmMetricConventions.js +290 -0
  263. package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
  264. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  265. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  266. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
  267. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  268. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
  269. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  270. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
  271. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
  272. package/build/dist/UI/Components/Detail/Detail.js +9 -0
  273. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  274. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
  275. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  276. package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
  277. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  278. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  279. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
  280. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  281. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
  282. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
  283. package/build/dist/UI/Components/Forms/Validation.js +27 -0
  284. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  285. package/build/dist/UI/Components/Input/Input.js +1 -1
  286. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  287. package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
  288. package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
  289. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
  290. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  291. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
  292. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  293. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
  294. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
  295. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
  296. package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
  297. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
  298. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  299. package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
  300. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  301. package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
  302. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  303. package/build/dist/UI/Components/Types/FieldType.js +1 -0
  304. package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
  305. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
  306. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
  307. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
  308. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
  309. package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
  310. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
  311. package/build/dist/Utils/Telemetry/LlmMetricQuery.js +175 -0
  312. package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
  313. package/jest.config.json +2 -0
  314. package/package.json +1 -1
  315. package/tsconfig.json +2 -1
  316. package/Tests/UI/Components/Toast.test.tsx +0 -197
  317. package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
  318. package/UI/Components/Toast/Toast.tsx +0 -189
  319. package/UI/Components/Toast/ToastInit.tsx +0 -110
  320. package/build/dist/UI/Components/Toast/Toast.js +0 -123
  321. package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
  322. package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
  323. package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
@@ -0,0 +1,957 @@
1
+ import MetricMonitorCriteria from "../../../Server/Utils/Monitor/Criteria/MetricMonitorCriteria";
2
+ import ProfileMonitorCriteria from "../../../Server/Utils/Monitor/Criteria/ProfileMonitorCriteria";
3
+ import DataToProcess from "../../../Server/Utils/Monitor/DataToProcess";
4
+ import AggregateModel from "../../../Types/BaseDatabase/AggregatedModel";
5
+ import AggregatedResult from "../../../Types/BaseDatabase/AggregatedResult";
6
+ import FilterCondition from "../../../Types/Filter/FilterCondition";
7
+ import MetricAliasData from "../../../Types/Metrics/MetricAliasData";
8
+ import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData";
9
+ import MetricQueryData from "../../../Types/Metrics/MetricQueryData";
10
+ import MetricsViewConfig from "../../../Types/Metrics/MetricsViewConfig";
11
+ import {
12
+ CheckOn,
13
+ CriteriaFilter,
14
+ EvaluateOverTimeType,
15
+ FilterType,
16
+ } from "../../../Types/Monitor/CriteriaFilter";
17
+ import { CriteriaAlert } from "../../../Types/Monitor/CriteriaAlert";
18
+ import { CriteriaIncident } from "../../../Types/Monitor/CriteriaIncident";
19
+ import MetricMonitorResponse from "../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
20
+ import MonitorCriteria from "../../../Types/Monitor/MonitorCriteria";
21
+ import MonitorCriteriaInstance from "../../../Types/Monitor/MonitorCriteriaInstance";
22
+ import MonitorStep from "../../../Types/Monitor/MonitorStep";
23
+ import MonitorType, {
24
+ MonitorTypeHelper,
25
+ } from "../../../Types/Monitor/MonitorType";
26
+ import ProfileMonitorResponse from "../../../Types/Monitor/ProfileMonitor/ProfileMonitorResponse";
27
+ import ObjectID from "../../../Types/ObjectID";
28
+ import RollingTime from "../../../Types/RollingTime/RollingTime";
29
+ import { describe, expect, test } from "@jest/globals";
30
+
31
+ /*
32
+ * Nine monitor types used to fall through both default-criteria builders.
33
+ * `getDefaultOfflineMonitorCriteriaInstance` returned the bare
34
+ * `new MonitorCriteriaInstance()` the constructor hands back - no name, no
35
+ * description, and a single placeholder `Is Online / True` filter - while
36
+ * `getDefaultOnlineMonitorCriteriaInstance` returned null outright. So a
37
+ * freshly created Docker, Host, Podman, Docker Swarm, Proxmox, Ceph, IoT
38
+ * Device or Profiles monitor shipped with one criteria that was broken three
39
+ * ways over:
40
+ *
41
+ * 1. It could never fire. MonitorCriteriaEvaluator routes the first eight
42
+ * of those types to MetricMonitorCriteria, which reads only
43
+ * `CheckOn.MetricValue`, and Profiles to ProfileMonitorCriteria, which
44
+ * reads only `CheckOn.ProfileCount`. Neither has any branch for
45
+ * `Is Online`, so the seeded rule was inert whatever the monitor did.
46
+ * 2. It could never be saved. `MonitorCriteriaInstance.getValidationError`
47
+ * rejects a criteria with no name and a criteria with no description,
48
+ * and the blank instance had neither.
49
+ * 3. It could not be drawn honestly. The criteria form narrows these types
50
+ * to the one check their evaluator reads, so the seeded `Is Online` was
51
+ * not even on the "check on" dropdown it was supposed to be showing.
52
+ *
53
+ * They now seed real defaults on that one check. This file pins what those
54
+ * defaults are, that they survive the round trip a monitor's criteria takes
55
+ * through JSON, that validation accepts them, and - the part that actually
56
+ * answers (1) - that feeding them to the real evaluators produces a match.
57
+ *
58
+ * The renderability half of the story (every seeded check is on the monitor
59
+ * type's dropdown, every seeded condition is on its check's dropdown) is
60
+ * swept for every monitor type in
61
+ * Common/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts.
62
+ */
63
+
64
+ const ONLINE_STATUS_ID: ObjectID = new ObjectID("a1a1a1a1a1a1a1a1a1a1a1a1");
65
+ const OFFLINE_STATUS_ID: ObjectID = new ObjectID("b2b2b2b2b2b2b2b2b2b2b2b2");
66
+ const INCIDENT_SEVERITY_ID: ObjectID = new ObjectID("c3c3c3c3c3c3c3c3c3c3c3c3");
67
+ const ALERT_SEVERITY_ID: ObjectID = new ObjectID("d4d4d4d4d4d4d4d4d4d4d4d4");
68
+
69
+ const MONITOR_NAME: string = "Acme Prod";
70
+
71
+ /*
72
+ * The exact list MonitorCriteriaEvaluator hands to MetricMonitorCriteria.
73
+ * Written out rather than derived from `isMetricBackedMonitorType` so this
74
+ * file disagrees loudly if that helper is edited without the evaluator.
75
+ */
76
+ const METRIC_BACKED_MONITOR_TYPES: Array<MonitorType> = [
77
+ MonitorType.Metrics,
78
+ MonitorType.Kubernetes,
79
+ MonitorType.Docker,
80
+ MonitorType.Host,
81
+ MonitorType.Podman,
82
+ MonitorType.DockerSwarm,
83
+ MonitorType.Proxmox,
84
+ MonitorType.Ceph,
85
+ MonitorType.IoTDevice,
86
+ ];
87
+
88
+ /*
89
+ * The eight that had no defaults before this change. Metrics and Kubernetes
90
+ * already had a pair each and are excluded here so the "this used to be
91
+ * broken" assertions stay honest about which types they are describing.
92
+ */
93
+ const NEWLY_SEEDED_MONITOR_TYPES: Array<MonitorType> = [
94
+ MonitorType.Docker,
95
+ MonitorType.Host,
96
+ MonitorType.Podman,
97
+ MonitorType.DockerSwarm,
98
+ MonitorType.Proxmox,
99
+ MonitorType.Ceph,
100
+ MonitorType.IoTDevice,
101
+ MonitorType.Profiles,
102
+ ];
103
+
104
+ const ALL_MONITOR_TYPES: Array<MonitorType> = Object.values(
105
+ MonitorType,
106
+ ) as Array<MonitorType>;
107
+
108
+ function offlineFor(
109
+ monitorType: MonitorType,
110
+ metricAliases?: Array<string>,
111
+ ): MonitorCriteriaInstance {
112
+ return MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({
113
+ monitorType: monitorType,
114
+ monitorStatusId: OFFLINE_STATUS_ID,
115
+ incidentSeverityId: INCIDENT_SEVERITY_ID,
116
+ alertSeverityId: ALERT_SEVERITY_ID,
117
+ monitorName: MONITOR_NAME,
118
+ ...(metricAliases ? { metricOptions: { metricAliases } } : {}),
119
+ });
120
+ }
121
+
122
+ function onlineFor(
123
+ monitorType: MonitorType,
124
+ metricAliases?: Array<string>,
125
+ ): MonitorCriteriaInstance | null {
126
+ return MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
127
+ monitorType: monitorType,
128
+ monitorStatusId: ONLINE_STATUS_ID,
129
+ monitorName: MONITOR_NAME,
130
+ ...(metricAliases ? { metricOptions: { metricAliases } } : {}),
131
+ });
132
+ }
133
+
134
+ function onlyFilterOf(instance: MonitorCriteriaInstance): CriteriaFilter {
135
+ expect(instance.data?.filters).toHaveLength(1);
136
+ return instance.data!.filters[0]!;
137
+ }
138
+
139
+ /*
140
+ * A MonitorStep + MetricMonitorResponse pair carrying a single metric alias
141
+ * with the supplied samples, so a seeded criteria filter can be run through
142
+ * the real MetricMonitorCriteria rather than merely inspected.
143
+ */
144
+ function buildMetricInputs(input: {
145
+ metricAlias: string;
146
+ sampleValues: Array<number>;
147
+ }): {
148
+ monitorStep: MonitorStep;
149
+ dataToProcess: MetricMonitorResponse;
150
+ } {
151
+ const aliasData: MetricAliasData = {
152
+ metricVariable: input.metricAlias,
153
+ title: "CPU Utilization",
154
+ description: undefined,
155
+ legend: undefined,
156
+ legendUnit: undefined,
157
+ };
158
+
159
+ const queryConfig: MetricQueryConfigData = {
160
+ metricAliasData: aliasData,
161
+ metricQueryData: {
162
+ filterData: {
163
+ metricName: "container.cpu.utilization",
164
+ },
165
+ } as unknown as MetricQueryData,
166
+ };
167
+
168
+ const metricViewConfig: MetricsViewConfig = {
169
+ queryConfigs: [queryConfig],
170
+ formulaConfigs: [],
171
+ };
172
+
173
+ const monitorStep: MonitorStep = new MonitorStep();
174
+ monitorStep.data = {
175
+ id: ObjectID.generate().toString(),
176
+ monitorCriteria: { data: undefined } as never,
177
+ } as unknown as MonitorStep["data"];
178
+ monitorStep.data!.metricMonitor = {
179
+ metricViewConfig,
180
+ rollingTime: RollingTime.Past1Minute,
181
+ };
182
+
183
+ const aggregated: AggregatedResult = {
184
+ data: input.sampleValues.map((value: number) => {
185
+ return {
186
+ timestamp: new Date(),
187
+ value: value,
188
+ } as AggregateModel;
189
+ }),
190
+ };
191
+
192
+ return {
193
+ monitorStep,
194
+ dataToProcess: {
195
+ projectId: ObjectID.generate(),
196
+ metricResult: [aggregated],
197
+ metricViewConfig,
198
+ monitorId: ObjectID.generate(),
199
+ },
200
+ };
201
+ }
202
+
203
+ function evaluateMetric(input: {
204
+ criteriaFilter: CriteriaFilter;
205
+ sampleValues: Array<number>;
206
+ }): Promise<string | null> {
207
+ const inputs: ReturnType<typeof buildMetricInputs> = buildMetricInputs({
208
+ metricAlias:
209
+ input.criteriaFilter.metricMonitorOptions?.metricAlias || "cpu",
210
+ sampleValues: input.sampleValues,
211
+ });
212
+
213
+ return MetricMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
214
+ dataToProcess: inputs.dataToProcess,
215
+ criteriaFilter: input.criteriaFilter,
216
+ monitorStep: inputs.monitorStep,
217
+ });
218
+ }
219
+
220
+ function evaluateProfile(input: {
221
+ criteriaFilter: CriteriaFilter;
222
+ profileCount: number;
223
+ }): Promise<string | null> {
224
+ const response: Partial<ProfileMonitorResponse> = {
225
+ projectId: ObjectID.generate(),
226
+ monitorId: ObjectID.generate(),
227
+ profileCount: input.profileCount,
228
+ profileQuery: {},
229
+ };
230
+
231
+ return ProfileMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
232
+ dataToProcess: response as DataToProcess,
233
+ criteriaFilter: input.criteriaFilter,
234
+ });
235
+ }
236
+
237
+ describe("Default criteria for metric-backed and profile monitors", () => {
238
+ describe("isMetricBackedMonitorType", () => {
239
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
240
+ "%s is routed to MetricMonitorCriteria, so it is metric-backed",
241
+ (monitorType: MonitorType) => {
242
+ expect(
243
+ MonitorCriteriaInstance.isMetricBackedMonitorType(monitorType),
244
+ ).toBe(true);
245
+ },
246
+ );
247
+
248
+ test("no other monitor type claims to be metric-backed", () => {
249
+ const claimed: Array<MonitorType> = ALL_MONITOR_TYPES.filter(
250
+ (monitorType: MonitorType) => {
251
+ return MonitorCriteriaInstance.isMetricBackedMonitorType(monitorType);
252
+ },
253
+ );
254
+
255
+ expect(claimed.sort()).toEqual([...METRIC_BACKED_MONITOR_TYPES].sort());
256
+ });
257
+
258
+ /*
259
+ * Profiles is the odd one out of the nine types this change covers: it
260
+ * is telemetry, but ProfileMonitorCriteria - not MetricMonitorCriteria -
261
+ * decides on it, so it gets a count filter rather than a metric one.
262
+ */
263
+ test("Profiles is not metric-backed - it has its own evaluator", () => {
264
+ expect(
265
+ MonitorCriteriaInstance.isMetricBackedMonitorType(MonitorType.Profiles),
266
+ ).toBe(false);
267
+ });
268
+ });
269
+
270
+ describe("the gap these defaults close", () => {
271
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
272
+ "%s no longer falls through to the blank starting criteria",
273
+ (monitorType: MonitorType) => {
274
+ const blank: MonitorCriteriaInstance = new MonitorCriteriaInstance();
275
+ const offline: MonitorCriteriaInstance = offlineFor(monitorType);
276
+
277
+ expect(blank.data!.name).toBe("");
278
+ expect(offline.data!.name).not.toBe("");
279
+ expect(offline.data!.filters).not.toEqual(blank.data!.filters);
280
+ },
281
+ );
282
+
283
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
284
+ "%s no longer seeds the placeholder Is Online filter no evaluator reads",
285
+ (monitorType: MonitorType) => {
286
+ for (const instance of [
287
+ offlineFor(monitorType),
288
+ onlineFor(monitorType),
289
+ ]) {
290
+ expect(instance).not.toBeNull();
291
+
292
+ for (const filter of instance!.data!.filters) {
293
+ expect(filter.checkOn).not.toBe(CheckOn.IsOnline);
294
+ }
295
+ }
296
+ },
297
+ );
298
+
299
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
300
+ "%s now gets an online criteria instead of null",
301
+ (monitorType: MonitorType) => {
302
+ expect(onlineFor(monitorType)).not.toBeNull();
303
+ },
304
+ );
305
+
306
+ /*
307
+ * The blank instance failed getValidationError on both the name and the
308
+ * description, which is what made it unsaveable. Sweeping every monitor
309
+ * type - not just the newly seeded ones - keeps a future type from
310
+ * quietly reintroducing the same fall-through.
311
+ */
312
+ test.each(
313
+ ALL_MONITOR_TYPES.filter((monitorType: MonitorType) => {
314
+ return MonitorTypeHelper.doesMonitorTypeHaveCriteria(monitorType);
315
+ }),
316
+ )(
317
+ "%s: every seeded criteria passes getValidationError",
318
+ (monitorType: MonitorType) => {
319
+ const instances: Array<MonitorCriteriaInstance> = [
320
+ offlineFor(monitorType, ["cpu"]),
321
+ onlineFor(monitorType, ["cpu"]),
322
+ ].filter(Boolean) as Array<MonitorCriteriaInstance>;
323
+
324
+ for (const instance of instances) {
325
+ expect(
326
+ MonitorCriteriaInstance.getValidationError(instance, monitorType),
327
+ ).toBeNull();
328
+ }
329
+ },
330
+ );
331
+
332
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
333
+ "%s: MonitorCriteria.getDefaultMonitorCriteria yields both criteria",
334
+ (monitorType: MonitorType) => {
335
+ const criteria: MonitorCriteria =
336
+ MonitorCriteria.getDefaultMonitorCriteria({
337
+ monitorType: monitorType,
338
+ monitorName: MONITOR_NAME,
339
+ onlineMonitorStatusId: ONLINE_STATUS_ID,
340
+ offlineMonitorStatusId: OFFLINE_STATUS_ID,
341
+ defaultIncidentSeverityId: INCIDENT_SEVERITY_ID,
342
+ defaultAlertSeverityId: ALERT_SEVERITY_ID,
343
+ });
344
+
345
+ expect(criteria.data?.monitorCriteriaInstanceArray).toHaveLength(2);
346
+ expect(
347
+ MonitorCriteria.getValidationError(criteria, monitorType),
348
+ ).toBeNull();
349
+ },
350
+ );
351
+ });
352
+
353
+ describe("metric-backed monitor types", () => {
354
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
355
+ "%s offline: metric value equal to zero",
356
+ (monitorType: MonitorType) => {
357
+ const filter: CriteriaFilter = onlyFilterOf(offlineFor(monitorType));
358
+
359
+ expect(filter.checkOn).toBe(CheckOn.MetricValue);
360
+ expect(filter.filterType).toBe(FilterType.EqualTo);
361
+ expect(filter.value).toBe(0);
362
+ expect(filter.metricMonitorOptions?.metricAggregationType).toBe(
363
+ EvaluateOverTimeType.AnyValue,
364
+ );
365
+ },
366
+ );
367
+
368
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
369
+ "%s online: metric value greater than zero",
370
+ (monitorType: MonitorType) => {
371
+ const filter: CriteriaFilter = onlyFilterOf(onlineFor(monitorType)!);
372
+
373
+ expect(filter.checkOn).toBe(CheckOn.MetricValue);
374
+ expect(filter.filterType).toBe(FilterType.GreaterThan);
375
+ expect(filter.value).toBe(0);
376
+ expect(filter.metricMonitorOptions?.metricAggregationType).toBe(
377
+ EvaluateOverTimeType.AnyValue,
378
+ );
379
+ },
380
+ );
381
+
382
+ /*
383
+ * Absence is deliberately not what the offline rule fires on. With no
384
+ * samples in the window MetricMonitorCriteria consults
385
+ * metricMonitorOptions.onNoDataPolicy, which defaults to Ignore, so a
386
+ * brand new monitor whose step has no metric query configured yet does
387
+ * not immediately open an incident. Pinned because setting the policy
388
+ * here would be a quiet, incident-generating behaviour change.
389
+ */
390
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
391
+ "%s does not opt into a no-data policy on either criteria",
392
+ (monitorType: MonitorType) => {
393
+ expect(
394
+ onlyFilterOf(offlineFor(monitorType)).metricMonitorOptions
395
+ ?.onNoDataPolicy,
396
+ ).toBeUndefined();
397
+ expect(
398
+ onlyFilterOf(onlineFor(monitorType)!).metricMonitorOptions
399
+ ?.onNoDataPolicy,
400
+ ).toBeUndefined();
401
+ },
402
+ );
403
+
404
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
405
+ "%s threads the first metric alias into both criteria",
406
+ (monitorType: MonitorType) => {
407
+ expect(
408
+ onlyFilterOf(offlineFor(monitorType, ["cpu", "memory"]))
409
+ .metricMonitorOptions?.metricAlias,
410
+ ).toBe("cpu");
411
+ expect(
412
+ onlyFilterOf(onlineFor(monitorType, ["cpu", "memory"])!)
413
+ .metricMonitorOptions?.metricAlias,
414
+ ).toBe("cpu");
415
+ },
416
+ );
417
+
418
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
419
+ "%s leaves the alias undefined when no metric options are supplied",
420
+ (monitorType: MonitorType) => {
421
+ expect(
422
+ onlyFilterOf(offlineFor(monitorType)).metricMonitorOptions
423
+ ?.metricAlias,
424
+ ).toBeUndefined();
425
+ expect(
426
+ onlyFilterOf(onlineFor(monitorType)!).metricMonitorOptions
427
+ ?.metricAlias,
428
+ ).toBeUndefined();
429
+ },
430
+ );
431
+
432
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
433
+ "%s leaves the alias undefined when the alias list is empty",
434
+ (monitorType: MonitorType) => {
435
+ expect(
436
+ onlyFilterOf(offlineFor(monitorType, [])).metricMonitorOptions
437
+ ?.metricAlias,
438
+ ).toBeUndefined();
439
+ expect(
440
+ onlyFilterOf(onlineFor(monitorType, [])!).metricMonitorOptions
441
+ ?.metricAlias,
442
+ ).toBeUndefined();
443
+ },
444
+ );
445
+
446
+ /*
447
+ * Metrics and Kubernetes had this exact pair before the change. The rest
448
+ * were given the same one rather than a per-type invention, so assert
449
+ * they are actually identical apart from the generated ids.
450
+ */
451
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
452
+ "%s seeds the same filters Metrics does",
453
+ (monitorType: MonitorType) => {
454
+ expect(onlyFilterOf(offlineFor(monitorType, ["cpu"]))).toEqual(
455
+ onlyFilterOf(offlineFor(MonitorType.Metrics, ["cpu"])),
456
+ );
457
+ expect(onlyFilterOf(onlineFor(monitorType, ["cpu"])!)).toEqual(
458
+ onlyFilterOf(onlineFor(MonitorType.Metrics, ["cpu"])!),
459
+ );
460
+ },
461
+ );
462
+ });
463
+
464
+ describe("Profiles monitors", () => {
465
+ /*
466
+ * Profiles takes its count shape from Exceptions but its polarity from
467
+ * Logs and Traces, and the difference matters. An exception is a fault,
468
+ * so Exceptions treats "count is zero" as healthy. A profile is
469
+ * telemetry: profiles arriving is the sign of life, and their absence is
470
+ * what says the profiled process has stopped reporting.
471
+ */
472
+ test("offline: no profiles arrived in the window", () => {
473
+ const filter: CriteriaFilter = onlyFilterOf(
474
+ offlineFor(MonitorType.Profiles),
475
+ );
476
+
477
+ expect(filter.checkOn).toBe(CheckOn.ProfileCount);
478
+ expect(filter.filterType).toBe(FilterType.EqualTo);
479
+ expect(filter.value).toBe(0);
480
+ });
481
+
482
+ test("online: at least one profile arrived in the window", () => {
483
+ const filter: CriteriaFilter = onlyFilterOf(
484
+ onlineFor(MonitorType.Profiles)!,
485
+ );
486
+
487
+ expect(filter.checkOn).toBe(CheckOn.ProfileCount);
488
+ expect(filter.filterType).toBe(FilterType.GreaterThan);
489
+ expect(filter.value).toBe(0);
490
+ });
491
+
492
+ test("takes the Logs polarity, not the inverted Exceptions one", () => {
493
+ expect(onlyFilterOf(offlineFor(MonitorType.Profiles)).filterType).toBe(
494
+ onlyFilterOf(offlineFor(MonitorType.Logs)).filterType,
495
+ );
496
+ expect(onlyFilterOf(onlineFor(MonitorType.Profiles)!).filterType).toBe(
497
+ onlyFilterOf(onlineFor(MonitorType.Logs)!).filterType,
498
+ );
499
+
500
+ // Exceptions is the inverse of both, on purpose.
501
+ expect(onlyFilterOf(offlineFor(MonitorType.Exceptions)).filterType).toBe(
502
+ FilterType.GreaterThan,
503
+ );
504
+ expect(onlyFilterOf(onlineFor(MonitorType.Exceptions)!).filterType).toBe(
505
+ FilterType.EqualTo,
506
+ );
507
+ });
508
+
509
+ test("carries no metric options - it is not evaluated as a metric", () => {
510
+ expect(
511
+ onlyFilterOf(offlineFor(MonitorType.Profiles, ["cpu"]))
512
+ .metricMonitorOptions,
513
+ ).toBeUndefined();
514
+ expect(
515
+ onlyFilterOf(onlineFor(MonitorType.Profiles, ["cpu"])!)
516
+ .metricMonitorOptions,
517
+ ).toBeUndefined();
518
+ });
519
+ });
520
+
521
+ describe("criteria wiring shared by every newly seeded type", () => {
522
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
523
+ "%s offline: carries the offline status, an incident and an alert",
524
+ (monitorType: MonitorType) => {
525
+ const instance: MonitorCriteriaInstance = offlineFor(monitorType);
526
+
527
+ expect(instance.data!.monitorStatusId).toBe(OFFLINE_STATUS_ID);
528
+ expect(instance.data!.filterCondition).toBe(FilterCondition.Any);
529
+ expect(instance.data!.changeMonitorStatus).toBe(true);
530
+ expect(instance.data!.createIncidents).toBe(true);
531
+ expect(instance.data!.createAlerts).toBe(false);
532
+
533
+ expect(instance.data!.incidents).toHaveLength(1);
534
+ const incident: CriteriaIncident = instance.data!.incidents[0]!;
535
+ expect(incident.title).toBe(`${MONITOR_NAME} is offline`);
536
+ expect(incident.description).toBe(
537
+ `${MONITOR_NAME} is currently offline.`,
538
+ );
539
+ expect(incident.incidentSeverityId).toBe(INCIDENT_SEVERITY_ID);
540
+ expect(incident.autoResolveIncident).toBe(true);
541
+ expect(incident.onCallPolicyIds).toEqual([]);
542
+
543
+ expect(instance.data!.alerts).toHaveLength(1);
544
+ const alert: CriteriaAlert = instance.data!.alerts[0]!;
545
+ expect(alert.title).toBe(`${MONITOR_NAME} is offline`);
546
+ expect(alert.description).toBe(`${MONITOR_NAME} is currently offline.`);
547
+ expect(alert.alertSeverityId).toBe(ALERT_SEVERITY_ID);
548
+ expect(alert.autoResolveAlert).toBe(true);
549
+ expect(alert.onCallPolicyIds).toEqual([]);
550
+ },
551
+ );
552
+
553
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
554
+ "%s online: carries the online status and opens nothing",
555
+ (monitorType: MonitorType) => {
556
+ const instance: MonitorCriteriaInstance = onlineFor(monitorType)!;
557
+
558
+ expect(instance.data!.monitorStatusId).toBe(ONLINE_STATUS_ID);
559
+ expect(instance.data!.filterCondition).toBe(FilterCondition.Any);
560
+ expect(instance.data!.changeMonitorStatus).toBe(true);
561
+ expect(instance.data!.createIncidents).toBe(false);
562
+ expect(instance.data!.createAlerts).toBe(false);
563
+ expect(instance.data!.incidents).toEqual([]);
564
+ expect(instance.data!.alerts).toEqual([]);
565
+ },
566
+ );
567
+
568
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
569
+ "%s names both criteria after the monitor",
570
+ (monitorType: MonitorType) => {
571
+ const offline: MonitorCriteriaInstance = offlineFor(monitorType);
572
+ const online: MonitorCriteriaInstance = onlineFor(monitorType)!;
573
+
574
+ expect(offline.data!.name).toBe(`Check if ${MONITOR_NAME} is offline`);
575
+ expect(offline.data!.description).toBe(
576
+ `This criteria checks if the ${MONITOR_NAME} is offline`,
577
+ );
578
+ expect(online.data!.name).toBe(`Check if ${MONITOR_NAME} is online`);
579
+ expect(online.data!.description).toBe(
580
+ `This criteria checks if the ${MONITOR_NAME} is online`,
581
+ );
582
+ },
583
+ );
584
+
585
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
586
+ "%s: each call mints fresh ids rather than sharing one",
587
+ (monitorType: MonitorType) => {
588
+ const first: MonitorCriteriaInstance = offlineFor(monitorType);
589
+ const second: MonitorCriteriaInstance = offlineFor(monitorType);
590
+ const online: MonitorCriteriaInstance = onlineFor(monitorType)!;
591
+
592
+ expect(first.data!.id).not.toBe(second.data!.id);
593
+ expect(first.data!.id).not.toBe(online.data!.id);
594
+ expect(first.data!.incidents[0]!.id).not.toBe(
595
+ second.data!.incidents[0]!.id,
596
+ );
597
+ expect(first.data!.alerts[0]!.id).not.toBe(second.data!.alerts[0]!.id);
598
+ },
599
+ );
600
+
601
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
602
+ "%s: mutating one seeded criteria does not touch the next",
603
+ (monitorType: MonitorType) => {
604
+ const first: MonitorCriteriaInstance = offlineFor(monitorType);
605
+ first.data!.filters[0]!.value = 999;
606
+
607
+ expect(offlineFor(monitorType).data!.filters[0]!.value).toBe(0);
608
+ },
609
+ );
610
+
611
+ /*
612
+ * These criteria are persisted on the monitor and read back through
613
+ * MonitorCriteriaInstance.fromJSON, so a filter that survives inspection
614
+ * but not the round trip would still reach the user broken.
615
+ */
616
+ test.each(NEWLY_SEEDED_MONITOR_TYPES)(
617
+ "%s: both criteria survive a JSON round trip intact",
618
+ (monitorType: MonitorType) => {
619
+ for (const instance of [
620
+ offlineFor(monitorType, ["cpu"]),
621
+ onlineFor(monitorType, ["cpu"])!,
622
+ ]) {
623
+ const restored: MonitorCriteriaInstance =
624
+ MonitorCriteriaInstance.fromJSON(instance.toJSON());
625
+
626
+ expect(restored.data!.name).toBe(instance.data!.name);
627
+ expect(restored.data!.description).toBe(instance.data!.description);
628
+ expect(restored.data!.filters).toEqual(instance.data!.filters);
629
+ expect(
630
+ MonitorCriteriaInstance.getValidationError(restored, monitorType),
631
+ ).toBeNull();
632
+ }
633
+ },
634
+ );
635
+ });
636
+
637
+ /*
638
+ * The point of the whole change. "Is Online" against MetricMonitorCriteria
639
+ * or ProfileMonitorCriteria returns null forever; these assertions run the
640
+ * seeded filters through those same evaluators and show them deciding.
641
+ */
642
+ describe("the seeded filters actually fire in the evaluator that reads them", () => {
643
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
644
+ "%s online criteria matches a metric reporting above zero",
645
+ async (monitorType: MonitorType) => {
646
+ const result: string | null = await evaluateMetric({
647
+ criteriaFilter: onlyFilterOf(onlineFor(monitorType, ["cpu"])!),
648
+ sampleValues: [42],
649
+ });
650
+
651
+ expect(result).toBeTruthy();
652
+ expect(result).toContain("greater than");
653
+ },
654
+ );
655
+
656
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
657
+ "%s online criteria does not match a metric sitting at zero",
658
+ async (monitorType: MonitorType) => {
659
+ expect(
660
+ await evaluateMetric({
661
+ criteriaFilter: onlyFilterOf(onlineFor(monitorType, ["cpu"])!),
662
+ sampleValues: [0],
663
+ }),
664
+ ).toBeNull();
665
+ },
666
+ );
667
+
668
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
669
+ "%s offline criteria matches a metric sitting at zero",
670
+ async (monitorType: MonitorType) => {
671
+ const result: string | null = await evaluateMetric({
672
+ criteriaFilter: onlyFilterOf(offlineFor(monitorType, ["cpu"])),
673
+ sampleValues: [0],
674
+ });
675
+
676
+ expect(result).toBeTruthy();
677
+ expect(result).toContain("equal to 0");
678
+ },
679
+ );
680
+
681
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
682
+ "%s offline criteria does not match a metric reporting above zero",
683
+ async (monitorType: MonitorType) => {
684
+ expect(
685
+ await evaluateMetric({
686
+ criteriaFilter: onlyFilterOf(offlineFor(monitorType, ["cpu"])),
687
+ sampleValues: [42],
688
+ }),
689
+ ).toBeNull();
690
+ },
691
+ );
692
+
693
+ /*
694
+ * The seeded aggregation is AnyValue, so one zero sample in an otherwise
695
+ * healthy window is enough to trip the offline rule. Spelled out because
696
+ * every other EvaluateOverTimeType collapses to "all samples must
697
+ * breach" in this evaluator, and the two read very differently.
698
+ */
699
+ test("AnyValue means a single zero sample trips the offline rule", async () => {
700
+ const result: string | null = await evaluateMetric({
701
+ criteriaFilter: onlyFilterOf(offlineFor(MonitorType.Docker, ["cpu"])),
702
+ sampleValues: [12, 0, 30],
703
+ });
704
+
705
+ expect(result).toBeTruthy();
706
+ });
707
+
708
+ test("the placeholder Is Online filter these types used to seed never fires", async () => {
709
+ const blank: MonitorCriteriaInstance = new MonitorCriteriaInstance();
710
+
711
+ expect(
712
+ await evaluateMetric({
713
+ criteriaFilter: onlyFilterOf(blank),
714
+ sampleValues: [42],
715
+ }),
716
+ ).toBeNull();
717
+ expect(
718
+ await evaluateMetric({
719
+ criteriaFilter: onlyFilterOf(blank),
720
+ sampleValues: [0],
721
+ }),
722
+ ).toBeNull();
723
+ expect(
724
+ await evaluateProfile({
725
+ criteriaFilter: onlyFilterOf(blank),
726
+ profileCount: 7,
727
+ }),
728
+ ).toBeNull();
729
+ expect(
730
+ await evaluateProfile({
731
+ criteriaFilter: onlyFilterOf(blank),
732
+ profileCount: 0,
733
+ }),
734
+ ).toBeNull();
735
+ });
736
+
737
+ test("Profiles online criteria matches when profiles arrived", async () => {
738
+ const result: string | null = await evaluateProfile({
739
+ criteriaFilter: onlyFilterOf(onlineFor(MonitorType.Profiles)!),
740
+ profileCount: 7,
741
+ });
742
+
743
+ expect(result).toBeTruthy();
744
+ expect(result).toContain("Profile Count");
745
+ expect(result).toContain("greater than");
746
+ });
747
+
748
+ test("Profiles online criteria does not match when none arrived", async () => {
749
+ expect(
750
+ await evaluateProfile({
751
+ criteriaFilter: onlyFilterOf(onlineFor(MonitorType.Profiles)!),
752
+ profileCount: 0,
753
+ }),
754
+ ).toBeNull();
755
+ });
756
+
757
+ test("Profiles offline criteria matches when none arrived", async () => {
758
+ const result: string | null = await evaluateProfile({
759
+ criteriaFilter: onlyFilterOf(offlineFor(MonitorType.Profiles)),
760
+ profileCount: 0,
761
+ });
762
+
763
+ expect(result).toBeTruthy();
764
+ expect(result).toContain("Profile Count");
765
+ expect(result).toContain("equal to 0");
766
+ });
767
+
768
+ test("Profiles offline criteria does not match when profiles arrived", async () => {
769
+ expect(
770
+ await evaluateProfile({
771
+ criteriaFilter: onlyFilterOf(offlineFor(MonitorType.Profiles)),
772
+ profileCount: 7,
773
+ }),
774
+ ).toBeNull();
775
+ });
776
+
777
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
778
+ "%s: exactly one criteria matches a window that is all zero or all non-zero",
779
+ async (monitorType: MonitorType) => {
780
+ for (const sampleValues of [[0], [42], [0, 0], [42, 42]]) {
781
+ const online: string | null = await evaluateMetric({
782
+ criteriaFilter: onlyFilterOf(onlineFor(monitorType, ["cpu"])!),
783
+ sampleValues,
784
+ });
785
+ const offline: string | null = await evaluateMetric({
786
+ criteriaFilter: onlyFilterOf(offlineFor(monitorType, ["cpu"])),
787
+ sampleValues,
788
+ });
789
+
790
+ expect(Boolean(online)).toBe(!offline);
791
+ }
792
+ },
793
+ );
794
+
795
+ /*
796
+ * On a MIXED window the two are not mutually exclusive, and pretending
797
+ * otherwise would be the easy mistake to make here. The seeded
798
+ * aggregation is AnyValue, which CompareCriteria implements as
799
+ * `.some(...)`, so "some sample is above zero" and "some sample is zero"
800
+ * are both true of [12, 0, 30] at once. Nothing about the pair can make
801
+ * that go away - it is the rule Metrics and Kubernetes have always
802
+ * shipped, now shared by the seven infrastructure types and IoT Device.
803
+ *
804
+ * What decides the monitor's status is therefore ordering, not
805
+ * exclusivity: MonitorCriteriaEvaluator.processMonitorStep walks
806
+ * monitorCriteriaInstanceArray and breaks on the first criteria that
807
+ * matches. So the two assertions below are a pair - the overlap is real,
808
+ * and offline is stored first, which is what makes a container that dips
809
+ * to zero for one sample read as offline rather than online.
810
+ */
811
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
812
+ "%s: a mixed window matches both criteria, so ordering decides",
813
+ async (monitorType: MonitorType) => {
814
+ const sampleValues: Array<number> = [12, 0, 30];
815
+
816
+ const online: string | null = await evaluateMetric({
817
+ criteriaFilter: onlyFilterOf(onlineFor(monitorType, ["cpu"])!),
818
+ sampleValues,
819
+ });
820
+ const offline: string | null = await evaluateMetric({
821
+ criteriaFilter: onlyFilterOf(offlineFor(monitorType, ["cpu"])),
822
+ sampleValues,
823
+ });
824
+
825
+ expect(online).toBeTruthy();
826
+ expect(offline).toBeTruthy();
827
+ },
828
+ );
829
+
830
+ test.each(METRIC_BACKED_MONITOR_TYPES)(
831
+ "%s: the offline criteria is stored first, so it wins the overlap",
832
+ (monitorType: MonitorType) => {
833
+ const criteria: MonitorCriteria =
834
+ MonitorCriteria.getDefaultMonitorCriteria({
835
+ monitorType: monitorType,
836
+ monitorName: MONITOR_NAME,
837
+ onlineMonitorStatusId: ONLINE_STATUS_ID,
838
+ offlineMonitorStatusId: OFFLINE_STATUS_ID,
839
+ defaultIncidentSeverityId: INCIDENT_SEVERITY_ID,
840
+ defaultAlertSeverityId: ALERT_SEVERITY_ID,
841
+ });
842
+
843
+ const instances: Array<MonitorCriteriaInstance> =
844
+ criteria.data!.monitorCriteriaInstanceArray;
845
+
846
+ expect(instances).toHaveLength(2);
847
+ expect(instances[0]!.data!.monitorStatusId).toBe(OFFLINE_STATUS_ID);
848
+ expect(instances[1]!.data!.monitorStatusId).toBe(ONLINE_STATUS_ID);
849
+ },
850
+ );
851
+
852
+ /*
853
+ * Profiles has no such overlap: profileCount is one scalar, not a window
854
+ * of samples, so it cannot be both zero and above zero. This is the
855
+ * assertion the metric pair cannot make.
856
+ */
857
+ test.each([0, 1, 7, 1000])(
858
+ "Profiles: online and offline never both match a count of %s",
859
+ async (profileCount: number) => {
860
+ const online: string | null = await evaluateProfile({
861
+ criteriaFilter: onlyFilterOf(onlineFor(MonitorType.Profiles)!),
862
+ profileCount,
863
+ });
864
+ const offline: string | null = await evaluateProfile({
865
+ criteriaFilter: onlyFilterOf(offlineFor(MonitorType.Profiles)),
866
+ profileCount,
867
+ });
868
+
869
+ expect(Boolean(online) && Boolean(offline)).toBe(false);
870
+ expect(Boolean(online) || Boolean(offline)).toBe(true);
871
+ },
872
+ );
873
+ });
874
+
875
+ describe("monitor types this change deliberately left alone", () => {
876
+ /*
877
+ * Manual monitors are the one type with no criteria at all: nothing polls
878
+ * them, MonitorCriteriaEvaluator has no branch for them, and the Criteria
879
+ * page draws an empty state in place of the form. They are the only
880
+ * remaining fall-through, and that is correct.
881
+ */
882
+ test("Manual monitors still seed nothing but the blank starting criteria", () => {
883
+ const offline: MonitorCriteriaInstance = offlineFor(MonitorType.Manual);
884
+
885
+ expect(
886
+ MonitorTypeHelper.doesMonitorTypeHaveCriteria(MonitorType.Manual),
887
+ ).toBe(false);
888
+ expect(offline.data!.name).toBe("");
889
+ expect(offline.data!.filters).toEqual([
890
+ {
891
+ checkOn: CheckOn.IsOnline,
892
+ filterType: FilterType.True,
893
+ value: undefined,
894
+ },
895
+ ]);
896
+ expect(onlineFor(MonitorType.Manual)).toBeNull();
897
+ });
898
+
899
+ test("Manual is now the only monitor type without a named offline criteria", () => {
900
+ const unnamed: Array<MonitorType> = ALL_MONITOR_TYPES.filter(
901
+ (monitorType: MonitorType) => {
902
+ return !offlineFor(monitorType, ["cpu"]).data!.name;
903
+ },
904
+ );
905
+
906
+ expect(unnamed).toEqual([MonitorType.Manual]);
907
+ });
908
+
909
+ test.each([MonitorType.Metrics, MonitorType.Kubernetes])(
910
+ "%s keeps the exact defaults it already shipped",
911
+ (monitorType: MonitorType) => {
912
+ expect(onlyFilterOf(offlineFor(monitorType, ["cpu"]))).toEqual({
913
+ checkOn: CheckOn.MetricValue,
914
+ filterType: FilterType.EqualTo,
915
+ metricMonitorOptions: {
916
+ metricAggregationType: EvaluateOverTimeType.AnyValue,
917
+ metricAlias: "cpu",
918
+ },
919
+ value: 0,
920
+ });
921
+ expect(onlyFilterOf(onlineFor(monitorType, ["cpu"])!)).toEqual({
922
+ checkOn: CheckOn.MetricValue,
923
+ filterType: FilterType.GreaterThan,
924
+ metricMonitorOptions: {
925
+ metricAggregationType: EvaluateOverTimeType.AnyValue,
926
+ metricAlias: "cpu",
927
+ },
928
+ value: 0,
929
+ });
930
+ },
931
+ );
932
+
933
+ /*
934
+ * Guarding the merge that folded the per-type Metrics and Kubernetes
935
+ * branches into one metric-backed branch: nothing outside that family
936
+ * should have picked up a metric filter along the way.
937
+ */
938
+ test("no non-metric-backed type seeds a metric value filter", () => {
939
+ for (const monitorType of ALL_MONITOR_TYPES) {
940
+ if (MonitorCriteriaInstance.isMetricBackedMonitorType(monitorType)) {
941
+ continue;
942
+ }
943
+
944
+ const instances: Array<MonitorCriteriaInstance> = [
945
+ offlineFor(monitorType, ["cpu"]),
946
+ onlineFor(monitorType, ["cpu"]),
947
+ ].filter(Boolean) as Array<MonitorCriteriaInstance>;
948
+
949
+ for (const instance of instances) {
950
+ for (const filter of instance.data!.filters) {
951
+ expect(filter.checkOn).not.toBe(CheckOn.MetricValue);
952
+ }
953
+ }
954
+ }
955
+ });
956
+ });
957
+ });