@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,559 @@
1
+ import "@testing-library/jest-dom";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import * as React from "react";
4
+ import { describe, expect, it } from "@jest/globals";
5
+ import IconProp from "../../../Types/Icon/IconProp";
6
+ import {
7
+ REPLAY_CONTROL_HEIGHT_CLASS,
8
+ ReplayButtonGroup,
9
+ ReplayClock,
10
+ ReplayPill,
11
+ ReplaySwitch,
12
+ ReplayToolButton,
13
+ ReplayToolbarDivider,
14
+ getReplaySegmentClassName,
15
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SessionReplay/ReplayUi";
16
+
17
+ /*
18
+ * The player's shared chrome.
19
+ *
20
+ * These are presentational primitives, so most of what is worth pinning
21
+ * is the part a redesign is most likely to quietly break: the accessible
22
+ * name, the toggle semantics, and the two or three class invariants the
23
+ * whole look rests on (one control height, one radius, "on" drawn
24
+ * differently inside a group than outside one). The visual details that
25
+ * are genuinely taste - which gray, how much padding - are deliberately
26
+ * NOT asserted; a test that pins those makes the design unchangeable
27
+ * without testing anything a viewer would notice.
28
+ */
29
+
30
+ function noop(): void {
31
+ /* Click handlers the assertion does not care about. */
32
+ }
33
+
34
+ describe("ReplayToolButton accessible name", () => {
35
+ /*
36
+ * The regression this exists for: the button used to fall back to its
37
+ * title for aria-label whenever no explicit label was passed, so the
38
+ * stage's "Fit" button announced itself as "Scale the picture to fit
39
+ * the stage". That breaks WCAG 2.5.3 (Label in Name) - a voice user
40
+ * saying "click Fit" hits a control whose name does not contain the
41
+ * word - and it broke getByRole(name: "Fit") for anyone testing it.
42
+ */
43
+ it("uses the visible label as the name and keeps the title a tooltip", () => {
44
+ render(
45
+ <ReplayToolButton
46
+ label="Fit"
47
+ title="Scale the picture to fit the stage"
48
+ onClick={noop}
49
+ />,
50
+ );
51
+
52
+ const button: HTMLElement = screen.getByRole("button", { name: "Fit" });
53
+
54
+ expect(button).toHaveAttribute(
55
+ "title",
56
+ "Scale the picture to fit the stage",
57
+ );
58
+ expect(button).not.toHaveAttribute("aria-label");
59
+ });
60
+
61
+ it("falls back to the title only when there is no visible text at all", () => {
62
+ render(
63
+ <ReplayToolButton
64
+ icon={IconProp.Keyboard}
65
+ title="Keyboard shortcuts (?)"
66
+ onClick={noop}
67
+ />,
68
+ );
69
+
70
+ expect(
71
+ screen.getByRole("button", { name: "Keyboard shortcuts (?)" }),
72
+ ).toHaveAttribute("aria-label", "Keyboard shortcuts (?)");
73
+ });
74
+
75
+ it("lets an explicit ariaLabel win over a short visible label", () => {
76
+ render(
77
+ <ReplayToolButton
78
+ label="10s"
79
+ icon={IconProp.Backward}
80
+ ariaLabel="Back 10 seconds (J)"
81
+ title="Back 10 seconds (J)"
82
+ onClick={noop}
83
+ />,
84
+ );
85
+
86
+ const button: HTMLElement = screen.getByRole("button", {
87
+ name: "Back 10 seconds (J)",
88
+ });
89
+
90
+ expect(button).toHaveTextContent("10s");
91
+ });
92
+ });
93
+
94
+ describe("ReplayToolButton toggle semantics", () => {
95
+ it("renders aria-pressed only when it is actually a toggle", () => {
96
+ const { rerender } = render(
97
+ <ReplayToolButton label="Details" onClick={noop} />,
98
+ );
99
+
100
+ expect(screen.getByRole("button")).not.toHaveAttribute("aria-pressed");
101
+
102
+ rerender(
103
+ <ReplayToolButton label="Wide" isPressed={false} onClick={noop} />,
104
+ );
105
+ expect(screen.getByRole("button")).toHaveAttribute("aria-pressed", "false");
106
+
107
+ rerender(<ReplayToolButton label="Wide" isPressed={true} onClick={noop} />);
108
+ expect(screen.getByRole("button")).toHaveAttribute("aria-pressed", "true");
109
+ });
110
+
111
+ it("draws a pressed ghost as a solid fill and a pressed segment as a raised thumb", () => {
112
+ const { rerender } = render(
113
+ <ReplayToolButton label="Wide" isPressed={true} onClick={noop} />,
114
+ );
115
+
116
+ /*
117
+ * Outside a group there is no track behind the button, so "on" has to
118
+ * be carried by the button itself.
119
+ */
120
+ expect(screen.getByRole("button").className).toContain("bg-gray-900");
121
+
122
+ rerender(
123
+ <ReplayToolButton
124
+ label="Wide"
125
+ variant="segment"
126
+ isPressed={true}
127
+ onClick={noop}
128
+ />,
129
+ );
130
+
131
+ /* Inside a group the recessed track is the contrast; the thumb is white. */
132
+ expect(screen.getByRole("button").className).toContain("bg-white");
133
+ expect(screen.getByRole("button").className).toContain("shadow-sm");
134
+ });
135
+ });
136
+
137
+ describe("ReplayToolButton disabled state", () => {
138
+ it("does not fire onClick and is announced as disabled", () => {
139
+ let clicks: number = 0;
140
+
141
+ render(
142
+ <ReplayToolButton
143
+ label="Next error"
144
+ isDisabled={true}
145
+ onClick={(): void => {
146
+ clicks += 1;
147
+ }}
148
+ />,
149
+ );
150
+
151
+ const button: HTMLElement = screen.getByRole("button", {
152
+ name: "Next error",
153
+ });
154
+
155
+ expect(button).toBeDisabled();
156
+ fireEvent.click(button);
157
+ expect(clicks).toBe(0);
158
+ });
159
+
160
+ it("keeps the disabled look even when the button is a pressed segment", () => {
161
+ render(
162
+ <ReplayToolButton
163
+ label="Tab 3 · no footage"
164
+ variant="segment"
165
+ isPressed={true}
166
+ isDisabled={true}
167
+ onClick={noop}
168
+ />,
169
+ );
170
+
171
+ /* Disabled wins: a dead control must never look selected. */
172
+ expect(screen.getByRole("button").className).toContain("text-gray-300");
173
+ expect(screen.getByRole("button").className).not.toContain("shadow-sm");
174
+ });
175
+ });
176
+
177
+ describe("ReplayToolButton geometry", () => {
178
+ it("is square when it draws a single glyph and padded when it draws more", () => {
179
+ const { rerender } = render(
180
+ <ReplayToolButton
181
+ icon={IconProp.Keyboard}
182
+ title="Keyboard shortcuts"
183
+ onClick={noop}
184
+ />,
185
+ );
186
+
187
+ expect(screen.getByRole("button").className).toContain("w-8");
188
+
189
+ /*
190
+ * "Previous error" is a chevron AND an alert glyph with no label; a
191
+ * 32px square would clip the second one.
192
+ */
193
+ rerender(
194
+ <ReplayToolButton
195
+ icon={IconProp.ChevronLeft}
196
+ trailingIcon={IconProp.Alert}
197
+ title="Previous error"
198
+ onClick={noop}
199
+ />,
200
+ );
201
+ expect(screen.getByRole("button").className).not.toContain("w-8");
202
+ expect(screen.getByRole("button").className).toContain("px-2.5");
203
+
204
+ rerender(<ReplayToolButton label="Details" onClick={noop} />);
205
+ expect(screen.getByRole("button").className).toContain("px-2.5");
206
+ });
207
+
208
+ /*
209
+ * The single rule the whole chrome rests on: one height. Twenty
210
+ * controls at four different heights is what made the old row look
211
+ * like a pile rather than a toolbar, so every path through the
212
+ * component - ghost, segment, disabled, icon-only - keeps it, and the
213
+ * exported class stays the one source of that number.
214
+ */
215
+ it("gives every variant the same control height and radius", () => {
216
+ const variants: Array<React.ReactElement> = [
217
+ <ReplayToolButton key="a" label="Ghost" onClick={noop} />,
218
+ <ReplayToolButton
219
+ key="b"
220
+ label="Segment"
221
+ variant="segment"
222
+ isPressed={true}
223
+ onClick={noop}
224
+ />,
225
+ <ReplayToolButton
226
+ key="c"
227
+ label="Disabled"
228
+ isDisabled={true}
229
+ onClick={noop}
230
+ />,
231
+ <ReplayToolButton
232
+ key="d"
233
+ icon={IconProp.Info}
234
+ title="Icon only"
235
+ onClick={noop}
236
+ />,
237
+ ];
238
+
239
+ variants.forEach((element: React.ReactElement): void => {
240
+ const { unmount } = render(element);
241
+ const button: HTMLElement = screen.getByRole("button");
242
+
243
+ expect(button.className).toContain(REPLAY_CONTROL_HEIGHT_CLASS);
244
+ expect(button.className).toContain("rounded-lg");
245
+ unmount();
246
+ });
247
+ });
248
+ });
249
+
250
+ describe("getReplaySegmentClassName", () => {
251
+ /*
252
+ * The header's browser-tab pills and the rail's signal tabs are real
253
+ * tabs (role="tab", aria-selected, roving tabindex), so they cannot be
254
+ * ReplayToolButtons - but they must not look like a fifth kind of chip
255
+ * either. The exported class is what keeps them identical.
256
+ */
257
+ it("matches what a ReplayToolButton segment renders, for each state", () => {
258
+ render(
259
+ <ReplayToolButton
260
+ label="Selected"
261
+ variant="segment"
262
+ isPressed={true}
263
+ onClick={noop}
264
+ />,
265
+ );
266
+
267
+ const rendered: string = screen.getByRole("button").className;
268
+ const helper: string = getReplaySegmentClassName({ isSelected: true });
269
+
270
+ helper.split(" ").forEach((token: string): void => {
271
+ expect(rendered).toContain(token);
272
+ });
273
+ });
274
+
275
+ it("distinguishes selected, idle and disabled", () => {
276
+ const selected: string = getReplaySegmentClassName({ isSelected: true });
277
+ const idle: string = getReplaySegmentClassName({ isSelected: false });
278
+ const disabled: string = getReplaySegmentClassName({
279
+ isSelected: true,
280
+ isDisabled: true,
281
+ });
282
+
283
+ expect(selected).toContain("bg-white");
284
+ expect(idle).not.toContain("bg-white ");
285
+ expect(disabled).toContain("cursor-not-allowed");
286
+ expect(disabled).not.toContain("shadow-sm");
287
+ });
288
+
289
+ it("keeps the shared height on every state so a tab row cannot go ragged", () => {
290
+ [
291
+ getReplaySegmentClassName({ isSelected: true }),
292
+ getReplaySegmentClassName({ isSelected: false }),
293
+ getReplaySegmentClassName({ isSelected: false, isDisabled: true }),
294
+ ].forEach((className: string): void => {
295
+ expect(className).toContain(REPLAY_CONTROL_HEIGHT_CLASS);
296
+ });
297
+ });
298
+ });
299
+
300
+ describe("ReplayButtonGroup", () => {
301
+ it("is a labelled group holding its members on one recessed track", () => {
302
+ render(
303
+ <ReplayButtonGroup
304
+ ariaLabel="Jump between signals"
305
+ dataTestId="signal-group"
306
+ >
307
+ <ReplayToolButton label="Next error" variant="segment" onClick={noop} />
308
+ <ReplayToolButton
309
+ label="Frustration"
310
+ variant="segment"
311
+ onClick={noop}
312
+ />
313
+ </ReplayButtonGroup>,
314
+ );
315
+
316
+ const group: HTMLElement = screen.getByRole("group", {
317
+ name: "Jump between signals",
318
+ });
319
+
320
+ expect(group).toBe(screen.getByTestId("signal-group"));
321
+ /* ONE container background for N buttons: no per-button outlines. */
322
+ expect(group.className).toContain("bg-gray-100");
323
+ expect(group.querySelectorAll("button")).toHaveLength(2);
324
+ });
325
+
326
+ it("can carry another role for the tablists that reuse the track", () => {
327
+ render(
328
+ <ReplayButtonGroup role="tablist" ariaLabel="Browser tabs">
329
+ <button type="button" role="tab" aria-selected={true}>
330
+ Tab 1
331
+ </button>
332
+ </ReplayButtonGroup>,
333
+ );
334
+
335
+ expect(
336
+ screen.getByRole("tablist", { name: "Browser tabs" }),
337
+ ).toBeInTheDocument();
338
+ });
339
+ });
340
+
341
+ describe("ReplayToolbarDivider", () => {
342
+ it("is decorative and never announced", () => {
343
+ render(<ReplayToolbarDivider />);
344
+
345
+ expect(screen.getByTestId("replay-toolbar-divider")).toHaveAttribute(
346
+ "aria-hidden",
347
+ "true",
348
+ );
349
+ });
350
+ });
351
+
352
+ describe("ReplayPill", () => {
353
+ it("renders its text with a tone and an optional pulsing dot", () => {
354
+ render(
355
+ <ReplayPill dataTestId="live" tone="live" hasPulse={true} title="Live">
356
+ Live
357
+ </ReplayPill>,
358
+ );
359
+
360
+ const pill: HTMLElement = screen.getByTestId("live");
361
+
362
+ expect(pill).toHaveTextContent("Live");
363
+ expect(pill.className).toContain("bg-red-50");
364
+ expect(pill.querySelector(".animate-pulse")).not.toBeNull();
365
+ });
366
+
367
+ it("omits the dot unless it is asked for", () => {
368
+ render(<ReplayPill dataTestId="quiet">Sealed</ReplayPill>);
369
+
370
+ expect(
371
+ screen.getByTestId("quiet").querySelector(".animate-pulse"),
372
+ ).toBeNull();
373
+ });
374
+
375
+ it("passes a role through for the pills that are alerts", () => {
376
+ render(
377
+ <ReplayPill dataTestId="err" tone="danger" role="alert">
378
+ Footage did not arrive.
379
+ </ReplayPill>,
380
+ );
381
+
382
+ expect(screen.getByRole("alert")).toHaveTextContent(
383
+ "Footage did not arrive.",
384
+ );
385
+ });
386
+
387
+ /*
388
+ * The buffering pill is a duplicate of what the stage overlay already
389
+ * announces; two live regions meant a screen reader said "Buffering"
390
+ * twice per stall. It stays visible and stays silent.
391
+ */
392
+ it("can hide itself from assistive tech without hiding itself from the eye", () => {
393
+ render(
394
+ <ReplayPill dataTestId="buffering" isHiddenFromScreenReaders={true}>
395
+ Loading footage
396
+ </ReplayPill>,
397
+ );
398
+
399
+ expect(screen.getByTestId("buffering")).toHaveAttribute(
400
+ "aria-hidden",
401
+ "true",
402
+ );
403
+ expect(screen.getByTestId("buffering")).toHaveTextContent(
404
+ "Loading footage",
405
+ );
406
+ });
407
+ });
408
+
409
+ describe("ReplaySwitch", () => {
410
+ it("is a switch with its state on the pressable element", () => {
411
+ render(
412
+ <ReplaySwitch
413
+ dataTestId="skip-idle"
414
+ label="Skip idle"
415
+ isChecked={false}
416
+ onChange={noop}
417
+ />,
418
+ );
419
+
420
+ const control: HTMLElement = screen.getByTestId("skip-idle");
421
+
422
+ expect(control).toBe(screen.getByRole("switch"));
423
+ expect(control).toHaveAttribute("aria-checked", "false");
424
+ expect(control).toHaveTextContent("Skip idle");
425
+ });
426
+
427
+ /*
428
+ * Common/UI's Toggle calls its consumer's onChange twice per click for
429
+ * anything that also wires onFocus/onBlur; a transport switch that
430
+ * fires twice would toggle the engine's skip-idle intent straight back
431
+ * off. One click, one call, with the NEXT value.
432
+ */
433
+ it("emits exactly one change per click, carrying the next value", () => {
434
+ const values: Array<boolean> = [];
435
+
436
+ const { rerender } = render(
437
+ <ReplaySwitch
438
+ dataTestId="skip-idle"
439
+ label="Skip idle"
440
+ isChecked={false}
441
+ onChange={(value: boolean): void => {
442
+ values.push(value);
443
+ }}
444
+ />,
445
+ );
446
+
447
+ fireEvent.click(screen.getByTestId("skip-idle"));
448
+ expect(values).toEqual([true]);
449
+
450
+ rerender(
451
+ <ReplaySwitch
452
+ dataTestId="skip-idle"
453
+ label="Skip idle"
454
+ isChecked={true}
455
+ onChange={(value: boolean): void => {
456
+ values.push(value);
457
+ }}
458
+ />,
459
+ );
460
+
461
+ expect(screen.getByTestId("skip-idle")).toHaveAttribute(
462
+ "aria-checked",
463
+ "true",
464
+ );
465
+
466
+ fireEvent.click(screen.getByTestId("skip-idle"));
467
+ expect(values).toEqual([true, false]);
468
+ });
469
+
470
+ it("is controlled: it never moves on its own", () => {
471
+ render(
472
+ <ReplaySwitch
473
+ dataTestId="skip-idle"
474
+ label="Skip idle"
475
+ isChecked={false}
476
+ onChange={noop}
477
+ />,
478
+ );
479
+
480
+ fireEvent.click(screen.getByTestId("skip-idle"));
481
+
482
+ /* The parent did not change the prop, so the switch must not lie. */
483
+ expect(screen.getByTestId("skip-idle")).toHaveAttribute(
484
+ "aria-checked",
485
+ "false",
486
+ );
487
+ });
488
+
489
+ it("stands at the chrome's own height rather than Common/UI's 24px toggle", () => {
490
+ render(
491
+ <ReplaySwitch
492
+ dataTestId="skip-idle"
493
+ label="Skip idle"
494
+ isChecked={false}
495
+ onChange={noop}
496
+ />,
497
+ );
498
+
499
+ expect(screen.getByTestId("skip-idle").className).toContain(
500
+ REPLAY_CONTROL_HEIGHT_CLASS,
501
+ );
502
+ });
503
+ });
504
+
505
+ describe("ReplayClock", () => {
506
+ /*
507
+ * The readout is two spans so the elapsed half can carry the weight -
508
+ * but its TEXT is still read as one string by the E2E clock
509
+ * assertions and by anyone copying it. Two adjacent spans separated by
510
+ * nothing but a flex gap would render "0:12/ 10:00".
511
+ */
512
+ it("reads as one 'current / total' string", () => {
513
+ render(
514
+ <ReplayClock
515
+ dataTestId="replay-time"
516
+ currentText="0:12"
517
+ totalText="10:00"
518
+ />,
519
+ );
520
+
521
+ expect(screen.getByTestId("replay-time")).toHaveTextContent("0:12 / 10:00");
522
+ expect(screen.getByTestId("replay-time").textContent).toBe("0:12 / 10:00");
523
+ });
524
+
525
+ it("weights the elapsed half and mutes the total", () => {
526
+ render(
527
+ <ReplayClock
528
+ dataTestId="replay-time"
529
+ currentText="1:02.3"
530
+ totalText="4:12"
531
+ />,
532
+ );
533
+
534
+ const clock: HTMLElement = screen.getByTestId("replay-time");
535
+ const parts: Array<Element> = Array.from(clock.children);
536
+
537
+ expect(parts[0]?.textContent).toBe("1:02.3");
538
+ expect(parts[0]?.className).toContain("font-semibold");
539
+ expect(parts[1]?.className).toContain("text-gray-400");
540
+ /* Both halves tabular, so a tick never nudges the buttons beside it. */
541
+ expect(clock.className).toContain("tabular-nums");
542
+ });
543
+
544
+ /* aria-live off: the offset is announced once, by the player's own region. */
545
+ it("does not announce every tick", () => {
546
+ render(
547
+ <ReplayClock
548
+ dataTestId="replay-time"
549
+ currentText="0:01"
550
+ totalText="1:00"
551
+ />,
552
+ );
553
+
554
+ expect(screen.getByTestId("replay-time")).toHaveAttribute(
555
+ "aria-live",
556
+ "off",
557
+ );
558
+ });
559
+ });
@@ -2839,6 +2839,11 @@ const realBreadcrumbTrails: Array<BreadcrumbTrailFixture> = [
2839
2839
  pagePattern: "/dashboard/:projectId/user-settings/calendar-feed",
2840
2840
  titles: ["Project", "User Settings", "Calendar Feed"],
2841
2841
  },
2842
+ {
2843
+ getter: "getUserSettingsBreadcrumbs",
2844
+ pagePattern: "/dashboard/:projectId/user-settings/email-preferences",
2845
+ titles: ["Project", "User Settings", "Email Preferences"],
2846
+ },
2842
2847
  {
2843
2848
  getter: "getUserSettingsBreadcrumbs",
2844
2849
  pagePattern:
@@ -638,6 +638,7 @@ const realRoutePatterns: Array<string> = [
638
638
  "/dashboard/:projectId/user-settings/on-call-logs/:id",
639
639
  "/dashboard/:projectId/user-settings/incoming-call-phone-numbers",
640
640
  "/dashboard/:projectId/user-settings/calendar-feed",
641
+ "/dashboard/:projectId/user-settings/email-preferences",
641
642
  "/dashboard/:projectId/user-settings/setup",
642
643
  "/dashboard/:projectId/users/*",
643
644
  "/dashboard/:projectId/users",
@@ -55,6 +55,7 @@ const EXPECTED_ARGUMENT_IDS: Array<ArgumentId> = [
55
55
  "monitorStatusIds",
56
56
  "monitorTypes",
57
57
  "labelIds",
58
+ "labelVariableId",
58
59
  ];
59
60
 
60
61
  const DISPLAY_SECTION_ARGUMENT_IDS: Array<ArgumentId> = [
@@ -68,6 +69,7 @@ const FILTER_SECTION_ARGUMENT_IDS: Array<ArgumentId> = [
68
69
  "monitorStatusIds",
69
70
  "monitorTypes",
70
71
  "labelIds",
72
+ "labelVariableId",
71
73
  ];
72
74
 
73
75
  const VALID_INPUT_TYPES: Set<string> = new Set<string>(
@@ -157,6 +159,7 @@ describe("DashboardMonitorListComponentUtil", () => {
157
159
  expect(component.arguments.monitorStatusIds).toBeUndefined();
158
160
  expect(component.arguments.monitorTypes).toBeUndefined();
159
161
  expect(component.arguments.labelIds).toBeUndefined();
162
+ expect(component.arguments.labelVariableId).toBeUndefined();
160
163
  });
161
164
 
162
165
  test("uses the same default row count that the maxRows field shows as its placeholder", () => {
@@ -188,7 +191,7 @@ describe("DashboardMonitorListComponentUtil", () => {
188
191
  });
189
192
 
190
193
  describe("getComponentConfigArguments", () => {
191
- test("declares exactly the seven documented arguments, in form order", () => {
194
+ test("declares the documented arguments, in form order", () => {
192
195
  expect(
193
196
  getArguments().map((arg: MonitorListArgument) => {
194
197
  return arg.id;
@@ -343,6 +346,13 @@ describe("DashboardMonitorListComponentUtil", () => {
343
346
  expect(arg.dropdownOptions).toBeUndefined();
344
347
  });
345
348
 
349
+ test("declares a dedicated project-label variable binding alongside fixed labels", () => {
350
+ const arg: MonitorListArgument = getArgumentById("labelVariableId");
351
+ expect(arg.type).toBe(ComponentInputType.ProjectLabelVariable);
352
+ expect(arg.required).toBe(false);
353
+ expect(arg.entityFilterModelType).toBeUndefined();
354
+ });
355
+
346
356
  test("renders the monitor-type filter as a static multi-select, not an entity lookup", () => {
347
357
  const arg: MonitorListArgument = getArgumentById("monitorTypes");
348
358