@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,324 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ cleanup,
4
+ fireEvent,
5
+ render,
6
+ screen,
7
+ waitFor,
8
+ within,
9
+ } from "@testing-library/react";
10
+ import React from "react";
11
+ import NotificationSettings from "../../../../App/FeatureSet/Dashboard/src/Pages/UserSettings/NotificationSettings";
12
+ import UserNotificationSetting from "../../../Models/DatabaseModels/UserNotificationSetting";
13
+ import Route from "../../../Types/API/Route";
14
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
15
+ import Includes from "../../../Types/BaseDatabase/Includes";
16
+ import Query from "../../../Types/BaseDatabase/Query";
17
+ import NotificationSettingEventType from "../../../Types/NotificationSetting/NotificationSettingEventType";
18
+ import ObjectID from "../../../Types/ObjectID";
19
+ import API from "../../../UI/Utils/API/API";
20
+ import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
21
+ import ProjectUtil from "../../../UI/Utils/Project";
22
+ import User from "../../../UI/Utils/User";
23
+
24
+ /*
25
+ * User Settings > Notification Settings: the per-event, per-channel matrix,
26
+ * and nothing else.
27
+ *
28
+ * The two email-VOLUME controls that used to sit above the tabs here - the
29
+ * routine-email preset and the rollup switch - moved to User Settings > Email
30
+ * Preferences, because neither is a per-event choice and both were being read
31
+ * as settings for whichever tab happened to be open. They are covered by
32
+ * UserSettingsEmailPreferences.test.tsx. What this file still has to prove is
33
+ * that the move did not strand anybody: the matrix reads and writes the
34
+ * signed-in member's own rows, and the page says out loud where the volume
35
+ * controls went.
36
+ */
37
+
38
+ const PROJECT_ID: ObjectID = new ObjectID(
39
+ "11111111-1111-4111-8111-111111111111",
40
+ );
41
+ const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
42
+
43
+ describe("user settings > notification settings", () => {
44
+ let rows: Array<UserNotificationSetting>;
45
+ let getList: jest.SpyInstance;
46
+
47
+ beforeEach(() => {
48
+ rows = Object.values(NotificationSettingEventType).map(
49
+ (
50
+ eventType: NotificationSettingEventType,
51
+ index: number,
52
+ ): UserNotificationSetting => {
53
+ const row: UserNotificationSetting = new UserNotificationSetting();
54
+ row._id = `33333333-3333-4333-8333-${String(index).padStart(12, "0")}`;
55
+ row.projectId = PROJECT_ID;
56
+ row.userId = USER_ID;
57
+ row.eventType = eventType;
58
+ row.alertByEmail = true;
59
+ row.alertBySMS = true;
60
+ return row;
61
+ },
62
+ );
63
+ jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
64
+ jest.spyOn(User, "getUserId").mockReturnValue(USER_ID);
65
+ jest
66
+ .spyOn(ModelAPI, "getCommonHeaders")
67
+ .mockReturnValue({ tenantid: PROJECT_ID.toString() });
68
+ jest
69
+ .spyOn(API, "getFriendlyMessage")
70
+ .mockImplementation((err: unknown): string => {
71
+ return err instanceof Error || err instanceof HTTPErrorResponse
72
+ ? err.message
73
+ : "Request failed";
74
+ });
75
+ getList = jest
76
+ .spyOn(ModelAPI, "getList")
77
+ .mockImplementation(
78
+ async (data: Parameters<typeof ModelAPI.getList>[0]): Promise<any> => {
79
+ const query: Query<UserNotificationSetting> = data.query;
80
+ const eventTypes: Array<string> = (query.eventType as Includes)
81
+ .values as Array<string>;
82
+ const settings: Array<UserNotificationSetting> = rows.filter(
83
+ (row: UserNotificationSetting): boolean => {
84
+ return (
85
+ eventTypes.includes(row.eventType!) &&
86
+ row.projectId?.toString() ===
87
+ (query.projectId as ObjectID).toString() &&
88
+ row.userId?.toString() === (query.userId as ObjectID).toString()
89
+ );
90
+ },
91
+ );
92
+ return {
93
+ data: settings,
94
+ count: settings.length,
95
+ };
96
+ },
97
+ );
98
+ });
99
+
100
+ afterEach(() => {
101
+ cleanup();
102
+ jest.restoreAllMocks();
103
+ });
104
+
105
+ function renderPage(): void {
106
+ render(
107
+ <NotificationSettings
108
+ pageRoute={new Route("/user-settings/notification-settings")}
109
+ currentProject={null}
110
+ hasPaymentMethod={true}
111
+ />,
112
+ );
113
+ }
114
+
115
+ async function findEventRow(label: string): Promise<HTMLElement> {
116
+ return (await screen.findByText(label)).closest("tr")!;
117
+ }
118
+
119
+ /*
120
+ * THE POINTER, which is the whole cost of splitting the pages.
121
+ *
122
+ * Somebody drowning in OneUptime mail opens this page, because the menu and
123
+ * every owner-email footer call it the place your email choices live. The
124
+ * two controls that actually reduce the VOLUME are no longer on it. Without
125
+ * this line the matrix reads as the whole story, and the only remedy on
126
+ * offer is switching twenty-one events off by hand.
127
+ */
128
+ describe("the pointer to Email Preferences", () => {
129
+ test("names what this page does not do, and links to the page that does", async () => {
130
+ renderPage();
131
+ await findEventRow("Incident created");
132
+
133
+ expect(
134
+ screen.getByText(
135
+ "These switches decide which notifications reach you, not how much email they add up to.",
136
+ ),
137
+ ).toBeVisible();
138
+
139
+ const link: HTMLElement = screen.getByRole("link", {
140
+ name: "Reduce routine emails or change email rollup in Email Preferences",
141
+ });
142
+
143
+ expect(link).toHaveAttribute(
144
+ "href",
145
+ `/dashboard/${PROJECT_ID.toString()}/user-settings/email-preferences`,
146
+ );
147
+ });
148
+
149
+ test("the link carries the real project id, not the :projectId template", async () => {
150
+ renderPage();
151
+ await findEventRow("Incident created");
152
+
153
+ const href: string = screen
154
+ .getByRole("link", {
155
+ name: "Reduce routine emails or change email rollup in Email Preferences",
156
+ })
157
+ .getAttribute("href")!;
158
+
159
+ expect(href).not.toContain(":projectId");
160
+ expect(href).toContain(PROJECT_ID.toString());
161
+ });
162
+
163
+ /*
164
+ * The volume controls left; nothing of them may be left behind. A stray
165
+ * "Reduce routine emails" button here would post to the bulk endpoint
166
+ * with no card explaining what it does.
167
+ */
168
+ test("neither moved card is still rendered here", async () => {
169
+ renderPage();
170
+ await findEventRow("Incident created");
171
+
172
+ expect(
173
+ screen.queryByRole("button", { name: "Reduce routine emails" }),
174
+ ).not.toBeInTheDocument();
175
+ expect(
176
+ screen.queryByText("Fewer routine emails"),
177
+ ).not.toBeInTheDocument();
178
+ expect(screen.queryByText("Email Rollup")).not.toBeInTheDocument();
179
+ expect(
180
+ screen.queryByRole("switch", { name: /^Roll up notification emails:/ }),
181
+ ).not.toBeInTheDocument();
182
+ });
183
+ });
184
+
185
+ describe("the matrix", () => {
186
+ test("every event has one reachable preference and reads are scoped to the current member", async () => {
187
+ renderPage();
188
+ await findEventRow("Assigned to an incident");
189
+ for (const tabName of [
190
+ "Alerts",
191
+ "Monitoring",
192
+ "Status Pages",
193
+ "Scheduled Maintenance",
194
+ "On-Call",
195
+ ]) {
196
+ fireEvent.click(screen.getByRole("tab", { name: tabName }));
197
+ await waitFor(() => {
198
+ expect(screen.getAllByRole("switch").length).toBeGreaterThan(1);
199
+ });
200
+ if (tabName === "Monitoring") {
201
+ await screen.findByText("AI agent status changed");
202
+ await screen.findByText("Added as AI agent owner");
203
+ }
204
+ }
205
+ const eventReads: Array<any> = getList.mock.calls.map(
206
+ (call: Array<any>): any => {
207
+ return call[0];
208
+ },
209
+ );
210
+ const queriedTypes: Array<string> = eventReads.flatMap(
211
+ (call: any): Array<string> => {
212
+ return call.query.eventType.values;
213
+ },
214
+ );
215
+ expect(queriedTypes.sort()).toEqual(
216
+ Object.values(NotificationSettingEventType).sort(),
217
+ );
218
+ for (const call of eventReads) {
219
+ expect(call.query.projectId).toEqual(PROJECT_ID);
220
+ expect(call.query.userId).toEqual(USER_ID);
221
+ }
222
+ });
223
+
224
+ /*
225
+ * A single cell used to be blocked while the bulk preset ran, because
226
+ * both lived on this page. That interlock is gone with the preset, so the
227
+ * one thing left to prove is that an ordinary toggle still writes exactly
228
+ * one column of exactly one row.
229
+ */
230
+ test("toggling a cell updates that row by id, one column only", async () => {
231
+ const updateById: jest.SpyInstance = jest
232
+ .spyOn(ModelAPI, "updateById")
233
+ .mockResolvedValue({} as any);
234
+
235
+ renderPage();
236
+ const row: HTMLElement = await findEventRow("Incident note posted");
237
+ const emailSwitch: HTMLElement = within(row).getByRole("switch", {
238
+ name: /^Email:/,
239
+ });
240
+
241
+ expect(emailSwitch).toHaveAttribute("aria-checked", "true");
242
+ fireEvent.click(emailSwitch);
243
+
244
+ await waitFor(() => {
245
+ expect(updateById).toHaveBeenCalledTimes(1);
246
+ });
247
+ expect(updateById.mock.calls[0][0].data).toEqual({ alertByEmail: false });
248
+ expect(updateById.mock.calls[0][0].modelType).toBe(
249
+ UserNotificationSetting,
250
+ );
251
+ await waitFor(() => {
252
+ expect(
253
+ within(
254
+ screen.getByText("Incident note posted").closest("tr")!,
255
+ ).getByRole("switch", { name: /^Email:/ }),
256
+ ).toHaveAttribute("aria-checked", "false");
257
+ });
258
+ /* The other channel on the same row is untouched. */
259
+ expect(
260
+ within(
261
+ screen.getByText("Incident note posted").closest("tr")!,
262
+ ).getByRole("switch", { name: /^SMS:/ }),
263
+ ).toHaveAttribute("aria-checked", "true");
264
+ });
265
+
266
+ test("a failed save puts the switch back and says why", async () => {
267
+ jest
268
+ .spyOn(ModelAPI, "updateById")
269
+ .mockRejectedValue(new Error("Preference could not be saved"));
270
+
271
+ renderPage();
272
+ const row: HTMLElement = await findEventRow("Incident note posted");
273
+ fireEvent.click(within(row).getByRole("switch", { name: /^Email:/ }));
274
+
275
+ expect(
276
+ await screen.findByText("Preference could not be saved"),
277
+ ).toBeVisible();
278
+ await waitFor(() => {
279
+ expect(
280
+ within(
281
+ screen.getByText("Incident note posted").closest("tr")!,
282
+ ).getByRole("switch", { name: /^Email:/ }),
283
+ ).toHaveAttribute("aria-checked", "true");
284
+ });
285
+ });
286
+
287
+ test("switching projects re-reads every row against the new project", async () => {
288
+ const { rerender } = render(
289
+ <NotificationSettings
290
+ pageRoute={new Route("/user-settings/notification-settings")}
291
+ currentProject={null}
292
+ hasPaymentMethod={true}
293
+ />,
294
+ );
295
+ await findEventRow("Incident created");
296
+
297
+ const otherProjectId: ObjectID = new ObjectID(
298
+ "44444444-4444-4444-8444-444444444444",
299
+ );
300
+ jest
301
+ .spyOn(ProjectUtil, "getCurrentProjectId")
302
+ .mockReturnValue(otherProjectId);
303
+ getList.mockClear();
304
+ rerender(
305
+ <NotificationSettings
306
+ pageRoute={new Route("/user-settings/notification-settings")}
307
+ currentProject={null}
308
+ hasPaymentMethod={true}
309
+ />,
310
+ );
311
+
312
+ const row: HTMLElement = await findEventRow("Incident created");
313
+ /* No rows exist for the other project, so every cell reads as off. */
314
+ expect(
315
+ within(row).getByRole("switch", { name: /^Email:/ }),
316
+ ).toHaveAttribute("aria-checked", "false");
317
+ expect(getList.mock.calls.length).toBeGreaterThan(0);
318
+ for (const call of getList.mock.calls) {
319
+ expect(call[0].query.projectId).toEqual(otherProjectId);
320
+ expect(call[0].query.userId).toEqual(USER_ID);
321
+ }
322
+ });
323
+ });
324
+ });
@@ -55,6 +55,7 @@ import DashboardVariable, {
55
55
  import DashboardVariableInterpolation, {
56
56
  ResolvedVariableValue,
57
57
  } from "../../../Utils/Dashboard/VariableInterpolation";
58
+ import { VariableValueChange } from "../../../UI/Components/Dashboard/DashboardVariableControl";
58
59
  import ObjectID from "../../../Types/ObjectID";
59
60
 
60
61
  const DASHBOARD_ID: ObjectID = new ObjectID(
@@ -127,11 +128,19 @@ const ViewerHarness: React.FunctionComponent<HarnessProps> = (
127
128
  <DashboardVariableSelector
128
129
  variables={variables}
129
130
  dashboardId={DASHBOARD_ID}
130
- onVariableValueChange={(variableId: string, value: string) => {
131
+ onVariableValueChange={(
132
+ variableId: string,
133
+ change: VariableValueChange,
134
+ ) => {
135
+ const value: string = change.selectedValue || "";
131
136
  const updated: Array<DashboardVariable> = variables.map(
132
137
  (v: DashboardVariable): DashboardVariable => {
133
138
  if (v.id === variableId) {
134
- return { ...v, selectedValue: value };
139
+ return {
140
+ ...v,
141
+ selectedValue: change.selectedValue,
142
+ selectedValues: change.selectedValues,
143
+ };
135
144
  }
136
145
  return v;
137
146
  },
@@ -436,8 +445,8 @@ describe("Public dashboard variable selector", () => {
436
445
  test("each is labelled with its variable name", () => {
437
446
  renderViewer([customListVariable({}), textVariable({})]);
438
447
 
439
- expect(screen.getByText("region:")).toBeInTheDocument();
440
- expect(screen.getByText("service:")).toBeInTheDocument();
448
+ expect(screen.getByText("region")).toBeInTheDocument();
449
+ expect(screen.getByText("service")).toBeInTheDocument();
441
450
  });
442
451
 
443
452
  test('picking "All" on one leaves the others on their defaults', async () => {
@@ -0,0 +1,223 @@
1
+ import type * as StatusPageConfig from "../../../../App/FeatureSet/StatusPage/src/Utils/Config";
2
+ import type { ComponentProps } from "../../../../App/FeatureSet/StatusPage/src/Pages/Accounts/SSO";
3
+ import type * as SharedConfig from "../../../UI/Config";
4
+ import type NavigationUtil from "../../../UI/Utils/Navigation";
5
+ import URL from "../../../Types/API/URL";
6
+ import ObjectID from "../../../Types/ObjectID";
7
+ import { cleanup, fireEvent, render, screen } from "@testing-library/react";
8
+ import React, { FunctionComponent, ReactElement } from "react";
9
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
10
+
11
+ interface ProviderListProps {
12
+ id: string;
13
+ overrideFetchApiUrl: URL;
14
+ onSelectChange: (list: Array<{ _id: string }>) => void;
15
+ }
16
+
17
+ interface LoginEnvironment {
18
+ pageUrl: string;
19
+ host: string;
20
+ protocol: string;
21
+ }
22
+
23
+ /*
24
+ * Keep provider discovery local to this test while exercising the real SSO
25
+ * page's selection handler, URL construction, and redirect storage.
26
+ */
27
+ jest.mock("Common/UI/Components/ModelList/ModelList", () => {
28
+ return {
29
+ __esModule: true,
30
+ default: (props: ProviderListProps): ReactElement => {
31
+ return (
32
+ <button
33
+ data-testid={props.id}
34
+ data-fetch-url={props.overrideFetchApiUrl.toString()}
35
+ onClick={() => {
36
+ props.onSelectChange([
37
+ { _id: "22222222-2222-4222-8222-222222222222" },
38
+ ]);
39
+ }}
40
+ >
41
+ {props.id}
42
+ </button>
43
+ );
44
+ },
45
+ };
46
+ });
47
+
48
+ jest.mock("react-i18next", () => {
49
+ return {
50
+ useTranslation: () => {
51
+ return {
52
+ t: (key: string): string => {
53
+ return key;
54
+ },
55
+ };
56
+ },
57
+ };
58
+ });
59
+
60
+ /*
61
+ * Isolated imports re-evaluate the build's environment-dependent constants.
62
+ * Share React with Testing Library so rendered cases use one hook dispatcher.
63
+ */
64
+ jest.doMock("react", () => {
65
+ return React;
66
+ });
67
+
68
+ const originalLocation: Location = window.location;
69
+ const originalHost: string | undefined = process.env["HOST"];
70
+ const originalProtocol: string | undefined = process.env["HTTP_PROTOCOL"];
71
+ const statusPageId: string = "11111111-1111-4111-8111-111111111111";
72
+ const providerId: string = "22222222-2222-4222-8222-222222222222";
73
+
74
+ function configureEnvironment(data: LoginEnvironment): void {
75
+ process.env["HOST"] = data.host;
76
+ process.env["HTTP_PROTOCOL"] = data.protocol;
77
+ Object.defineProperty(window, "location", {
78
+ configurable: true,
79
+ value: new globalThis.URL(data.pageUrl),
80
+ });
81
+ }
82
+
83
+ afterEach(() => {
84
+ cleanup();
85
+ localStorage.clear();
86
+ jest.restoreAllMocks();
87
+ Object.defineProperty(window, "location", {
88
+ configurable: true,
89
+ value: originalLocation,
90
+ });
91
+ if (originalHost === undefined) {
92
+ delete process.env["HOST"];
93
+ } else {
94
+ process.env["HOST"] = originalHost;
95
+ }
96
+ if (originalProtocol === undefined) {
97
+ delete process.env["HTTP_PROTOCOL"];
98
+ } else {
99
+ process.env["HTTP_PROTOCOL"] = originalProtocol;
100
+ }
101
+ });
102
+
103
+ describe("Status Page OIDC login origin", () => {
104
+ test.each([
105
+ {
106
+ name: "custom status-page domain",
107
+ pageUrl: "https://status.example.com/sso",
108
+ host: "oneuptime.example.com",
109
+ protocol: "https",
110
+ },
111
+ {
112
+ name: "preview on the OneUptime domain",
113
+ pageUrl: `https://oneuptime.example.com/status-page/${statusPageId}/sso`,
114
+ host: "oneuptime.example.com",
115
+ protocol: "https",
116
+ },
117
+ {
118
+ name: "HTTP development with a configured port",
119
+ pageUrl: "http://status.localhost:3002/sso",
120
+ host: "localhost:3000",
121
+ protocol: "http",
122
+ },
123
+ {
124
+ name: "configured HTTPS and port differing from the page",
125
+ pageUrl: "http://status.example.com/sso",
126
+ host: "oneuptime.example.com:8443",
127
+ protocol: "https",
128
+ },
129
+ ])("uses the identity origin for $name", (data: LoginEnvironment) => {
130
+ configureEnvironment(data);
131
+
132
+ jest.isolateModules(() => {
133
+ const config: typeof StatusPageConfig = jest.requireActual(
134
+ "../../../../App/FeatureSet/StatusPage/src/Utils/Config",
135
+ ) as typeof StatusPageConfig;
136
+ const shared: typeof SharedConfig = jest.requireActual(
137
+ "../../../UI/Config",
138
+ ) as typeof SharedConfig;
139
+ const identityOrigin: string = `${data.protocol}://${data.host}`;
140
+ const pageOrigin: string = new globalThis.URL(data.pageUrl).origin;
141
+
142
+ expect(config.STATUS_PAGE_OIDC_API_URL.toString()).toBe(
143
+ `${identityOrigin}/identity/status-page-oidc`,
144
+ );
145
+ // Deriving the OIDC endpoint must not mutate the shared identity URL.
146
+ expect(shared.IDENTITY_URL.toString()).toBe(`${identityOrigin}/identity`);
147
+ expect(config.STATUS_PAGE_API_URL.toString()).toBe(
148
+ `${pageOrigin}/status-page-api`,
149
+ );
150
+ expect(config.STATUS_PAGE_SSO_API_URL.toString()).toBe(
151
+ `${pageOrigin}/status-page-sso-api`,
152
+ );
153
+ expect(config.STATUS_PAGE_IDENTITY_API_URL.toString()).toBe(
154
+ `${pageOrigin}/status-page-identity-api`,
155
+ );
156
+ });
157
+ });
158
+
159
+ test.each([
160
+ {
161
+ name: "custom domain",
162
+ pageOrigin: "https://status.example.com",
163
+ loginPath: "/sso",
164
+ redirectPath: "/incidents/incident-id?tab=timeline#update",
165
+ },
166
+ {
167
+ name: "preview",
168
+ pageOrigin: "https://oneuptime.example.com",
169
+ loginPath: `/status-page/${statusPageId}/sso`,
170
+ redirectPath: `/status-page/${statusPageId}/incidents/incident-id`,
171
+ },
172
+ ])(
173
+ "selecting an OIDC provider from $name navigates to identity and preserves the return path",
174
+ (data: { pageOrigin: string; loginPath: string; redirectPath: string }) => {
175
+ configureEnvironment({
176
+ pageUrl: `${data.pageOrigin}${data.loginPath}?redirectUrl=${encodeURIComponent(data.redirectPath)}`,
177
+ host: "oneuptime.example.com",
178
+ protocol: "https",
179
+ });
180
+ localStorage.setItem("statusPageId", statusPageId);
181
+ localStorage.setItem(`isPrivateStatusPage-${statusPageId}`, "true");
182
+
183
+ jest.isolateModules(() => {
184
+ /*
185
+ * The page's default export is bound as `Sso` rather than `SSO` so the
186
+ * JSX tag stays PascalCase; the status page app renders this same
187
+ * component under the same name in App/FeatureSet/StatusPage/src/App.tsx.
188
+ */
189
+ const Sso: FunctionComponent<ComponentProps> = (
190
+ jest.requireActual(
191
+ "../../../../App/FeatureSet/StatusPage/src/Pages/Accounts/SSO",
192
+ ) as { default: FunctionComponent<ComponentProps> }
193
+ ).default;
194
+ const Navigation: typeof NavigationUtil = (
195
+ jest.requireActual("../../../UI/Utils/Navigation") as {
196
+ default: typeof NavigationUtil;
197
+ }
198
+ ).default;
199
+ const navigate: ReturnType<typeof jest.spyOn> = jest
200
+ .spyOn(Navigation, "navigate")
201
+ .mockImplementation(() => {});
202
+
203
+ render(
204
+ <Sso
205
+ statusPageName="Example Status"
206
+ logoFileId={new ObjectID(statusPageId)}
207
+ />,
208
+ );
209
+ expect(
210
+ screen.getByTestId("oidc-list").getAttribute("data-fetch-url"),
211
+ ).toBe(`${data.pageOrigin}/status-page-api/oidc/${statusPageId}`);
212
+
213
+ fireEvent.click(screen.getByTestId("oidc-list"));
214
+
215
+ expect(navigate).toHaveBeenCalledTimes(1);
216
+ expect(navigate.mock.calls[0]?.[0]?.toString()).toBe(
217
+ `https://oneuptime.example.com/identity/status-page-oidc/${statusPageId}/${providerId}`,
218
+ );
219
+ expect(localStorage.getItem("redirectUrl")).toBe(data.redirectPath);
220
+ });
221
+ },
222
+ );
223
+ });