@oneuptime/common 12.0.34 → 13.0.0

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 (259) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +12 -12
  2. package/Models/DatabaseModels/InventoryItem.ts +44 -1
  3. package/Models/DatabaseModels/NetworkDevice.ts +126 -0
  4. package/Server/EnvironmentConfig.ts +47 -16
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Infrastructure/Redis.ts +8 -8
  8. package/Server/Infrastructure/Status.ts +10 -4
  9. package/Server/Services/DatabaseService.ts +65 -13
  10. package/Server/Services/InventoryItemService.ts +31 -1
  11. package/Server/Services/MonitorTemplateService.ts +140 -19
  12. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
  13. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
  14. package/Server/Services/NetworkDeviceService.ts +69 -0
  15. package/Server/Services/UserNotificationRuleService.ts +76 -25
  16. package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
  17. package/Server/Types/Database/QueryUtil.ts +56 -0
  18. package/Server/Types/Markdown.ts +93 -1
  19. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
  20. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
  21. package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
  25. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
  26. package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
  27. package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
  28. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
  29. package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
  30. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
  31. package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
  32. package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
  33. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
  34. package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
  35. package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
  36. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
  37. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
  38. package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
  39. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
  40. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
  41. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
  42. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
  43. package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
  44. package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
  45. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
  46. package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
  47. package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
  48. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
  49. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
  50. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
  51. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
  52. package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
  53. package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
  54. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
  55. package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
  56. package/Tests/Models/InventoryItemStatus.test.ts +147 -0
  57. package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
  58. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
  59. package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
  60. package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
  61. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
  62. package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
  63. package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
  64. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
  65. package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
  66. package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
  67. package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
  68. package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
  69. package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
  70. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
  71. package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
  72. package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
  73. package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
  74. package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
  75. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
  76. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
  77. package/Tests/Server/Types/Markdown.test.ts +194 -1
  78. package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
  79. package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
  80. package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
  81. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
  82. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
  83. package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
  84. package/Tests/Server/Utils/EventLoop.test.ts +359 -0
  85. package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
  86. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
  87. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
  88. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
  90. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
  91. package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
  92. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
  93. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
  94. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
  95. package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
  96. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
  97. package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
  98. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
  99. package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
  100. package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
  101. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
  102. package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
  103. package/Tests/Types/JSONFunctions.test.ts +40 -0
  104. package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
  105. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
  106. package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
  107. package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
  108. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
  109. package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
  110. package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
  111. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
  112. package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
  113. package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
  114. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  115. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
  116. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
  117. package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
  118. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
  119. package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
  120. package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
  121. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
  122. package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
  123. package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
  124. package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
  125. package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
  126. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  127. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  128. package/Types/Dashboard/DashboardVariable.ts +8 -0
  129. package/Types/JSON.ts +4 -0
  130. package/Types/JSONFunctions.ts +16 -0
  131. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  132. package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
  133. package/Types/SerializableObjectDictionary.ts +4 -0
  134. package/Types/Telemetry/InventoryLiveness.ts +56 -0
  135. package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
  136. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
  137. package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
  138. package/UI/esbuild-config.js +6 -0
  139. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
  140. package/Utils/Dashboard/LabelVariable.ts +152 -0
  141. package/Utils/Dashboard/VariableUrlState.ts +27 -5
  142. package/Utils/MetricUnitUtil.ts +34 -0
  143. package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
  144. package/Utils/Monitor/MetricValueFormatter.ts +342 -0
  145. package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
  146. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
  147. package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
  148. package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
  149. package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
  150. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
  152. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
  154. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  155. package/build/dist/Server/EnvironmentConfig.js +44 -12
  156. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  157. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
  158. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
  159. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  160. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  161. package/build/dist/Server/Infrastructure/Redis.js +8 -8
  162. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  163. package/build/dist/Server/Infrastructure/Status.js +10 -4
  164. package/build/dist/Server/Infrastructure/Status.js.map +1 -1
  165. package/build/dist/Server/Services/DatabaseService.js +58 -13
  166. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  167. package/build/dist/Server/Services/InventoryItemService.js +28 -1
  168. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  169. package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
  170. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  171. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
  172. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  173. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
  174. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  175. package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
  176. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  177. package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
  178. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  179. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
  180. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  181. package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
  182. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  183. package/build/dist/Server/Types/Markdown.js +75 -0
  184. package/build/dist/Server/Types/Markdown.js.map +1 -1
  185. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
  186. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  187. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
  188. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  189. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
  190. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
  192. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  193. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
  194. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  195. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
  196. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  197. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
  198. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  199. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
  200. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
  201. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
  202. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
  203. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
  204. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  205. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
  206. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
  207. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
  208. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  209. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
  210. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  211. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
  212. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  213. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
  214. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
  215. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  216. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  217. package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
  218. package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
  219. package/build/dist/Types/JSON.js +1 -0
  220. package/build/dist/Types/JSON.js.map +1 -1
  221. package/build/dist/Types/JSONFunctions.js +13 -0
  222. package/build/dist/Types/JSONFunctions.js.map +1 -1
  223. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
  224. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  225. package/build/dist/Types/SerializableObjectDictionary.js +4 -0
  226. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  227. package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
  228. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
  229. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
  230. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
  231. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
  232. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  233. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
  234. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
  235. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
  236. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  237. package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
  238. package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
  239. package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
  240. package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
  241. package/build/dist/Utils/MetricUnitUtil.js +26 -0
  242. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  243. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
  244. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
  245. package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
  246. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
  247. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
  248. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
  249. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
  250. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  251. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
  252. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  253. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
  254. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
  255. package/jest.config.json +2 -0
  256. package/package.json +1 -1
  257. package/test-setup.sh +9 -9
  258. package/tsconfig.json +51 -11
  259. package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
@@ -0,0 +1,570 @@
1
+ /*
2
+ * The evaluator's import chain pulls the native isolated-vm addon
3
+ * (MonitorCriteriaEvaluator → VMAPI → VMRunner). Nothing under test here
4
+ * touches the sandbox, and the prebuilt binary cannot always dlopen in the
5
+ * test environment — so stub the module out before anything imports it.
6
+ */
7
+ jest.mock("isolated-vm", () => {
8
+ return {};
9
+ });
10
+
11
+ import MonitorCriteriaEvaluator from "../../../../Server/Utils/Monitor/MonitorCriteriaEvaluator";
12
+ import CompareCriteria from "../../../../Server/Utils/Monitor/Criteria/CompareCriteria";
13
+ import Monitor from "../../../../Models/DatabaseModels/Monitor";
14
+ import MonitorStep from "../../../../Types/Monitor/MonitorStep";
15
+ import MonitorCriteriaInstance from "../../../../Types/Monitor/MonitorCriteriaInstance";
16
+ import MetricCriteriaContext from "../../../../Types/Monitor/MetricMonitor/MetricCriteriaContext";
17
+ import {
18
+ CheckOn,
19
+ CriteriaFilter,
20
+ FilterType,
21
+ } from "../../../../Types/Monitor/CriteriaFilter";
22
+ import FilterCondition from "../../../../Types/Filter/FilterCondition";
23
+ import ObjectID from "../../../../Types/ObjectID";
24
+ import { describe, expect, test } from "@jest/globals";
25
+
26
+ /*
27
+ * HUMAN-READABLE METRIC VALUES IN THE ROOT CAUSE.
28
+ *
29
+ * The metric root cause is what an alert or incident email shows under
30
+ * "Root Cause" — MonitorCriteriaEvaluator writes it, AlertService and
31
+ * IncidentService copy it onto the row, and the owner-notification jobs
32
+ * run it through Markdown.convertToHTML for the email body.
33
+ *
34
+ * Before this suite existed, nothing anywhere pinned the Breaching
35
+ * Samples table or the "- Unit:" line, and both rendered raw digits with
36
+ * the exporter's raw UCUM code glued on: a memory breach that the
37
+ * dashboard drew as "1.07 GB" arrived in the inbox as "1073741824 By",
38
+ * and a ratio metric's Value column read "0.06 1".
39
+ *
40
+ * These tests pin:
41
+ * - the Value column at human scale, in the unit each row landed on,
42
+ * - component columns each in their OWN unit, with no unit in the header
43
+ * (auto-scaling means neighbouring rows disagree with any one header),
44
+ * - the "- Unit:" line spelled out, and dropped when there is no unit,
45
+ * - the formula guard: a formula expression is not a metric name,
46
+ * - that the table and the "Filter Conditions Met" sentence in the same
47
+ * email render the same sample identically.
48
+ */
49
+
50
+ type EvaluatorPrivate = {
51
+ buildMetricRootCauseContext: (input: {
52
+ criteriaInstance: MonitorCriteriaInstance;
53
+ monitor: Monitor;
54
+ monitorStep?: MonitorStep | undefined;
55
+ }) => string | null;
56
+ };
57
+
58
+ const Evaluator: EvaluatorPrivate =
59
+ MonitorCriteriaEvaluator as unknown as EvaluatorPrivate;
60
+
61
+ const BREACH_TIME: Date = new Date("2026-08-14T10:30:00.000Z");
62
+ const BREACH_TIME_ISO: string = "2026-08-14T10:30:00.000Z";
63
+
64
+ function makeContext(
65
+ overrides: Partial<MetricCriteriaContext> = {},
66
+ ): MetricCriteriaContext {
67
+ return {
68
+ metricName: "k8s.pod.memory.usage",
69
+ alias: "a",
70
+ unit: null,
71
+ aggregationType: null,
72
+ isFormula: false,
73
+ filterAttributes: {},
74
+ groupBy: [],
75
+ breachingSamples: [
76
+ {
77
+ value: 1073741824,
78
+ timestamp: BREACH_TIME,
79
+ attributes: {},
80
+ },
81
+ ],
82
+ ...overrides,
83
+ };
84
+ }
85
+
86
+ function makeCriteriaInstance(
87
+ ctx: MetricCriteriaContext,
88
+ ): MonitorCriteriaInstance {
89
+ const filter: CriteriaFilter = {
90
+ checkOn: CheckOn.MetricValue,
91
+ metricCriteriaContext: ctx,
92
+ } as CriteriaFilter;
93
+
94
+ const instance: MonitorCriteriaInstance = new MonitorCriteriaInstance();
95
+ instance.data = {
96
+ monitorStatusId: undefined,
97
+ filterCondition: FilterCondition.All,
98
+ filters: [filter],
99
+ incidents: [],
100
+ alerts: [],
101
+ name: "Memory breach",
102
+ description: "Memory breach",
103
+ id: ObjectID.generate().toString(),
104
+ };
105
+ return instance;
106
+ }
107
+
108
+ function rootCause(ctx: MetricCriteriaContext): string {
109
+ const context: string | null = Evaluator.buildMetricRootCauseContext({
110
+ criteriaInstance: makeCriteriaInstance(ctx),
111
+ monitor: new Monitor(),
112
+ });
113
+
114
+ expect(context).not.toBeNull();
115
+ return context as string;
116
+ }
117
+
118
+ /** The one data row of the Breaching Samples table. */
119
+ function sampleRow(context: string): string {
120
+ const row: string | undefined = context.split("\n").find((line: string) => {
121
+ return line.includes(BREACH_TIME_ISO);
122
+ });
123
+
124
+ expect(row).toBeDefined();
125
+ return row as string;
126
+ }
127
+
128
+ /** The header row of the Breaching Samples table. */
129
+ function headerRow(context: string): string {
130
+ const row: string | undefined = context.split("\n").find((line: string) => {
131
+ return line.startsWith("| Timestamp |");
132
+ });
133
+
134
+ expect(row).toBeDefined();
135
+ return row as string;
136
+ }
137
+
138
+ describe("MonitorCriteriaEvaluator - Breaching Samples value column", () => {
139
+ test("renders bytes at human scale instead of raw digits", () => {
140
+ const context: string = rootCause(makeContext({ unit: "By" }));
141
+
142
+ expect(sampleRow(context)).toBe(
143
+ `| \`${BREACH_TIME_ISO}\` | \`k8s.pod.memory.usage\` | \`a\` | 1.07 GB |`,
144
+ );
145
+ expect(context).not.toContain("1073741824");
146
+ });
147
+
148
+ test("renders a duration at human scale", () => {
149
+ const context: string = rootCause(
150
+ makeContext({
151
+ metricName: "http.server.duration",
152
+ unit: "ms",
153
+ breachingSamples: [
154
+ { value: 1500, timestamp: BREACH_TIME, attributes: {} },
155
+ ],
156
+ }),
157
+ );
158
+
159
+ expect(sampleRow(context)).toContain("| 1.5 sec |");
160
+ });
161
+
162
+ test("renders a value the user typed a GB threshold against in GB terms", () => {
163
+ /*
164
+ * When the criteria carries a thresholdUnit, samples arrive already
165
+ * converted into it — so ctx.unit is "GB" and the values are counts
166
+ * of gigabytes. ValueFormatter has no ladder for "GB" on its own;
167
+ * MetricValueFormatter restates the value in bytes first.
168
+ */
169
+ const context: string = rootCause(
170
+ makeContext({
171
+ unit: "GB",
172
+ breachingSamples: [
173
+ { value: 2500, timestamp: BREACH_TIME, attributes: {} },
174
+ ],
175
+ }),
176
+ );
177
+
178
+ expect(sampleRow(context)).toContain("| 2.5 TB |");
179
+ });
180
+
181
+ test("each row names the unit IT landed on", () => {
182
+ const context: string = rootCause(
183
+ makeContext({
184
+ unit: "By",
185
+ breachingSamples: [
186
+ {
187
+ value: 921600,
188
+ timestamp: new Date("2026-08-14T10:29:00.000Z"),
189
+ attributes: {},
190
+ },
191
+ { value: 1258291, timestamp: BREACH_TIME, attributes: {} },
192
+ ],
193
+ }),
194
+ );
195
+
196
+ /*
197
+ * 922 KB and 1.26 MB in the same table. This is exactly why the unit
198
+ * rides each cell instead of the column header — no single header
199
+ * could describe both rows.
200
+ */
201
+ expect(context).toContain("| 922 KB |");
202
+ expect(context).toContain("| 1.26 MB |");
203
+ });
204
+
205
+ test("a ratio metric's fraction is shown as the percentage it means", () => {
206
+ const context: string = rootCause(
207
+ makeContext({
208
+ metricName: "system.cpu.utilization",
209
+ unit: "1",
210
+ breachingSamples: [
211
+ { value: 0.0585, timestamp: BREACH_TIME, attributes: {} },
212
+ ],
213
+ }),
214
+ );
215
+
216
+ expect(sampleRow(context)).toContain("| 5.85% |");
217
+ // The old rendering: a bare "0.06", which reads as 0.06%.
218
+ expect(context).not.toContain("| 0.06 |");
219
+ expect(context).not.toContain("0.06 1");
220
+ });
221
+
222
+ test("a UCUM annotation-only unit is dropped rather than printed", () => {
223
+ const context: string = rootCause(
224
+ makeContext({
225
+ metricName: "process.threads",
226
+ unit: "{thread}",
227
+ breachingSamples: [
228
+ { value: 512, timestamp: BREACH_TIME, attributes: {} },
229
+ ],
230
+ }),
231
+ );
232
+
233
+ expect(sampleRow(context)).toContain("| 512 |");
234
+ expect(context).not.toContain("{thread}");
235
+ });
236
+
237
+ /*
238
+ * BACKWARD COMPATIBILITY. A metric with no unit must keep every digit —
239
+ * a counter at 5000 is not "5K" in an alert, because the reader may be
240
+ * scanning the table for the exact value that crossed the threshold.
241
+ */
242
+ test("a unitless value keeps its exact digits", () => {
243
+ const context: string = rootCause(
244
+ makeContext({
245
+ metricName: "http.server.request.count",
246
+ unit: null,
247
+ breachingSamples: [
248
+ { value: 5000, timestamp: BREACH_TIME, attributes: {} },
249
+ ],
250
+ }),
251
+ );
252
+
253
+ expect(sampleRow(context)).toContain("| 5000 |");
254
+ expect(context).not.toContain("5K");
255
+ });
256
+
257
+ test("a unitless non-integer is rounded to two decimals", () => {
258
+ const context: string = rootCause(
259
+ makeContext({
260
+ unit: null,
261
+ breachingSamples: [
262
+ { value: 2.3333333333333335, timestamp: BREACH_TIME, attributes: {} },
263
+ ],
264
+ }),
265
+ );
266
+
267
+ expect(sampleRow(context)).toContain("| 2.33 |");
268
+ });
269
+
270
+ /*
271
+ * ValueFormatter renders Infinity as "InfinityP PB" — its
272
+ * formatLargeNumber divides by 1e15, gets Infinity back and appends the
273
+ * "P". A broken metric must not produce that in an on-call email.
274
+ */
275
+ test("a non-finite sample is reported verbatim", () => {
276
+ const context: string = rootCause(
277
+ makeContext({
278
+ unit: "By",
279
+ breachingSamples: [
280
+ { value: Infinity, timestamp: BREACH_TIME, attributes: {} },
281
+ ],
282
+ }),
283
+ );
284
+
285
+ expect(sampleRow(context)).toContain("| Infinity |");
286
+ expect(context).not.toContain("InfinityP");
287
+ });
288
+
289
+ test("attribute columns are unaffected", () => {
290
+ const context: string = rootCause(
291
+ makeContext({
292
+ unit: "By",
293
+ groupBy: ["k8s.pod.name"],
294
+ breachingSamples: [
295
+ {
296
+ value: 1073741824,
297
+ timestamp: BREACH_TIME,
298
+ attributes: { "k8s.pod.name": "web-1" },
299
+ },
300
+ ],
301
+ }),
302
+ );
303
+
304
+ expect(headerRow(context)).toBe(
305
+ "| Timestamp | Metric | Alias | Value | k8s.pod.name |",
306
+ );
307
+ expect(sampleRow(context)).toContain("| web-1 |");
308
+ });
309
+ });
310
+
311
+ describe("MonitorCriteriaEvaluator - Breaching Samples component columns", () => {
312
+ function formulaContext(): MetricCriteriaContext {
313
+ return makeContext({
314
+ metricName: "a + b",
315
+ alias: "c",
316
+ isFormula: true,
317
+ formulaExpression: "a + b",
318
+ unit: "By",
319
+ components: [
320
+ {
321
+ alias: "a",
322
+ name: "container.memory.usage",
323
+ unit: "By",
324
+ isFormula: false,
325
+ },
326
+ {
327
+ alias: "b",
328
+ name: "container.memory.cache",
329
+ unit: "ms",
330
+ isFormula: false,
331
+ },
332
+ ],
333
+ breachingSamples: [
334
+ {
335
+ value: 1073741824,
336
+ timestamp: BREACH_TIME,
337
+ attributes: {},
338
+ componentValues: [
339
+ { alias: "a", value: 536870912 },
340
+ { alias: "b", value: 1500 },
341
+ ],
342
+ },
343
+ ],
344
+ });
345
+ }
346
+
347
+ test("each component cell is formatted in its own unit", () => {
348
+ const context: string = rootCause(formulaContext());
349
+
350
+ expect(sampleRow(context)).toBe(
351
+ `| \`${BREACH_TIME_ISO}\` | \`a + b\` | \`c\` | 1.07 GB | 537 MB | 1.5 sec |`,
352
+ );
353
+ });
354
+
355
+ /*
356
+ * The header used to read "a (By)". Now that a cell says which scale it
357
+ * landed on, a header unit is a claim two rows of the same column can
358
+ * contradict — and the Components bullet list above the table still
359
+ * records the configured unit, so nothing is lost.
360
+ */
361
+ test("component headers carry the alias only, not the configured unit", () => {
362
+ const context: string = rootCause(formulaContext());
363
+
364
+ expect(headerRow(context)).toBe(
365
+ "| Timestamp | Metric | Alias | Value | a | b |",
366
+ );
367
+ expect(context).not.toContain("| a (By) |");
368
+ });
369
+
370
+ test("the Components bullet list spells each component's unit out", () => {
371
+ const context: string = rootCause(formulaContext());
372
+
373
+ expect(context).toContain("- `a` = `container.memory.usage` — unit: Bytes");
374
+ expect(context).toContain(
375
+ "- `b` = `container.memory.cache` — unit: Milliseconds",
376
+ );
377
+ });
378
+
379
+ test("a component with no value for this sample still renders a dash", () => {
380
+ const ctx: MetricCriteriaContext = formulaContext();
381
+ ctx.breachingSamples![0]!.componentValues = [
382
+ { alias: "a", value: 536870912 },
383
+ { alias: "b", value: null },
384
+ ];
385
+
386
+ expect(sampleRow(rootCause(ctx))).toContain("| 537 MB | - |");
387
+ });
388
+ });
389
+
390
+ describe("MonitorCriteriaEvaluator - the Unit line", () => {
391
+ test("spells the UCUM code out", () => {
392
+ expect(rootCause(makeContext({ unit: "By" }))).toContain("- Unit: Bytes");
393
+ });
394
+
395
+ test("names the percent a fraction metric carries", () => {
396
+ const context: string = rootCause(
397
+ makeContext({ metricName: "system.cpu.utilization", unit: "1" }),
398
+ );
399
+
400
+ expect(context).toContain("- Unit: Percent");
401
+ });
402
+
403
+ /*
404
+ * The line used to read "- Unit: 1" for every ratio metric — a unit
405
+ * that names nothing, printed as if it did.
406
+ */
407
+ test("is dropped entirely when the unit names no dimension", () => {
408
+ expect(rootCause(makeContext({ unit: "1" }))).not.toContain("- Unit:");
409
+ expect(rootCause(makeContext({ unit: "{thread}" }))).not.toContain(
410
+ "- Unit:",
411
+ );
412
+ expect(rootCause(makeContext({ unit: null }))).not.toContain("- Unit:");
413
+ });
414
+ });
415
+
416
+ /*
417
+ * THE FORMULA GUARD.
418
+ *
419
+ * MetricCriteriaContext.metricName is the FORMULA EXPRESSION when the
420
+ * criteria targets a formula. The only use a name has downstream is
421
+ * ValueFormatter.isFractionMetric, whose regex asks whether the name ends
422
+ * in `.utilization` / `_ratio` / `_percent`. A formula written as
423
+ * `a / b_ratio` ends in `_ratio`, so feeding it through would silently
424
+ * report every value at 100× its real magnitude.
425
+ */
426
+ describe("MonitorCriteriaEvaluator - formula names are not metric names", () => {
427
+ test("a formula ending in _ratio is not multiplied by 100", () => {
428
+ const context: string = rootCause(
429
+ makeContext({
430
+ metricName: "a / b_ratio",
431
+ alias: "c",
432
+ isFormula: true,
433
+ formulaExpression: "a / b_ratio",
434
+ unit: "1",
435
+ breachingSamples: [
436
+ { value: 0.42, timestamp: BREACH_TIME, attributes: {} },
437
+ ],
438
+ }),
439
+ );
440
+
441
+ expect(sampleRow(context)).toContain("| 0.42 |");
442
+ expect(context).not.toContain("42.00%");
443
+ expect(context).not.toContain("- Unit:");
444
+ });
445
+
446
+ test("a formula COMPONENT ending in _ratio is not multiplied by 100 either", () => {
447
+ const context: string = rootCause(
448
+ makeContext({
449
+ metricName: "c",
450
+ alias: "c",
451
+ isFormula: true,
452
+ formulaExpression: "x",
453
+ unit: null,
454
+ components: [
455
+ {
456
+ alias: "x",
457
+ name: "p / q_utilization",
458
+ unit: "1",
459
+ isFormula: true,
460
+ },
461
+ ],
462
+ breachingSamples: [
463
+ {
464
+ value: 1,
465
+ timestamp: BREACH_TIME,
466
+ attributes: {},
467
+ componentValues: [{ alias: "x", value: 0.42 }],
468
+ },
469
+ ],
470
+ }),
471
+ );
472
+
473
+ expect(sampleRow(context)).toContain("| 0.42 |");
474
+ expect(context).not.toContain("42.00%");
475
+ });
476
+
477
+ /*
478
+ * A plain metric criteria DOES get the heuristic — this is the control
479
+ * that shows the guard is narrow rather than a blanket opt-out.
480
+ */
481
+ test("a plain metric criteria still gets the fraction heuristic", () => {
482
+ const context: string = rootCause(
483
+ makeContext({
484
+ metricName: "system.memory.utilization",
485
+ isFormula: false,
486
+ unit: "1",
487
+ breachingSamples: [
488
+ { value: 0.42, timestamp: BREACH_TIME, attributes: {} },
489
+ ],
490
+ }),
491
+ );
492
+
493
+ expect(sampleRow(context)).toContain("| 42.00% |");
494
+ });
495
+ });
496
+
497
+ /*
498
+ * ONE EMAIL, ONE ANSWER.
499
+ *
500
+ * The unit-suppression rule was hand-rolled three separate times before
501
+ * this change — in CompareCriteria's "Filter Conditions Met" sentence, in
502
+ * MonitorCriteriaObservationBuilder, and nowhere at all in the Breaching
503
+ * Samples table — with the result that a single evaluation read
504
+ * differently in its own two halves: "0.31" in the sentence and "0.31 1"
505
+ * in the table directly beneath it.
506
+ *
507
+ * Both sections of one email now go through MetricValueFormatter. These
508
+ * tests assert the two strings are IDENTICAL rather than merely both
509
+ * plausible, so a fourth copy of the rule cannot quietly appear.
510
+ */
511
+ describe("MonitorCriteriaEvaluator - the table and the sentence agree", () => {
512
+ function bothRenderings(input: {
513
+ value: number;
514
+ unit: string | null;
515
+ metricName: string;
516
+ }): { cell: string; sentence: string } {
517
+ const ctx: MetricCriteriaContext = makeContext({
518
+ metricName: input.metricName,
519
+ unit: input.unit,
520
+ breachingSamples: [
521
+ { value: input.value, timestamp: BREACH_TIME, attributes: {} },
522
+ ],
523
+ });
524
+
525
+ const row: string = sampleRow(rootCause(ctx));
526
+ const cells: Array<string> = row.split("|").map((cell: string) => {
527
+ return cell.trim();
528
+ });
529
+
530
+ return {
531
+ // | ts | metric | alias | VALUE | → index 4 once the leading "" is counted.
532
+ cell: cells[4] as string,
533
+ sentence: CompareCriteria.getCompareMessage({
534
+ values: input.value,
535
+ threshold: input.value,
536
+ criteriaFilter: {
537
+ checkOn: CheckOn.MetricValue,
538
+ filterType: FilterType.GreaterThan,
539
+ } as CriteriaFilter,
540
+ metricDisplayName: input.metricName,
541
+ metricName: input.metricName,
542
+ unit: input.unit || undefined,
543
+ }),
544
+ };
545
+ }
546
+
547
+ const cases: Array<{
548
+ value: number;
549
+ unit: string | null;
550
+ metricName: string;
551
+ }> = [
552
+ { value: 1073741824, unit: "By", metricName: "k8s.pod.memory.usage" },
553
+ { value: 1500, unit: "ms", metricName: "http.server.duration" },
554
+ { value: 0.0585, unit: "1", metricName: "system.cpu.utilization" },
555
+ { value: 0.31, unit: "1", metricName: "browser.cls" },
556
+ { value: 5000, unit: null, metricName: "http.server.request.count" },
557
+ { value: 2500, unit: "GB", metricName: "system.filesystem.usage" },
558
+ ];
559
+
560
+ for (const testCase of cases) {
561
+ test(`${testCase.metricName} @ ${testCase.unit ?? "no unit"} reads the same in both`, () => {
562
+ const rendered: { cell: string; sentence: string } =
563
+ bothRenderings(testCase);
564
+
565
+ expect(rendered.cell.length).toBeGreaterThan(0);
566
+ expect(rendered.sentence).toContain(`is ${rendered.cell} which is`);
567
+ expect(rendered.sentence).toContain(`greater than ${rendered.cell}.`);
568
+ });
569
+ }
570
+ });
@@ -517,7 +517,18 @@ describe("MonitorCriteriaExpectationBuilder.describeCriteriaExpectation", () =>
517
517
  * template string, so a negative sign and any decimals survive verbatim
518
518
  * (no toFixed / number formatting), and the unit suffix still follows.
519
519
  */
520
- describe("negative thresholds with a unit are interpolated raw", () => {
520
+ /*
521
+ * A threshold carrying a unit is rendered by the same formatter as the
522
+ * observed value beside it, so both halves of one sentence describe the
523
+ * quantity the same way. That means it can be RESCALED — "-0.5 sec" is
524
+ * written "-500 ms", the same number at the scale a reader reads it at,
525
+ * and the observation in that sentence uses milliseconds too.
526
+ *
527
+ * What must never happen is decimal PADDING: a threshold the user typed
528
+ * as "-5" must not come back as "-5.00", which is what the old raw
529
+ * interpolation was protecting against and what these still assert.
530
+ */
531
+ describe("negative thresholds with a unit are formatted, never padded", () => {
521
532
  test("GreaterThan '-5' with { unit: 'sec' } => 'to be greater than -5 sec'", () => {
522
533
  const filter: CriteriaFilter = makeFilter({
523
534
  filterType: FilterType.GreaterThan,
@@ -530,11 +541,11 @@ describe("MonitorCriteriaExpectationBuilder.describeCriteriaExpectation", () =>
530
541
  });
531
542
 
532
543
  expect(result).toBe("to be greater than -5 sec");
533
- // Raw interpolation — never number-formatted.
544
+ // Integers keep their digits — never padded to "-5.00".
534
545
  expect(result).not.toContain("-5.00");
535
546
  });
536
547
 
537
- test("LessThan '-0.5' with { unit: 'sec' } => 'to be less than -0.5 sec'", () => {
548
+ test("LessThan '-0.5' with { unit: 'sec' } rescales to milliseconds", () => {
538
549
  const filter: CriteriaFilter = makeFilter({
539
550
  filterType: FilterType.LessThan,
540
551
  value: "-0.5",
@@ -545,9 +556,23 @@ describe("MonitorCriteriaExpectationBuilder.describeCriteriaExpectation", () =>
545
556
  unit: "sec",
546
557
  });
547
558
 
548
- expect(result).toBe("to be less than -0.5 sec");
549
- // Raw interpolation — the decimal is kept as-is, not padded to "-0.50".
559
+ expect(result).toBe("to be less than -500 ms");
550
560
  expect(result).not.toContain("-0.50");
551
561
  });
562
+
563
+ /*
564
+ * The boundary the formatter is gated on: with no unit resolved — every
565
+ * non-metric checkOn — the threshold is interpolated exactly as before.
566
+ */
567
+ test("a threshold with no unit is still interpolated raw", () => {
568
+ const filter: CriteriaFilter = makeFilter({
569
+ filterType: FilterType.LessThan,
570
+ value: "-0.5",
571
+ });
572
+
573
+ expect(
574
+ MonitorCriteriaExpectationBuilder.describeCriteriaExpectation(filter),
575
+ ).toBe("to be less than -0.5");
576
+ });
552
577
  });
553
578
  });