@oneuptime/common 11.7.3 → 11.7.4

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 (365) hide show
  1. package/Models/DatabaseModels/AlertEpisodeStateTimeline.ts +1 -0
  2. package/Models/DatabaseModels/AlertStateTimeline.ts +1 -0
  3. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  4. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  5. package/Models/DatabaseModels/Index.ts +5 -0
  6. package/Models/DatabaseModels/LogDropFilter.ts +89 -0
  7. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +13 -3
  8. package/Models/DatabaseModels/RecommendationDismissal.ts +463 -0
  9. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  10. package/Models/DatabaseModels/StatusPageGroup.ts +95 -0
  11. package/Models/DatabaseModels/TraceDropFilter.ts +89 -0
  12. package/Models/DatabaseModels/WorkflowVariable.ts +5 -1
  13. package/Server/API/AlertAPI.ts +32 -17
  14. package/Server/API/BaseAPI.ts +31 -2
  15. package/Server/API/BaseAnalyticsAPI.ts +36 -2
  16. package/Server/API/CommonAPI.ts +57 -0
  17. package/Server/API/IncidentAPI.ts +60 -33
  18. package/Server/API/IncidentEpisodeAPI.ts +31 -16
  19. package/Server/API/MonitorAPI.ts +30 -0
  20. package/Server/API/MonitorTemplateAPI.ts +8 -0
  21. package/Server/API/ProbeAPI.ts +196 -15
  22. package/Server/API/ProjectAPI.ts +79 -1
  23. package/Server/API/ScheduledMaintenanceAPI.ts +33 -18
  24. package/Server/API/StatusPageAPI.ts +74 -37
  25. package/Server/API/WorkspaceNotificationRuleAPI.ts +34 -4
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.ts +527 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.ts +31 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.ts +55 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.ts +53 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  31. package/Server/Services/AlertEpisodeService.ts +47 -0
  32. package/Server/Services/AlertService.ts +125 -0
  33. package/Server/Services/BillingService.ts +98 -0
  34. package/Server/Services/DatabaseService.ts +184 -20
  35. package/Server/Services/IncidentEpisodeService.ts +47 -0
  36. package/Server/Services/IncidentService.ts +123 -0
  37. package/Server/Services/IncidentTemplateService.ts +123 -1
  38. package/Server/Services/Index.ts +2 -0
  39. package/Server/Services/LogDropFilterService.ts +133 -0
  40. package/Server/Services/MonitorProbeService.ts +94 -4
  41. package/Server/Services/MonitorService.ts +48 -43
  42. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +70 -0
  43. package/Server/Services/ProbeService.ts +66 -0
  44. package/Server/Services/ProjectService.ts +131 -7
  45. package/Server/Services/RecommendationDismissalService.ts +52 -0
  46. package/Server/Services/RunbookAgentJobService.ts +68 -7
  47. package/Server/Services/ScheduledMaintenanceService.ts +103 -0
  48. package/Server/Services/ScheduledMaintenanceTemplateService.ts +90 -0
  49. package/Server/Services/StatusPageGroupService.ts +186 -0
  50. package/Server/Services/TraceDropFilterService.ts +133 -0
  51. package/Server/Types/Workflow/Components/API/Delete.ts +12 -0
  52. package/Server/Types/Workflow/Components/API/Get.ts +12 -0
  53. package/Server/Types/Workflow/Components/API/Patch.ts +12 -0
  54. package/Server/Types/Workflow/Components/API/Post.ts +12 -0
  55. package/Server/Types/Workflow/Components/API/Put.ts +13 -0
  56. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +212 -0
  57. package/Server/Utils/DropFilterValidation.ts +127 -0
  58. package/Server/Utils/Errors.ts +5 -0
  59. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  60. package/Server/Utils/Monitor/MonitorIncident.ts +31 -2
  61. package/Server/Utils/Telemetry/AppMetrics.ts +31 -0
  62. package/Tests/Models/DatabaseModels/MonitorProbeColumnAccessControl.test.ts +196 -0
  63. package/Tests/Models/RecommendationDismissalModel.test.ts +269 -0
  64. package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +320 -0
  65. package/Tests/Server/API/BaseAnalyticsAPIUpdatePayloadValidation.test.ts +293 -0
  66. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +164 -0
  67. package/Tests/Server/API/ProbeAPI.test.ts +422 -53
  68. package/Tests/Server/API/ProjectAPI.test.ts +180 -0
  69. package/Tests/Server/API/ProjectAPIExtendTrialBillingDisabled.test.ts +138 -0
  70. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +458 -0
  71. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +359 -0
  72. package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +427 -0
  73. package/Tests/Server/Services/BillingServiceExtendTrial.test.ts +439 -0
  74. package/Tests/Server/Services/CrossProjectReferenceWriteGuard.test.ts +810 -0
  75. package/Tests/Server/Services/DatabaseServiceAtomicAddToColumns.test.ts +269 -0
  76. package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +277 -0
  77. package/Tests/Server/Services/DatabaseServiceSortColumnSelect.test.ts +201 -0
  78. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +81 -23
  79. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +398 -0
  80. package/Tests/Server/Services/MonitorProbeDeleteHooks.test.ts +280 -0
  81. package/Tests/Server/Services/MonitorProbeServiceProbeTenancy.test.ts +304 -0
  82. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +262 -0
  83. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +614 -0
  84. package/Tests/Server/Services/RecommendationDismissalService.test.ts +306 -0
  85. package/Tests/Server/Services/RepairCrossProjectIncidentReferencesMigration.test.ts +528 -0
  86. package/Tests/Server/Services/StatusPageGroupNesting.test.ts +395 -0
  87. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +26 -21
  88. package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +227 -0
  89. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +285 -0
  90. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +463 -0
  91. package/Tests/Server/Utils/DropFilterValidation.test.ts +269 -0
  92. package/Tests/Server/Utils/Telemetry/SpanUtil.test.ts +94 -0
  93. package/Tests/Types/DateUserTimezone.test.ts +52 -0
  94. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +177 -9
  95. package/Tests/Types/Monitor/MonitorStepDnsMonitor.test.ts +105 -0
  96. package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +131 -0
  97. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +60 -0
  98. package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +82 -0
  99. package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +268 -0
  100. package/Tests/Types/Monitor/MonitorStepMetricMonitor.test.ts +386 -0
  101. package/Tests/Types/Monitor/MonitorStepProfileMonitor.test.ts +185 -0
  102. package/Tests/Types/Monitor/MonitorStepSnmpMonitor.test.ts +156 -0
  103. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +371 -0
  104. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +901 -0
  105. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +607 -0
  106. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +754 -0
  107. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +960 -0
  108. package/Tests/Types/RollingTime/RollingTimeUtil.test.ts +186 -0
  109. package/Tests/Types/Runbook/RunbookStepTimeout.test.ts +410 -0
  110. package/Tests/Types/Telemetry/DropFilterSampling.test.ts +241 -0
  111. package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +353 -0
  112. package/Tests/Types/Workspace/NotificationRuleUtil.test.ts +566 -0
  113. package/Tests/UI/Components/CodeEditor.test.tsx +174 -0
  114. package/Tests/UI/Components/ErrorBoundary.test.tsx +555 -24
  115. package/Tests/UI/Components/InfoCard.test.tsx +358 -0
  116. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +138 -0
  117. package/Tests/UI/Components/Modal.test.tsx +43 -5
  118. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +397 -0
  119. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +427 -0
  120. package/Tests/UI/Components/Runbook/StepTimeoutInput.test.tsx +386 -0
  121. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +671 -0
  122. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +404 -0
  123. package/Tests/UI/Utils/DropdownAlignment.test.ts +306 -0
  124. package/Tests/UI/Utils/TableFilterUrlState.test.ts +292 -0
  125. package/Tests/Utils/Monitor/MonitorSummaryProbeUtil.test.ts +263 -0
  126. package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +583 -0
  127. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +1072 -0
  128. package/Tests/Utils/StatusPage/GroupTree.test.ts +583 -0
  129. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +411 -0
  130. package/Types/Date.ts +15 -6
  131. package/Types/Monitor/KubernetesAlertTemplates.ts +205 -0
  132. package/Types/Monitor/MonitorStep.ts +10 -1
  133. package/Types/Monitor/MonitorStepMetricMonitor.ts +46 -1
  134. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +413 -0
  135. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +168 -0
  136. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +175 -0
  137. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +743 -0
  138. package/Types/Permission.ts +45 -0
  139. package/Types/Recommendation/RecommendationType.ts +20 -0
  140. package/Types/Runbook/RunbookExecutionDeadline.ts +129 -0
  141. package/Types/Runbook/RunbookStepTimeout.ts +202 -0
  142. package/Types/Telemetry/DropFilterSampling.ts +92 -0
  143. package/UI/Components/AI/GenerateFromAIModal.tsx +0 -2
  144. package/UI/Components/Accordion/Accordion.tsx +10 -2
  145. package/UI/Components/BulkUpdate/BulkLabelActions.tsx +0 -1
  146. package/UI/Components/CodeEditor/CodeEditor.tsx +34 -20
  147. package/UI/Components/Date/RangeStartAndEndDateView.tsx +0 -1
  148. package/UI/Components/EditionLabel/EditionLabel.tsx +1 -27
  149. package/UI/Components/Error/PageError.tsx +0 -4
  150. package/UI/Components/ErrorBoundary.tsx +252 -15
  151. package/UI/Components/InfoCard/InfoCard.tsx +58 -1
  152. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +34 -1
  153. package/UI/Components/Modal/Modal.tsx +16 -60
  154. package/UI/Components/ModelFormModal/ModelFormModal.tsx +0 -8
  155. package/UI/Components/ModelTable/BaseModelTable.tsx +116 -67
  156. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +150 -47
  157. package/UI/Components/Runbook/StepTimeoutInput.tsx +173 -0
  158. package/UI/Components/StatusPage/ResourceGroupSection.tsx +231 -0
  159. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +34 -1
  160. package/UI/Utils/DropdownAlignment.ts +162 -0
  161. package/UI/Utils/TableFilterUrlState.ts +59 -20
  162. package/Utils/API.ts +106 -2
  163. package/Utils/Monitor/MonitorSummaryProbeUtil.ts +130 -0
  164. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +434 -0
  165. package/Utils/StatusPage/GroupNestingLayout.ts +343 -0
  166. package/Utils/StatusPage/GroupTree.ts +307 -0
  167. package/Utils/StatusPage/ResourceUptime.ts +90 -17
  168. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js +1 -0
  169. package/build/dist/Models/DatabaseModels/AlertEpisodeStateTimeline.js.map +1 -1
  170. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +1 -0
  171. package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
  172. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  173. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  174. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  175. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  176. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  177. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  178. package/build/dist/Models/DatabaseModels/LogDropFilter.js +91 -0
  179. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  180. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +14 -4
  181. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  182. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js +484 -0
  183. package/build/dist/Models/DatabaseModels/RecommendationDismissal.js.map +1 -0
  184. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  185. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/StatusPageGroup.js +94 -0
  187. package/build/dist/Models/DatabaseModels/StatusPageGroup.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +91 -0
  189. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +5 -1
  191. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  192. package/build/dist/Server/API/AlertAPI.js +22 -13
  193. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  194. package/build/dist/Server/API/BaseAPI.js +22 -1
  195. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  196. package/build/dist/Server/API/BaseAnalyticsAPI.js +24 -1
  197. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  198. package/build/dist/Server/API/CommonAPI.js +42 -0
  199. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  200. package/build/dist/Server/API/IncidentAPI.js +43 -26
  201. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  202. package/build/dist/Server/API/IncidentEpisodeAPI.js +21 -12
  203. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  204. package/build/dist/Server/API/MonitorAPI.js +23 -0
  205. package/build/dist/Server/API/MonitorAPI.js.map +1 -1
  206. package/build/dist/Server/API/MonitorTemplateAPI.js +4 -0
  207. package/build/dist/Server/API/MonitorTemplateAPI.js.map +1 -1
  208. package/build/dist/Server/API/ProbeAPI.js +131 -15
  209. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  210. package/build/dist/Server/API/ProjectAPI.js +62 -3
  211. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  212. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +23 -14
  213. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  214. package/build/dist/Server/API/StatusPageAPI.js +61 -33
  215. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  216. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js +26 -2
  217. package/build/dist/Server/API/WorkspaceNotificationRuleAPI.js.map +1 -1
  218. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js +397 -0
  219. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785320000000-RepairCrossProjectIncidentReferences.js.map +1 -0
  220. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js +16 -0
  221. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785329453269-AddParentGroupToStatusPageGroup.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js +30 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785397674289-AddRecommendationDismissalTable.js.map +1 -0
  224. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js +34 -0
  225. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785405581596-AddDroppedCountersToDropFilters.js.map +1 -0
  226. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  227. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  228. package/build/dist/Server/Services/AlertEpisodeService.js +39 -0
  229. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  230. package/build/dist/Server/Services/AlertService.js +95 -0
  231. package/build/dist/Server/Services/AlertService.js.map +1 -1
  232. package/build/dist/Server/Services/BillingService.js +71 -0
  233. package/build/dist/Server/Services/BillingService.js.map +1 -1
  234. package/build/dist/Server/Services/DatabaseService.js +140 -17
  235. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  236. package/build/dist/Server/Services/IncidentEpisodeService.js +39 -0
  237. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  238. package/build/dist/Server/Services/IncidentService.js +100 -0
  239. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  240. package/build/dist/Server/Services/IncidentTemplateService.js +97 -0
  241. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  242. package/build/dist/Server/Services/Index.js +2 -0
  243. package/build/dist/Server/Services/Index.js.map +1 -1
  244. package/build/dist/Server/Services/LogDropFilterService.js +93 -0
  245. package/build/dist/Server/Services/LogDropFilterService.js.map +1 -1
  246. package/build/dist/Server/Services/MonitorProbeService.js +73 -5
  247. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  248. package/build/dist/Server/Services/MonitorService.js +34 -39
  249. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  250. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +71 -0
  251. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  252. package/build/dist/Server/Services/ProbeService.js +64 -0
  253. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  254. package/build/dist/Server/Services/ProjectService.js +103 -7
  255. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  256. package/build/dist/Server/Services/RecommendationDismissalService.js +50 -0
  257. package/build/dist/Server/Services/RecommendationDismissalService.js.map +1 -0
  258. package/build/dist/Server/Services/RunbookAgentJobService.js +63 -5
  259. package/build/dist/Server/Services/RunbookAgentJobService.js.map +1 -1
  260. package/build/dist/Server/Services/ScheduledMaintenanceService.js +83 -0
  261. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  262. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +65 -0
  263. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  264. package/build/dist/Server/Services/StatusPageGroupService.js +136 -0
  265. package/build/dist/Server/Services/StatusPageGroupService.js.map +1 -1
  266. package/build/dist/Server/Services/TraceDropFilterService.js +93 -0
  267. package/build/dist/Server/Services/TraceDropFilterService.js.map +1 -1
  268. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +11 -0
  269. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  270. package/build/dist/Server/Types/Workflow/Components/API/Get.js +11 -0
  271. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  272. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +11 -0
  273. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  274. package/build/dist/Server/Types/Workflow/Components/API/Post.js +11 -0
  275. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  276. package/build/dist/Server/Types/Workflow/Components/API/Put.js +12 -0
  277. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  278. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +132 -0
  279. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -0
  280. package/build/dist/Server/Utils/DropFilterValidation.js +80 -0
  281. package/build/dist/Server/Utils/DropFilterValidation.js.map +1 -0
  282. package/build/dist/Server/Utils/Errors.js +3 -0
  283. package/build/dist/Server/Utils/Errors.js.map +1 -1
  284. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +30 -12
  285. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  286. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +38 -18
  287. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  288. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +26 -0
  289. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -1
  290. package/build/dist/Types/Date.js +14 -4
  291. package/build/dist/Types/Date.js.map +1 -1
  292. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +192 -0
  293. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  294. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  295. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  296. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +32 -1
  297. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
  298. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +294 -0
  299. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -0
  300. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +110 -0
  301. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -0
  302. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +58 -0
  303. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -0
  304. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +451 -0
  305. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -0
  306. package/build/dist/Types/Permission.js +40 -0
  307. package/build/dist/Types/Permission.js.map +1 -1
  308. package/build/dist/Types/Recommendation/RecommendationType.js +21 -0
  309. package/build/dist/Types/Recommendation/RecommendationType.js.map +1 -0
  310. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js +93 -0
  311. package/build/dist/Types/Runbook/RunbookExecutionDeadline.js.map +1 -0
  312. package/build/dist/Types/Runbook/RunbookStepTimeout.js +142 -0
  313. package/build/dist/Types/Runbook/RunbookStepTimeout.js.map +1 -0
  314. package/build/dist/Types/Telemetry/DropFilterSampling.js +75 -0
  315. package/build/dist/Types/Telemetry/DropFilterSampling.js.map +1 -0
  316. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -2
  317. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  318. package/build/dist/UI/Components/Accordion/Accordion.js +9 -2
  319. package/build/dist/UI/Components/Accordion/Accordion.js.map +1 -1
  320. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +1 -1
  321. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
  322. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +27 -14
  323. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  324. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +1 -1
  325. package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
  326. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +2 -20
  327. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  328. package/build/dist/UI/Components/Error/PageError.js +1 -3
  329. package/build/dist/UI/Components/Error/PageError.js.map +1 -1
  330. package/build/dist/UI/Components/ErrorBoundary.js +145 -11
  331. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  332. package/build/dist/UI/Components/InfoCard/InfoCard.js +28 -1
  333. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  334. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +13 -2
  335. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  336. package/build/dist/UI/Components/Modal/Modal.js +4 -32
  337. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  338. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  339. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +69 -33
  340. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  341. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +73 -32
  342. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  343. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js +101 -0
  344. package/build/dist/UI/Components/Runbook/StepTimeoutInput.js.map +1 -0
  345. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +73 -0
  346. package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -0
  347. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +13 -2
  348. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  349. package/build/dist/UI/Utils/DropdownAlignment.js +98 -0
  350. package/build/dist/UI/Utils/DropdownAlignment.js.map +1 -0
  351. package/build/dist/UI/Utils/TableFilterUrlState.js +48 -15
  352. package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
  353. package/build/dist/Utils/API.js +39 -2
  354. package/build/dist/Utils/API.js.map +1 -1
  355. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js +97 -0
  356. package/build/dist/Utils/Monitor/MonitorSummaryProbeUtil.js.map +1 -0
  357. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +330 -0
  358. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -0
  359. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +280 -0
  360. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -0
  361. package/build/dist/Utils/StatusPage/GroupTree.js +214 -0
  362. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -0
  363. package/build/dist/Utils/StatusPage/ResourceUptime.js +60 -16
  364. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  365. package/package.json +1 -1
@@ -0,0 +1,173 @@
1
+ import React, {
2
+ FunctionComponent,
3
+ ReactElement,
4
+ ReactNode,
5
+ useEffect,
6
+ useId,
7
+ useState,
8
+ } from "react";
9
+ import {
10
+ NON_NUMERIC_TIMEOUT_MESSAGE,
11
+ TimeoutBounds,
12
+ getTimeoutValidationError,
13
+ secondsInputValueToTimeoutInMs,
14
+ timeoutInMsToSecondsInputValue,
15
+ willTimeoutBeClamped,
16
+ } from "../../../Types/Runbook/RunbookStepTimeout";
17
+
18
+ export interface ComponentProps {
19
+ label: string;
20
+ // Stored value, in milliseconds. Undefined means "use the default".
21
+ value: number | undefined;
22
+ bounds: TimeoutBounds;
23
+ onChange: (timeoutInMs: number | undefined) => void;
24
+ description?: ReactNode | undefined;
25
+ dataTestId?: string | undefined;
26
+ disabled?: boolean | undefined;
27
+ }
28
+
29
+ /*
30
+ * Seconds-in, milliseconds-out editor for a per-step timeout.
31
+ *
32
+ * The typed text is held locally rather than derived from the stored value on
33
+ * every keystroke: deriving it would fight the author mid-edit, because a
34
+ * partially typed "0" or an emptied field both resolve to "unset" and would
35
+ * erase what they were typing. The stored value only moves when the text
36
+ * parses to something usable.
37
+ */
38
+ const StepTimeoutInput: FunctionComponent<ComponentProps> = (
39
+ props: ComponentProps,
40
+ ): ReactElement => {
41
+ const uniqueId: string = useId();
42
+ const inputId: string = `step-timeout-${uniqueId}`;
43
+ const describedById: string = `step-timeout-help-${uniqueId}`;
44
+ const errorId: string = `step-timeout-error-${uniqueId}`;
45
+
46
+ const [text, setText] = useState<string>(
47
+ timeoutInMsToSecondsInputValue(props.value),
48
+ );
49
+
50
+ /*
51
+ * A number input reports an empty string for text it cannot parse — a
52
+ * dangling exponent ("1e"), a lone "-" — while still showing that text to
53
+ * the author. Tracked separately from `text` so an unparseable keystroke
54
+ * does not read as "the author cleared the field".
55
+ */
56
+ const [hasUnparseableInput, setHasUnparseableInput] =
57
+ useState<boolean>(false);
58
+
59
+ /*
60
+ * Re-sync when the step being edited changes underneath us (collapse /
61
+ * reorder / reload). Skipped while the text still represents the stored
62
+ * value so the author's in-progress typing survives re-renders.
63
+ */
64
+ useEffect(() => {
65
+ const storedAsText: string = timeoutInMsToSecondsInputValue(props.value);
66
+ setText((currentText: string) => {
67
+ return secondsInputValueToTimeoutInMs(currentText) === props.value
68
+ ? currentText
69
+ : storedAsText;
70
+ });
71
+ }, [props.value]);
72
+
73
+ const validationError: string | null = hasUnparseableInput
74
+ ? NON_NUMERIC_TIMEOUT_MESSAGE
75
+ : getTimeoutValidationError(text, props.bounds);
76
+
77
+ const defaultInSeconds: number = props.bounds.defaultInMs / 1000;
78
+
79
+ const handleChange: (value: string, isUnparseable: boolean) => void = (
80
+ value: string,
81
+ isUnparseable: boolean,
82
+ ): void => {
83
+ if (isUnparseable) {
84
+ /*
85
+ * Hold the stored value. Reporting undefined here would discard a
86
+ * timeout the author had already saved, without them touching a digit —
87
+ * the field would look edited, save cleanly, and the step would silently
88
+ * drop back to the default.
89
+ */
90
+ setHasUnparseableInput(true);
91
+ return;
92
+ }
93
+
94
+ setHasUnparseableInput(false);
95
+ setText(value);
96
+ props.onChange(secondsInputValueToTimeoutInMs(value));
97
+ };
98
+
99
+ /*
100
+ * Only a usable-but-out-of-range value gets clamped at run time. Anything
101
+ * unusable falls back to the default instead, so the two cases need
102
+ * different copy.
103
+ */
104
+ const consequence: string = hasUnparseableInput
105
+ ? "The saved value is unchanged."
106
+ : willTimeoutBeClamped(text, props.bounds)
107
+ ? "Values outside the range are clamped when the step runs."
108
+ : "Leave the field blank to use the default.";
109
+
110
+ let inputClassName: string =
111
+ "block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm";
112
+
113
+ if (validationError) {
114
+ inputClassName +=
115
+ " border-red-300 text-red-900 placeholder-red-300 focus:border-red-500 focus:ring-red-500";
116
+ }
117
+
118
+ if (props.disabled) {
119
+ inputClassName += " bg-gray-100 text-gray-500 cursor-not-allowed";
120
+ }
121
+
122
+ return (
123
+ <div>
124
+ <label
125
+ htmlFor={inputId}
126
+ className="block text-xs font-medium text-gray-700 mb-1.5"
127
+ >
128
+ {props.label}
129
+ <span className="ml-2 text-[10px] font-normal text-gray-400">
130
+ seconds
131
+ </span>
132
+ </label>
133
+ <input
134
+ id={inputId}
135
+ type="number"
136
+ inputMode="decimal"
137
+ min={props.bounds.minInMs / 1000}
138
+ max={props.bounds.maxInMs / 1000}
139
+ step="any"
140
+ disabled={props.disabled}
141
+ className={inputClassName}
142
+ value={text}
143
+ placeholder={`${defaultInSeconds}`}
144
+ /*
145
+ * The error is part of the field's description, not only a live region:
146
+ * a screen reader that reaches an already-invalid field on focus still
147
+ * hears why.
148
+ */
149
+ aria-describedby={
150
+ validationError ? `${describedById} ${errorId}` : describedById
151
+ }
152
+ aria-invalid={validationError ? true : undefined}
153
+ data-testid={props.dataTestId}
154
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
155
+ handleChange(e.target.value, e.target.validity?.badInput === true);
156
+ }}
157
+ />
158
+ <p id={describedById} className="text-xs text-gray-500 mt-1.5">
159
+ {props.description ? <>{props.description} </> : null}
160
+ Leave blank to use the default of {defaultInSeconds} seconds. Allowed
161
+ range: {props.bounds.minInMs / 1000}–{props.bounds.maxInMs / 1000}{" "}
162
+ seconds.
163
+ </p>
164
+ {validationError ? (
165
+ <p id={errorId} role="alert" className="text-xs text-red-600 mt-1">
166
+ {validationError} {consequence}
167
+ </p>
168
+ ) : null}
169
+ </div>
170
+ );
171
+ };
172
+
173
+ export default StepTimeoutInput;
@@ -0,0 +1,231 @@
1
+ import Icon, { ThickProp } from "../Icon/Icon";
2
+ import MarkdownViewer from "../Markdown.tsx/LazyMarkdownViewer";
3
+ import IconProp from "../../../Types/Icon/IconProp";
4
+ import StatusPageGroupNestingLayoutUtil from "../../../Utils/StatusPage/GroupNestingLayout";
5
+ import React, {
6
+ FunctionComponent,
7
+ ReactElement,
8
+ useEffect,
9
+ useState,
10
+ } from "react";
11
+
12
+ export interface ComponentProps {
13
+ /*
14
+ * 0 for a top level group, 1 for its children, and so on. Everything about
15
+ * how this section is drawn comes from it - see GroupNestingLayout.
16
+ */
17
+ depth: number;
18
+ name: string;
19
+ /*
20
+ * Markdown. Rendered below the header rather than inside it - a description
21
+ * may contain links, and a link inside a button is not something a browser or
22
+ * a screen reader can make sense of.
23
+ */
24
+ description?: string | undefined;
25
+ /*
26
+ * Rendered next to the name when the group has sub groups. Pre-translated by
27
+ * the caller: this component has no i18n of its own.
28
+ */
29
+ subGroupCountLabel?: string | undefined;
30
+ /*
31
+ * The group's rolled up status or uptime, already formatted and translated by
32
+ * the caller ("99.9% uptime", "Operational"). Drawn with a status dot so it
33
+ * reads as a rollup of everything below rather than as one more resource
34
+ * reading, and it stays visible while the section is open - the point of the
35
+ * hierarchy is that every level always shows what it rolls up, and a number
36
+ * that disappears on expand is a number you cannot compare against the level
37
+ * below it.
38
+ */
39
+ rollupLabel?: string | undefined;
40
+ /* Hex or css colour for the rollup, from the rolled up status. */
41
+ rollupColor?: string | undefined;
42
+ isInitiallyExpanded?: boolean | undefined;
43
+ /* The group's own resources. */
44
+ resourcesElement?: ReactElement | undefined;
45
+ /* Nested sections, normally one per sub group. */
46
+ subGroupsElement?: ReactElement | undefined;
47
+ hasOwnResources?: boolean | undefined;
48
+ subGroupCount?: number | undefined;
49
+ testId?: string | undefined;
50
+ }
51
+
52
+ /*
53
+ * One level of the status page resource group hierarchy.
54
+ *
55
+ * A top level group is a card. Everything under it is a row on a tree rail -
56
+ * no card, no second border, no background tint stacked on the last one - so
57
+ * the hierarchy stays readable however deep it goes and the uptime bars keep
58
+ * the width they need.
59
+ */
60
+ const ResourceGroupSection: FunctionComponent<ComponentProps> = (
61
+ props: ComponentProps,
62
+ ): ReactElement => {
63
+ const [isOpen, setIsOpen] = useState<boolean>(
64
+ Boolean(props.isInitiallyExpanded),
65
+ );
66
+
67
+ /*
68
+ * Groups arrive with the page payload, so isInitiallyExpanded can flip after
69
+ * the first render. Keyed on the *boolean*, not on the prop: the column is an
70
+ * optional boolean, so it routinely arrives as undefined and settles to false,
71
+ * and keying on the prop would fire on that and slam shut a section the
72
+ * visitor had just opened.
73
+ */
74
+ const initiallyExpanded: boolean = Boolean(props.isInitiallyExpanded);
75
+
76
+ useEffect(() => {
77
+ setIsOpen(initiallyExpanded);
78
+ }, [initiallyExpanded]);
79
+
80
+ const generatedId: string = React.useId();
81
+ const bodyId: string = `status-page-group-body-${generatedId}`;
82
+ const descriptionId: string = `status-page-group-description-${generatedId}`;
83
+
84
+ const isRootLevel: boolean = StatusPageGroupNestingLayoutUtil.isRootLevel({
85
+ depth: props.depth,
86
+ });
87
+
88
+ const subGroupCount: number = props.subGroupCount || 0;
89
+
90
+ const showSubGroupDivider: boolean =
91
+ StatusPageGroupNestingLayoutUtil.shouldShowSubGroupDivider({
92
+ hasOwnResources: Boolean(props.hasOwnResources),
93
+ subGroupCount: subGroupCount,
94
+ });
95
+
96
+ return (
97
+ <div
98
+ className={StatusPageGroupNestingLayoutUtil.getContainerClassName({
99
+ depth: props.depth,
100
+ })}
101
+ data-testid={
102
+ props.testId ||
103
+ (isRootLevel ? "status-page-group" : "status-page-nested-group")
104
+ }
105
+ data-depth={props.depth}
106
+ >
107
+ <button
108
+ type="button"
109
+ className={StatusPageGroupNestingLayoutUtil.getHeaderClassName({
110
+ depth: props.depth,
111
+ })}
112
+ aria-expanded={isOpen}
113
+ aria-controls={isOpen ? bodyId : undefined}
114
+ aria-describedby={props.description ? descriptionId : undefined}
115
+ data-testid="status-page-group-header"
116
+ onClick={() => {
117
+ setIsOpen(!isOpen);
118
+ }}
119
+ >
120
+ <span
121
+ className={StatusPageGroupNestingLayoutUtil.getHeaderRowClassName()}
122
+ >
123
+ <span
124
+ className={`${StatusPageGroupNestingLayoutUtil.getChevronBoxClassName()} ${
125
+ isOpen ? "bg-gray-900/5 text-gray-700" : "text-gray-400"
126
+ }`}
127
+ data-testid="status-page-group-chevron"
128
+ aria-hidden="true"
129
+ >
130
+ <Icon
131
+ className={`h-3.5 w-3.5 transition-transform duration-200 ease-out ${
132
+ isOpen ? "rotate-90" : ""
133
+ }`}
134
+ icon={IconProp.ChevronRight}
135
+ thick={ThickProp.Thick}
136
+ />
137
+ </span>
138
+ <span
139
+ className={StatusPageGroupNestingLayoutUtil.getTitleClassName({
140
+ depth: props.depth,
141
+ })}
142
+ title={props.name}
143
+ >
144
+ {props.name}
145
+ </span>
146
+ {props.subGroupCountLabel ? (
147
+ <span
148
+ className={StatusPageGroupNestingLayoutUtil.getSubGroupCountBadgeClassName()}
149
+ data-testid="status-page-group-sub-group-count"
150
+ >
151
+ {props.subGroupCountLabel}
152
+ </span>
153
+ ) : (
154
+ <></>
155
+ )}
156
+ </span>
157
+ {props.rollupLabel ? (
158
+ <span
159
+ className={StatusPageGroupNestingLayoutUtil.getRollupClassName()}
160
+ data-testid="status-page-group-rollup"
161
+ >
162
+ <span className="inline-flex items-center gap-1.5">
163
+ <span
164
+ className={StatusPageGroupNestingLayoutUtil.getRollupDotClassName()}
165
+ style={
166
+ props.rollupColor
167
+ ? { backgroundColor: props.rollupColor }
168
+ : undefined
169
+ }
170
+ data-testid="status-page-group-rollup-dot"
171
+ aria-hidden="true"
172
+ />
173
+ <span
174
+ className={StatusPageGroupNestingLayoutUtil.getRollupLabelClassName()}
175
+ style={
176
+ props.rollupColor ? { color: props.rollupColor } : undefined
177
+ }
178
+ >
179
+ {props.rollupLabel}
180
+ </span>
181
+ </span>
182
+ </span>
183
+ ) : (
184
+ <></>
185
+ )}
186
+ </button>
187
+ {props.description ? (
188
+ <div
189
+ id={descriptionId}
190
+ className={StatusPageGroupNestingLayoutUtil.getDescriptionClassName()}
191
+ data-testid="status-page-group-description"
192
+ >
193
+ <MarkdownViewer text={props.description} />
194
+ </div>
195
+ ) : (
196
+ <></>
197
+ )}
198
+ {isOpen ? (
199
+ <div
200
+ id={bodyId}
201
+ className={StatusPageGroupNestingLayoutUtil.getBodyClassName({
202
+ depth: props.depth,
203
+ })}
204
+ data-testid="status-page-group-body"
205
+ >
206
+ {props.resourcesElement || <></>}
207
+ {props.subGroupsElement ? (
208
+ <div
209
+ className={`${
210
+ showSubGroupDivider
211
+ ? StatusPageGroupNestingLayoutUtil.getSubGroupDividerClassName()
212
+ : ""
213
+ } ${StatusPageGroupNestingLayoutUtil.getSubGroupListClassName({
214
+ depth: props.depth,
215
+ })}`.trim()}
216
+ data-testid="status-page-sub-group-list"
217
+ >
218
+ {props.subGroupsElement}
219
+ </div>
220
+ ) : (
221
+ <></>
222
+ )}
223
+ </div>
224
+ ) : (
225
+ <></>
226
+ )}
227
+ </div>
228
+ );
229
+ };
230
+
231
+ export default ResourceGroupSection;
@@ -14,12 +14,20 @@ import InBetween from "../../../../Types/BaseDatabase/InBetween";
14
14
  import StartAndEndDate, {
15
15
  StartAndEndDateType,
16
16
  } from "../../Date/StartAndEndDate";
17
+ import {
18
+ DropdownHorizontalAlignment,
19
+ getDropdownAlignmentClassName,
20
+ useDropdownHorizontalAlignment,
21
+ } from "../../../Utils/DropdownAlignment";
17
22
 
18
23
  export interface TelemetryTimeRangePickerProps {
19
24
  value: RangeStartAndEndDateTime;
20
25
  onChange: (value: RangeStartAndEndDateTime) => void;
21
26
  }
22
27
 
28
+ // Matches the Tailwind `w-72` on the dropdown below (18rem).
29
+ export const TIME_RANGE_DROPDOWN_WIDTH_IN_PX: number = 288;
30
+
23
31
  const PRESET_OPTIONS: Array<{ range: TimeRange; label: string }> = [
24
32
  { range: TimeRange.PAST_FIVE_MINS, label: "Past 5 Minutes" },
25
33
  { range: TimeRange.PAST_FIFTEEN_MINS, label: "Past 15 Minutes" },
@@ -67,6 +75,20 @@ const TelemetryTimeRangePicker: FunctionComponent<
67
75
  const containerRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
68
76
  null!,
69
77
  );
78
+ const buttonRef: React.RefObject<HTMLButtonElement> =
79
+ useRef<HTMLButtonElement>(null!);
80
+ const dropdownRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
81
+ null!,
82
+ );
83
+
84
+ const alignment: DropdownHorizontalAlignment = useDropdownHorizontalAlignment(
85
+ {
86
+ isOpen: isOpen,
87
+ anchorRef: buttonRef,
88
+ dropdownRef: dropdownRef,
89
+ dropdownWidthInPx: TIME_RANGE_DROPDOWN_WIDTH_IN_PX,
90
+ },
91
+ );
70
92
 
71
93
  useEffect(() => {
72
94
  const handleClickOutside: (e: MouseEvent) => void = (
@@ -117,7 +139,11 @@ const TelemetryTimeRangePicker: FunctionComponent<
117
139
  return (
118
140
  <div ref={containerRef} className="relative">
119
141
  <button
142
+ ref={buttonRef}
120
143
  type="button"
144
+ data-testid="telemetry-time-range-picker-button"
145
+ aria-haspopup="true"
146
+ aria-expanded={isOpen}
121
147
  className={`inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs font-medium shadow-sm transition-colors ${
122
148
  isOpen
123
149
  ? "border-indigo-300 bg-indigo-50 text-indigo-700"
@@ -136,7 +162,14 @@ const TelemetryTimeRangePicker: FunctionComponent<
136
162
  </button>
137
163
 
138
164
  {isOpen && (
139
- <div className="absolute right-0 top-full z-50 mt-1 w-72 rounded-lg border border-gray-200 bg-white shadow-lg">
165
+ <div
166
+ ref={dropdownRef}
167
+ data-testid="telemetry-time-range-picker-dropdown"
168
+ data-align={alignment}
169
+ className={`absolute ${getDropdownAlignmentClassName(
170
+ alignment,
171
+ )} top-full z-50 mt-1 w-72 max-w-[calc(100vw-1rem)] rounded-lg border border-gray-200 bg-white shadow-lg`}
172
+ >
140
173
  <div className="max-h-64 overflow-y-auto py-1">
141
174
  {PRESET_OPTIONS.map(
142
175
  (option: { range: TimeRange; label: string }) => {
@@ -0,0 +1,162 @@
1
+ import React, { useEffect, useState } from "react";
2
+
3
+ /*
4
+ * Horizontal edge of the trigger that a popup pins itself to.
5
+ * "Left" means the popup grows rightwards from the trigger's left edge
6
+ * (Tailwind `left-0`), "Right" means it grows leftwards from the trigger's
7
+ * right edge (Tailwind `right-0`).
8
+ */
9
+ export enum DropdownHorizontalAlignment {
10
+ Left = "left",
11
+ Right = "right",
12
+ }
13
+
14
+ // Breathing room we keep between a popup and the edge of the viewport.
15
+ export const DEFAULT_DROPDOWN_VIEWPORT_MARGIN_IN_PX: number = 8;
16
+
17
+ export interface DropdownAlignmentInput {
18
+ // Viewport-relative left edge of the trigger, as reported by getBoundingClientRect().
19
+ anchorLeft: number;
20
+ // Viewport-relative right edge of the trigger.
21
+ anchorRight: number;
22
+ // Rendered width of the popup in pixels.
23
+ dropdownWidth: number;
24
+ // Width of the viewport in pixels.
25
+ viewportWidth: number;
26
+ // Optional override for the viewport gutter.
27
+ viewportMargin?: number | undefined;
28
+ }
29
+
30
+ /*
31
+ * Picks the edge a popup should align to so that it stays inside the viewport.
32
+ *
33
+ * Left alignment is preferred because it reads naturally next to the trigger.
34
+ * We only flip to the right edge when left alignment would spill past the right
35
+ * side of the viewport, and we never flip if doing so would spill past the left
36
+ * side by even more (which is what produced the off-screen popup on the metric
37
+ * explorer toolbar, where the trigger sits at the far left of the page).
38
+ */
39
+ export function getDropdownHorizontalAlignment(
40
+ input: DropdownAlignmentInput,
41
+ ): DropdownHorizontalAlignment {
42
+ const margin: number =
43
+ input.viewportMargin === undefined
44
+ ? DEFAULT_DROPDOWN_VIEWPORT_MARGIN_IN_PX
45
+ : input.viewportMargin;
46
+
47
+ /*
48
+ * Before the popup is laid out (or in a non-visual environment) measurements
49
+ * come back as zero. There is nothing to solve for, so keep the default.
50
+ */
51
+ if (
52
+ !Number.isFinite(input.dropdownWidth) ||
53
+ !Number.isFinite(input.viewportWidth) ||
54
+ input.dropdownWidth <= 0 ||
55
+ input.viewportWidth <= 0
56
+ ) {
57
+ return DropdownHorizontalAlignment.Left;
58
+ }
59
+
60
+ // How far a left-aligned popup would poke past the right gutter.
61
+ const leftAlignedOverflow: number =
62
+ input.anchorLeft + input.dropdownWidth - (input.viewportWidth - margin);
63
+
64
+ // How far a right-aligned popup would poke past the left gutter.
65
+ const rightAlignedOverflow: number =
66
+ margin - (input.anchorRight - input.dropdownWidth);
67
+
68
+ if (leftAlignedOverflow <= 0) {
69
+ return DropdownHorizontalAlignment.Left;
70
+ }
71
+
72
+ if (rightAlignedOverflow <= 0) {
73
+ return DropdownHorizontalAlignment.Right;
74
+ }
75
+
76
+ // Neither edge fits. Pick whichever clips the least, preferring left on a tie.
77
+ return leftAlignedOverflow <= rightAlignedOverflow
78
+ ? DropdownHorizontalAlignment.Left
79
+ : DropdownHorizontalAlignment.Right;
80
+ }
81
+
82
+ /*
83
+ * Tailwind positioning class for an alignment. Kept next to the resolver so
84
+ * callers cannot drift out of sync with the enum.
85
+ */
86
+ export function getDropdownAlignmentClassName(
87
+ alignment: DropdownHorizontalAlignment,
88
+ ): string {
89
+ return alignment === DropdownHorizontalAlignment.Right ? "right-0" : "left-0";
90
+ }
91
+
92
+ export interface UseDropdownHorizontalAlignmentOptions {
93
+ // Only measure while the popup is actually on screen.
94
+ isOpen: boolean;
95
+ // The trigger the popup hangs off.
96
+ anchorRef: React.RefObject<HTMLElement>;
97
+ // The popup itself. When it has not been laid out we fall back to the declared width.
98
+ dropdownRef?: React.RefObject<HTMLElement> | undefined;
99
+ // Width the popup is styled to (e.g. 288 for Tailwind `w-72`).
100
+ dropdownWidthInPx: number;
101
+ viewportMargin?: number | undefined;
102
+ }
103
+
104
+ /*
105
+ * Keeps a popup's horizontal alignment in sync with the space available around
106
+ * its trigger. Re-measures when the popup opens and whenever the viewport
107
+ * changes size or scrolls the trigger somewhere new.
108
+ */
109
+ export function useDropdownHorizontalAlignment(
110
+ options: UseDropdownHorizontalAlignmentOptions,
111
+ ): DropdownHorizontalAlignment {
112
+ const [alignment, setAlignment] = useState<DropdownHorizontalAlignment>(
113
+ DropdownHorizontalAlignment.Left,
114
+ );
115
+
116
+ const {
117
+ isOpen,
118
+ anchorRef,
119
+ dropdownRef,
120
+ dropdownWidthInPx,
121
+ viewportMargin,
122
+ }: UseDropdownHorizontalAlignmentOptions = options;
123
+
124
+ useEffect(() => {
125
+ if (!isOpen) {
126
+ return undefined;
127
+ }
128
+
129
+ const measure: () => void = (): void => {
130
+ const anchor: HTMLElement | null = anchorRef.current;
131
+
132
+ if (!anchor || typeof anchor.getBoundingClientRect !== "function") {
133
+ return;
134
+ }
135
+
136
+ const anchorRect: DOMRect = anchor.getBoundingClientRect();
137
+ const measuredWidth: number = dropdownRef?.current?.offsetWidth || 0;
138
+
139
+ setAlignment(
140
+ getDropdownHorizontalAlignment({
141
+ anchorLeft: anchorRect.left,
142
+ anchorRight: anchorRect.right,
143
+ dropdownWidth: measuredWidth > 0 ? measuredWidth : dropdownWidthInPx,
144
+ viewportWidth: window.innerWidth,
145
+ viewportMargin: viewportMargin,
146
+ }),
147
+ );
148
+ };
149
+
150
+ measure();
151
+
152
+ window.addEventListener("resize", measure);
153
+ window.addEventListener("scroll", measure, true);
154
+
155
+ return () => {
156
+ window.removeEventListener("resize", measure);
157
+ window.removeEventListener("scroll", measure, true);
158
+ };
159
+ }, [isOpen, anchorRef, dropdownRef, dropdownWidthInPx, viewportMargin]);
160
+
161
+ return alignment;
162
+ }