@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,320 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from "@jest/globals";
10
+ import {
11
+ act,
12
+ cleanup,
13
+ fireEvent,
14
+ render,
15
+ screen,
16
+ waitFor,
17
+ } from "@testing-library/react";
18
+ import React, { ReactElement } from "react";
19
+ import { MemoryRouter } from "react-router-dom";
20
+ import NetworkTopologyLiveView from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkTopologyLiveView";
21
+ import { ComponentProps as GraphProps } from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkDeviceGraph";
22
+ import getJestMockFunction, { MockFunction } from "../../MockType";
23
+
24
+ const postMock: MockFunction = getJestMockFunction();
25
+
26
+ jest.mock("../../../UI/Utils/API/API", () => {
27
+ return {
28
+ __esModule: true,
29
+ default: {
30
+ post: (...args: Array<unknown>) => {
31
+ return postMock(...args);
32
+ },
33
+ getFriendlyMessage: (error: Error) => {
34
+ return error.message;
35
+ },
36
+ },
37
+ };
38
+ });
39
+
40
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
41
+ return {
42
+ __esModule: true,
43
+ default: {
44
+ getCommonHeaders: () => {
45
+ return {};
46
+ },
47
+ },
48
+ };
49
+ });
50
+
51
+ jest.mock("../../../UI/Utils/PermissionGate", () => {
52
+ return {
53
+ __esModule: true,
54
+ default: {
55
+ check: () => {
56
+ return { isAllowed: false };
57
+ },
58
+ },
59
+ ModelAction: { Create: "create" },
60
+ };
61
+ });
62
+
63
+ jest.mock("../../../UI/Utils/Project", () => {
64
+ return {
65
+ __esModule: true,
66
+ default: {
67
+ getCurrentProjectId: () => {
68
+ return "10000000-0000-4000-8000-000000000001";
69
+ },
70
+ },
71
+ };
72
+ });
73
+
74
+ jest.mock("../../../Models/DatabaseModels/NetworkDevice", () => {
75
+ return { __esModule: true, default: class NetworkDevice {} };
76
+ });
77
+
78
+ jest.mock("../../../Models/DatabaseModels/NetworkTopologySuppression", () => {
79
+ return { __esModule: true, default: class NetworkTopologySuppression {} };
80
+ });
81
+
82
+ jest.mock(
83
+ "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkDeviceDetailPanel",
84
+ () => {
85
+ return {
86
+ __esModule: true,
87
+ default: () => {
88
+ return null;
89
+ },
90
+ };
91
+ },
92
+ );
93
+
94
+ jest.mock(
95
+ "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkLinkDetailPanel",
96
+ () => {
97
+ return {
98
+ __esModule: true,
99
+ default: () => {
100
+ return null;
101
+ },
102
+ };
103
+ },
104
+ );
105
+
106
+ jest.mock(
107
+ "../../../../App/FeatureSet/Dashboard/src/Components/Topology/AddNeighborToMonitoringModal",
108
+ () => {
109
+ return {
110
+ __esModule: true,
111
+ default: () => {
112
+ return null;
113
+ },
114
+ };
115
+ },
116
+ );
117
+
118
+ /*
119
+ * Keep the live view, controls and state real; the canvas is represented by
120
+ * its inputs so these tests do not need a browser graphics implementation.
121
+ */
122
+ jest.mock(
123
+ "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkDeviceGraph",
124
+ () => {
125
+ return {
126
+ __esModule: true,
127
+ default: (props: GraphProps): ReactElement => {
128
+ return (
129
+ <div
130
+ data-testid="live-network-graph"
131
+ data-search={props.searchText}
132
+ data-layout={props.layoutMode}
133
+ data-health={props.healthFilterMode}
134
+ data-kinds={Array.from(props.visibleKinds || [])
135
+ .sort()
136
+ .join(",")}
137
+ data-node-count={props.topology.nodes.length}
138
+ >
139
+ Network canvas
140
+ </div>
141
+ );
142
+ },
143
+ };
144
+ },
145
+ );
146
+
147
+ const PAYLOAD: Record<string, unknown> = {
148
+ data: {
149
+ nodes: [
150
+ {
151
+ id: "router-1",
152
+ name: "Branch router",
153
+ kind: "device",
154
+ isManaged: true,
155
+ status: "down",
156
+ },
157
+ {
158
+ id: "switch-1",
159
+ name: "Access switch",
160
+ kind: "device",
161
+ isManaged: true,
162
+ status: "up",
163
+ },
164
+ {
165
+ id: "peer-1",
166
+ name: "Discovered phone",
167
+ kind: "unmanaged",
168
+ isManaged: false,
169
+ status: "unknown",
170
+ },
171
+ {
172
+ id: "endpoint-1",
173
+ name: "Workstation",
174
+ kind: "endpoint",
175
+ isManaged: false,
176
+ status: "up",
177
+ vlanId: 20,
178
+ },
179
+ ],
180
+ edges: [],
181
+ },
182
+ };
183
+
184
+ async function renderMap(): Promise<void> {
185
+ render(
186
+ <MemoryRouter>
187
+ <NetworkTopologyLiveView siteId="branch-1" layoutMode="tiered" />
188
+ </MemoryRouter>,
189
+ );
190
+ await screen.findByTestId("live-network-graph");
191
+ }
192
+
193
+ describe("network topology live view integration", () => {
194
+ beforeEach(() => {
195
+ postMock.mockReset();
196
+ postMock.mockResolvedValue(PAYLOAD);
197
+ window.localStorage.clear();
198
+ });
199
+
200
+ afterEach(() => {
201
+ cleanup();
202
+ jest.useRealTimers();
203
+ });
204
+
205
+ test("fetches only the requested site and preserves its initial layout", async () => {
206
+ await renderMap();
207
+ expect(postMock).toHaveBeenCalledWith(
208
+ expect.objectContaining({
209
+ data: {
210
+ projectId: "10000000-0000-4000-8000-000000000001",
211
+ siteId: "branch-1",
212
+ },
213
+ }),
214
+ );
215
+ expect(screen.getByTestId("live-network-graph")).toHaveAttribute(
216
+ "data-layout",
217
+ "tiered",
218
+ );
219
+ expect(screen.getByText(/Updated \d/)).toBeVisible();
220
+ });
221
+
222
+ test("clear filters restores the map while preserving the chosen arrangement", async () => {
223
+ await renderMap();
224
+ fireEvent.click(screen.getByRole("button", { name: /Map options/ }));
225
+ fireEvent.click(screen.getByRole("button", { name: "Hub and spoke" }));
226
+ fireEvent.click(screen.getByRole("button", { name: "Endpoints" }));
227
+ fireEvent.click(screen.getByRole("button", { name: /Needs attention/ }));
228
+ fireEvent.change(
229
+ screen.getByRole("textbox", { name: "Find a network device" }),
230
+ { target: { value: "router" } },
231
+ );
232
+ const graph: HTMLElement = screen.getByTestId("live-network-graph");
233
+ expect(graph).toHaveAttribute("data-health", "attention");
234
+ expect(graph).toHaveAttribute("data-search", "router");
235
+ expect(graph).toHaveAttribute("data-kinds", "device,unmanaged");
236
+ fireEvent.click(screen.getByRole("button", { name: "Clear filters" }));
237
+ expect(graph).toHaveAttribute("data-health", "all");
238
+ expect(graph).toHaveAttribute("data-search", "");
239
+ expect(graph).toHaveAttribute("data-kinds", "device,endpoint,unmanaged");
240
+ expect(graph).toHaveAttribute("data-layout", "star");
241
+ expect(
242
+ screen.queryByRole("button", { name: "Clear filters" }),
243
+ ).not.toBeInTheDocument();
244
+ });
245
+
246
+ test("closing and reopening options keeps the selected filters", async () => {
247
+ await renderMap();
248
+ fireEvent.click(screen.getByRole("button", { name: /Map options/ }));
249
+ fireEvent.click(
250
+ screen.getByRole("button", { name: "Discovered neighbors" }),
251
+ );
252
+ fireEvent.click(screen.getByRole("button", { name: /Map options/ }));
253
+ expect(
254
+ screen.getByRole("button", { name: /Map options/ }),
255
+ ).toHaveTextContent("1");
256
+ expect(screen.getByTestId("live-network-graph")).toHaveAttribute(
257
+ "data-kinds",
258
+ "device,endpoint",
259
+ );
260
+ fireEvent.click(screen.getByRole("button", { name: /Map options/ }));
261
+ expect(
262
+ screen.getByRole("button", { name: "Discovered neighbors" }),
263
+ ).toHaveAttribute("aria-pressed", "false");
264
+ });
265
+
266
+ test("an initial failure offers a working retry", async () => {
267
+ postMock.mockRejectedValueOnce(new Error("Network unavailable"));
268
+ render(
269
+ <MemoryRouter>
270
+ <NetworkTopologyLiveView />
271
+ </MemoryRouter>,
272
+ );
273
+ await screen.findByText("Network unavailable");
274
+ expect(screen.queryByTestId("live-network-graph")).not.toBeInTheDocument();
275
+ fireEvent.click(screen.getByRole("button", { name: "Try again" }));
276
+ await screen.findByTestId("live-network-graph");
277
+ expect(postMock).toHaveBeenCalledTimes(2);
278
+ });
279
+
280
+ test("a manual refresh failure preserves the visible map and its filters", async () => {
281
+ await renderMap();
282
+ fireEvent.change(
283
+ screen.getByRole("textbox", { name: "Find a network device" }),
284
+ { target: { value: "switch" } },
285
+ );
286
+ postMock.mockRejectedValueOnce(new Error("Refresh unavailable"));
287
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
288
+ await screen.findByTestId("network-topology-refresh-error");
289
+ expect(screen.getByTestId("live-network-graph")).toHaveAttribute(
290
+ "data-search",
291
+ "switch",
292
+ );
293
+ expect(screen.getByText("Refresh failed")).toBeVisible();
294
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
295
+ await waitFor(() => {
296
+ expect(
297
+ screen.queryByTestId("network-topology-refresh-error"),
298
+ ).not.toBeInTheDocument();
299
+ });
300
+ });
301
+
302
+ test("an automatic refresh failure is visible and recovers on the next successful poll", async () => {
303
+ jest.useFakeTimers();
304
+ await renderMap();
305
+ postMock.mockRejectedValueOnce(new Error("Polling unavailable"));
306
+ await act(async () => {
307
+ jest.advanceTimersByTime(60000);
308
+ });
309
+ expect(
310
+ screen.getByTestId("network-topology-refresh-error"),
311
+ ).toHaveTextContent("Polling unavailable");
312
+ expect(screen.getByTestId("live-network-graph")).toBeVisible();
313
+ await act(async () => {
314
+ jest.advanceTimersByTime(60000);
315
+ });
316
+ expect(
317
+ screen.queryByTestId("network-topology-refresh-error"),
318
+ ).not.toBeInTheDocument();
319
+ });
320
+ });
@@ -0,0 +1,275 @@
1
+ import "@testing-library/jest-dom";
2
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
3
+ import {
4
+ cleanup,
5
+ fireEvent,
6
+ render,
7
+ screen,
8
+ within,
9
+ } from "@testing-library/react";
10
+ import React from "react";
11
+ import NetworkTopologyToolbar, {
12
+ ComponentProps,
13
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkTopologyToolbar";
14
+ import { ALL_NODE_KINDS } from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/NetworkTopologyViewModel";
15
+ import {
16
+ buildHealthFilterOptions,
17
+ TopologyHealthSummary,
18
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/TopologyHealthFilter";
19
+
20
+ const HEALTH: TopologyHealthSummary = {
21
+ total: 12,
22
+ healthy: 8,
23
+ down: 2,
24
+ degraded: 1,
25
+ unknown: 1,
26
+ attention: 3,
27
+ };
28
+
29
+ function toolbarProps(overrides: Partial<ComponentProps> = {}): ComponentProps {
30
+ return {
31
+ searchText: "",
32
+ onSearchChange: jest.fn(),
33
+ layoutMode: "force",
34
+ onLayoutChange: jest.fn(),
35
+ availableKinds: ALL_NODE_KINDS,
36
+ visibleKinds: ALL_NODE_KINDS,
37
+ onKindToggle: jest.fn(),
38
+ vlanOptions: [
39
+ { label: "All VLANs", value: "all" },
40
+ { label: "VLAN 20", value: "20" },
41
+ ],
42
+ selectedVlan: "all",
43
+ onVlanChange: jest.fn(),
44
+ healthSummary: HEALTH,
45
+ healthFilterMode: "all",
46
+ healthChipOptions: buildHealthFilterOptions(HEALTH),
47
+ healthFilterMatchCount: 12,
48
+ onHealthChange: jest.fn(),
49
+ onResetFilters: jest.fn(),
50
+ ...overrides,
51
+ };
52
+ }
53
+
54
+ function openOptions(): void {
55
+ fireEvent.click(screen.getByRole("button", { name: /Map options/ }));
56
+ }
57
+
58
+ describe("network map controls", () => {
59
+ afterEach(cleanup);
60
+
61
+ test("starts with search and health filters while advanced options are closed", () => {
62
+ render(<NetworkTopologyToolbar {...toolbarProps()} />);
63
+ expect(
64
+ screen.getByRole("textbox", { name: "Find a network device" }),
65
+ ).toBeVisible();
66
+ expect(
67
+ screen.getByRole("group", { name: "Filter by device health" }),
68
+ ).toBeVisible();
69
+ expect(screen.getByRole("button", { name: /Map options/ })).toHaveAttribute(
70
+ "aria-expanded",
71
+ "false",
72
+ );
73
+ expect(
74
+ screen.queryByRole("group", { name: "Topology layout" }),
75
+ ).not.toBeInTheDocument();
76
+ expect(
77
+ screen.queryByRole("button", { name: "Clear filters" }),
78
+ ).not.toBeInTheDocument();
79
+ });
80
+
81
+ test("the disclosure is accessible and can be closed again", () => {
82
+ render(<NetworkTopologyToolbar {...toolbarProps()} />);
83
+ openOptions();
84
+ const toggle: HTMLElement = screen.getByRole("button", {
85
+ name: /Map options/,
86
+ });
87
+ expect(toggle).toHaveAttribute("aria-expanded", "true");
88
+ expect(screen.getByTestId("network-topology-options")).toHaveAttribute(
89
+ "id",
90
+ toggle.getAttribute("aria-controls"),
91
+ );
92
+ expect(
93
+ screen.getByRole("group", { name: "Topology layout" }),
94
+ ).toBeVisible();
95
+ fireEvent.click(toggle);
96
+ expect(
97
+ screen.queryByTestId("network-topology-options"),
98
+ ).not.toBeInTheDocument();
99
+ });
100
+
101
+ test.each([
102
+ ["Automatic", "force"],
103
+ ["By network layer", "tiered"],
104
+ ["Concentric", "radial"],
105
+ ["Hub and spoke", "star"],
106
+ ["Parent and child", "parentChild"],
107
+ ])("keeps the %s arrangement available", (label: string, mode: string) => {
108
+ const props: ComponentProps = toolbarProps();
109
+ render(<NetworkTopologyToolbar {...props} />);
110
+ openOptions();
111
+ fireEvent.click(screen.getByRole("button", { name: label }));
112
+ expect(props.onLayoutChange).toHaveBeenCalledWith(mode);
113
+ });
114
+
115
+ test("explains the selected layout and reports it as selected", () => {
116
+ render(
117
+ <NetworkTopologyToolbar {...toolbarProps({ layoutMode: "tiered" })} />,
118
+ );
119
+ openOptions();
120
+ expect(
121
+ screen.getByRole("button", { name: "By network layer" }),
122
+ ).toHaveAttribute("aria-pressed", "true");
123
+ expect(screen.getByRole("button", { name: "Automatic" })).toHaveAttribute(
124
+ "aria-pressed",
125
+ "false",
126
+ );
127
+ expect(
128
+ screen.getByText(/Places core, distribution and access devices in rows/),
129
+ ).toBeVisible();
130
+ });
131
+
132
+ test("only offers node kinds present in this network", () => {
133
+ render(
134
+ <NetworkTopologyToolbar
135
+ {...toolbarProps({ availableKinds: new Set(["device"]) })}
136
+ />,
137
+ );
138
+ openOptions();
139
+ const kinds: HTMLElement = screen.getByRole("group", {
140
+ name: "Node types",
141
+ });
142
+ expect(
143
+ within(kinds).getByRole("button", { name: "Monitored devices" }),
144
+ ).toBeVisible();
145
+ expect(
146
+ within(kinds).queryByRole("button", { name: "Endpoints" }),
147
+ ).not.toBeInTheDocument();
148
+ expect(
149
+ within(kinds).queryByRole("button", { name: "Discovered neighbors" }),
150
+ ).not.toBeInTheDocument();
151
+ });
152
+
153
+ test("can hide and restore discovered neighbors", () => {
154
+ const props: ComponentProps = toolbarProps({
155
+ visibleKinds: new Set(["device", "endpoint"]),
156
+ });
157
+ render(<NetworkTopologyToolbar {...props} />);
158
+ openOptions();
159
+ const neighbors: HTMLElement = screen.getByRole("button", {
160
+ name: "Discovered neighbors",
161
+ });
162
+ expect(neighbors).toHaveAttribute("aria-pressed", "false");
163
+ fireEvent.click(neighbors);
164
+ expect(props.onKindToggle).toHaveBeenCalledWith("unmanaged");
165
+ });
166
+
167
+ test("indicates hidden filters even while Map options is closed", () => {
168
+ render(
169
+ <NetworkTopologyToolbar
170
+ {...toolbarProps({
171
+ visibleKinds: new Set(["device"]),
172
+ selectedVlan: "20",
173
+ })}
174
+ />,
175
+ );
176
+ expect(
177
+ screen.getByRole("button", { name: /Map options/ }),
178
+ ).toHaveTextContent("2");
179
+ expect(screen.getByRole("button", { name: "Clear filters" })).toBeVisible();
180
+ expect(
181
+ screen.queryByTestId("network-topology-options"),
182
+ ).not.toBeInTheDocument();
183
+ });
184
+
185
+ test("does not claim a type filter is active for a kind absent from the payload", () => {
186
+ render(
187
+ <NetworkTopologyToolbar
188
+ {...toolbarProps({
189
+ availableKinds: new Set(["device"]),
190
+ visibleKinds: new Set(["device"]),
191
+ })}
192
+ />,
193
+ );
194
+ expect(
195
+ screen.queryByRole("button", { name: "Clear filters" }),
196
+ ).not.toBeInTheDocument();
197
+ });
198
+
199
+ test("does not offer a VLAN selector when none were discovered", () => {
200
+ render(
201
+ <NetworkTopologyToolbar
202
+ {...toolbarProps({
203
+ vlanOptions: [{ label: "All VLANs", value: "all" }],
204
+ })}
205
+ />,
206
+ );
207
+ openOptions();
208
+ expect(
209
+ screen.queryByTestId("network-topology-vlan-filter"),
210
+ ).not.toBeInTheDocument();
211
+ });
212
+
213
+ test("explains the endpoint-only scope of the VLAN selector", () => {
214
+ render(<NetworkTopologyToolbar {...toolbarProps()} />);
215
+ openOptions();
216
+ expect(screen.getByTestId("network-topology-vlan-filter")).toBeVisible();
217
+ expect(
218
+ screen.getByText(
219
+ "Filters endpoints. Network devices stay visible for context.",
220
+ ),
221
+ ).toBeVisible();
222
+ });
223
+
224
+ test("search reports input to the live map", () => {
225
+ const props: ComponentProps = toolbarProps();
226
+ render(<NetworkTopologyToolbar {...props} />);
227
+ fireEvent.change(
228
+ screen.getByRole("textbox", { name: "Find a network device" }),
229
+ { target: { value: "branch router" } },
230
+ );
231
+ expect(props.onSearchChange).toHaveBeenCalledWith("branch router");
232
+ });
233
+
234
+ test("selecting a health filter reports the intended mode", () => {
235
+ const props: ComponentProps = toolbarProps();
236
+ render(<NetworkTopologyToolbar {...props} />);
237
+ fireEvent.click(screen.getByRole("button", { name: /Needs attention/ }));
238
+ expect(props.onHealthChange).toHaveBeenCalledWith("attention");
239
+ });
240
+
241
+ test("announces exact health matches without mislabeling degraded devices as down", () => {
242
+ render(
243
+ <NetworkTopologyToolbar
244
+ {...toolbarProps({
245
+ healthFilterMode: "down",
246
+ healthFilterMatchCount: 2,
247
+ })}
248
+ />,
249
+ );
250
+ const hint: HTMLElement = screen.getByTestId(
251
+ "network-topology-health-filter-hint",
252
+ );
253
+ expect(hint).toHaveAttribute("role", "status");
254
+ expect(hint).toHaveTextContent("2 of 12 match this health filter.");
255
+ expect(hint).toHaveTextContent(
256
+ "Connected neighbors stay dimmed for context.",
257
+ );
258
+ });
259
+
260
+ test.each([
261
+ { searchText: "router" },
262
+ { selectedVlan: "20" },
263
+ { healthFilterMode: "down" as const },
264
+ { visibleKinds: new Set<"device">(["device"]) },
265
+ ])(
266
+ "offers a single reset for any active filter",
267
+ (overrides: Partial<ComponentProps>) => {
268
+ const props: ComponentProps = toolbarProps(overrides);
269
+ render(<NetworkTopologyToolbar {...props} />);
270
+ fireEvent.click(screen.getByRole("button", { name: "Clear filters" }));
271
+ expect(props.onResetFilters).toHaveBeenCalledTimes(1);
272
+ expect(props.onLayoutChange).not.toHaveBeenCalled();
273
+ },
274
+ );
275
+ });