@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,546 @@
1
+ import MetricValueFormatter from "../../../Utils/Monitor/MetricValueFormatter";
2
+ import ValueFormatter from "../../../Utils/ValueFormatter";
3
+ import { describe, expect, test } from "@jest/globals";
4
+
5
+ /*
6
+ * How a metric value is written into an alert or incident.
7
+ *
8
+ * The behaviour has three tiers and every test below belongs to one:
9
+ *
10
+ * 1. The unit names a SCALE LADDER (bytes, seconds and their prefixed
11
+ * members) — the value is rescaled the way the dashboard rescales it,
12
+ * so 1073741824 By reads "1.07 GB".
13
+ * 2. The unit names a dimension but NO ladder (cores, Celsius, ops) —
14
+ * the unit symbol is appended and the digits are left alone,
15
+ * because an
16
+ * alert reader needs to tell 1500 restarts from 1549 and "1.5K" does
17
+ * not let them.
18
+ * 3. The unit names NO dimension ("", "1" on a counter, "{restarts}",
19
+ * the catalogs' "count") — the number stands on its own.
20
+ */
21
+ describe("MetricValueFormatter", () => {
22
+ describe("format — units with a scale ladder", () => {
23
+ test("scales bytes the way the dashboard does", () => {
24
+ expect(MetricValueFormatter.format({ value: 0, unit: "By" })).toBe("0 B");
25
+ expect(MetricValueFormatter.format({ value: 512, unit: "By" })).toBe(
26
+ "512 B",
27
+ );
28
+ expect(MetricValueFormatter.format({ value: 1000, unit: "By" })).toBe(
29
+ "1 KB",
30
+ );
31
+ expect(
32
+ MetricValueFormatter.format({ value: 1048576, unit: "bytes" }),
33
+ ).toBe("1.05 MB");
34
+ expect(
35
+ MetricValueFormatter.format({ value: 1073741824, unit: "By" }),
36
+ ).toBe("1.07 GB");
37
+ expect(
38
+ MetricValueFormatter.format({ value: 1099511627776, unit: "bytes" }),
39
+ ).toBe("1.1 TB");
40
+ });
41
+
42
+ test("scales time units", () => {
43
+ expect(MetricValueFormatter.format({ value: 750, unit: "ms" })).toBe(
44
+ "750 ms",
45
+ );
46
+ expect(MetricValueFormatter.format({ value: 1500, unit: "ms" })).toBe(
47
+ "1.5 sec",
48
+ );
49
+ expect(
50
+ MetricValueFormatter.format({ value: 3661, unit: "seconds" }),
51
+ ).toBe("1.02 hours");
52
+ expect(MetricValueFormatter.format({ value: 1500, unit: "ns" })).toBe(
53
+ "1.5 µs",
54
+ );
55
+ expect(MetricValueFormatter.format({ value: 0, unit: "seconds" })).toBe(
56
+ "0 sec",
57
+ );
58
+ });
59
+
60
+ /*
61
+ * A user who types a threshold in "GB" makes every sample arrive
62
+ * carrying the unit "GB". ValueFormatter has a ladder for "bytes" but
63
+ * not for "GB", so on its own it rendered 2500 of them as "2.5K GB".
64
+ * The value is restated in the family's base unit first.
65
+ */
66
+ test("rescales a PREFIXED unit through its family base", () => {
67
+ expect(MetricValueFormatter.format({ value: 2500, unit: "GB" })).toBe(
68
+ "2.5 TB",
69
+ );
70
+ expect(MetricValueFormatter.format({ value: 2, unit: "GB" })).toBe(
71
+ "2 GB",
72
+ );
73
+ expect(MetricValueFormatter.format({ value: 0.5, unit: "MB" })).toBe(
74
+ "500 KB",
75
+ );
76
+ expect(MetricValueFormatter.format({ value: 1500, unit: "KB" })).toBe(
77
+ "1.5 MB",
78
+ );
79
+ expect(MetricValueFormatter.format({ value: 36, unit: "hours" })).toBe(
80
+ "1.5 days",
81
+ );
82
+ expect(MetricValueFormatter.format({ value: 5, unit: "min" })).toBe(
83
+ "5 min",
84
+ );
85
+ expect(MetricValueFormatter.format({ value: 90, unit: "min" })).toBe(
86
+ "1.5 hours",
87
+ );
88
+ });
89
+
90
+ test("scales the numerator of a rate unit and keeps the denominator", () => {
91
+ expect(
92
+ MetricValueFormatter.format({ value: 1500000, unit: "By/s" }),
93
+ ).toBe("1.5 MB/s");
94
+ expect(MetricValueFormatter.format({ value: 2.5, unit: "GB/s" })).toBe(
95
+ "2.5 GB/s",
96
+ );
97
+ expect(MetricValueFormatter.format({ value: 2000, unit: "GB/s" })).toBe(
98
+ "2 TB/s",
99
+ );
100
+ });
101
+
102
+ test("a rate whose numerator has no ladder keeps its digits", () => {
103
+ expect(
104
+ MetricValueFormatter.format({ value: 1500, unit: "requests/s" }),
105
+ ).toBe("1500 requests/s");
106
+ });
107
+
108
+ test("negatives keep their sign through the ladder", () => {
109
+ expect(MetricValueFormatter.format({ value: -2048, unit: "bytes" })).toBe(
110
+ "-2.05 KB",
111
+ );
112
+ expect(MetricValueFormatter.format({ value: -1500, unit: "ms" })).toBe(
113
+ "-1.5 sec",
114
+ );
115
+ });
116
+ });
117
+
118
+ describe("format — percent and fraction metrics", () => {
119
+ test("percent units render inline with two decimals", () => {
120
+ expect(MetricValueFormatter.format({ value: 87.5, unit: "%" })).toBe(
121
+ "87.50%",
122
+ );
123
+ expect(MetricValueFormatter.format({ value: 100, unit: "percent" })).toBe(
124
+ "100.00%",
125
+ );
126
+ expect(MetricValueFormatter.format({ value: 2.04, unit: "pct" })).toBe(
127
+ "2.04%",
128
+ );
129
+ });
130
+
131
+ /*
132
+ * The flagship case: a ratio metric whose samples are fractions in
133
+ * [0, 1] carrying OTel's dimensionless "1". The alert used to say
134
+ * "0.06 1"; the dashboard has always said "5.85%".
135
+ */
136
+ test("a [0,1] fraction on a ratio metric becomes a percentage", () => {
137
+ expect(
138
+ MetricValueFormatter.format({
139
+ value: 0.0585,
140
+ unit: "1",
141
+ metricName: "system.cpu.utilization",
142
+ }),
143
+ ).toBe("5.85%");
144
+ expect(
145
+ MetricValueFormatter.format({
146
+ value: 0.2534,
147
+ unit: "1",
148
+ metricName: "db.client.connection.usage_ratio",
149
+ }),
150
+ ).toBe("25.34%");
151
+ });
152
+
153
+ /*
154
+ * kubeletstats reports these two as CPU CORES despite the
155
+ * `.utilization` name, and ValueFormatter.isFractionMetric excludes
156
+ * them by exact name. Four other places in the repo depend on that
157
+ * exclusion; this pins that the alert path honours it too, so 0.18
158
+ * cores never reads as "18.31%".
159
+ */
160
+ test("the kubeletstats cores gauges are NOT treated as fractions", () => {
161
+ expect(
162
+ MetricValueFormatter.format({
163
+ value: 0.1831,
164
+ unit: "1",
165
+ metricName: "k8s.pod.cpu.utilization",
166
+ }),
167
+ ).toBe("0.18");
168
+ expect(
169
+ MetricValueFormatter.format({
170
+ value: 0.1831,
171
+ unit: "1",
172
+ metricName: "k8s.node.cpu.utilization",
173
+ }),
174
+ ).toBe("0.18");
175
+ });
176
+
177
+ test("a fraction unit with no metric name stays a bare number", () => {
178
+ expect(MetricValueFormatter.format({ value: 0.25, unit: "1" })).toBe(
179
+ "0.25",
180
+ );
181
+ });
182
+
183
+ /*
184
+ * The catalogs spell the dimensionless fraction "ratio". It means what
185
+ * "1" means, so it needs the same metric-name signal before it can be
186
+ * shown as a percentage.
187
+ */
188
+ test("the catalogs' 'ratio' behaves exactly like '1'", () => {
189
+ expect(
190
+ MetricValueFormatter.format({
191
+ value: 0.97,
192
+ unit: "ratio",
193
+ metricName: "pve_cpu_usage_ratio",
194
+ }),
195
+ ).toBe("97.00%");
196
+ expect(MetricValueFormatter.format({ value: 0.97, unit: "ratio" })).toBe(
197
+ "0.97",
198
+ );
199
+ });
200
+
201
+ /*
202
+ * Values reaching this formatter have ALREADY been converted into the
203
+ * threshold unit by MetricMonitorCriteria, so a ratio metric whose
204
+ * threshold is in "%" arrives pre-multiplied. Re-applying the fraction
205
+ * heuristic there would report 6% as 600%.
206
+ */
207
+ test("does not double-scale a value already converted into percent", () => {
208
+ expect(
209
+ MetricValueFormatter.format({
210
+ value: 6,
211
+ unit: "%",
212
+ metricName: "system.cpu.utilization",
213
+ }),
214
+ ).toBe("6.00%");
215
+ });
216
+ });
217
+
218
+ describe("format — units with a dimension but no ladder", () => {
219
+ test("appends the unit symbol and leaves every digit alone", () => {
220
+ expect(MetricValueFormatter.format({ value: 0.42, unit: "cores" })).toBe(
221
+ "0.42 cores",
222
+ );
223
+ expect(
224
+ MetricValueFormatter.format({ value: 1234567, unit: "cores" }),
225
+ ).toBe("1234567 cores");
226
+ expect(MetricValueFormatter.format({ value: 95, unit: "Cel" })).toBe(
227
+ "95 °C",
228
+ );
229
+ expect(MetricValueFormatter.format({ value: 1500, unit: "kbit" })).toBe(
230
+ "1500 kbit",
231
+ );
232
+ expect(MetricValueFormatter.format({ value: 1024, unit: "KiBy" })).toBe(
233
+ "1024 KiB",
234
+ );
235
+ expect(MetricValueFormatter.format({ value: 4200, unit: "ops" })).toBe(
236
+ "4200 ops",
237
+ );
238
+ });
239
+
240
+ /*
241
+ * This is the whole reason the formatter is not a bare call to
242
+ * ValueFormatter: on a chart axis "1.23M cores" is right, in an alert
243
+ * the exact count is the thing the reader came for.
244
+ */
245
+ test("does NOT abbreviate large numbers the way a chart axis would", () => {
246
+ expect(ValueFormatter.formatValue(1234567, "cores")).toBe("1.23M cores");
247
+ expect(
248
+ MetricValueFormatter.format({ value: 1234567, unit: "cores" }),
249
+ ).toBe("1234567 cores");
250
+ });
251
+
252
+ test("rounds a non-integer to two decimals", () => {
253
+ expect(MetricValueFormatter.format({ value: 3.14159, unit: "ops" })).toBe(
254
+ "3.14 ops",
255
+ );
256
+ expect(MetricValueFormatter.format({ value: 2.5, unit: "ops" })).toBe(
257
+ "2.5 ops",
258
+ );
259
+ });
260
+ });
261
+
262
+ describe("format — units that name no dimension", () => {
263
+ test("no unit at all leaves the number exactly as it was", () => {
264
+ expect(MetricValueFormatter.format({ value: 97 })).toBe("97");
265
+ expect(MetricValueFormatter.format({ value: 5000 })).toBe("5000");
266
+ expect(MetricValueFormatter.format({ value: 250, unit: "" })).toBe("250");
267
+ expect(MetricValueFormatter.format({ value: 12, unit: null })).toBe("12");
268
+ expect(MetricValueFormatter.format({ value: 12, unit: " " })).toBe(
269
+ "12",
270
+ );
271
+ });
272
+
273
+ test("rounds a bare non-integer to two decimals", () => {
274
+ // A count metric arrives as a float from an aggregation.
275
+ expect(MetricValueFormatter.format({ value: 2.3333333333333335 })).toBe(
276
+ "2.33",
277
+ );
278
+ expect(MetricValueFormatter.format({ value: 0.9712 })).toBe("0.97");
279
+ // Trailing zeros are dropped: 1.50 reads "1.5".
280
+ expect(MetricValueFormatter.format({ value: 1.5 })).toBe("1.5");
281
+ });
282
+
283
+ test("UCUM annotation-only units are dropped", () => {
284
+ expect(
285
+ MetricValueFormatter.format({ value: 1500, unit: "{restarts}" }),
286
+ ).toBe("1500");
287
+ expect(MetricValueFormatter.format({ value: 42, unit: "{cpu}" })).toBe(
288
+ "42",
289
+ );
290
+ expect(MetricValueFormatter.format({ value: 7, unit: "{packets}" })).toBe(
291
+ "7",
292
+ );
293
+ });
294
+
295
+ test("the catalogs' 'count' is dropped like the dimensionless '1'", () => {
296
+ expect(MetricValueFormatter.format({ value: 250, unit: "count" })).toBe(
297
+ "250",
298
+ );
299
+ expect(MetricValueFormatter.format({ value: 12, unit: "COUNT" })).toBe(
300
+ "12",
301
+ );
302
+ });
303
+
304
+ test("the dimensionless '1' on a counter is dropped", () => {
305
+ expect(
306
+ MetricValueFormatter.format({
307
+ value: 0.31,
308
+ unit: "1",
309
+ metricName: "browser.cumulative_layout_shift",
310
+ }),
311
+ ).toBe("0.31");
312
+ });
313
+ });
314
+
315
+ /*
316
+ * Three Proxmox catalog entries declare `unit: "seconds"` for a value
317
+ * that is a Unix epoch, not a duration. Run through the time ladder,
318
+ * an epoch of 1750000000 renders "20.25K days" — which does not just
319
+ * fail to help, it actively misleads: a replication job that synced a
320
+ * minute ago reads as 55 years stale.
321
+ */
322
+ describe("format — metrics whose 'seconds' is a point in time", () => {
323
+ test("epoch timestamps keep their digits instead of becoming durations", () => {
324
+ for (const metricName of [
325
+ "pve_replication_last_sync_timestamp_seconds",
326
+ "pve_replication_last_try_timestamp_seconds",
327
+ "pve_replication_next_sync_timestamp_seconds",
328
+ ]) {
329
+ expect(
330
+ MetricValueFormatter.format({
331
+ value: 1750000000,
332
+ unit: "seconds",
333
+ metricName: metricName,
334
+ }),
335
+ ).toBe("1750000000 s");
336
+ }
337
+ });
338
+
339
+ test("the ladder is what would otherwise mislead", () => {
340
+ expect(ValueFormatter.formatValue(1750000000, "seconds")).toBe(
341
+ "20.25K days",
342
+ );
343
+ });
344
+
345
+ test("recognises the shapes a timestamp metric name takes", () => {
346
+ expect(
347
+ MetricValueFormatter.isAbsoluteTimestampMetric(
348
+ "pve_replication_last_sync_timestamp_seconds",
349
+ ),
350
+ ).toBe(true);
351
+ expect(
352
+ MetricValueFormatter.isAbsoluteTimestampMetric(
353
+ "process.start.timestamp",
354
+ ),
355
+ ).toBe(true);
356
+ expect(
357
+ MetricValueFormatter.isAbsoluteTimestampMetric("build_timestamps"),
358
+ ).toBe(true);
359
+ });
360
+
361
+ /*
362
+ * Narrow on purpose. A genuine duration whose name merely mentions
363
+ * time must still scale — this is the control that stops the carve-out
364
+ * from swallowing the common case.
365
+ */
366
+ test("does not swallow genuine durations", () => {
367
+ expect(
368
+ MetricValueFormatter.isAbsoluteTimestampMetric(
369
+ "http.server.request.duration",
370
+ ),
371
+ ).toBe(false);
372
+ expect(
373
+ MetricValueFormatter.isAbsoluteTimestampMetric(
374
+ "timestamp_delta_seconds",
375
+ ),
376
+ ).toBe(false);
377
+ expect(MetricValueFormatter.isAbsoluteTimestampMetric(undefined)).toBe(
378
+ false,
379
+ );
380
+
381
+ expect(
382
+ MetricValueFormatter.format({
383
+ value: 3661,
384
+ unit: "seconds",
385
+ metricName: "http.server.request.duration",
386
+ }),
387
+ ).toBe("1.02 hours");
388
+ });
389
+
390
+ test("only suppresses the ladder, not the unit label", () => {
391
+ expect(
392
+ MetricValueFormatter.getReadableUnit(
393
+ "seconds",
394
+ "pve_replication_last_sync_timestamp_seconds",
395
+ ),
396
+ ).toBe("Seconds");
397
+ });
398
+ });
399
+
400
+ describe("format — non-finite values", () => {
401
+ /*
402
+ * ValueFormatter has no non-finite guard outside its percent branch:
403
+ * formatLargeNumber divides Infinity by 1e15, gets Infinity, and
404
+ * appends the "P" suffix. Anything routed through it would report a
405
+ * broken metric as "InfinityP PB" in an on-call email.
406
+ */
407
+ test("are reported verbatim, never through the scaling ladder", () => {
408
+ expect(ValueFormatter.formatValue(Infinity, "By")).toBe("InfinityP PB");
409
+
410
+ expect(MetricValueFormatter.format({ value: NaN, unit: "By" })).toBe(
411
+ "NaN",
412
+ );
413
+ expect(MetricValueFormatter.format({ value: Infinity, unit: "By" })).toBe(
414
+ "Infinity",
415
+ );
416
+ expect(
417
+ MetricValueFormatter.format({ value: -Infinity, unit: "seconds" }),
418
+ ).toBe("-Infinity");
419
+ expect(MetricValueFormatter.format({ value: NaN })).toBe("NaN");
420
+ expect(MetricValueFormatter.format({ value: NaN, unit: "%" })).toBe(
421
+ "NaN",
422
+ );
423
+ });
424
+ });
425
+
426
+ describe("hasDisplayableUnit", () => {
427
+ test("true for units that name a dimension", () => {
428
+ for (const unit of [
429
+ "By",
430
+ "bytes",
431
+ "GB",
432
+ "ms",
433
+ "seconds",
434
+ "%",
435
+ "percent",
436
+ "cores",
437
+ "Cel",
438
+ "ops",
439
+ "By/s",
440
+ ]) {
441
+ expect(MetricValueFormatter.hasDisplayableUnit(unit)).toBe(true);
442
+ }
443
+ });
444
+
445
+ test("false for units that do not", () => {
446
+ for (const unit of [
447
+ undefined,
448
+ null,
449
+ "",
450
+ " ",
451
+ "1",
452
+ "count",
453
+ "counts",
454
+ "{restarts}",
455
+ "{}",
456
+ ]) {
457
+ expect(MetricValueFormatter.hasDisplayableUnit(unit)).toBe(false);
458
+ }
459
+ });
460
+
461
+ test("'1' and 'ratio' become displayable on a fraction metric", () => {
462
+ expect(
463
+ MetricValueFormatter.hasDisplayableUnit("1", "system.cpu.utilization"),
464
+ ).toBe(true);
465
+ expect(
466
+ MetricValueFormatter.hasDisplayableUnit("ratio", "pve_cpu_usage_ratio"),
467
+ ).toBe(true);
468
+ expect(
469
+ MetricValueFormatter.hasDisplayableUnit(
470
+ "1",
471
+ "k8s.node.cpu.utilization",
472
+ ),
473
+ ).toBe(false);
474
+ });
475
+ });
476
+
477
+ describe("getReadableUnit", () => {
478
+ test("spells UCUM codes out", () => {
479
+ expect(MetricValueFormatter.getReadableUnit("By")).toBe("Bytes");
480
+ expect(MetricValueFormatter.getReadableUnit("ms")).toBe("Milliseconds");
481
+ expect(MetricValueFormatter.getReadableUnit("s")).toBe("Seconds");
482
+ expect(MetricValueFormatter.getReadableUnit("Cel")).toBe("Celsius");
483
+ expect(MetricValueFormatter.getReadableUnit("%")).toBe("Percent");
484
+ expect(MetricValueFormatter.getReadableUnit("By/s")).toBe(
485
+ "Bytes per Second",
486
+ );
487
+ });
488
+
489
+ test("echoes a unit it does not recognise", () => {
490
+ expect(MetricValueFormatter.getReadableUnit("cores")).toBe("cores");
491
+ expect(MetricValueFormatter.getReadableUnit("widgets")).toBe("widgets");
492
+ });
493
+
494
+ /*
495
+ * Returning null rather than "" is what lets the caller drop the whole
496
+ * "- Unit:" line instead of emitting the noise "- Unit: 1".
497
+ */
498
+ test("returns null when there is no dimension to name", () => {
499
+ expect(MetricValueFormatter.getReadableUnit(undefined)).toBeNull();
500
+ expect(MetricValueFormatter.getReadableUnit(null)).toBeNull();
501
+ expect(MetricValueFormatter.getReadableUnit("")).toBeNull();
502
+ expect(MetricValueFormatter.getReadableUnit("1")).toBeNull();
503
+ expect(MetricValueFormatter.getReadableUnit("count")).toBeNull();
504
+ expect(MetricValueFormatter.getReadableUnit("{restarts}")).toBeNull();
505
+ });
506
+
507
+ test("names the percent a fraction metric really carries", () => {
508
+ expect(
509
+ MetricValueFormatter.getReadableUnit("1", "system.cpu.utilization"),
510
+ ).toBe("Percent");
511
+ expect(
512
+ MetricValueFormatter.getReadableUnit("ratio", "pve_cpu_usage_ratio"),
513
+ ).toBe("Percent");
514
+ expect(
515
+ MetricValueFormatter.getReadableUnit("1", "k8s.node.cpu.utilization"),
516
+ ).toBeNull();
517
+ });
518
+ });
519
+
520
+ describe("agreement with the dashboard", () => {
521
+ /*
522
+ * The point of reusing ValueFormatter is that an email and the metric
523
+ * explorer say the same thing about the same sample. Where a ladder
524
+ * applies, the two must be byte-identical.
525
+ */
526
+ test("matches ValueFormatter exactly for every ladder unit", () => {
527
+ const cases: Array<[number, string]> = [
528
+ [1073741824, "By"],
529
+ [1048576, "bytes"],
530
+ [512, "By"],
531
+ [1500, "ms"],
532
+ [3661, "seconds"],
533
+ [1500, "ns"],
534
+ [1500000, "By/s"],
535
+ [87.5, "%"],
536
+ [0, "bytes"],
537
+ ];
538
+
539
+ for (const [value, unit] of cases) {
540
+ expect(MetricValueFormatter.format({ value: value, unit: unit })).toBe(
541
+ ValueFormatter.formatValue(value, unit),
542
+ );
543
+ }
544
+ });
545
+ });
546
+ });