@oneuptime/common 12.0.30 → 12.0.32

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 (645) hide show
  1. package/Models/AnalyticsModels/SecurityEvent.ts +48 -16
  2. package/Models/AnalyticsModels/ThreatIntelIndicator.ts +10 -5
  3. package/Models/DatabaseModels/AlertSeverity.ts +44 -0
  4. package/Models/DatabaseModels/AlertState.ts +0 -2
  5. package/Models/DatabaseModels/DetectionRule.ts +16 -5
  6. package/Models/DatabaseModels/Domain.ts +1 -1
  7. package/Models/DatabaseModels/GoogleSecOpsConnection.ts +14 -5
  8. package/Models/DatabaseModels/Incident.ts +1 -1
  9. package/Models/DatabaseModels/IncidentSeverity.ts +44 -0
  10. package/Models/DatabaseModels/IncidentTemplate.ts +1 -1
  11. package/Models/DatabaseModels/Index.ts +4 -0
  12. package/Models/DatabaseModels/MetricType.ts +0 -2
  13. package/Models/DatabaseModels/MonitorStatusTimeline.ts +0 -2
  14. package/Models/DatabaseModels/MonitorTest.ts +0 -2
  15. package/Models/DatabaseModels/NetworkDevice.ts +234 -5
  16. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +88 -0
  17. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +22 -0
  18. package/Models/DatabaseModels/NetworkDeviceOidTemplate.ts +501 -0
  19. package/Models/DatabaseModels/NetworkDeviceRole.ts +648 -0
  20. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -1
  21. package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -1
  22. package/Models/DatabaseModels/StatusPageAnnouncement.ts +0 -2
  23. package/Models/DatabaseModels/StatusPageAnnouncementTemplate.ts +0 -2
  24. package/Models/DatabaseModels/StatusPagePrivateUser.ts +0 -2
  25. package/Models/DatabaseModels/StatusPageResource.ts +0 -2
  26. package/Models/DatabaseModels/StatusPageSubscriber.ts +0 -2
  27. package/Models/DatabaseModels/ThreatIntelFeed.ts +16 -5
  28. package/Server/API/GlobalConfigAPI.ts +362 -183
  29. package/Server/API/TelemetryAPI.ts +42 -4
  30. package/Server/EnvironmentConfig.ts +112 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1790500000000-AddNetworkDeviceOidTemplate.ts +59 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1790600000000-AddAutoImportRuleOidTemplate.ts +25 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.ts +72 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  35. package/Server/Middleware/MultipartFormData.ts +118 -32
  36. package/Server/Services/AlertEpisodeLabelRuleEngineService.ts +9 -1
  37. package/Server/Services/AlertEpisodeOnCallRuleEngineService.ts +9 -1
  38. package/Server/Services/AlertEpisodeOwnerRuleEngineService.ts +9 -1
  39. package/Server/Services/AlertEpisodePrivacyRuleEngineService.ts +9 -1
  40. package/Server/Services/AlertGroupingEngineService.ts +9 -1
  41. package/Server/Services/AlertLabelRuleEngineService.ts +9 -1
  42. package/Server/Services/AlertOnCallRuleEngineService.ts +9 -1
  43. package/Server/Services/AlertOwnerRuleEngineService.ts +9 -1
  44. package/Server/Services/AlertPrivacyRuleEngineService.ts +9 -1
  45. package/Server/Services/AlertReminderRuleService.ts +9 -1
  46. package/Server/Services/AutoRemediationRuleEngineService.ts +9 -1
  47. package/Server/Services/CephClusterLabelRuleEngineService.ts +9 -1
  48. package/Server/Services/CephClusterOwnerRuleEngineService.ts +9 -1
  49. package/Server/Services/CloudResourceLabelRuleEngineService.ts +9 -1
  50. package/Server/Services/CloudResourceOwnerRuleEngineService.ts +9 -1
  51. package/Server/Services/DashboardLabelRuleEngineService.ts +9 -1
  52. package/Server/Services/DashboardOwnerRuleEngineService.ts +9 -1
  53. package/Server/Services/DatabaseService.ts +39 -9
  54. package/Server/Services/DockerHostLabelRuleEngineService.ts +9 -1
  55. package/Server/Services/DockerHostOwnerRuleEngineService.ts +9 -1
  56. package/Server/Services/DockerSwarmClusterLabelRuleEngineService.ts +9 -1
  57. package/Server/Services/DockerSwarmClusterOwnerRuleEngineService.ts +9 -1
  58. package/Server/Services/HostLabelRuleEngineService.ts +9 -1
  59. package/Server/Services/HostOwnerRuleEngineService.ts +9 -1
  60. package/Server/Services/IncidentEpisodeLabelRuleEngineService.ts +9 -1
  61. package/Server/Services/IncidentEpisodeOnCallRuleEngineService.ts +9 -1
  62. package/Server/Services/IncidentEpisodeOwnerRuleEngineService.ts +9 -1
  63. package/Server/Services/IncidentEpisodePrivacyRuleEngineService.ts +9 -1
  64. package/Server/Services/IncidentGroupingEngineService.ts +9 -1
  65. package/Server/Services/IncidentLabelRuleEngineService.ts +9 -1
  66. package/Server/Services/IncidentOnCallRuleEngineService.ts +9 -1
  67. package/Server/Services/IncidentOwnerRuleEngineService.ts +9 -1
  68. package/Server/Services/IncidentPrivacyRuleEngineService.ts +9 -1
  69. package/Server/Services/IncidentReminderRuleService.ts +9 -1
  70. package/Server/Services/IncidentSlaRuleService.ts +9 -1
  71. package/Server/Services/IncomingCallPolicyLabelRuleEngineService.ts +9 -1
  72. package/Server/Services/IncomingCallPolicyOwnerRuleEngineService.ts +9 -1
  73. package/Server/Services/Index.ts +4 -0
  74. package/Server/Services/IoTFleetLabelRuleEngineService.ts +9 -1
  75. package/Server/Services/IoTFleetOwnerRuleEngineService.ts +9 -1
  76. package/Server/Services/KubernetesClusterLabelRuleEngineService.ts +9 -1
  77. package/Server/Services/KubernetesClusterOwnerRuleEngineService.ts +9 -1
  78. package/Server/Services/KubernetesResourceService.ts +5 -1
  79. package/Server/Services/MonitorLabelRuleEngineService.ts +9 -1
  80. package/Server/Services/MonitorOwnerRuleEngineService.ts +9 -1
  81. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +35 -0
  82. package/Server/Services/NetworkDeviceAutoImportRuleService.ts +96 -0
  83. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +9 -1
  84. package/Server/Services/NetworkDeviceOidTemplateService.ts +201 -0
  85. package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +9 -1
  86. package/Server/Services/NetworkDeviceRoleService.ts +90 -0
  87. package/Server/Services/NetworkDeviceService.ts +209 -1
  88. package/Server/Services/OnCallDutyPolicyLabelRuleEngineService.ts +9 -1
  89. package/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.ts +9 -1
  90. package/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.ts +9 -1
  91. package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.ts +9 -1
  92. package/Server/Services/PodmanHostLabelRuleEngineService.ts +9 -1
  93. package/Server/Services/PodmanHostOwnerRuleEngineService.ts +9 -1
  94. package/Server/Services/ProjectService.ts +85 -0
  95. package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +9 -1
  96. package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +9 -1
  97. package/Server/Services/RumApplicationLabelRuleEngineService.ts +9 -1
  98. package/Server/Services/RumApplicationOwnerRuleEngineService.ts +9 -1
  99. package/Server/Services/RunbookLabelRuleEngineService.ts +9 -1
  100. package/Server/Services/RunbookOwnerRuleEngineService.ts +9 -1
  101. package/Server/Services/RunbookRuleEngineService.ts +9 -1
  102. package/Server/Services/ScheduledMaintenanceLabelRuleEngineService.ts +9 -1
  103. package/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.ts +9 -1
  104. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +9 -1
  105. package/Server/Services/ServerlessFunctionLabelRuleEngineService.ts +9 -1
  106. package/Server/Services/ServerlessFunctionOwnerRuleEngineService.ts +9 -1
  107. package/Server/Services/ServiceLabelRuleEngineService.ts +9 -1
  108. package/Server/Services/ServiceOwnerRuleEngineService.ts +9 -1
  109. package/Server/Services/StatusPageLabelRuleEngineService.ts +9 -1
  110. package/Server/Services/StatusPageMonitorRuleEngineService.ts +7 -0
  111. package/Server/Services/StatusPageOwnerRuleEngineService.ts +9 -1
  112. package/Server/Services/TelemetrySourceMapService.ts +266 -23
  113. package/Server/Services/UserNotificationSettingService.ts +0 -6
  114. package/Server/Services/UserService.ts +27 -0
  115. package/Server/Services/WorkflowLabelRuleEngineService.ts +9 -1
  116. package/Server/Services/WorkflowOwnerRuleEngineService.ts +9 -1
  117. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +186 -0
  118. package/Server/Utils/Monitor/CapturedMetricAttributeUtil.ts +245 -0
  119. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +21 -2
  120. package/Server/Utils/Monitor/MonitorMetricUtil.ts +55 -21
  121. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +32 -0
  122. package/Server/Utils/Monitor/NetworkDeviceMetricUtil.ts +15 -2
  123. package/Server/Utils/Monitor/NetworkDeviceWalkUtil.ts +18 -1
  124. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +10 -0
  125. package/Server/Utils/Monitor/SeriesResourceLabels.ts +34 -0
  126. package/Server/Utils/Rules/RuleEngineRuleRead.ts +48 -0
  127. package/Server/Utils/Telemetry/ExceptionSanitizer.ts +14 -1
  128. package/Server/Utils/Telemetry/LogExceptionExtractor.ts +14 -2
  129. package/Server/Utils/Telemetry/SourceMapResolver.ts +16 -5
  130. package/Server/Utils/Telemetry/StackTraceParser.ts +399 -0
  131. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +31 -1
  132. package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +35 -8
  133. package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +107 -3
  134. package/Tests/App/Dashboard/DashboardWideChartZoom.test.tsx +401 -0
  135. package/Tests/App/Dashboard/EventOverlayHook.test.tsx +125 -0
  136. package/Tests/App/Dashboard/HeaderImageAssets.test.tsx +93 -0
  137. package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +318 -0
  138. package/Tests/App/Dashboard/NetworkDeviceBulkDelete.test.tsx +357 -0
  139. package/Tests/App/Dashboard/NetworkSideMenu.test.tsx +590 -0
  140. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3 -3
  141. package/Tests/App/Dashboard/SideMenuHarness.tsx +11 -3
  142. package/Tests/App/Dashboard/UserElementEmail.test.tsx +12 -4
  143. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +1 -1
  144. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +14 -0
  145. package/Tests/Models/DatabaseModels/ModelRegistryInvariants.test.ts +543 -0
  146. package/Tests/Models/SecurityDomainAccessControl.test.ts +429 -0
  147. package/Tests/Server/API/GlobalConfigLicense.test.ts +657 -0
  148. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +84 -6
  149. package/Tests/Server/API/TelemetryResolveStackTraceAPI.test.ts +805 -0
  150. package/Tests/Server/Infrastructure/Postgres/AddOnCallCalendarFeedsMigration.test.ts +32 -7
  151. package/Tests/Server/Infrastructure/Postgres/NetworkDeviceRoleTableMigration.test.ts +476 -0
  152. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +1144 -0
  153. package/Tests/Server/Middleware/MultipartFormData.test.ts +618 -3
  154. package/Tests/Server/Services/DatabaseServiceGenerateSlug.test.ts +215 -0
  155. package/Tests/Server/Services/MonitorLabelRuleEngineScale.test.ts +520 -0
  156. package/Tests/Server/Services/NetworkDeviceOidTemplateService.test.ts +332 -0
  157. package/Tests/Server/Services/NetworkDeviceOidTemplateWriteGuards.test.ts +409 -0
  158. package/Tests/Server/Services/NetworkDeviceRoleModel.test.ts +672 -0
  159. package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +1 -1
  160. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +67 -4
  161. package/Tests/Server/Services/RuleEngineRuleFetchLimit.test.ts +239 -0
  162. package/Tests/Server/Services/ScheduledMaintenanceSubscriberEndTime.test.ts +369 -0
  163. package/Tests/Server/Services/TelemetrySourceMapService.test.ts +1210 -2
  164. package/Tests/Server/Services/UserNotificationSettingMonitorDefaults.test.ts +248 -0
  165. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +43 -2
  166. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +182 -0
  167. package/Tests/Server/Types/AnalyticsDatabase/SecurityEventModelPermission.test.ts +255 -0
  168. package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +212 -0
  169. package/Tests/Server/Types/Database/Permissions/SecurityRoleAccess.test.ts +413 -0
  170. package/Tests/Server/Utils/BasicCron.test.ts +283 -0
  171. package/Tests/Server/Utils/CodeRepository.test.ts +689 -0
  172. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +495 -0
  173. package/Tests/Server/Utils/ExceptionSanitizer.test.ts +77 -0
  174. package/Tests/Server/Utils/IncomingCallPhoneNumber.test.ts +174 -0
  175. package/Tests/Server/Utils/Monitor/CapturedMetricAttributeUtil.test.ts +284 -0
  176. package/Tests/Server/Utils/Monitor/MonitorCustomMetricAttributes.test.ts +503 -0
  177. package/Tests/Server/Utils/Monitor/MonitorTemplatePrototypePollution.test.ts +106 -0
  178. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkLogWritePath.test.ts +33 -4
  179. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +14 -4
  180. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +44 -0
  181. package/Tests/Server/Utils/OpenAPISpec.test.ts +536 -0
  182. package/Tests/Server/Utils/Rules/RuleEngineRuleRead.test.ts +115 -0
  183. package/Tests/Server/Utils/StackTraceParser.test.ts +610 -0
  184. package/Tests/Server/Utils/Telemetry/LogExceptionExtractor.test.ts +0 -0
  185. package/Tests/Server/Utils/Telemetry/SourceMapLimits.test.ts +656 -0
  186. package/Tests/Server/Utils/Telemetry/SourceMapResolver.test.ts +100 -0
  187. package/Tests/Types/Monitor/SnmpMonitor/SnmpOidListUtil.test.ts +454 -0
  188. package/Tests/Types/NetworkDevice/DefaultNetworkDeviceRole.test.ts +417 -0
  189. package/Tests/Types/SecurityRolePermissions.test.ts +193 -0
  190. package/Tests/UI/Components/Charts/AnnotationLayout.test.ts +773 -0
  191. package/Tests/UI/Components/Charts/ChartAnnotationRail.test.tsx +789 -0
  192. package/Tests/UI/Components/Charts/ChartBucketIdentity.test.ts +254 -0
  193. package/Tests/UI/Components/Charts/ChartClickDoubleClickDisambiguation.test.tsx +221 -0
  194. package/Tests/UI/Components/Charts/ChartDoubleClickReset.test.tsx +220 -0
  195. package/Tests/UI/Components/Charts/TimeAnnotation.test.ts +471 -0
  196. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +405 -0
  197. package/Tests/UI/Components/ErrorMessageRefreshControl.test.tsx +83 -0
  198. package/Tests/UI/Components/MarkdownConverters.test.ts +686 -0
  199. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +751 -0
  200. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +475 -43
  201. package/Tests/UI/Components/ModelTable/BaseModelTableDeleteAndEmptyStates.test.tsx +341 -0
  202. package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +89 -0
  203. package/Tests/UI/Components/NavBarCommandKGating.test.tsx +103 -8
  204. package/Tests/UI/Components/OrderedStatesList.test.tsx +5 -5
  205. package/Tests/UI/Components/PendingProjectInvitations.test.tsx +1 -1
  206. package/Tests/UI/Components/SideMenuItem.test.tsx +30 -0
  207. package/Tests/UI/Components/TableSortAndMobileColumns.test.tsx +195 -0
  208. package/Tests/UI/Components/XAxis.test.ts +270 -5
  209. package/Tests/UI/Components/XAxisDaylightSaving.test.ts +113 -0
  210. package/Tests/UI/Styles/ChartFocusRing.test.ts +153 -0
  211. package/Tests/UI/Styles/ChartFocusRingCoverage.test.tsx +377 -0
  212. package/Tests/UI/Styles/ThemeStylesheet.ts +162 -0
  213. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +30 -0
  214. package/Tests/UI/Utils/UseDashboardTimeRangeZoom.test.tsx +187 -0
  215. package/Tests/Utils/Dashboard/DashboardResourceListSharedArgs.test.ts +302 -0
  216. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +242 -0
  217. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +507 -0
  218. package/Tests/Utils/Monitor/NetworkDeviceRoleCatalog.test.ts +981 -0
  219. package/Tests/Utils/Monitor/NetworkTopologyRoleCatalog.test.ts +865 -0
  220. package/Tests/Utils/NetworkDevice/DeviceRoleKeyUtil.test.ts +538 -0
  221. package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +951 -1
  222. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +238 -0
  223. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +1056 -0
  224. package/Tests/Utils/NetworkDiscovery/ReverseDnsNameUtil.test.ts +882 -0
  225. package/Tests/Utils/Slo/SloEvaluation.test.ts +171 -0
  226. package/Tests/Utils/Slug.test.ts +61 -1
  227. package/Tests/jest.setup.ts +19 -0
  228. package/Types/Date.ts +39 -0
  229. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +61 -0
  230. package/Types/Monitor/SnmpMonitor/SnmpOidListUtil.ts +333 -0
  231. package/Types/NetworkDevice/DefaultNetworkDeviceRole.ts +145 -0
  232. package/Types/Permission.ts +141 -0
  233. package/Types/Rum/SessionReplay.ts +21 -0
  234. package/Types/Telemetry/SourceMap.ts +8 -0
  235. package/UI/Components/Charts/Area/AreaChart.tsx +7 -0
  236. package/UI/Components/Charts/Bar/BarChart.tsx +6 -0
  237. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +9 -1
  238. package/UI/Components/Charts/ChartLibrary/Annotations/AnnotationHoverCard.tsx +176 -0
  239. package/UI/Components/Charts/ChartLibrary/Annotations/AnnotationLayout.ts +745 -0
  240. package/UI/Components/Charts/ChartLibrary/Annotations/ChartAnnotationLayer.tsx +576 -0
  241. package/UI/Components/Charts/ChartLibrary/Annotations/UseChartAnnotations.tsx +204 -0
  242. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +554 -545
  243. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +337 -371
  244. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +549 -540
  245. package/UI/Components/Charts/ChartLibrary/Types/FormattedReferenceRegion.ts +7 -3
  246. package/UI/Components/Charts/ChartLibrary/Types/FormattedTimeReferenceLine.ts +14 -3
  247. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +13 -0
  248. package/UI/Components/Charts/Line/LineChart.tsx +7 -0
  249. package/UI/Components/Charts/Types/ChartEventKind.ts +13 -0
  250. package/UI/Components/Charts/Types/ReferenceRegionProps.ts +6 -0
  251. package/UI/Components/Charts/Types/TimeReferenceLineProps.ts +20 -3
  252. package/UI/Components/Charts/Utils/TimeAnnotation.ts +69 -35
  253. package/UI/Components/Charts/Utils/XAxis.ts +125 -18
  254. package/UI/Components/EditionLabel/EditionLabel.tsx +280 -26
  255. package/UI/Components/ErrorMessage/ErrorMessage.tsx +21 -10
  256. package/UI/Components/Feed/FeedItem.tsx +4 -5
  257. package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -2
  258. package/UI/Components/Header/UserProfile/UserProfile.tsx +1 -2
  259. package/UI/Components/Image/Image.tsx +20 -3
  260. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  261. package/UI/Components/MemberRoleAssignment/MemberRoleAssignment.tsx +1 -2
  262. package/UI/Components/ModelTable/BaseModelTable.tsx +200 -39
  263. package/UI/Components/Navbar/NavBar.tsx +62 -13
  264. package/UI/Components/Navbar/NavBarMenuModal.tsx +7 -2
  265. package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +2 -0
  266. package/UI/Components/SideMenu/SideMenu.tsx +12 -2
  267. package/UI/Components/SideMenu/SideMenuItem.tsx +4 -1
  268. package/UI/Components/SideMenu/SideMenuSection.tsx +14 -2
  269. package/UI/Components/Table/TableHeader.tsx +64 -48
  270. package/UI/Components/Table/TableRow.tsx +13 -3
  271. package/UI/Styles/Theme.css +38 -0
  272. package/UI/Utils/UseDashboardTimeRangeZoom.ts +77 -0
  273. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +118 -0
  274. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +271 -0
  275. package/Utils/Monitor/NetworkDeviceRoleCatalog.ts +232 -0
  276. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +2 -1
  277. package/Utils/Monitor/NetworkTopologyUtil.ts +116 -33
  278. package/Utils/NetworkDevice/DeviceRoleKeyUtil.ts +137 -0
  279. package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +6 -0
  280. package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +141 -10
  281. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +55 -3
  282. package/Utils/NetworkDiscovery/ReverseDnsNameUtil.ts +151 -0
  283. package/Utils/Rules/RuleEngineLimits.ts +32 -0
  284. package/Utils/Rules/RulePatternMatchUtil.ts +1 -1
  285. package/Utils/Slug.ts +34 -1
  286. package/build/dist/Models/AnalyticsModels/SecurityEvent.js +48 -16
  287. package/build/dist/Models/AnalyticsModels/SecurityEvent.js.map +1 -1
  288. package/build/dist/Models/AnalyticsModels/ThreatIntelIndicator.js +10 -5
  289. package/build/dist/Models/AnalyticsModels/ThreatIntelIndicator.js.map +1 -1
  290. package/build/dist/Models/DatabaseModels/AlertSeverity.js +46 -1
  291. package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
  292. package/build/dist/Models/DatabaseModels/AlertState.js +0 -2
  293. package/build/dist/Models/DatabaseModels/AlertState.js.map +1 -1
  294. package/build/dist/Models/DatabaseModels/DetectionRule.js +16 -5
  295. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
  296. package/build/dist/Models/DatabaseModels/Domain.js +1 -1
  297. package/build/dist/Models/DatabaseModels/Domain.js.map +1 -1
  298. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +14 -5
  299. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +1 -1
  300. package/build/dist/Models/DatabaseModels/Incident.js +1 -1
  301. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  302. package/build/dist/Models/DatabaseModels/IncidentSeverity.js +46 -1
  303. package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
  304. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +1 -1
  305. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  306. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  307. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  308. package/build/dist/Models/DatabaseModels/MetricType.js +0 -2
  309. package/build/dist/Models/DatabaseModels/MetricType.js.map +1 -1
  310. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +0 -2
  311. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  312. package/build/dist/Models/DatabaseModels/MonitorTest.js +0 -2
  313. package/build/dist/Models/DatabaseModels/MonitorTest.js.map +1 -1
  314. package/build/dist/Models/DatabaseModels/NetworkDevice.js +239 -6
  315. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  316. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +88 -0
  317. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
  318. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  319. package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js +521 -0
  320. package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js.map +1 -0
  321. package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js +677 -0
  322. package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js.map +1 -0
  323. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -1
  324. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  325. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -1
  326. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
  327. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +0 -2
  328. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  329. package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js +0 -2
  330. package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js.map +1 -1
  331. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +0 -2
  332. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  333. package/build/dist/Models/DatabaseModels/StatusPageResource.js +0 -2
  334. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  335. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +0 -2
  336. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  337. package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js +16 -5
  338. package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js.map +1 -1
  339. package/build/dist/Server/API/GlobalConfigAPI.js +293 -176
  340. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  341. package/build/dist/Server/API/TelemetryAPI.js +31 -3
  342. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  343. package/build/dist/Server/EnvironmentConfig.js +83 -0
  344. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  345. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790500000000-AddNetworkDeviceOidTemplate.js +26 -0
  346. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790500000000-AddNetworkDeviceOidTemplate.js.map +1 -0
  347. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790600000000-AddAutoImportRuleOidTemplate.js +14 -0
  348. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790600000000-AddAutoImportRuleOidTemplate.js.map +1 -0
  349. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js +39 -0
  350. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js.map +1 -0
  351. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  352. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  353. package/build/dist/Server/Middleware/MultipartFormData.js +93 -28
  354. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
  355. package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js +8 -1
  356. package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js.map +1 -1
  357. package/build/dist/Server/Services/AlertEpisodeOnCallRuleEngineService.js +8 -1
  358. package/build/dist/Server/Services/AlertEpisodeOnCallRuleEngineService.js.map +1 -1
  359. package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js +8 -1
  360. package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js.map +1 -1
  361. package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js +8 -1
  362. package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js.map +1 -1
  363. package/build/dist/Server/Services/AlertGroupingEngineService.js +8 -1
  364. package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
  365. package/build/dist/Server/Services/AlertLabelRuleEngineService.js +8 -1
  366. package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
  367. package/build/dist/Server/Services/AlertOnCallRuleEngineService.js +8 -1
  368. package/build/dist/Server/Services/AlertOnCallRuleEngineService.js.map +1 -1
  369. package/build/dist/Server/Services/AlertOwnerRuleEngineService.js +8 -1
  370. package/build/dist/Server/Services/AlertOwnerRuleEngineService.js.map +1 -1
  371. package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js +8 -1
  372. package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js.map +1 -1
  373. package/build/dist/Server/Services/AlertReminderRuleService.js +8 -1
  374. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
  375. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +8 -1
  376. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  377. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +8 -1
  378. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -1
  379. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +8 -1
  380. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -1
  381. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js +8 -1
  382. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js.map +1 -1
  383. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +8 -1
  384. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
  385. package/build/dist/Server/Services/DashboardLabelRuleEngineService.js +8 -1
  386. package/build/dist/Server/Services/DashboardLabelRuleEngineService.js.map +1 -1
  387. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js +8 -1
  388. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js.map +1 -1
  389. package/build/dist/Server/Services/DatabaseService.js +31 -5
  390. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  391. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js +8 -1
  392. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js.map +1 -1
  393. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js +8 -1
  394. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js.map +1 -1
  395. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js +8 -1
  396. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js.map +1 -1
  397. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js +8 -1
  398. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js.map +1 -1
  399. package/build/dist/Server/Services/HostLabelRuleEngineService.js +8 -1
  400. package/build/dist/Server/Services/HostLabelRuleEngineService.js.map +1 -1
  401. package/build/dist/Server/Services/HostOwnerRuleEngineService.js +8 -1
  402. package/build/dist/Server/Services/HostOwnerRuleEngineService.js.map +1 -1
  403. package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js +8 -1
  404. package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js.map +1 -1
  405. package/build/dist/Server/Services/IncidentEpisodeOnCallRuleEngineService.js +8 -1
  406. package/build/dist/Server/Services/IncidentEpisodeOnCallRuleEngineService.js.map +1 -1
  407. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js +8 -1
  408. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js.map +1 -1
  409. package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js +8 -1
  410. package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js.map +1 -1
  411. package/build/dist/Server/Services/IncidentGroupingEngineService.js +8 -1
  412. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  413. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +8 -1
  414. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
  415. package/build/dist/Server/Services/IncidentOnCallRuleEngineService.js +8 -1
  416. package/build/dist/Server/Services/IncidentOnCallRuleEngineService.js.map +1 -1
  417. package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js +8 -1
  418. package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js.map +1 -1
  419. package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js +8 -1
  420. package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js.map +1 -1
  421. package/build/dist/Server/Services/IncidentReminderRuleService.js +8 -1
  422. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
  423. package/build/dist/Server/Services/IncidentSlaRuleService.js +8 -1
  424. package/build/dist/Server/Services/IncidentSlaRuleService.js.map +1 -1
  425. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js +8 -1
  426. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js.map +1 -1
  427. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js +8 -1
  428. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js.map +1 -1
  429. package/build/dist/Server/Services/Index.js +4 -0
  430. package/build/dist/Server/Services/Index.js.map +1 -1
  431. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js +8 -1
  432. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js.map +1 -1
  433. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +8 -1
  434. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -1
  435. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js +8 -1
  436. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js.map +1 -1
  437. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js +8 -1
  438. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js.map +1 -1
  439. package/build/dist/Server/Services/KubernetesResourceService.js +5 -1
  440. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
  441. package/build/dist/Server/Services/MonitorLabelRuleEngineService.js +8 -1
  442. package/build/dist/Server/Services/MonitorLabelRuleEngineService.js.map +1 -1
  443. package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js +8 -1
  444. package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js.map +1 -1
  445. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +30 -21
  446. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  447. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +64 -0
  448. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
  449. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +8 -1
  450. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  451. package/build/dist/Server/Services/NetworkDeviceOidTemplateService.js +180 -0
  452. package/build/dist/Server/Services/NetworkDeviceOidTemplateService.js.map +1 -0
  453. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +8 -1
  454. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
  455. package/build/dist/Server/Services/NetworkDeviceRoleService.js +70 -0
  456. package/build/dist/Server/Services/NetworkDeviceRoleService.js.map +1 -0
  457. package/build/dist/Server/Services/NetworkDeviceService.js +154 -1
  458. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  459. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js +8 -1
  460. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js.map +1 -1
  461. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js +8 -1
  462. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js.map +1 -1
  463. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js +8 -1
  464. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js.map +1 -1
  465. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js +8 -1
  466. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js.map +1 -1
  467. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js +8 -1
  468. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js.map +1 -1
  469. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js +8 -1
  470. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js.map +1 -1
  471. package/build/dist/Server/Services/ProjectService.js +65 -0
  472. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  473. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +8 -1
  474. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -1
  475. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +8 -1
  476. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -1
  477. package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js +8 -1
  478. package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js.map +1 -1
  479. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js +8 -1
  480. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js.map +1 -1
  481. package/build/dist/Server/Services/RunbookLabelRuleEngineService.js +8 -1
  482. package/build/dist/Server/Services/RunbookLabelRuleEngineService.js.map +1 -1
  483. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js +8 -1
  484. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js.map +1 -1
  485. package/build/dist/Server/Services/RunbookRuleEngineService.js +8 -1
  486. package/build/dist/Server/Services/RunbookRuleEngineService.js.map +1 -1
  487. package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js +8 -1
  488. package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js.map +1 -1
  489. package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js +8 -1
  490. package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js.map +1 -1
  491. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +8 -1
  492. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
  493. package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js +8 -1
  494. package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js.map +1 -1
  495. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js +8 -1
  496. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js.map +1 -1
  497. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js +8 -1
  498. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js.map +1 -1
  499. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js +8 -1
  500. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js.map +1 -1
  501. package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js +8 -1
  502. package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js.map +1 -1
  503. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +6 -0
  504. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -1
  505. package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js +8 -1
  506. package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js.map +1 -1
  507. package/build/dist/Server/Services/TelemetrySourceMapService.js +213 -19
  508. package/build/dist/Server/Services/TelemetrySourceMapService.js.map +1 -1
  509. package/build/dist/Server/Services/UserNotificationSettingService.js +0 -1
  510. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  511. package/build/dist/Server/Services/UserService.js +25 -0
  512. package/build/dist/Server/Services/UserService.js.map +1 -1
  513. package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js +8 -1
  514. package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js.map +1 -1
  515. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js +8 -1
  516. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js.map +1 -1
  517. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +145 -0
  518. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +1 -0
  519. package/build/dist/Server/Utils/Monitor/CapturedMetricAttributeUtil.js +201 -0
  520. package/build/dist/Server/Utils/Monitor/CapturedMetricAttributeUtil.js.map +1 -0
  521. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +19 -2
  522. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
  523. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +42 -23
  524. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  525. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +29 -0
  526. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  527. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js +11 -2
  528. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js.map +1 -1
  529. package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js +16 -1
  530. package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js.map +1 -1
  531. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +10 -0
  532. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  533. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +33 -0
  534. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  535. package/build/dist/Server/Utils/Rules/RuleEngineRuleRead.js +37 -0
  536. package/build/dist/Server/Utils/Rules/RuleEngineRuleRead.js.map +1 -0
  537. package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js +12 -1
  538. package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js.map +1 -1
  539. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +14 -2
  540. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
  541. package/build/dist/Server/Utils/Telemetry/SourceMapResolver.js +15 -5
  542. package/build/dist/Server/Utils/Telemetry/SourceMapResolver.js.map +1 -1
  543. package/build/dist/Server/Utils/Telemetry/StackTraceParser.js +307 -0
  544. package/build/dist/Server/Utils/Telemetry/StackTraceParser.js.map +1 -1
  545. package/build/dist/Types/Date.js +27 -0
  546. package/build/dist/Types/Date.js.map +1 -1
  547. package/build/dist/Types/Monitor/SnmpMonitor/SnmpOidListUtil.js +253 -0
  548. package/build/dist/Types/Monitor/SnmpMonitor/SnmpOidListUtil.js.map +1 -0
  549. package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js +105 -0
  550. package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js.map +1 -0
  551. package/build/dist/Types/Permission.js +126 -0
  552. package/build/dist/Types/Permission.js.map +1 -1
  553. package/build/dist/Types/Rum/SessionReplay.js +19 -0
  554. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  555. package/build/dist/UI/Components/Charts/Area/AreaChart.js +1 -1
  556. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  557. package/build/dist/UI/Components/Charts/Bar/BarChart.js +1 -1
  558. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  559. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -1
  560. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  561. package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/AnnotationHoverCard.js +83 -0
  562. package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/AnnotationHoverCard.js.map +1 -0
  563. package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/AnnotationLayout.js +531 -0
  564. package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/AnnotationLayout.js.map +1 -0
  565. package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/ChartAnnotationLayer.js +264 -0
  566. package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/ChartAnnotationLayer.js.map +1 -0
  567. package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/UseChartAnnotations.js +122 -0
  568. package/build/dist/UI/Components/Charts/ChartLibrary/Annotations/UseChartAnnotations.js.map +1 -0
  569. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +262 -234
  570. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  571. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +146 -153
  572. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  573. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +261 -233
  574. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  575. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +14 -0
  576. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -0
  577. package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
  578. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  579. package/build/dist/UI/Components/Charts/Types/ChartEventKind.js +14 -0
  580. package/build/dist/UI/Components/Charts/Types/ChartEventKind.js.map +1 -0
  581. package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js +57 -28
  582. package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js.map +1 -1
  583. package/build/dist/UI/Components/Charts/Utils/XAxis.js +103 -17
  584. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  585. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +198 -23
  586. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  587. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +15 -5
  588. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
  589. package/build/dist/UI/Components/Feed/FeedItem.js +4 -5
  590. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  591. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -2
  592. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  593. package/build/dist/UI/Components/Header/UserProfile/UserProfile.js.map +1 -1
  594. package/build/dist/UI/Components/Image/Image.js +6 -1
  595. package/build/dist/UI/Components/Image/Image.js.map +1 -1
  596. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  597. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  598. package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js +1 -2
  599. package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js.map +1 -1
  600. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +143 -36
  601. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  602. package/build/dist/UI/Components/Navbar/NavBar.js +44 -9
  603. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  604. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
  605. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  606. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +1 -1
  607. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
  608. package/build/dist/UI/Components/SideMenu/SideMenu.js +9 -6
  609. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  610. package/build/dist/UI/Components/SideMenu/SideMenuItem.js +1 -1
  611. package/build/dist/UI/Components/SideMenu/SideMenuItem.js.map +1 -1
  612. package/build/dist/UI/Components/SideMenu/SideMenuSection.js +7 -2
  613. package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
  614. package/build/dist/UI/Components/Table/TableHeader.js +20 -22
  615. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  616. package/build/dist/UI/Components/Table/TableRow.js +13 -4
  617. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  618. package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js +42 -0
  619. package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js.map +1 -0
  620. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +74 -0
  621. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -0
  622. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +153 -0
  623. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +1 -0
  624. package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js +126 -0
  625. package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js.map +1 -0
  626. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +2 -1
  627. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
  628. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +67 -32
  629. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  630. package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js +114 -0
  631. package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js.map +1 -0
  632. package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -1
  633. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +127 -7
  634. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
  635. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +50 -3
  636. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  637. package/build/dist/Utils/NetworkDiscovery/ReverseDnsNameUtil.js +132 -0
  638. package/build/dist/Utils/NetworkDiscovery/ReverseDnsNameUtil.js.map +1 -0
  639. package/build/dist/Utils/Rules/RuleEngineLimits.js +31 -0
  640. package/build/dist/Utils/Rules/RuleEngineLimits.js.map +1 -0
  641. package/build/dist/Utils/Rules/RulePatternMatchUtil.js +1 -1
  642. package/build/dist/Utils/Slug.js +31 -1
  643. package/build/dist/Utils/Slug.js.map +1 -1
  644. package/jest.config.json +1 -0
  645. package/package.json +2 -2
@@ -1,3 +1,43 @@
1
+ /*
2
+ * The per-release ceiling used to be a hardcoded 100 that also bounded what
3
+ * resolveFramesForService would READ. This suite pins the two apart:
4
+ *
5
+ * - the ceiling is an operator knob (SOURCE_MAP_MAX_MAPS_PER_RELEASE) that
6
+ * gates WRITES, enforced on the create path itself;
7
+ * - the read path bounds itself by BYTES loaded
8
+ * (SOURCE_MAP_MAX_BYTES_PER_RESOLVE), so a release that fits the write
9
+ * gate always resolves in full.
10
+ *
11
+ * SourceMapMaxBytesPerResolve is read at call time, so a getter over the real
12
+ * config lets a case pick a budget measured in tens of bytes while every
13
+ * other value stays production-real. bytesPerResolve === null means "use the
14
+ * configured value", which is what every non-budget case runs with.
15
+ */
16
+ const mockResolveBudget: { bytesPerResolve: number | null } = {
17
+ bytesPerResolve: null,
18
+ };
19
+
20
+ jest.mock("../../../Server/EnvironmentConfig", () => {
21
+ const actualConfig: Record<string, unknown> = jest.requireActual(
22
+ "../../../Server/EnvironmentConfig",
23
+ ) as Record<string, unknown>;
24
+
25
+ const mockedConfig: Record<string, unknown> = { ...actualConfig };
26
+
27
+ Object.defineProperty(mockedConfig, "SourceMapMaxBytesPerResolve", {
28
+ configurable: true,
29
+ enumerable: true,
30
+ get: (): number => {
31
+ return (
32
+ mockResolveBudget.bytesPerResolve ??
33
+ (actualConfig["SourceMapMaxBytesPerResolve"] as number)
34
+ );
35
+ },
36
+ });
37
+
38
+ return mockedConfig;
39
+ });
40
+
1
41
  import TelemetrySourceMapService, {
2
42
  MAX_SOURCE_MAP_SIZE_IN_BYTES,
3
43
  MAX_SOURCE_MAPS_PER_RELEASE,
@@ -7,7 +47,18 @@ import CreateBy from "../../../Server/Types/Database/CreateBy";
7
47
  import TelemetrySourceMap from "../../../Models/DatabaseModels/TelemetrySourceMap";
8
48
  import BadDataException from "../../../Types/Exception/BadDataException";
9
49
  import ObjectID from "../../../Types/ObjectID";
10
- import { ResolveStackTraceResult } from "../../../Types/Telemetry/SourceMap";
50
+ import PositiveNumber from "../../../Types/PositiveNumber";
51
+ import LIMIT_MAX from "../../../Types/Database/LimitMax";
52
+ import logger from "../../../Server/Utils/Logger";
53
+ import { MAX_FRAMES_TO_RESOLVE } from "../../../Server/Utils/Telemetry/SourceMapResolver";
54
+ import {
55
+ SourceMapMaxMapsPerRelease,
56
+ SourceMapRetentionInDays,
57
+ } from "../../../Server/EnvironmentConfig";
58
+ import {
59
+ MinifiedStackFrame,
60
+ ResolveStackTraceResult,
61
+ } from "../../../Types/Telemetry/SourceMap";
11
62
  import { afterEach, describe, expect, it, jest } from "@jest/globals";
12
63
 
13
64
  /*
@@ -76,6 +127,25 @@ const invokeOnBeforeCreate: InvokeOnBeforeCreateFunction = async (
76
127
  ).onBeforeCreate(createBy);
77
128
  };
78
129
 
130
+ type CaptureOnBeforeCreateRejectionFunction = (
131
+ createBy: CreateBy<TelemetrySourceMap>,
132
+ ) => Promise<Error>;
133
+
134
+ /*
135
+ * The rejection itself, not just its type: the per-release message has to
136
+ * name the knob an operator would raise, so the message is asserted on.
137
+ */
138
+ const captureOnBeforeCreateRejection: CaptureOnBeforeCreateRejectionFunction =
139
+ async (createBy: CreateBy<TelemetrySourceMap>): Promise<Error> => {
140
+ try {
141
+ await invokeOnBeforeCreate(createBy);
142
+ } catch (err) {
143
+ return err as Error;
144
+ }
145
+
146
+ throw new Error("Expected onBeforeCreate to reject, but it resolved.");
147
+ };
148
+
79
149
  type MakeRowFunction = (
80
150
  bundlePath: string,
81
151
  content?: string,
@@ -94,8 +164,144 @@ const makeRow: MakeRowFunction = (
94
164
  return row;
95
165
  };
96
166
 
167
+ type MakeListingRowFunction = (data: {
168
+ bundlePath: string;
169
+ sizeInBytes?: number | null | undefined;
170
+ createdAt?: Date | undefined;
171
+ }) => TelemetrySourceMap;
172
+
173
+ /*
174
+ * A row as the resolver's FIRST findBy returns it: no content, but the two
175
+ * columns the byte budget reads — the stored size, and the timestamp that
176
+ * breaks ties between maps that match a frame equally well.
177
+ */
178
+ const makeListingRow: MakeListingRowFunction = (data: {
179
+ bundlePath: string;
180
+ sizeInBytes?: number | null | undefined;
181
+ createdAt?: Date | undefined;
182
+ }): TelemetrySourceMap => {
183
+ const row: TelemetrySourceMap = makeRow(data.bundlePath);
184
+
185
+ if (data.sizeInBytes !== undefined) {
186
+ /*
187
+ * Widened deliberately: the column is typed number, but a row written
188
+ * outside the create hook can hold null or a nonsense count, and the
189
+ * budget has to charge those something.
190
+ */
191
+ (row as unknown as { sizeInBytes: number | null | undefined }).sizeInBytes =
192
+ data.sizeInBytes;
193
+ }
194
+
195
+ if (data.createdAt) {
196
+ row.createdAt = data.createdAt;
197
+ }
198
+
199
+ return row;
200
+ };
201
+
202
+ type MakeFrameFunction = (fileName: string) => MinifiedStackFrame;
203
+
204
+ /*
205
+ * A frame that the fixture map can actually resolve (line 1, column 46 maps
206
+ * to onSelect), so "did this map get loaded" and "did this frame resolve" are
207
+ * the same question.
208
+ */
209
+ const makeFrame: MakeFrameFunction = (fileName: string): MinifiedStackFrame => {
210
+ return {
211
+ functionName: "e.onSelect",
212
+ fileName: fileName,
213
+ lineNumber: 1,
214
+ columnNumber: 46,
215
+ inApp: true,
216
+ };
217
+ };
218
+
219
+ type FindBySpy = ReturnType<typeof jest.spyOn>;
220
+
221
+ type ContentFetchArgsFunction = (findBySpy: FindBySpy) => {
222
+ query: Record<string, unknown>;
223
+ limit: number;
224
+ };
225
+
226
+ /** Arguments of the SECOND findBy — the content fetch. */
227
+ const contentFetchArgs: ContentFetchArgsFunction = (
228
+ findBySpy: FindBySpy,
229
+ ): { query: Record<string, unknown>; limit: number } => {
230
+ return findBySpy.mock.calls[1]![0] as never;
231
+ };
232
+
233
+ type RequestedContentIdsFunction = (findBySpy: FindBySpy) => Array<string>;
234
+
235
+ /*
236
+ * The ids the content fetch actually asked for. QueryHelper.any produces a
237
+ * TypeORM Raw operator whose parameters object holds the id list under a
238
+ * random key, so it is dug out rather than compared structurally.
239
+ */
240
+ const requestedContentIds: RequestedContentIdsFunction = (
241
+ findBySpy: FindBySpy,
242
+ ): Array<string> => {
243
+ const idOperator: { objectLiteralParameters?: Record<string, unknown> } =
244
+ contentFetchArgs(findBySpy).query["_id"] as never;
245
+
246
+ const idList: Array<unknown> = Object.values(
247
+ idOperator.objectLiteralParameters || {},
248
+ )[0] as Array<unknown>;
249
+
250
+ return idList.map((id: unknown) => {
251
+ return String(id);
252
+ });
253
+ };
254
+
255
+ type MockReleaseRowCountFunction = (count: number) => FindBySpy;
256
+
257
+ const mockReleaseRowCount: MockReleaseRowCountFunction = (
258
+ count: number,
259
+ ): FindBySpy => {
260
+ return jest
261
+ .spyOn(TelemetrySourceMapService, "countBy")
262
+ .mockResolvedValue(new PositiveNumber(count) as never);
263
+ };
264
+
265
+ type MockStoredBundlePathsFunction = (bundlePaths: Array<string>) => FindBySpy;
266
+
267
+ const mockStoredBundlePaths: MockStoredBundlePathsFunction = (
268
+ bundlePaths: Array<string>,
269
+ ): FindBySpy => {
270
+ return jest
271
+ .spyOn(TelemetrySourceMapService, "getStoredBundlePathsForRelease")
272
+ .mockResolvedValue(bundlePaths as never);
273
+ };
274
+
275
+ type MakeBundlePathsFunction = (count: number) => Array<string>;
276
+
277
+ const makeBundlePaths: MakeBundlePathsFunction = (
278
+ count: number,
279
+ ): Array<string> => {
280
+ const bundlePaths: Array<string> = [];
281
+
282
+ for (let index: number = 0; index < count; index++) {
283
+ bundlePaths.push(`chunk-${index}.js`);
284
+ }
285
+
286
+ return bundlePaths;
287
+ };
288
+
289
+ /*
290
+ * One frame, three stored bundles that match it at three different strengths.
291
+ * The score is the number of trailing path segments the frame and the bundle
292
+ * share, so "x/y/main.abc123.js" (3) beats "y/main.abc123.js" (2) beats
293
+ * "main.abc123.js" (1). That ordering is what the byte budget spends: when
294
+ * not everything fits, the weakest match is what goes.
295
+ */
296
+ const SCORED_FRAME_FILE_NAME: string =
297
+ "https://cdn.example.com/x/y/main.abc123.js";
298
+ const STRONG_MATCH_PATH: string = "x/y/main.abc123.js";
299
+ const MEDIUM_MATCH_PATH: string = "y/main.abc123.js";
300
+ const WEAK_MATCH_PATH: string = "main.abc123.js";
301
+
97
302
  afterEach(() => {
98
303
  jest.restoreAllMocks();
304
+ mockResolveBudget.bytesPerResolve = null;
99
305
  });
100
306
 
101
307
  describe("retention configuration", () => {
@@ -107,10 +313,91 @@ describe("retention configuration", () => {
107
313
  SOURCE_MAP_RETENTION_DAYS,
108
314
  );
109
315
  });
316
+
317
+ it("drives retention from the SOURCE_MAP_RETENTION_DAYS knob rather than a literal", () => {
318
+ /*
319
+ * The service must not re-declare the number. If the constant ever drifts
320
+ * from the config, an operator who sets SOURCE_MAP_RETENTION_DAYS gets a
321
+ * sweep interval they did not ask for — silently, since nothing else
322
+ * reports the effective value.
323
+ */
324
+ expect(SOURCE_MAP_RETENTION_DAYS).toBe(SourceMapRetentionInDays);
325
+ expect(TelemetrySourceMapService.hardDeleteItemsOlderThanDays).toBe(
326
+ SourceMapRetentionInDays,
327
+ );
328
+ });
329
+
330
+ it("drives the per-release ceiling from the SOURCE_MAP_MAX_MAPS_PER_RELEASE knob", () => {
331
+ expect(MAX_SOURCE_MAPS_PER_RELEASE).toBe(SourceMapMaxMapsPerRelease);
332
+ });
333
+
334
+ /*
335
+ * The two assertions above are only as strong as the environment they run
336
+ * in: with nothing configured, SourceMapRetentionInDays IS 90 and
337
+ * SourceMapMaxMapsPerRelease IS 1000, so a service that went back to
338
+ * hardcoding those two literals would satisfy both of them and every other
339
+ * case in this file.
340
+ *
341
+ * The only way to tell "reads the knob" from "happens to equal the knob's
342
+ * default" is to configure something the defaults are not, and re-enter the
343
+ * module — both constants are evaluated once, at import, and
344
+ * hardDeleteItemsOlderThanDays is stamped from one of them in the
345
+ * constructor, so a fresh registry is what it takes to observe them at all.
346
+ */
347
+ it("picks up configured values rather than the defaults it happens to match", async () => {
348
+ const originalEnv: NodeJS.ProcessEnv = { ...process.env };
349
+
350
+ // Two numbers no default in this system is, so neither can coincide.
351
+ process.env["SOURCE_MAP_MAX_MAPS_PER_RELEASE"] = "4242";
352
+ process.env["SOURCE_MAP_RETENTION_DAYS"] = "17";
353
+
354
+ try {
355
+ /*
356
+ * resetModules rather than isolateModules: the mock factory reaches for
357
+ * the real EnvironmentConfig with requireActual, and an isolated
358
+ * registry still serves that from the copy loaded when this file was
359
+ * imported — so the module would come back holding the defaults and the
360
+ * case would prove nothing. This is the same reload the config suite
361
+ * uses.
362
+ */
363
+ jest.resetModules();
364
+
365
+ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
366
+ const reloaded: {
367
+ default: { hardDeleteItemsOlderThanDays: number };
368
+ MAX_SOURCE_MAPS_PER_RELEASE: number;
369
+ SOURCE_MAP_RETENTION_DAYS: number;
370
+ } = require("../../../Server/Services/TelemetrySourceMapService");
371
+ /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
372
+
373
+ expect(reloaded.MAX_SOURCE_MAPS_PER_RELEASE).toBe(4242);
374
+ expect(reloaded.SOURCE_MAP_RETENTION_DAYS).toBe(17);
375
+ /*
376
+ * And the configured retention reaches the sweep the worker actually
377
+ * reads, not just the exported constant.
378
+ */
379
+ expect(reloaded.default.hardDeleteItemsOlderThanDays).toBe(17);
380
+ } finally {
381
+ process.env = originalEnv;
382
+ /*
383
+ * Leave the registry as it was found. Every other case in this file
384
+ * works against the singleton captured by the imports at the top, which
385
+ * this does not disturb.
386
+ */
387
+ jest.resetModules();
388
+ }
389
+ });
110
390
  });
111
391
 
112
392
  describe("onBeforeCreate", () => {
113
393
  it("stamps sizeInBytes from the content", async () => {
394
+ /*
395
+ * Every create that survives validation now counts the release's rows, so
396
+ * the count is mocked here rather than reaching a database that no unit
397
+ * test connects to.
398
+ */
399
+ mockReleaseRowCount(0);
400
+
114
401
  const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy();
115
402
 
116
403
  await invokeOnBeforeCreate(createBy);
@@ -179,6 +466,232 @@ describe("onBeforeCreate", () => {
179
466
  });
180
467
  });
181
468
 
469
+ /*
470
+ * The per-release ceiling on the create path.
471
+ *
472
+ * The upload endpoint checks a whole batch up front, but TelemetrySourceMap
473
+ * is a full CRUD resource: POST /api/telemetry-source-map reaches create()
474
+ * without ever passing that check, so without this hook the configured
475
+ * ceiling was advisory on that path.
476
+ */
477
+ describe("onBeforeCreate per-release ceiling", () => {
478
+ it("counts the release's rows scoped to the tenant, service and version, as root", async () => {
479
+ const countBySpy: FindBySpy = mockReleaseRowCount(0);
480
+
481
+ await invokeOnBeforeCreate(makeCreateBy());
482
+
483
+ const countArgs: {
484
+ query: Record<string, unknown>;
485
+ props: Record<string, unknown>;
486
+ skip: number;
487
+ limit: number;
488
+ } = countBySpy.mock.calls[0]![0] as never;
489
+
490
+ expect(countArgs.query["projectId"]).toBe(PROJECT_ID);
491
+ expect(countArgs.query["serviceId"]).toBe(SERVICE_ID);
492
+ expect(countArgs.query["serviceVersion"]).toBe("1.4.2");
493
+ expect(countArgs.skip).toBe(0);
494
+ expect(countArgs.limit).toBe(LIMIT_MAX);
495
+ // The hook runs inside create, before permissions are re-derived.
496
+ expect(countArgs.props["isRoot"]).toBe(true);
497
+ });
498
+
499
+ it("admits a create one map under the ceiling without paying for the distinct-bundle listing", async () => {
500
+ mockReleaseRowCount(MAX_SOURCE_MAPS_PER_RELEASE - 1);
501
+ const bundlePathsSpy: FindBySpy = mockStoredBundlePaths([]);
502
+
503
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy();
504
+
505
+ await invokeOnBeforeCreate(createBy);
506
+
507
+ /*
508
+ * Below the ceiling there is nothing to decide, whatever the rows hold —
509
+ * so the second, more expensive query must not run. This is what keeps a
510
+ * bulk upload paying one indexed COUNT per insert and no more.
511
+ */
512
+ expect(bundlePathsSpy).not.toHaveBeenCalled();
513
+ expect(createBy.data.sizeInBytes).toBe(
514
+ Buffer.byteLength(FIXTURE_MAP, "utf8"),
515
+ );
516
+ });
517
+
518
+ it("rejects a new bundle when the release already holds the ceiling in distinct bundles", async () => {
519
+ mockReleaseRowCount(MAX_SOURCE_MAPS_PER_RELEASE);
520
+ mockStoredBundlePaths(makeBundlePaths(MAX_SOURCE_MAPS_PER_RELEASE));
521
+
522
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy({
523
+ bundlePath: "brand-new-chunk.js",
524
+ });
525
+
526
+ const error: Error = await captureOnBeforeCreateRejection(createBy);
527
+
528
+ expect(error).toBeInstanceOf(BadDataException);
529
+ expect(error.message).toContain(
530
+ `limit of ${MAX_SOURCE_MAPS_PER_RELEASE} per release`,
531
+ );
532
+ /*
533
+ * The message has to name the knob: the only fix a self-hosted operator
534
+ * has for a full release is raising it (or deleting maps), and nothing
535
+ * else in the response says what it is called.
536
+ */
537
+ expect(error.message).toContain("SOURCE_MAP_MAX_MAPS_PER_RELEASE");
538
+ });
539
+
540
+ it("rejects a new bundle when the release is already above the ceiling", async () => {
541
+ const overCeiling: number = MAX_SOURCE_MAPS_PER_RELEASE + 7;
542
+
543
+ mockReleaseRowCount(overCeiling);
544
+ mockStoredBundlePaths(makeBundlePaths(overCeiling));
545
+
546
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy({
547
+ bundlePath: "brand-new-chunk.js",
548
+ });
549
+
550
+ const error: Error = await captureOnBeforeCreateRejection(createBy);
551
+
552
+ expect(error).toBeInstanceOf(BadDataException);
553
+ expect(error.message).toContain("SOURCE_MAP_MAX_MAPS_PER_RELEASE");
554
+ });
555
+
556
+ it("refuses the full release before stamping sizeInBytes, so nothing half-processed is handed on", async () => {
557
+ mockReleaseRowCount(MAX_SOURCE_MAPS_PER_RELEASE);
558
+ mockStoredBundlePaths(makeBundlePaths(MAX_SOURCE_MAPS_PER_RELEASE));
559
+
560
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy({
561
+ bundlePath: "brand-new-chunk.js",
562
+ });
563
+
564
+ await captureOnBeforeCreateRejection(createBy);
565
+
566
+ expect(createBy.data.sizeInBytes).toBeUndefined();
567
+ });
568
+
569
+ it("still admits a re-upload of a bundle the release already holds when it is at the ceiling", async () => {
570
+ /*
571
+ * A CI re-run replaces every bundle of a release. Rejecting a path the
572
+ * release already contains would fail that re-run outright, even though
573
+ * it does not grow the release by a single bundle.
574
+ */
575
+ mockReleaseRowCount(MAX_SOURCE_MAPS_PER_RELEASE);
576
+ mockStoredBundlePaths(makeBundlePaths(MAX_SOURCE_MAPS_PER_RELEASE));
577
+
578
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy({
579
+ bundlePath: "chunk-0.js",
580
+ });
581
+
582
+ await invokeOnBeforeCreate(createBy);
583
+
584
+ expect(createBy.data.sizeInBytes).toBe(
585
+ Buffer.byteLength(FIXTURE_MAP, "utf8"),
586
+ );
587
+ });
588
+
589
+ it("compares the incoming bundle against stored paths after normalization", async () => {
590
+ /*
591
+ * "/main.abc123.js" and "main.abc123.js" are the same bundle to the
592
+ * resolver, so they must be the same bundle to the gate too — otherwise a
593
+ * release at the ceiling rejects a re-upload purely over a leading slash.
594
+ */
595
+ mockReleaseRowCount(MAX_SOURCE_MAPS_PER_RELEASE);
596
+ mockStoredBundlePaths([
597
+ "/main.abc123.js",
598
+ ...makeBundlePaths(MAX_SOURCE_MAPS_PER_RELEASE - 1),
599
+ ]);
600
+
601
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy({
602
+ bundlePath: "main.abc123.js",
603
+ });
604
+
605
+ await invokeOnBeforeCreate(createBy);
606
+
607
+ expect(createBy.data.sizeInBytes).toBe(
608
+ Buffer.byteLength(FIXTURE_MAP, "utf8"),
609
+ );
610
+ });
611
+
612
+ it("admits a new bundle when duplicate rows, not distinct bundles, reached the row count", async () => {
613
+ /*
614
+ * The model deliberately allows more than one row per bundle (a racing
615
+ * double upload must not fail CI). Rejecting on the ROW count alone would
616
+ * therefore refuse a legitimate new bundle purely because a duplicate row
617
+ * exists somewhere in the release — the ceiling counts distinct bundles.
618
+ */
619
+ mockReleaseRowCount(MAX_SOURCE_MAPS_PER_RELEASE);
620
+ mockStoredBundlePaths([
621
+ ...makeBundlePaths(MAX_SOURCE_MAPS_PER_RELEASE - 1),
622
+ "chunk-0.js",
623
+ ]);
624
+
625
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy({
626
+ bundlePath: "brand-new-chunk.js",
627
+ });
628
+
629
+ await invokeOnBeforeCreate(createBy);
630
+
631
+ expect(createBy.data.sizeInBytes).toBe(
632
+ Buffer.byteLength(FIXTURE_MAP, "utf8"),
633
+ );
634
+ });
635
+
636
+ it("falls back to the tenant on props when the row itself carries no projectId", async () => {
637
+ /*
638
+ * A tenant-scoped create leaves projectId to the tenant column, so the
639
+ * gate has to read props.tenantId — otherwise the whole check silently
640
+ * no-ops on exactly the path most creates take.
641
+ */
642
+ const countBySpy: FindBySpy = mockReleaseRowCount(0);
643
+
644
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy();
645
+ delete createBy.data.projectId;
646
+ createBy.props.tenantId = PROJECT_ID;
647
+
648
+ await invokeOnBeforeCreate(createBy);
649
+
650
+ const countArgs: { query: Record<string, unknown> } = countBySpy.mock
651
+ .calls[0]![0] as never;
652
+ expect(countArgs.query["projectId"]).toBe(PROJECT_ID);
653
+ });
654
+
655
+ it("does not count when the create can be scoped to no project at all", async () => {
656
+ const countBySpy: FindBySpy = mockReleaseRowCount(0);
657
+
658
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy();
659
+ delete createBy.data.projectId;
660
+
661
+ await invokeOnBeforeCreate(createBy);
662
+
663
+ /*
664
+ * Not a silent pass: the tenant column check rejects this create on its
665
+ * own, with a better message than a count could give.
666
+ */
667
+ expect(countBySpy).not.toHaveBeenCalled();
668
+ });
669
+
670
+ it("does not count when the create carries no serviceId", async () => {
671
+ const countBySpy: FindBySpy = mockReleaseRowCount(0);
672
+
673
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy();
674
+ delete createBy.data.serviceId;
675
+
676
+ await invokeOnBeforeCreate(createBy);
677
+
678
+ expect(countBySpy).not.toHaveBeenCalled();
679
+ });
680
+
681
+ it("does not count when the release version is missing, because validation rejects first", async () => {
682
+ const countBySpy: FindBySpy = mockReleaseRowCount(0);
683
+
684
+ const createBy: CreateBy<TelemetrySourceMap> = makeCreateBy({
685
+ serviceVersion: "",
686
+ });
687
+
688
+ await expect(invokeOnBeforeCreate(createBy)).rejects.toThrow(
689
+ BadDataException,
690
+ );
691
+ expect(countBySpy).not.toHaveBeenCalled();
692
+ });
693
+ });
694
+
182
695
  describe("replaceSourceMap", () => {
183
696
  it("hard deletes existing rows for the same bundle before creating the new one", async () => {
184
697
  const callOrder: Array<string> = [];
@@ -305,9 +818,23 @@ describe("resolveFramesForService", () => {
305
818
  expect(listArgs.query["projectId"]).toBe(PROJECT_ID);
306
819
  expect(listArgs.query["serviceId"]).toBe(SERVICE_ID);
307
820
  expect(listArgs.query["serviceVersion"]).toBe("1.4.2");
308
- expect(listArgs.limit).toBe(MAX_SOURCE_MAPS_PER_RELEASE);
821
+ /*
822
+ * LIMIT_MAX, deliberately NOT the per-release ceiling. The ceiling counts
823
+ * distinct bundle PATHS while a read limit counts ROWS; tying the read to
824
+ * it made duplicate rows push real bundles out of resolution (see the
825
+ * duplicate-row case below). Memory is bounded by the byte budget now.
826
+ */
827
+ expect(listArgs.limit).toBe(LIMIT_MAX);
309
828
  // The listing must not pull content.
310
829
  expect(listArgs.select["content"]).toBeUndefined();
830
+ /*
831
+ * It must pull the size, though: the byte budget charges each candidate
832
+ * its stored size, and a listing that did not select the column would
833
+ * charge every row the per-map ceiling instead.
834
+ */
835
+ expect(listArgs.select["sizeInBytes"]).toBe(true);
836
+ expect(listArgs.select["createdAt"]).toBe(true);
837
+ expect(listArgs.select["bundlePath"]).toBe(true);
311
838
 
312
839
  // The content fetch stays tenant-scoped too.
313
840
  const contentArgs: {
@@ -362,6 +889,37 @@ describe("resolveFramesForService", () => {
362
889
  expect(String(idList[0])).toBe(matchedRow.id!.toString());
363
890
  });
364
891
 
892
+ it("asks the content fetch for exactly as many rows as it selected", async () => {
893
+ /*
894
+ * The content limit used to be the per-release ceiling, which asked the
895
+ * database for room for a thousand rows to fetch two. It is the size of
896
+ * the selected set now, so the query cannot return more than was chosen.
897
+ */
898
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
899
+ .spyOn(TelemetrySourceMapService, "findBy")
900
+ .mockResolvedValueOnce([
901
+ makeListingRow({ bundlePath: STRONG_MATCH_PATH, sizeInBytes: 100 }),
902
+ makeListingRow({ bundlePath: WEAK_MATCH_PATH, sizeInBytes: 100 }),
903
+ makeListingRow({ bundlePath: "admin.f00baa.js", sizeInBytes: 100 }),
904
+ ] as never)
905
+ .mockResolvedValueOnce([
906
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
907
+ makeRow(WEAK_MATCH_PATH, FIXTURE_MAP),
908
+ ] as never);
909
+
910
+ const result: ResolveStackTraceResult =
911
+ await TelemetrySourceMapService.resolveFramesForService({
912
+ projectId: PROJECT_ID,
913
+ serviceId: SERVICE_ID,
914
+ serviceVersion: "1.4.2",
915
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
916
+ });
917
+
918
+ expect(result.sourceMapCount).toBe(3);
919
+ expect(requestedContentIds(findBySpy)).toHaveLength(2);
920
+ expect(contentFetchArgs(findBySpy).limit).toBe(2);
921
+ });
922
+
365
923
  it("skips the content fetch entirely when no stored bundle matches any frame", async () => {
366
924
  const findBySpy: ReturnType<typeof jest.spyOn> = jest
367
925
  .spyOn(TelemetrySourceMapService, "findBy")
@@ -426,6 +984,87 @@ describe("resolveFramesForService", () => {
426
984
  expect(result.frames[0]!.originalFunctionName).toBe("newest");
427
985
  });
428
986
 
987
+ it("still resolves the last distinct bundle of a full release when a duplicate row exists", async () => {
988
+ /*
989
+ * THE REGRESSION THIS FIXES.
990
+ *
991
+ * A release holding the maximum number of distinct bundles, plus one
992
+ * duplicate row of an early bundle — which the model allows, because a
993
+ * racing double upload must not fail CI. That is one row MORE than the
994
+ * per-release ceiling.
995
+ *
996
+ * The listing used to read with limit = MAX_SOURCE_MAPS_PER_RELEASE, so
997
+ * the duplicate consumed the slot of the last distinct bundle: the map
998
+ * uploaded fine, showed in the dashboard, and then silently never
999
+ * resolved, with nothing anywhere saying why. The listing reads LIMIT_MAX
1000
+ * and dedupes afterwards now, so a release that fits the write gate is
1001
+ * fully reachable however many duplicate rows it accumulated.
1002
+ *
1003
+ * The findBy mock honours the limit it is handed, exactly as the database
1004
+ * would — otherwise this case would pass under the old code too.
1005
+ */
1006
+ const listingRows: Array<TelemetrySourceMap> = [];
1007
+
1008
+ for (let index: number = 0; index < MAX_SOURCE_MAPS_PER_RELEASE; index++) {
1009
+ listingRows.push(
1010
+ makeListingRow({ bundlePath: `chunk-${index}.js`, sizeInBytes: 1024 }),
1011
+ );
1012
+
1013
+ if (index === 0) {
1014
+ // The duplicate row, newest-first next to the bundle it duplicates.
1015
+ listingRows.push(
1016
+ makeListingRow({ bundlePath: "chunk-0.js", sizeInBytes: 1024 }),
1017
+ );
1018
+ }
1019
+ }
1020
+
1021
+ const lastDistinctBundlePath: string = `chunk-${
1022
+ MAX_SOURCE_MAPS_PER_RELEASE - 1
1023
+ }.js`;
1024
+ const lastDistinctRow: TelemetrySourceMap =
1025
+ listingRows[listingRows.length - 1]!;
1026
+
1027
+ expect(listingRows).toHaveLength(MAX_SOURCE_MAPS_PER_RELEASE + 1);
1028
+ expect(lastDistinctRow.bundlePath).toBe(lastDistinctBundlePath);
1029
+
1030
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
1031
+ TelemetrySourceMapService,
1032
+ "findBy",
1033
+ );
1034
+
1035
+ findBySpy.mockImplementationOnce((async (listQuery: {
1036
+ limit: number;
1037
+ skip: number;
1038
+ }): Promise<Array<TelemetrySourceMap>> => {
1039
+ return listingRows.slice(
1040
+ listQuery.skip,
1041
+ listQuery.skip + listQuery.limit,
1042
+ );
1043
+ }) as never);
1044
+
1045
+ findBySpy.mockResolvedValueOnce([
1046
+ makeRow(lastDistinctBundlePath, FIXTURE_MAP),
1047
+ ] as never);
1048
+
1049
+ const result: ResolveStackTraceResult =
1050
+ await TelemetrySourceMapService.resolveFramesForService({
1051
+ projectId: PROJECT_ID,
1052
+ serviceId: SERVICE_ID,
1053
+ serviceVersion: "1.4.2",
1054
+ frames: [makeFrame(lastDistinctBundlePath)],
1055
+ });
1056
+
1057
+ // The duplicate collapsed away, leaving every distinct bundle listed.
1058
+ expect(result.sourceMapCount).toBe(MAX_SOURCE_MAPS_PER_RELEASE);
1059
+ expect(requestedContentIds(findBySpy)).toEqual([
1060
+ lastDistinctRow.id!.toString(),
1061
+ ]);
1062
+ expect(result.resolvedCount).toBe(1);
1063
+ expect(result.frames[0]!.resolved).toBe(true);
1064
+ expect(result.frames[0]!.originalFunctionName).toBe("onSelect");
1065
+ expect(result.sourceMapsSkippedForSize).toBe(0);
1066
+ });
1067
+
429
1068
  it("returns unresolved frames without querying when the release is empty", async () => {
430
1069
  const findBySpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
431
1070
  TelemetrySourceMapService,
@@ -453,6 +1092,32 @@ describe("resolveFramesForService", () => {
453
1092
  expect(result.resolvedCount).toBe(0);
454
1093
  expect(result.frames).toHaveLength(1);
455
1094
  expect(result.frames[0]!.resolved).toBe(false);
1095
+ /*
1096
+ * Zero, never undefined: the field is required on the response, and the
1097
+ * dashboard reads it to decide whether to tell the user that symbols are
1098
+ * missing for a reason an operator can fix.
1099
+ */
1100
+ expect(result.sourceMapsSkippedForSize).toBe(0);
1101
+ });
1102
+
1103
+ it("returns nothing skipped when there are no frames to resolve", async () => {
1104
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
1105
+ TelemetrySourceMapService,
1106
+ "findBy",
1107
+ );
1108
+
1109
+ const result: ResolveStackTraceResult =
1110
+ await TelemetrySourceMapService.resolveFramesForService({
1111
+ projectId: PROJECT_ID,
1112
+ serviceId: SERVICE_ID,
1113
+ serviceVersion: "1.4.2",
1114
+ frames: [],
1115
+ });
1116
+
1117
+ expect(findBySpy).not.toHaveBeenCalled();
1118
+ expect(result.frames).toHaveLength(0);
1119
+ expect(result.sourceMapCount).toBe(0);
1120
+ expect(result.sourceMapsSkippedForSize).toBe(0);
456
1121
  });
457
1122
 
458
1123
  it("returns unresolved frames when no maps exist for the release", async () => {
@@ -481,3 +1146,546 @@ describe("resolveFramesForService", () => {
481
1146
  expect(result.frames[0]!.resolved).toBe(false);
482
1147
  });
483
1148
  });
1149
+
1150
+ /*
1151
+ * The byte budget: the bound that used to be implied by the per-release
1152
+ * count, and a far tighter one, because it measures the thing that actually
1153
+ * consumes memory. Resolution materialises whole maps (up to 50 MB each) and
1154
+ * the set it loads is chosen by a caller-supplied frames array.
1155
+ */
1156
+ describe("resolveFramesForService byte budget", () => {
1157
+ it("loads every matching map when the whole matched set fits", async () => {
1158
+ const warnSpy: ReturnType<typeof jest.spyOn> = jest
1159
+ .spyOn(logger, "warn")
1160
+ .mockImplementation((): void => {});
1161
+
1162
+ mockResolveBudget.bytesPerResolve = 1024 * 1024;
1163
+
1164
+ const strongRow: TelemetrySourceMap = makeListingRow({
1165
+ bundlePath: STRONG_MATCH_PATH,
1166
+ sizeInBytes: 100,
1167
+ });
1168
+ const mediumRow: TelemetrySourceMap = makeListingRow({
1169
+ bundlePath: MEDIUM_MATCH_PATH,
1170
+ sizeInBytes: 100,
1171
+ });
1172
+ const weakRow: TelemetrySourceMap = makeListingRow({
1173
+ bundlePath: WEAK_MATCH_PATH,
1174
+ sizeInBytes: 100,
1175
+ });
1176
+
1177
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1178
+ .spyOn(TelemetrySourceMapService, "findBy")
1179
+ .mockResolvedValueOnce([strongRow, mediumRow, weakRow] as never)
1180
+ .mockResolvedValueOnce([
1181
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1182
+ ] as never);
1183
+
1184
+ const result: ResolveStackTraceResult =
1185
+ await TelemetrySourceMapService.resolveFramesForService({
1186
+ projectId: PROJECT_ID,
1187
+ serviceId: SERVICE_ID,
1188
+ serviceVersion: "1.4.2",
1189
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1190
+ });
1191
+
1192
+ expect(requestedContentIds(findBySpy).sort()).toEqual(
1193
+ [
1194
+ strongRow.id!.toString(),
1195
+ mediumRow.id!.toString(),
1196
+ weakRow.id!.toString(),
1197
+ ].sort(),
1198
+ );
1199
+ expect(contentFetchArgs(findBySpy).limit).toBe(3);
1200
+ expect(result.sourceMapsSkippedForSize).toBe(0);
1201
+ // Nothing was dropped, so there is nothing for an operator to hear about.
1202
+ expect(warnSpy).not.toHaveBeenCalled();
1203
+ });
1204
+
1205
+ it("skips the maps that do not fit and reports them on the response", async () => {
1206
+ const warnSpy: ReturnType<typeof jest.spyOn> = jest
1207
+ .spyOn(logger, "warn")
1208
+ .mockImplementation((): void => {});
1209
+
1210
+ // Room for two of the three 100-byte maps.
1211
+ mockResolveBudget.bytesPerResolve = 250;
1212
+
1213
+ const strongRow: TelemetrySourceMap = makeListingRow({
1214
+ bundlePath: STRONG_MATCH_PATH,
1215
+ sizeInBytes: 100,
1216
+ });
1217
+ const mediumRow: TelemetrySourceMap = makeListingRow({
1218
+ bundlePath: MEDIUM_MATCH_PATH,
1219
+ sizeInBytes: 100,
1220
+ });
1221
+ const weakRow: TelemetrySourceMap = makeListingRow({
1222
+ bundlePath: WEAK_MATCH_PATH,
1223
+ sizeInBytes: 100,
1224
+ });
1225
+
1226
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1227
+ .spyOn(TelemetrySourceMapService, "findBy")
1228
+ .mockResolvedValueOnce([strongRow, mediumRow, weakRow] as never)
1229
+ .mockResolvedValueOnce([
1230
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1231
+ ] as never);
1232
+
1233
+ const result: ResolveStackTraceResult =
1234
+ await TelemetrySourceMapService.resolveFramesForService({
1235
+ projectId: PROJECT_ID,
1236
+ serviceId: SERVICE_ID,
1237
+ serviceVersion: "1.4.2",
1238
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1239
+ });
1240
+
1241
+ expect(requestedContentIds(findBySpy).sort()).toEqual(
1242
+ [strongRow.id!.toString(), mediumRow.id!.toString()].sort(),
1243
+ );
1244
+ expect(contentFetchArgs(findBySpy).limit).toBe(2);
1245
+ /*
1246
+ * The count is the whole point of the field: a half-resolved stack trace
1247
+ * looks identical to "the maps were never uploaded" without it.
1248
+ */
1249
+ expect(result.sourceMapsSkippedForSize).toBe(1);
1250
+
1251
+ // One warning for the whole request, naming the knob that would fix it.
1252
+ expect(warnSpy).toHaveBeenCalledTimes(1);
1253
+ expect(String(warnSpy.mock.calls[0]![0])).toContain(
1254
+ "SOURCE_MAP_MAX_BYTES_PER_RESOLVE",
1255
+ );
1256
+ });
1257
+
1258
+ it("keeps the best-matching map and drops the weakest when only one fits", async () => {
1259
+ /*
1260
+ * What gets dropped must be the weakest match, not an arbitrary row —
1261
+ * that is what makes a budget defensible at all. The frame shares three
1262
+ * trailing segments with the strong bundle and one with the weak one.
1263
+ */
1264
+ mockResolveBudget.bytesPerResolve = 100;
1265
+
1266
+ const strongRow: TelemetrySourceMap = makeListingRow({
1267
+ bundlePath: STRONG_MATCH_PATH,
1268
+ sizeInBytes: 100,
1269
+ });
1270
+ const mediumRow: TelemetrySourceMap = makeListingRow({
1271
+ bundlePath: MEDIUM_MATCH_PATH,
1272
+ sizeInBytes: 100,
1273
+ });
1274
+ const weakRow: TelemetrySourceMap = makeListingRow({
1275
+ bundlePath: WEAK_MATCH_PATH,
1276
+ sizeInBytes: 100,
1277
+ });
1278
+
1279
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1280
+ .spyOn(TelemetrySourceMapService, "findBy")
1281
+ // Listed weakest-first, so only the score can explain the choice.
1282
+ .mockResolvedValueOnce([weakRow, mediumRow, strongRow] as never)
1283
+ .mockResolvedValueOnce([
1284
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1285
+ ] as never);
1286
+
1287
+ const result: ResolveStackTraceResult =
1288
+ await TelemetrySourceMapService.resolveFramesForService({
1289
+ projectId: PROJECT_ID,
1290
+ serviceId: SERVICE_ID,
1291
+ serviceVersion: "1.4.2",
1292
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1293
+ });
1294
+
1295
+ expect(requestedContentIds(findBySpy)).toEqual([strongRow.id!.toString()]);
1296
+ expect(result.sourceMapsSkippedForSize).toBe(2);
1297
+ // The one map that was worth loading still resolved the frame.
1298
+ expect(result.resolvedCount).toBe(1);
1299
+ });
1300
+
1301
+ it("still fits a smaller lower-scoring map after skipping a big one", async () => {
1302
+ /*
1303
+ * The budget loop continues rather than breaking. One oversized map in
1304
+ * the middle of the ranking must not cost the rest of the stack trace its
1305
+ * symbols — a break here would drop the small map that still fits.
1306
+ */
1307
+ mockResolveBudget.bytesPerResolve = 1000;
1308
+
1309
+ const strongRow: TelemetrySourceMap = makeListingRow({
1310
+ bundlePath: STRONG_MATCH_PATH,
1311
+ sizeInBytes: 100,
1312
+ });
1313
+ const oversizedMediumRow: TelemetrySourceMap = makeListingRow({
1314
+ bundlePath: MEDIUM_MATCH_PATH,
1315
+ sizeInBytes: 5000,
1316
+ });
1317
+ const smallWeakRow: TelemetrySourceMap = makeListingRow({
1318
+ bundlePath: WEAK_MATCH_PATH,
1319
+ sizeInBytes: 50,
1320
+ });
1321
+
1322
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1323
+ .spyOn(TelemetrySourceMapService, "findBy")
1324
+ .mockResolvedValueOnce([
1325
+ strongRow,
1326
+ oversizedMediumRow,
1327
+ smallWeakRow,
1328
+ ] as never)
1329
+ .mockResolvedValueOnce([
1330
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1331
+ makeRow(WEAK_MATCH_PATH, FIXTURE_MAP),
1332
+ ] as never);
1333
+
1334
+ const result: ResolveStackTraceResult =
1335
+ await TelemetrySourceMapService.resolveFramesForService({
1336
+ projectId: PROJECT_ID,
1337
+ serviceId: SERVICE_ID,
1338
+ serviceVersion: "1.4.2",
1339
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1340
+ });
1341
+
1342
+ expect(requestedContentIds(findBySpy).sort()).toEqual(
1343
+ [strongRow.id!.toString(), smallWeakRow.id!.toString()].sort(),
1344
+ );
1345
+ expect(contentFetchArgs(findBySpy).limit).toBe(2);
1346
+ expect(result.sourceMapsSkippedForSize).toBe(1);
1347
+ });
1348
+
1349
+ it("attempts the single best map even when it alone exceeds the budget", async () => {
1350
+ /*
1351
+ * A budget configured below the per-map ceiling should degrade to "one
1352
+ * map at a time", not to "nothing ever resolves". Peak memory is
1353
+ * therefore max(budget, one map) — bounded either way.
1354
+ */
1355
+ mockResolveBudget.bytesPerResolve = 10;
1356
+
1357
+ const strongRow: TelemetrySourceMap = makeListingRow({
1358
+ bundlePath: STRONG_MATCH_PATH,
1359
+ sizeInBytes: 5000,
1360
+ });
1361
+
1362
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1363
+ .spyOn(TelemetrySourceMapService, "findBy")
1364
+ .mockResolvedValueOnce([strongRow] as never)
1365
+ .mockResolvedValueOnce([
1366
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1367
+ ] as never);
1368
+
1369
+ const result: ResolveStackTraceResult =
1370
+ await TelemetrySourceMapService.resolveFramesForService({
1371
+ projectId: PROJECT_ID,
1372
+ serviceId: SERVICE_ID,
1373
+ serviceVersion: "1.4.2",
1374
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1375
+ });
1376
+
1377
+ expect(requestedContentIds(findBySpy)).toEqual([strongRow.id!.toString()]);
1378
+ // It was loaded, not skipped: nothing to report and nothing to warn about.
1379
+ expect(result.sourceMapsSkippedForSize).toBe(0);
1380
+ expect(result.resolvedCount).toBe(1);
1381
+ });
1382
+
1383
+ it("skips everything after the over-budget best map, having already spent the budget", async () => {
1384
+ mockResolveBudget.bytesPerResolve = 10;
1385
+
1386
+ const strongRow: TelemetrySourceMap = makeListingRow({
1387
+ bundlePath: STRONG_MATCH_PATH,
1388
+ sizeInBytes: 5000,
1389
+ });
1390
+ const tinyWeakRow: TelemetrySourceMap = makeListingRow({
1391
+ bundlePath: WEAK_MATCH_PATH,
1392
+ sizeInBytes: 5,
1393
+ });
1394
+
1395
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1396
+ .spyOn(TelemetrySourceMapService, "findBy")
1397
+ .mockResolvedValueOnce([strongRow, tinyWeakRow] as never)
1398
+ .mockResolvedValueOnce([
1399
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1400
+ ] as never);
1401
+
1402
+ const result: ResolveStackTraceResult =
1403
+ await TelemetrySourceMapService.resolveFramesForService({
1404
+ projectId: PROJECT_ID,
1405
+ serviceId: SERVICE_ID,
1406
+ serviceVersion: "1.4.2",
1407
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1408
+ });
1409
+
1410
+ expect(requestedContentIds(findBySpy)).toEqual([strongRow.id!.toString()]);
1411
+ expect(result.sourceMapsSkippedForSize).toBe(1);
1412
+ });
1413
+
1414
+ it("loads a map that lands exactly on the budget", async () => {
1415
+ // Boundary: the check is "over budget", not "at budget".
1416
+ mockResolveBudget.bytesPerResolve = 200;
1417
+
1418
+ const strongRow: TelemetrySourceMap = makeListingRow({
1419
+ bundlePath: STRONG_MATCH_PATH,
1420
+ sizeInBytes: 100,
1421
+ });
1422
+ const weakRow: TelemetrySourceMap = makeListingRow({
1423
+ bundlePath: WEAK_MATCH_PATH,
1424
+ sizeInBytes: 100,
1425
+ });
1426
+
1427
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1428
+ .spyOn(TelemetrySourceMapService, "findBy")
1429
+ .mockResolvedValueOnce([strongRow, weakRow] as never)
1430
+ .mockResolvedValueOnce([
1431
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1432
+ makeRow(WEAK_MATCH_PATH, FIXTURE_MAP),
1433
+ ] as never);
1434
+
1435
+ const result: ResolveStackTraceResult =
1436
+ await TelemetrySourceMapService.resolveFramesForService({
1437
+ projectId: PROJECT_ID,
1438
+ serviceId: SERVICE_ID,
1439
+ serviceVersion: "1.4.2",
1440
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1441
+ });
1442
+
1443
+ expect(requestedContentIds(findBySpy)).toHaveLength(2);
1444
+ expect(result.sourceMapsSkippedForSize).toBe(0);
1445
+ });
1446
+
1447
+ it("skips a map that lands one byte over the budget", async () => {
1448
+ mockResolveBudget.bytesPerResolve = 199;
1449
+
1450
+ const strongRow: TelemetrySourceMap = makeListingRow({
1451
+ bundlePath: STRONG_MATCH_PATH,
1452
+ sizeInBytes: 100,
1453
+ });
1454
+ const weakRow: TelemetrySourceMap = makeListingRow({
1455
+ bundlePath: WEAK_MATCH_PATH,
1456
+ sizeInBytes: 100,
1457
+ });
1458
+
1459
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1460
+ .spyOn(TelemetrySourceMapService, "findBy")
1461
+ .mockResolvedValueOnce([strongRow, weakRow] as never)
1462
+ .mockResolvedValueOnce([
1463
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1464
+ ] as never);
1465
+
1466
+ const result: ResolveStackTraceResult =
1467
+ await TelemetrySourceMapService.resolveFramesForService({
1468
+ projectId: PROJECT_ID,
1469
+ serviceId: SERVICE_ID,
1470
+ serviceVersion: "1.4.2",
1471
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1472
+ });
1473
+
1474
+ expect(requestedContentIds(findBySpy)).toEqual([strongRow.id!.toString()]);
1475
+ expect(result.sourceMapsSkippedForSize).toBe(1);
1476
+ });
1477
+
1478
+ it("prefers the newer upload when two maps match equally well and only one fits", async () => {
1479
+ /*
1480
+ * Same match quality, so the tie-break decides — and it is the same
1481
+ * newest-wins rule the dedupe uses, rather than whatever order the rows
1482
+ * happened to arrive in.
1483
+ */
1484
+ mockResolveBudget.bytesPerResolve = 100;
1485
+
1486
+ const olderRow: TelemetrySourceMap = makeListingRow({
1487
+ bundlePath: "a.abc123.js",
1488
+ sizeInBytes: 100,
1489
+ createdAt: new Date("2024-01-01T00:00:00.000Z"),
1490
+ });
1491
+ const newerRow: TelemetrySourceMap = makeListingRow({
1492
+ bundlePath: "b.abc123.js",
1493
+ sizeInBytes: 100,
1494
+ createdAt: new Date("2024-06-01T00:00:00.000Z"),
1495
+ });
1496
+
1497
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1498
+ .spyOn(TelemetrySourceMapService, "findBy")
1499
+ .mockResolvedValueOnce([olderRow, newerRow] as never)
1500
+ .mockResolvedValueOnce([makeRow("b.abc123.js", FIXTURE_MAP)] as never);
1501
+
1502
+ const result: ResolveStackTraceResult =
1503
+ await TelemetrySourceMapService.resolveFramesForService({
1504
+ projectId: PROJECT_ID,
1505
+ serviceId: SERVICE_ID,
1506
+ serviceVersion: "1.4.2",
1507
+ frames: [makeFrame("a.abc123.js"), makeFrame("b.abc123.js")],
1508
+ });
1509
+
1510
+ expect(requestedContentIds(findBySpy)).toEqual([newerRow.id!.toString()]);
1511
+ expect(result.sourceMapsSkippedForSize).toBe(1);
1512
+ });
1513
+
1514
+ /*
1515
+ * Every shape of "no usable size" a row can carry. onBeforeCreate stamps
1516
+ * sizeInBytes on every insert, so these belong to rows written straight to
1517
+ * the database — and an unknown size must not become the way to slip past a
1518
+ * budget that exists to bound memory. Each is charged the per-map ceiling.
1519
+ */
1520
+ const UNKNOWN_SIZE_CASES: Array<{
1521
+ label: string;
1522
+ sizeInBytes: number | null | undefined;
1523
+ }> = [
1524
+ { label: "undefined", sizeInBytes: undefined },
1525
+ { label: "null", sizeInBytes: null },
1526
+ { label: "zero", sizeInBytes: 0 },
1527
+ { label: "NaN", sizeInBytes: Number.NaN },
1528
+ { label: "negative", sizeInBytes: -1 },
1529
+ ];
1530
+
1531
+ for (const unknownSizeCase of UNKNOWN_SIZE_CASES) {
1532
+ it(`charges a row whose sizeInBytes is ${unknownSizeCase.label} the per-map ceiling, not zero`, async () => {
1533
+ mockResolveBudget.bytesPerResolve = 1000;
1534
+
1535
+ const strongRow: TelemetrySourceMap = makeListingRow({
1536
+ bundlePath: STRONG_MATCH_PATH,
1537
+ sizeInBytes: 100,
1538
+ });
1539
+ const unknownSizeRow: TelemetrySourceMap = makeListingRow({
1540
+ bundlePath: MEDIUM_MATCH_PATH,
1541
+ sizeInBytes: unknownSizeCase.sizeInBytes,
1542
+ });
1543
+
1544
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1545
+ .spyOn(TelemetrySourceMapService, "findBy")
1546
+ .mockResolvedValueOnce([strongRow, unknownSizeRow] as never)
1547
+ .mockResolvedValueOnce([
1548
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1549
+ ] as never);
1550
+
1551
+ const result: ResolveStackTraceResult =
1552
+ await TelemetrySourceMapService.resolveFramesForService({
1553
+ projectId: PROJECT_ID,
1554
+ serviceId: SERVICE_ID,
1555
+ serviceVersion: "1.4.2",
1556
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1557
+ });
1558
+
1559
+ /*
1560
+ * Charged 50 MB against a 1000-byte budget, so it cannot fit — where a
1561
+ * zero charge would have let it through unmetered.
1562
+ */
1563
+ expect(requestedContentIds(findBySpy)).toEqual([
1564
+ strongRow.id!.toString(),
1565
+ ]);
1566
+ expect(result.sourceMapsSkippedForSize).toBe(1);
1567
+ });
1568
+ }
1569
+
1570
+ it("still loads an unknown-size map when the budget can absorb the per-map ceiling", async () => {
1571
+ /*
1572
+ * The ceiling is a charge, not a ban: a budget with room for a
1573
+ * worst-case map still loads a row whose size was never stamped.
1574
+ */
1575
+ mockResolveBudget.bytesPerResolve = MAX_SOURCE_MAP_SIZE_IN_BYTES + 100;
1576
+
1577
+ const strongRow: TelemetrySourceMap = makeListingRow({
1578
+ bundlePath: STRONG_MATCH_PATH,
1579
+ sizeInBytes: 100,
1580
+ });
1581
+ const unknownSizeRow: TelemetrySourceMap = makeListingRow({
1582
+ bundlePath: MEDIUM_MATCH_PATH,
1583
+ sizeInBytes: undefined,
1584
+ });
1585
+
1586
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1587
+ .spyOn(TelemetrySourceMapService, "findBy")
1588
+ .mockResolvedValueOnce([strongRow, unknownSizeRow] as never)
1589
+ .mockResolvedValueOnce([
1590
+ makeRow(STRONG_MATCH_PATH, FIXTURE_MAP),
1591
+ makeRow(MEDIUM_MATCH_PATH, FIXTURE_MAP),
1592
+ ] as never);
1593
+
1594
+ const result: ResolveStackTraceResult =
1595
+ await TelemetrySourceMapService.resolveFramesForService({
1596
+ projectId: PROJECT_ID,
1597
+ serviceId: SERVICE_ID,
1598
+ serviceVersion: "1.4.2",
1599
+ frames: [makeFrame(SCORED_FRAME_FILE_NAME)],
1600
+ });
1601
+
1602
+ expect(requestedContentIds(findBySpy).sort()).toEqual(
1603
+ [strongRow.id!.toString(), unknownSizeRow.id!.toString()].sort(),
1604
+ );
1605
+ expect(result.sourceMapsSkippedForSize).toBe(0);
1606
+ });
1607
+ });
1608
+
1609
+ /*
1610
+ * Only the frames resolveFrames will actually attempt can pull a map in. The
1611
+ * frames array is parsed from client-supplied stack trace text, so matching
1612
+ * against all of it would let a caller drive an O(rows x frames) scan and
1613
+ * select maps that could never be used anyway.
1614
+ */
1615
+ describe("resolveFramesForService frame budget", () => {
1616
+ it("loads a map for a bundle referenced by the last resolvable frame", async () => {
1617
+ const frames: Array<MinifiedStackFrame> = [];
1618
+
1619
+ for (let index: number = 0; index < MAX_FRAMES_TO_RESOLVE - 1; index++) {
1620
+ frames.push(makeFrame("unrelated-chunk.js"));
1621
+ }
1622
+
1623
+ // The 500th frame — the last one resolveFrames will attempt.
1624
+ frames.push(makeFrame("main.abc123.js"));
1625
+ expect(frames).toHaveLength(MAX_FRAMES_TO_RESOLVE);
1626
+
1627
+ const listRow: TelemetrySourceMap = makeListingRow({
1628
+ bundlePath: "main.abc123.js",
1629
+ sizeInBytes: 100,
1630
+ });
1631
+
1632
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1633
+ .spyOn(TelemetrySourceMapService, "findBy")
1634
+ .mockResolvedValueOnce([listRow] as never)
1635
+ .mockResolvedValueOnce([makeRow("main.abc123.js", FIXTURE_MAP)] as never);
1636
+
1637
+ const result: ResolveStackTraceResult =
1638
+ await TelemetrySourceMapService.resolveFramesForService({
1639
+ projectId: PROJECT_ID,
1640
+ serviceId: SERVICE_ID,
1641
+ serviceVersion: "1.4.2",
1642
+ frames: frames,
1643
+ });
1644
+
1645
+ expect(requestedContentIds(findBySpy)).toEqual([listRow.id!.toString()]);
1646
+ expect(result.resolvedCount).toBe(1);
1647
+ expect(result.frames[MAX_FRAMES_TO_RESOLVE - 1]!.resolved).toBe(true);
1648
+ });
1649
+
1650
+ it("does not load a map for a bundle referenced only past the frame budget", async () => {
1651
+ const frames: Array<MinifiedStackFrame> = [];
1652
+
1653
+ for (let index: number = 0; index < MAX_FRAMES_TO_RESOLVE; index++) {
1654
+ frames.push(makeFrame("unrelated-chunk.js"));
1655
+ }
1656
+
1657
+ /*
1658
+ * The 501st frame. Its bundle IS stored — but the frame is past what
1659
+ * resolveFrames will attempt, so loading its map would spend the budget
1660
+ * on symbols that can never be handed back.
1661
+ */
1662
+ frames.push(makeFrame("main.abc123.js"));
1663
+
1664
+ const findBySpy: ReturnType<typeof jest.spyOn> = jest
1665
+ .spyOn(TelemetrySourceMapService, "findBy")
1666
+ .mockResolvedValueOnce([
1667
+ makeListingRow({ bundlePath: "main.abc123.js", sizeInBytes: 100 }),
1668
+ ] as never);
1669
+
1670
+ const result: ResolveStackTraceResult =
1671
+ await TelemetrySourceMapService.resolveFramesForService({
1672
+ projectId: PROJECT_ID,
1673
+ serviceId: SERVICE_ID,
1674
+ serviceVersion: "1.4.2",
1675
+ frames: frames,
1676
+ });
1677
+
1678
+ // No content fetch at all: nothing matched a frame that counts.
1679
+ expect(findBySpy).toHaveBeenCalledTimes(1);
1680
+ expect(result.sourceMapCount).toBe(1);
1681
+ expect(result.resolvedCount).toBe(0);
1682
+ expect(result.sourceMapsSkippedForSize).toBe(0);
1683
+
1684
+ /*
1685
+ * One output frame per input frame regardless — the dashboard overlay
1686
+ * discards length-mismatched responses wholesale.
1687
+ */
1688
+ expect(result.frames).toHaveLength(MAX_FRAMES_TO_RESOLVE + 1);
1689
+ expect(result.frames[MAX_FRAMES_TO_RESOLVE]!.resolved).toBe(false);
1690
+ });
1691
+ });