@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,235 @@
1
+ import {
2
+ AutoImportRuleRunResult,
3
+ RuleRunResultUtil,
4
+ } from "../../../Types/NetworkAutomation/RuleRunResult";
5
+ import { describe, expect, it } from "@jest/globals";
6
+
7
+ /*
8
+ * One press of "Run Rule" is now several capped server passes
9
+ * (OneUptime issue #3642), and this is the arithmetic that turns them back
10
+ * into one report. It is the part most easily got wrong: every pass re-reads
11
+ * the SAME scans, so most counters describe the estate rather than the work,
12
+ * and summing them would multiply the estate by the pass count.
13
+ */
14
+
15
+ function pass(
16
+ overrides: Partial<AutoImportRuleRunResult>,
17
+ ): AutoImportRuleRunResult {
18
+ return {
19
+ hostsEvaluated: 0,
20
+ hostsMatched: 0,
21
+ hostsExcluded: 0,
22
+ hostsSkippedAlreadyRegistered: 0,
23
+ devicesCreated: 0,
24
+ devicesFailed: 0,
25
+ monitorsWouldCreate: 0,
26
+ monitorsCreated: 0,
27
+ monitorsSkippedAlreadyExisting: 0,
28
+ monitorsSkippedUnsupportedHost: 0,
29
+ monitorsFailed: 0,
30
+ deviceFailureReasons: [],
31
+ monitorFailureReasons: [],
32
+ monitorProvisioningHalted: false,
33
+ isTruncated: false,
34
+ hostsPendingImport: 0,
35
+ monitorsPendingCreation: 0,
36
+ hasUnevaluatedScans: false,
37
+ hasMoreScans: false,
38
+ isDryRun: false,
39
+ matchedIpAddressSample: [],
40
+ ...overrides,
41
+ };
42
+ }
43
+
44
+ describe("RuleRunResultUtil.mergeAutoImportRunPasses", () => {
45
+ it("returns a single pass unchanged", () => {
46
+ const only: AutoImportRuleRunResult = pass({ devicesCreated: 7 });
47
+
48
+ expect(RuleRunResultUtil.mergeAutoImportRunPasses([only])).toBe(only);
49
+ });
50
+
51
+ it("refuses to invent a report out of no passes", () => {
52
+ expect(() => {
53
+ return RuleRunResultUtil.mergeAutoImportRunPasses([]);
54
+ }).toThrow("at least one run result");
55
+ });
56
+
57
+ /*
58
+ * The issue's own numbers: 909 hosts, a 500-per-pass cap, two passes.
59
+ */
60
+ it("sums the work done and keeps the estate from the first pass", () => {
61
+ const merged: AutoImportRuleRunResult =
62
+ RuleRunResultUtil.mergeAutoImportRunPasses([
63
+ pass({
64
+ hostsEvaluated: 909,
65
+ hostsMatched: 909,
66
+ devicesCreated: 500,
67
+ hostsPendingImport: 409,
68
+ isTruncated: true,
69
+ }),
70
+ pass({
71
+ hostsEvaluated: 909,
72
+ hostsMatched: 909,
73
+ hostsSkippedAlreadyRegistered: 500,
74
+ devicesCreated: 409,
75
+ }),
76
+ ]);
77
+
78
+ expect(merged.devicesCreated).toBe(909);
79
+ // Not 1,818: the second pass looked at the same 909 hosts.
80
+ expect(merged.hostsEvaluated).toBe(909);
81
+ expect(merged.hostsMatched).toBe(909);
82
+ /*
83
+ * And not 500 either. Reporting the run's own creates back as
84
+ * "already registered" would contradict "imported 909" in the same
85
+ * paragraph.
86
+ */
87
+ expect(merged.hostsSkippedAlreadyRegistered).toBe(0);
88
+ expect(merged.isTruncated).toBe(false);
89
+ expect(merged.hostsPendingImport).toBe(0);
90
+ });
91
+
92
+ it("sums monitor work and failures across passes", () => {
93
+ const merged: AutoImportRuleRunResult =
94
+ RuleRunResultUtil.mergeAutoImportRunPasses([
95
+ pass({
96
+ monitorsCreated: 500,
97
+ monitorsFailed: 2,
98
+ devicesFailed: 1,
99
+ monitorsSkippedAlreadyExisting: 4,
100
+ monitorsSkippedUnsupportedHost: 3,
101
+ isTruncated: true,
102
+ }),
103
+ pass({ monitorsCreated: 409, monitorsFailed: 1 }),
104
+ ]);
105
+
106
+ expect(merged.monitorsCreated).toBe(909);
107
+ expect(merged.monitorsFailed).toBe(3);
108
+ expect(merged.devicesFailed).toBe(1);
109
+ // Estate state, not work: taken from the first pass, never doubled.
110
+ expect(merged.monitorsSkippedAlreadyExisting).toBe(4);
111
+ expect(merged.monitorsSkippedUnsupportedHost).toBe(3);
112
+ });
113
+
114
+ it("carries the last pass's leftovers, not the first pass's", () => {
115
+ const merged: AutoImportRuleRunResult =
116
+ RuleRunResultUtil.mergeAutoImportRunPasses([
117
+ pass({
118
+ devicesCreated: 500,
119
+ hostsPendingImport: 900,
120
+ monitorsPendingCreation: 900,
121
+ hasUnevaluatedScans: true,
122
+ isTruncated: true,
123
+ }),
124
+ pass({
125
+ devicesCreated: 500,
126
+ hostsPendingImport: 400,
127
+ monitorsPendingCreation: 400,
128
+ hasUnevaluatedScans: false,
129
+ hasMoreScans: true,
130
+ isTruncated: true,
131
+ }),
132
+ ]);
133
+
134
+ expect(merged.hostsPendingImport).toBe(400);
135
+ expect(merged.monitorsPendingCreation).toBe(400);
136
+ expect(merged.hasUnevaluatedScans).toBe(false);
137
+ expect(merged.hasMoreScans).toBe(true);
138
+ expect(merged.isTruncated).toBe(true);
139
+ });
140
+
141
+ /*
142
+ * The sample answers "which hosts is this rule claiming". Drawn before the
143
+ * run changed anything, it still does; drawn from the final pass it would
144
+ * be whatever happened to be left over.
145
+ */
146
+ it("keeps the first pass's host sample", () => {
147
+ const merged: AutoImportRuleRunResult =
148
+ RuleRunResultUtil.mergeAutoImportRunPasses([
149
+ pass({
150
+ devicesCreated: 1,
151
+ isTruncated: true,
152
+ matchedIpAddressSample: ["10.0.0.1"],
153
+ }),
154
+ pass({ devicesCreated: 1, matchedIpAddressSample: ["10.9.9.9"] }),
155
+ ]);
156
+
157
+ expect(merged.matchedIpAddressSample).toEqual(["10.0.0.1"]);
158
+ });
159
+
160
+ /*
161
+ * Failure reasons are what issue #3643 added to this report, and across a
162
+ * chain they are a set: the same fault reappears in every pass, and a
163
+ * different fault in a later pass is exactly what has to be surfaced.
164
+ */
165
+ it("deduplicates failure reasons across passes and keeps new ones", () => {
166
+ const merged: AutoImportRuleRunResult =
167
+ RuleRunResultUtil.mergeAutoImportRunPasses([
168
+ pass({
169
+ devicesCreated: 500,
170
+ monitorsFailed: 1,
171
+ monitorFailureReasons: ["Template could not be loaded."],
172
+ deviceFailureReasons: ["Name already taken."],
173
+ isTruncated: true,
174
+ }),
175
+ pass({
176
+ devicesCreated: 400,
177
+ monitorsFailed: 2,
178
+ monitorFailureReasons: [
179
+ "Template could not be loaded.",
180
+ "Monitor limit reached.",
181
+ ],
182
+ deviceFailureReasons: ["Name already taken."],
183
+ }),
184
+ ]);
185
+
186
+ expect(merged.monitorFailureReasons).toEqual([
187
+ "Template could not be loaded.",
188
+ "Monitor limit reached.",
189
+ ]);
190
+ expect(merged.deviceFailureReasons).toEqual(["Name already taken."]);
191
+ });
192
+
193
+ it("caps the merged reason list the way one pass is capped", () => {
194
+ const merged: AutoImportRuleRunResult =
195
+ RuleRunResultUtil.mergeAutoImportRunPasses([
196
+ pass({
197
+ devicesCreated: 1,
198
+ isTruncated: true,
199
+ monitorFailureReasons: ["one", "two"],
200
+ }),
201
+ pass({ devicesCreated: 1, monitorFailureReasons: ["three", "four"] }),
202
+ ]);
203
+
204
+ expect(merged.monitorFailureReasons).toEqual(["one", "two", "three"]);
205
+ });
206
+
207
+ /*
208
+ * Halting is a property of the pass that halted, and the chain stops after
209
+ * one — so reading only the last pass would lose it.
210
+ */
211
+ it("reports a halt from any pass, not only the last", () => {
212
+ const merged: AutoImportRuleRunResult =
213
+ RuleRunResultUtil.mergeAutoImportRunPasses([
214
+ pass({
215
+ devicesCreated: 500,
216
+ monitorProvisioningHalted: true,
217
+ isTruncated: true,
218
+ }),
219
+ pass({ devicesCreated: 400, monitorProvisioningHalted: false }),
220
+ ]);
221
+
222
+ expect(merged.monitorProvisioningHalted).toBe(true);
223
+ });
224
+
225
+ it("keeps the dry-run flag so a preview never reads as a write", () => {
226
+ const merged: AutoImportRuleRunResult =
227
+ RuleRunResultUtil.mergeAutoImportRunPasses([
228
+ pass({ isDryRun: true, monitorsWouldCreate: 500, isTruncated: true }),
229
+ pass({ isDryRun: true, monitorsWouldCreate: 9 }),
230
+ ]);
231
+
232
+ expect(merged.isDryRun).toBe(true);
233
+ expect(merged.monitorsWouldCreate).toBe(509);
234
+ });
235
+ });
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AutoImportRuleRunResult,
3
3
  LabelRuleRunResult,
4
+ MAX_RUN_FAILURE_REASON_LENGTH,
4
5
  RuleRunResultUtil,
5
6
  SiteAssignmentRuleRunResult,
6
7
  } from "../../../Types/NetworkAutomation/RuleRunResult";
@@ -199,6 +200,9 @@ describe("RuleRunResultUtil.parseAutoImportRuleRunResult", () => {
199
200
  monitorsSkippedAlreadyExisting: 2,
200
201
  monitorsSkippedUnsupportedHost: 1,
201
202
  monitorsFailed: 1,
203
+ deviceFailureReasons: ["Name too long"],
204
+ monitorFailureReasons: ["Plan limit reached"],
205
+ monitorProvisioningHalted: true,
202
206
  isTruncated: true,
203
207
  hasMoreScans: true,
204
208
  isDryRun: false,
@@ -217,13 +221,75 @@ describe("RuleRunResultUtil.parseAutoImportRuleRunResult", () => {
217
221
  monitorsSkippedAlreadyExisting: 2,
218
222
  monitorsSkippedUnsupportedHost: 1,
219
223
  monitorsFailed: 1,
224
+ deviceFailureReasons: ["Name too long"],
225
+ monitorFailureReasons: ["Plan limit reached"],
226
+ monitorProvisioningHalted: true,
220
227
  isTruncated: true,
228
+ /*
229
+ * Absent from the payload above, which is what a pre-#3642 server
230
+ * sends: a truncated run from one of those reports nothing pending
231
+ * rather than NaN, and the summary falls back to its unquantified
232
+ * "stopped at the run cap" sentence.
233
+ */
234
+ hostsPendingImport: 0,
235
+ monitorsPendingCreation: 0,
236
+ hasUnevaluatedScans: false,
221
237
  hasMoreScans: true,
222
238
  isDryRun: false,
223
239
  matchedIpAddressSample: ["10.0.0.1", "10.0.0.2"],
224
240
  });
225
241
  });
226
242
 
243
+ /*
244
+ * The reasons come from a server that may be older than this dashboard, or
245
+ * from a hand-rolled client. Anything that is not a usable string is dropped
246
+ * rather than rendered into the modal, and the list is bounded on both axes
247
+ * so a pathological payload cannot become the whole dialog.
248
+ */
249
+ it("reads absent failure reasons as empty rather than undefined", () => {
250
+ const parsed: AutoImportRuleRunResult =
251
+ RuleRunResultUtil.parseAutoImportRuleRunResult({ hostsEvaluated: 1 });
252
+
253
+ expect(parsed.deviceFailureReasons).toEqual([]);
254
+ expect(parsed.monitorFailureReasons).toEqual([]);
255
+ expect(parsed.monitorProvisioningHalted).toBe(false);
256
+ });
257
+
258
+ it("drops non-string, blank and surplus failure reasons", () => {
259
+ const parsed: AutoImportRuleRunResult =
260
+ RuleRunResultUtil.parseAutoImportRuleRunResult({
261
+ monitorFailureReasons: [
262
+ " plan limit ",
263
+ "",
264
+ " ",
265
+ 42,
266
+ null,
267
+ "duplicate key",
268
+ "third",
269
+ "fourth",
270
+ ],
271
+ deviceFailureReasons: "not an array",
272
+ } as unknown as JSONObject);
273
+
274
+ expect(parsed.monitorFailureReasons).toEqual([
275
+ "plan limit",
276
+ "duplicate key",
277
+ "third",
278
+ ]);
279
+ expect(parsed.deviceFailureReasons).toEqual([]);
280
+ });
281
+
282
+ it("truncates an over-long failure reason", () => {
283
+ const parsed: AutoImportRuleRunResult =
284
+ RuleRunResultUtil.parseAutoImportRuleRunResult({
285
+ monitorFailureReasons: ["x".repeat(MAX_RUN_FAILURE_REASON_LENGTH + 50)],
286
+ } as unknown as JSONObject);
287
+
288
+ expect(parsed.monitorFailureReasons[0]!.length).toBe(
289
+ MAX_RUN_FAILURE_REASON_LENGTH,
290
+ );
291
+ });
292
+
227
293
  it("reads every missing monitor counter as zero for older servers", () => {
228
294
  const parsed: AutoImportRuleRunResult =
229
295
  RuleRunResultUtil.parseAutoImportRuleRunResult({
@@ -27,7 +27,13 @@ function result(
27
27
  monitorsSkippedAlreadyExisting: 0,
28
28
  monitorsSkippedUnsupportedHost: 0,
29
29
  monitorsFailed: 0,
30
+ deviceFailureReasons: [],
31
+ monitorFailureReasons: [],
32
+ monitorProvisioningHalted: false,
30
33
  isTruncated: false,
34
+ hostsPendingImport: 0,
35
+ monitorsPendingCreation: 0,
36
+ hasUnevaluatedScans: false,
31
37
  hasMoreScans: false,
32
38
  isDryRun: false,
33
39
  matchedIpAddressSample: [],
@@ -59,7 +65,29 @@ describe("RuleRunResultUtil.describeAutoImportRun", () => {
59
65
  hostsEvaluated: 12,
60
66
  });
61
67
  expect(summary).toContain("No devices were imported.");
62
- expect(summary).toContain("matched 0 hosts out of the 12 hosts");
68
+ expect(summary).toContain(
69
+ "matched 0 hosts out of the 12 discovered hosts it looked at",
70
+ );
71
+ });
72
+
73
+ /*
74
+ * The denominator used to compose two nouns — "out of the 501 hosts
75
+ * discovered hosts it looked at" — which is what the report on issue #3643
76
+ * actually read.
77
+ */
78
+ it("does not double the noun in the matched-out-of sentence", () => {
79
+ expect(describe_({ hostsMatched: 501, hostsEvaluated: 501 })).not.toContain(
80
+ "hosts discovered hosts",
81
+ );
82
+ expect(
83
+ describe_({ isDryRun: true, hostsMatched: 0, hostsEvaluated: 501 }),
84
+ ).not.toContain("hosts discovered hosts");
85
+ });
86
+
87
+ it("uses singular grammar for a single discovered host", () => {
88
+ expect(describe_({ hostsMatched: 0, hostsEvaluated: 1 })).toContain(
89
+ "out of the 1 discovered host it looked at",
90
+ );
63
91
  });
64
92
 
65
93
  it("a dry run that would import reports the projection and writes nothing", () => {
@@ -137,9 +165,289 @@ describe("RuleRunResultUtil.describeAutoImportRun", () => {
137
165
  expect(dry).not.toContain("run again to continue");
138
166
  });
139
167
 
168
+ /*
169
+ * Issue #3643. A run that reached the cap having created nothing has nothing
170
+ * to resume: the identical run would fail identically. "Run again to
171
+ * continue" was the advice that report gave, and it was an instruction to
172
+ * repeat a doomed run.
173
+ */
174
+ it("does not promise progress for a truncated run that created nothing", () => {
175
+ const summary: string = describe_({
176
+ isTruncated: true,
177
+ hostsMatched: 501,
178
+ hostsEvaluated: 501,
179
+ hostsSkippedAlreadyRegistered: 501,
180
+ devicesCreated: 0,
181
+ monitorsCreated: 0,
182
+ monitorsFailed: 500,
183
+ });
184
+
185
+ expect(summary).not.toContain("run again to continue");
186
+ expect(summary).toContain(
187
+ "Stopped at the run cap without creating anything",
188
+ );
189
+ expect(summary).toContain("fix the failures reported above first");
190
+ });
191
+
192
+ it("still promises progress for a truncated run that created monitors only", () => {
193
+ expect(
194
+ describe_({
195
+ isTruncated: true,
196
+ devicesCreated: 0,
197
+ monitorsCreated: 12,
198
+ }),
199
+ ).toContain("run again to continue");
200
+ });
201
+
202
+ it("gives the reason monitors could not be created instead of pointing at logs", () => {
203
+ const summary: string = describe_({
204
+ hostsMatched: 501,
205
+ hostsEvaluated: 501,
206
+ monitorsFailed: 501,
207
+ monitorFailureReasons: [
208
+ "This rule's Monitor Template could not be loaded",
209
+ ],
210
+ });
211
+
212
+ expect(summary).toContain(
213
+ "Reason: This rule's Monitor Template could not be loaded.",
214
+ );
215
+ expect(summary).not.toContain("check the server logs");
216
+ });
217
+
218
+ it("still points at the logs when the server recorded no reason", () => {
219
+ expect(describe_({ monitorsFailed: 2 })).toContain(
220
+ "Their network devices remain imported. Check the server logs for the reason.",
221
+ );
222
+ expect(describe_({ devicesFailed: 2 })).toContain(
223
+ "2 hosts could not be imported. Check the server logs for the reason.",
224
+ );
225
+ });
226
+
227
+ it("renders several distinct reasons as one list and punctuates each", () => {
228
+ const summary: string = describe_({
229
+ monitorsFailed: 4,
230
+ monitorFailureReasons: ["Plan limit reached", "Duplicate key."],
231
+ });
232
+
233
+ expect(summary).toContain("Reasons: Plan limit reached. Duplicate key.");
234
+ });
235
+
236
+ it("gives the reason devices could not be imported", () => {
237
+ expect(
238
+ describe_({
239
+ devicesFailed: 3,
240
+ deviceFailureReasons: ["Device name is too long"],
241
+ }),
242
+ ).toContain(
243
+ "3 hosts could not be imported. Reason: Device name is too long.",
244
+ );
245
+ });
246
+
247
+ it("says monitor provisioning stopped rather than leaving it implied", () => {
248
+ const summary: string = describe_({
249
+ hostsMatched: 501,
250
+ hostsEvaluated: 501,
251
+ monitorsFailed: 501,
252
+ monitorProvisioningHalted: true,
253
+ monitorFailureReasons: ["Plan limit reached"],
254
+ });
255
+
256
+ expect(summary).toContain(
257
+ "Monitor provisioning stopped for the rest of this run",
258
+ );
259
+ expect(summary).toContain("Fix it and run this rule again.");
260
+ });
261
+
140
262
  it("warns when older scans went unread", () => {
141
263
  expect(describe_({ hasMoreScans: true, devicesCreated: 1 })).toContain(
142
264
  "Only the newest 100 completed scans were read",
143
265
  );
144
266
  });
267
+
268
+ /*
269
+ * OneUptime issue #3642: "909 discovered, 500+ imported, the rest silently
270
+ * skipped". The cap itself was working as designed; what made it read as
271
+ * silence was a report that named neither how much was left nor the fact
272
+ * that the monitor half of the work had its own separate ceiling.
273
+ */
274
+ describe("what a capped run says it left behind (issue #3642)", () => {
275
+ it("names the hosts a device cap left un-imported", () => {
276
+ const summary: string = describe_({
277
+ isTruncated: true,
278
+ devicesCreated: 500,
279
+ hostsEvaluated: 909,
280
+ hostsMatched: 909,
281
+ hostsPendingImport: 409,
282
+ });
283
+
284
+ expect(summary).toContain("Imported 500 hosts as network devices.");
285
+ expect(summary).toContain(
286
+ "Stopped at the run cap with 409 hosts still to import.",
287
+ );
288
+ expect(summary).toContain("Run again to continue");
289
+ });
290
+
291
+ it("names the devices a monitor cap left unmonitored", () => {
292
+ const summary: string = describe_({
293
+ isTruncated: true,
294
+ devicesCreated: 0,
295
+ monitorsCreated: 500,
296
+ hostsEvaluated: 909,
297
+ hostsMatched: 909,
298
+ hostsSkippedAlreadyRegistered: 909,
299
+ monitorsPendingCreation: 409,
300
+ });
301
+
302
+ expect(summary).toContain(
303
+ "Stopped at the run cap with 409 active Network Device monitors still to create.",
304
+ );
305
+ // The device half is not claimed as pending — it was already imported.
306
+ expect(summary).not.toContain("still to import");
307
+ });
308
+
309
+ it("reports both halves when both caps left work behind", () => {
310
+ expect(
311
+ describe_({
312
+ isTruncated: true,
313
+ devicesCreated: 500,
314
+ monitorsCreated: 500,
315
+ hostsPendingImport: 12,
316
+ monitorsPendingCreation: 3,
317
+ }),
318
+ ).toContain(
319
+ "12 hosts still to import and 3 active Network Device monitors still to create",
320
+ );
321
+ });
322
+
323
+ it("uses singular grammar for a remainder of one", () => {
324
+ const summary: string = describe_({
325
+ isTruncated: true,
326
+ devicesCreated: 500,
327
+ hostsPendingImport: 1,
328
+ monitorsPendingCreation: 1,
329
+ });
330
+
331
+ expect(summary).toContain("1 host still to import");
332
+ expect(summary).toContain("1 active Network Device monitor still to");
333
+ });
334
+
335
+ /*
336
+ * A capped run stops opening scans, so what it counted is a floor. It
337
+ * must not be reported as the total, or the operator reads "409 left",
338
+ * runs it again, and finds more than 409 appear.
339
+ */
340
+ it("reports the remainder as a floor when scans went unread", () => {
341
+ expect(
342
+ describe_({
343
+ isTruncated: true,
344
+ devicesCreated: 500,
345
+ hostsPendingImport: 409,
346
+ hasUnevaluatedScans: true,
347
+ }),
348
+ ).toContain("at least 409 hosts still to import");
349
+ });
350
+
351
+ it("quantifies a dry run's remainder without promising a resume", () => {
352
+ const summary: string = describe_({
353
+ isDryRun: true,
354
+ isTruncated: true,
355
+ hostsMatched: 909,
356
+ hostsEvaluated: 909,
357
+ hostsPendingImport: 409,
358
+ });
359
+
360
+ expect(summary).toContain(
361
+ "Stopped counting at the run cap with 409 hosts still to import",
362
+ );
363
+ expect(summary).toContain("device-import and active-monitor creation");
364
+ expect(summary).not.toContain("run again to continue");
365
+ });
366
+
367
+ /*
368
+ * A pre-#3642 server sends no pending counters, so they parse as zero.
369
+ * The sentence must fall back rather than print "with —".
370
+ */
371
+ /*
372
+ * The two truncation truths compose. A run that created nothing resumes
373
+ * nowhere (issue #3643), but the remainder is still the size of the
374
+ * problem to fix, so it is named without the "run again" advice.
375
+ */
376
+ it("names the remainder without promising a resume when nothing was created", () => {
377
+ const summary: string = describe_({
378
+ isTruncated: true,
379
+ devicesCreated: 0,
380
+ monitorsCreated: 0,
381
+ monitorsFailed: 500,
382
+ monitorsPendingCreation: 409,
383
+ });
384
+
385
+ expect(summary).toContain(
386
+ "Stopped at the run cap without creating anything, with 409 active Network Device monitors still to create.",
387
+ );
388
+ expect(summary).toContain("would repeat the same failures");
389
+ expect(summary).not.toContain("Run again to continue");
390
+ });
391
+
392
+ it("falls back to the unquantified sentence with nothing counted", () => {
393
+ const summary: string = describe_({
394
+ isTruncated: true,
395
+ devicesCreated: 5,
396
+ });
397
+
398
+ expect(summary).toContain(
399
+ "Stopped at the run cap — run again to continue",
400
+ );
401
+ expect(summary).not.toContain("still to import");
402
+ });
403
+
404
+ /*
405
+ * A monitor backfill over an already-imported estate creates no devices
406
+ * at all. "No devices were imported" read as total failure directly
407
+ * above "Created 500 active Network Device monitors".
408
+ */
409
+ it("does not read a monitor backfill as an import that did nothing", () => {
410
+ const summary: string = describe_({
411
+ devicesCreated: 0,
412
+ monitorsCreated: 500,
413
+ hostsEvaluated: 909,
414
+ hostsMatched: 909,
415
+ hostsSkippedAlreadyRegistered: 909,
416
+ });
417
+
418
+ expect(summary).toContain("No new network devices were imported.");
419
+ expect(summary).toContain(
420
+ "Created 500 active Network Device monitors from the selected Monitor Template.",
421
+ );
422
+ });
423
+
424
+ it("still says nothing happened when nothing happened", () => {
425
+ expect(describe_({ hostsEvaluated: 9, hostsMatched: 0 })).toContain(
426
+ "No devices were imported. This rule matched 0 hosts out of the 9 discovered hosts it looked at.",
427
+ );
428
+ });
429
+ });
430
+
431
+ /*
432
+ * The "still working" line a chained press shows between passes. It exists
433
+ * because a run that now spans several requests must not look like a hang.
434
+ */
435
+ describe("progress while the passes are still chaining", () => {
436
+ it("names what has landed so far", () => {
437
+ const progress: string = RuleRunResultUtil.describeAutoImportProgress(
438
+ result({ devicesCreated: 500, monitorsCreated: 500 }),
439
+ );
440
+
441
+ expect(progress).toContain(
442
+ "500 hosts imported, 500 active Network Device monitors created so far",
443
+ );
444
+ expect(progress).toContain("leave this open");
445
+ });
446
+
447
+ it("says something even before the first pass has created anything", () => {
448
+ expect(RuleRunResultUtil.describeAutoImportProgress(result({}))).toBe(
449
+ "Still importing…",
450
+ );
451
+ });
452
+ });
145
453
  });