@oneuptime/common 12.0.5 → 12.0.6

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 (528) hide show
  1. package/Models/DatabaseModels/AIRun.ts +33 -0
  2. package/Models/DatabaseModels/DataSource.ts +744 -0
  3. package/Models/DatabaseModels/Index.ts +3 -0
  4. package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
  5. package/Models/DatabaseModels/User.ts +2 -0
  6. package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
  8. package/Models/DatabaseModels/UserSession.ts +2 -0
  9. package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
  10. package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
  11. package/Server/API/AIInsightAPI.ts +47 -4
  12. package/Server/API/AIInvestigationAPI.ts +25 -0
  13. package/Server/API/DashboardAPI.ts +176 -492
  14. package/Server/API/DataSourceAPI.ts +268 -0
  15. package/Server/API/StatusPageAPI.ts +11 -1
  16. package/Server/API/UserAPI.ts +119 -0
  17. package/Server/Infrastructure/ClickhouseConfig.ts +59 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  22. package/Server/Infrastructure/Queue.ts +26 -0
  23. package/Server/Infrastructure/QueueWorker.ts +17 -0
  24. package/Server/Middleware/TelemetryIngest.ts +8 -3
  25. package/Server/Services/AIInsightService.ts +132 -12
  26. package/Server/Services/AIRunService.ts +34 -0
  27. package/Server/Services/AIService.ts +17 -2
  28. package/Server/Services/AlertService.ts +38 -32
  29. package/Server/Services/AnalyticsDatabaseService.ts +8 -4
  30. package/Server/Services/DataSourceService.ts +200 -0
  31. package/Server/Services/HostService.ts +92 -0
  32. package/Server/Services/IncidentService.ts +45 -0
  33. package/Server/Services/Index.ts +2 -0
  34. package/Server/Services/MonitorService.ts +49 -0
  35. package/Server/Services/MonitorStatusService.ts +166 -0
  36. package/Server/Services/OpenTelemetryIngestService.ts +255 -184
  37. package/Server/Services/RumSessionPinService.ts +7 -21
  38. package/Server/Services/StatusPageSubscriberService.ts +13 -0
  39. package/Server/Services/UserService.ts +280 -9
  40. package/Server/Static/Vendor/README.md +62 -0
  41. package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
  42. package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
  43. package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
  44. package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
  45. package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
  46. package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
  47. package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
  48. package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
  49. package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
  50. package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
  51. package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
  52. package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
  53. package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
  54. package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
  55. package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
  56. package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
  57. package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
  58. package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
  59. package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
  60. package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
  61. package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
  62. package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
  63. package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
  64. package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
  65. package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
  66. package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
  67. package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
  68. package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
  69. package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
  70. package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
  71. package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
  72. package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
  73. package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
  74. package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
  75. package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
  76. package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
  77. package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
  78. package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
  79. package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
  80. package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
  81. package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
  82. package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
  83. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +2 -2
  84. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +90 -11
  85. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +1 -0
  86. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +1 -0
  87. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
  88. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
  89. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
  90. package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
  91. package/Server/Utils/AI/SRE/InvestigationTldr.ts +352 -0
  92. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +7 -2
  93. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
  94. package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
  95. package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
  96. package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
  97. package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
  98. package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
  99. package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
  100. package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
  101. package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
  102. package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
  103. package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
  104. package/Server/Utils/DataSource/EgressGuard.ts +446 -0
  105. package/Server/Utils/DataSource/HttpFetch.ts +221 -0
  106. package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
  107. package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
  108. package/Server/Utils/DataSource/Types.ts +93 -0
  109. package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
  110. package/Server/Utils/InProcessMemo.ts +141 -0
  111. package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -0
  112. package/Server/Utils/Monitor/MonitorMetricUtil.ts +58 -0
  113. package/Server/Utils/Monitor/MonitorResource.ts +14 -0
  114. package/Server/Utils/Response.ts +12 -1
  115. package/Server/Utils/SSRFProtection.ts +237 -0
  116. package/Server/Utils/StartServer.ts +8 -0
  117. package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
  118. package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
  119. package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
  120. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
  121. package/Server/Utils/Telemetry/Telemetry.ts +112 -1
  122. package/Server/Utils/Telemetry.ts +104 -3
  123. package/Server/Utils/VendorAssets.ts +165 -0
  124. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
  125. package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
  126. package/Server/Views/Partials/Head.ejs +21 -7
  127. package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
  128. package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
  129. package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
  130. package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
  131. package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
  132. package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
  133. package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
  134. package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
  135. package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
  136. package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
  137. package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
  138. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
  139. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +514 -3
  140. package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
  141. package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
  142. package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
  143. package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
  144. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
  145. package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
  146. package/Tests/Server/API/AIInvestigationAPI.test.ts +187 -0
  147. package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
  148. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
  149. package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
  150. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
  151. package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
  152. package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
  153. package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
  154. package/Tests/Server/Services/AIInsightService.test.ts +266 -11
  155. package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -0
  156. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +4 -0
  157. package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
  158. package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
  159. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +397 -0
  160. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
  161. package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
  162. package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
  163. package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
  164. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
  165. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
  166. package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
  167. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
  168. package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
  169. package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
  170. package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
  171. package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -0
  172. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +2 -2
  173. package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +6 -1
  174. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
  175. package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
  176. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +292 -0
  177. package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
  178. package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
  179. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
  180. package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
  181. package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
  182. package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
  183. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
  184. package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
  185. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
  186. package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
  187. package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
  188. package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
  189. package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
  190. package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
  191. package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
  192. package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
  193. package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
  194. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
  195. package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
  196. package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
  197. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -0
  198. package/Tests/Server/Utils/Monitor/MonitorMetricResourceAttributes.test.ts +336 -0
  199. package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
  200. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
  201. package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
  202. package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
  203. package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
  204. package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
  205. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
  206. package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
  207. package/Tests/Server/Utils/Telemetry.test.ts +87 -0
  208. package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
  209. package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
  210. package/Tests/Server/Views/OfflineRender.test.ts +297 -0
  211. package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
  212. package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
  213. package/Tests/Types/Password.test.ts +363 -0
  214. package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
  215. package/Tests/UI/Components/ErrorBoundary.test.tsx +53 -0
  216. package/Tests/UI/Components/Modal.test.tsx +281 -1
  217. package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
  218. package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
  219. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
  220. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  221. package/Tests/Utils/Analytics.test.ts +23 -0
  222. package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
  223. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
  224. package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
  225. package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
  226. package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -0
  227. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +931 -0
  228. package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
  229. package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
  230. package/Types/AI/AIInsightEvidence.ts +6 -0
  231. package/Types/Analytics/RevenueEvent.ts +31 -0
  232. package/Types/Dashboard/DashboardComponentType.ts +31 -0
  233. package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
  234. package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
  235. package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
  236. package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
  237. package/Types/DataSource/DataSourceLimits.ts +62 -0
  238. package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
  239. package/Types/DataSource/DataSourceTableResult.ts +38 -0
  240. package/Types/DataSource/DataSourceType.ts +213 -0
  241. package/Types/Date.ts +43 -3
  242. package/Types/Password.ts +64 -0
  243. package/Types/Permission.ts +46 -0
  244. package/Types/UserAuthenticationStatus.ts +34 -0
  245. package/UI/Components/Button/Button.tsx +1 -1
  246. package/UI/Components/ErrorBoundary.tsx +459 -108
  247. package/UI/Components/Icon/Icon.tsx +10 -9
  248. package/UI/Components/Modal/ConfirmModal.tsx +6 -1
  249. package/UI/Components/Modal/Modal.tsx +267 -35
  250. package/UI/Components/Modal/ModalFooter.tsx +6 -1
  251. package/UI/Components/MoreMenu/MoreMenu.tsx +19 -4
  252. package/UI/Components/MoreMenu/MoreMenuItem.tsx +6 -1
  253. package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
  254. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
  255. package/UI/Components/StatusPage/ResourceList.tsx +570 -0
  256. package/UI/Styles/Theme.css +18 -0
  257. package/UI/Utils/API/RequestOptions.ts +7 -0
  258. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
  259. package/UI/Utils/ModelAPI/ModelAPI.ts +1 -0
  260. package/UI/Utils/TestDataSource.ts +61 -0
  261. package/UI/esbuild-config.js +45 -0
  262. package/Utils/Analytics.ts +15 -0
  263. package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
  264. package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
  265. package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
  266. package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
  267. package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
  268. package/Utils/Dashboard/Components/Index.ts +28 -0
  269. package/Utils/DataSource/DataSourceQueryText.ts +94 -0
  270. package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
  271. package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
  272. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
  273. package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
  274. package/Utils/StatusPage/ResourceExplorer.ts +853 -0
  275. package/build/dist/Models/DatabaseModels/AIRun.js +34 -0
  276. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  277. package/build/dist/Models/DatabaseModels/DataSource.js +774 -0
  278. package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
  279. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  280. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  281. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +14 -4
  282. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
  283. package/build/dist/Models/DatabaseModels/User.js +2 -0
  284. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  285. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
  286. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  287. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
  288. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  289. package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
  290. package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
  291. package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
  292. package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
  293. package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
  294. package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
  295. package/build/dist/Server/API/AIInsightAPI.js +34 -4
  296. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  297. package/build/dist/Server/API/AIInvestigationAPI.js +23 -0
  298. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  299. package/build/dist/Server/API/DashboardAPI.js +119 -433
  300. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  301. package/build/dist/Server/API/DataSourceAPI.js +173 -0
  302. package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
  303. package/build/dist/Server/API/StatusPageAPI.js +11 -1
  304. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  305. package/build/dist/Server/API/UserAPI.js +94 -2
  306. package/build/dist/Server/API/UserAPI.js.map +1 -1
  307. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +49 -0
  308. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  309. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js +20 -0
  310. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js.map +1 -0
  311. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
  312. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
  313. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
  314. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
  315. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  316. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  317. package/build/dist/Server/Infrastructure/Queue.js +27 -0
  318. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  319. package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
  320. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  321. package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
  322. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  323. package/build/dist/Server/Services/AIInsightService.js +110 -10
  324. package/build/dist/Server/Services/AIInsightService.js.map +1 -1
  325. package/build/dist/Server/Services/AIRunService.js +34 -0
  326. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  327. package/build/dist/Server/Services/AIService.js +16 -2
  328. package/build/dist/Server/Services/AIService.js.map +1 -1
  329. package/build/dist/Server/Services/AlertService.js +28 -35
  330. package/build/dist/Server/Services/AlertService.js.map +1 -1
  331. package/build/dist/Server/Services/AnalyticsDatabaseService.js +8 -4
  332. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  333. package/build/dist/Server/Services/DataSourceService.js +159 -0
  334. package/build/dist/Server/Services/DataSourceService.js.map +1 -0
  335. package/build/dist/Server/Services/HostService.js +74 -0
  336. package/build/dist/Server/Services/HostService.js.map +1 -1
  337. package/build/dist/Server/Services/IncidentService.js +35 -24
  338. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  339. package/build/dist/Server/Services/Index.js +2 -0
  340. package/build/dist/Server/Services/Index.js.map +1 -1
  341. package/build/dist/Server/Services/MonitorService.js +45 -0
  342. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  343. package/build/dist/Server/Services/MonitorStatusService.js +129 -0
  344. package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
  345. package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
  346. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  347. package/build/dist/Server/Services/RumSessionPinService.js +7 -15
  348. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  349. package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
  350. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  351. package/build/dist/Server/Services/UserService.js +241 -10
  352. package/build/dist/Server/Services/UserService.js.map +1 -1
  353. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
  354. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  355. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
  356. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
  357. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
  358. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  359. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
  360. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  361. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +2 -2
  362. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  363. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +68 -8
  364. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  365. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +1 -0
  366. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  367. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +1 -0
  368. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  369. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
  370. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
  371. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
  372. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  373. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
  374. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  375. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
  376. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -1
  377. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js +303 -0
  378. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
  379. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
  380. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
  381. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
  382. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
  383. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
  384. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
  385. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
  386. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
  387. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
  388. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
  389. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
  390. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
  391. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
  392. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
  393. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
  394. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
  395. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
  396. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
  397. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
  398. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
  399. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
  400. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
  401. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
  402. package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
  403. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
  404. package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
  405. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
  406. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
  407. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
  408. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
  409. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
  410. package/build/dist/Server/Utils/DataSource/Types.js +2 -0
  411. package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
  412. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
  413. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
  414. package/build/dist/Server/Utils/InProcessMemo.js +113 -0
  415. package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
  416. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
  417. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js.map +1 -0
  418. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +34 -0
  419. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  420. package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
  421. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  422. package/build/dist/Server/Utils/Response.js +9 -1
  423. package/build/dist/Server/Utils/Response.js.map +1 -1
  424. package/build/dist/Server/Utils/SSRFProtection.js +192 -0
  425. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
  426. package/build/dist/Server/Utils/StartServer.js +7 -0
  427. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  428. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
  429. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
  430. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
  431. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
  432. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
  433. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
  434. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
  435. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
  436. package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
  437. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  438. package/build/dist/Server/Utils/Telemetry.js +93 -6
  439. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  440. package/build/dist/Server/Utils/VendorAssets.js +121 -0
  441. package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
  442. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
  443. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  444. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
  445. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  446. package/build/dist/Types/Analytics/RevenueEvent.js +26 -0
  447. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
  448. package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
  449. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  450. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
  451. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
  452. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
  453. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
  454. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
  455. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
  456. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
  457. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
  458. package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
  459. package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
  460. package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
  461. package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
  462. package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
  463. package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
  464. package/build/dist/Types/DataSource/DataSourceType.js +183 -0
  465. package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
  466. package/build/dist/Types/Date.js +38 -3
  467. package/build/dist/Types/Date.js.map +1 -1
  468. package/build/dist/Types/Password.js +56 -0
  469. package/build/dist/Types/Password.js.map +1 -0
  470. package/build/dist/Types/Permission.js +42 -0
  471. package/build/dist/Types/Permission.js.map +1 -1
  472. package/build/dist/Types/UserAuthenticationStatus.js +2 -0
  473. package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
  474. package/build/dist/UI/Components/ErrorBoundary.js +388 -72
  475. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  476. package/build/dist/UI/Components/Icon/Icon.js +10 -4
  477. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  478. package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
  479. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  480. package/build/dist/UI/Components/Modal/Modal.js +178 -19
  481. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  482. package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
  483. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  484. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +9 -6
  485. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  486. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
  487. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  488. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
  489. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
  490. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
  491. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
  492. package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
  493. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
  494. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
  495. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  496. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +3 -8
  497. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  498. package/build/dist/UI/Utils/TestDataSource.js +39 -0
  499. package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
  500. package/build/dist/Utils/Analytics.js +4 -0
  501. package/build/dist/Utils/Analytics.js.map +1 -1
  502. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
  503. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
  504. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
  505. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
  506. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
  507. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
  508. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
  509. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
  510. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
  511. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
  512. package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
  513. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  514. package/build/dist/Utils/DataSource/DataSourceQueryText.js +64 -0
  515. package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
  516. package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
  517. package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
  518. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
  519. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js.map +1 -0
  520. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +8 -6
  521. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
  522. package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
  523. package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
  524. package/build/dist/Utils/StatusPage/ResourceExplorer.js +519 -0
  525. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
  526. package/jest.config.json +1 -0
  527. package/package.json +5 -1
  528. package/tsconfig.json +12 -10
@@ -8,10 +8,45 @@ import { describe, expect, it, test } from "@jest/globals";
8
8
  * the whole suite is skipped before a single assertion runs.
9
9
  */
10
10
  import "@testing-library/jest-dom";
11
- import { fireEvent, render } from "@testing-library/react";
11
+ import { act, fireEvent, render, RenderResult } from "@testing-library/react";
12
12
  import React from "react";
13
13
  import getJestMockFunction, { MockFunction } from "../../../Tests/MockType";
14
14
 
15
+ /*
16
+ * The panel mounts closed and opens on the next animation frame, so anything
17
+ * asserting the settled look has to let that frame run first.
18
+ */
19
+ type FlushEntranceFrameFunction = () => Promise<void>;
20
+
21
+ const flushEntranceFrame: FlushEntranceFrameFunction =
22
+ async (): Promise<void> => {
23
+ await act(async () => {
24
+ await new Promise<void>((resolve: () => void) => {
25
+ requestAnimationFrame(() => {
26
+ resolve();
27
+ });
28
+ });
29
+ });
30
+ };
31
+
32
+ /*
33
+ * The backdrop a user actually clicks is the layer the panel is centred in,
34
+ * not the tinted sheet behind it, which is inert.
35
+ */
36
+ type GetBackdropLayerFunction = (panel: HTMLElement) => HTMLElement;
37
+
38
+ const getBackdropLayer: GetBackdropLayerFunction = (
39
+ panel: HTMLElement,
40
+ ): HTMLElement => {
41
+ return panel.parentElement!;
42
+ };
43
+
44
+ type GetHeaderFunction = (title: HTMLElement) => HTMLElement;
45
+
46
+ const getHeader: GetHeaderFunction = (title: HTMLElement): HTMLElement => {
47
+ return title.parentElement!.parentElement!;
48
+ };
49
+
15
50
  describe("Modal", () => {
16
51
  test("renders the modal with the title and description", () => {
17
52
  const onSubmit: MockFunction = getJestMockFunction();
@@ -453,4 +488,249 @@ describe("Modal", () => {
453
488
 
454
489
  expect(getByTestId("right-element")).toBeInTheDocument();
455
490
  });
491
+
492
+ it("fades and lifts the panel in from a closed state", async () => {
493
+ const { getByTestId } = render(
494
+ <Modal title="Entrance Modal">
495
+ <div>Modal content</div>
496
+ </Modal>,
497
+ );
498
+
499
+ const panel: HTMLElement = getByTestId("modal");
500
+ const backdrop: HTMLElement = getByTestId("modal-backdrop");
501
+
502
+ expect(panel).toHaveClass("opacity-0", "translate-y-6", "sm:scale-95");
503
+ expect(backdrop).toHaveClass("opacity-0");
504
+
505
+ await flushEntranceFrame();
506
+
507
+ expect(panel).toHaveClass("opacity-100");
508
+ expect(backdrop).toHaveClass("opacity-100");
509
+ });
510
+
511
+ /*
512
+ * A `scale-100` left behind after the entrance would make the panel the
513
+ * containing block for `position: fixed`, and a modal opened from inside
514
+ * this one would then be laid out against the panel instead of the viewport.
515
+ */
516
+ it("leaves no transform on the panel once it has settled", async () => {
517
+ const { getByTestId } = render(
518
+ <Modal title="Settled Modal">
519
+ <div>Modal content</div>
520
+ </Modal>,
521
+ );
522
+
523
+ await flushEntranceFrame();
524
+
525
+ expect(getByTestId("modal").className).not.toMatch(
526
+ /(^|\s)(sm:)?(scale|translate)-/,
527
+ );
528
+ });
529
+
530
+ it("closes on a click that both starts and ends on the backdrop", () => {
531
+ const onCloseMock: MockFunction = getJestMockFunction();
532
+ const { getByTestId } = render(
533
+ <Modal title="Dismissable Modal" onClose={onCloseMock}>
534
+ <div>Modal content</div>
535
+ </Modal>,
536
+ );
537
+
538
+ const backdropLayer: HTMLElement = getBackdropLayer(getByTestId("modal"));
539
+
540
+ fireEvent.mouseDown(backdropLayer);
541
+ fireEvent.click(backdropLayer);
542
+
543
+ expect(onCloseMock).toHaveBeenCalledTimes(1);
544
+ });
545
+
546
+ /*
547
+ * Selecting text in the body and releasing past the edge of the panel is a
548
+ * drag, not a dismissal. Closing on it is the fastest way to lose a
549
+ * half-filled form.
550
+ */
551
+ it("stays open when a drag that started inside the panel ends on the backdrop", () => {
552
+ const onCloseMock: MockFunction = getJestMockFunction();
553
+ const { getByText, getByTestId } = render(
554
+ <Modal title="Drag Modal" onClose={onCloseMock}>
555
+ <div>Modal content</div>
556
+ </Modal>,
557
+ );
558
+
559
+ fireEvent.mouseDown(getByText("Modal content"));
560
+ fireEvent.click(getBackdropLayer(getByTestId("modal")));
561
+
562
+ expect(onCloseMock).not.toHaveBeenCalled();
563
+ });
564
+
565
+ it("stays open on a backdrop click when backdrop dismissal is disabled", () => {
566
+ const onCloseMock: MockFunction = getJestMockFunction();
567
+ const { getByTestId } = render(
568
+ <Modal
569
+ title="Sticky Modal"
570
+ onClose={onCloseMock}
571
+ disableCloseOnBackdropClick={true}
572
+ >
573
+ <div>Modal content</div>
574
+ </Modal>,
575
+ );
576
+
577
+ const backdropLayer: HTMLElement = getBackdropLayer(getByTestId("modal"));
578
+
579
+ fireEvent.mouseDown(backdropLayer);
580
+ fireEvent.click(backdropLayer);
581
+
582
+ expect(onCloseMock).not.toHaveBeenCalled();
583
+ // The close button is still the way out.
584
+ fireEvent.click(getByTestId("close-button"));
585
+ expect(onCloseMock).toHaveBeenCalledTimes(1);
586
+ });
587
+
588
+ it("ignores a backdrop click on a modal that is not the topmost one", () => {
589
+ const onParentCloseMock: MockFunction = getJestMockFunction();
590
+ const onChildCloseMock: MockFunction = getJestMockFunction();
591
+
592
+ const NestedModalFixture: React.FunctionComponent =
593
+ (): React.ReactElement => {
594
+ const [isChildOpen, setIsChildOpen] = React.useState<boolean>(false);
595
+
596
+ return (
597
+ <Modal title="Parent Modal" onClose={onParentCloseMock}>
598
+ <>
599
+ <button
600
+ type="button"
601
+ onClick={() => {
602
+ setIsChildOpen(true);
603
+ }}
604
+ >
605
+ Open child
606
+ </button>
607
+ {isChildOpen && (
608
+ <Modal title="Child Modal" onClose={onChildCloseMock}>
609
+ <div>Child content</div>
610
+ </Modal>
611
+ )}
612
+ </>
613
+ </Modal>
614
+ );
615
+ };
616
+
617
+ const { getAllByTestId, getByRole } = render(<NestedModalFixture />);
618
+
619
+ fireEvent.click(getByRole("button", { name: "Open child" }));
620
+
621
+ const [parentPanel, childPanel] = getAllByTestId("modal") as [
622
+ HTMLElement,
623
+ HTMLElement,
624
+ ];
625
+ const parentBackdropLayer: HTMLElement = getBackdropLayer(parentPanel);
626
+
627
+ fireEvent.mouseDown(parentBackdropLayer);
628
+ fireEvent.click(parentBackdropLayer);
629
+
630
+ expect(onParentCloseMock).not.toHaveBeenCalled();
631
+ expect(onChildCloseMock).not.toHaveBeenCalled();
632
+
633
+ const childBackdropLayer: HTMLElement = getBackdropLayer(childPanel);
634
+
635
+ fireEvent.mouseDown(childBackdropLayer);
636
+ fireEvent.click(childBackdropLayer);
637
+
638
+ expect(onChildCloseMock).toHaveBeenCalledTimes(1);
639
+ expect(onParentCloseMock).not.toHaveBeenCalled();
640
+ });
641
+
642
+ it("locks page scrolling for as long as any modal is open", () => {
643
+ document.body.style.overflow = "auto";
644
+
645
+ const parent: RenderResult = render(
646
+ <Modal title="First Modal">
647
+ <div>Modal content</div>
648
+ </Modal>,
649
+ );
650
+
651
+ expect(document.body.style.overflow).toBe("hidden");
652
+
653
+ const child: RenderResult = render(
654
+ <Modal title="Second Modal">
655
+ <div>Modal content</div>
656
+ </Modal>,
657
+ );
658
+
659
+ child.unmount();
660
+
661
+ // The first modal is still up, so scrolling must not come back yet.
662
+ expect(document.body.style.overflow).toBe("hidden");
663
+
664
+ parent.unmount();
665
+
666
+ expect(document.body.style.overflow).toBe("auto");
667
+
668
+ document.body.style.overflow = "";
669
+ });
670
+
671
+ it("shows a scroll shadow only on the side that content is hidden", () => {
672
+ const { getByTestId } = render(
673
+ <Modal title="Tall Modal" onSubmit={getJestMockFunction()}>
674
+ <div>Modal content</div>
675
+ </Modal>,
676
+ );
677
+
678
+ const content: HTMLElement = getByTestId("modal-content");
679
+ const header: HTMLElement = getHeader(getByTestId("modal-title"));
680
+ const footer: HTMLElement = getByTestId("modal-footer");
681
+
682
+ /*
683
+ * jsdom lays nothing out, so the body is given a size that overflows its
684
+ * container by hand.
685
+ */
686
+ Object.defineProperty(content, "scrollHeight", {
687
+ configurable: true,
688
+ value: 600,
689
+ });
690
+ Object.defineProperty(content, "clientHeight", {
691
+ configurable: true,
692
+ value: 200,
693
+ });
694
+ Object.defineProperty(content, "scrollTop", {
695
+ configurable: true,
696
+ value: 0,
697
+ });
698
+
699
+ fireEvent.scroll(content);
700
+
701
+ expect(header.className).not.toMatch(/shadow-\[/);
702
+ expect(footer.className).toMatch(/shadow-\[0_-6px/);
703
+
704
+ Object.defineProperty(content, "scrollTop", {
705
+ configurable: true,
706
+ value: 400,
707
+ });
708
+
709
+ fireEvent.scroll(content);
710
+
711
+ expect(header.className).toMatch(/shadow-\[0_6px/);
712
+ expect(footer.className).not.toMatch(/shadow-\[/);
713
+ });
714
+
715
+ /*
716
+ * The close button used to be positioned over the header, so a title long
717
+ * enough to reach the right edge ran underneath it.
718
+ */
719
+ it("lays the close button out beside the title rather than over it", () => {
720
+ const { getByTestId } = render(
721
+ <Modal
722
+ title="A title long enough to reach the right edge of the header"
723
+ onClose={getJestMockFunction()}
724
+ >
725
+ <div>Modal content</div>
726
+ </Modal>,
727
+ );
728
+
729
+ const title: HTMLElement = getByTestId("modal-title");
730
+ const closeButton: HTMLElement = getByTestId("close-button");
731
+
732
+ expect(closeButton).not.toHaveClass("absolute");
733
+ expect(closeButton.parentElement).toBe(getHeader(title));
734
+ expect(title.parentElement).not.toHaveClass("pr-9");
735
+ });
456
736
  });
@@ -0,0 +1,396 @@
1
+ import FiltersForm from "../../../UI/Components/Filters/FiltersForm";
2
+ import ClassicFilter from "../../../UI/Components/Filters/Types/Filter";
3
+ import FilterData from "../../../UI/Components/Filters/Types/FilterData";
4
+ import ModelFilter from "../../../UI/Components/ModelFilter/Filter";
5
+ import buildQueryFromFilterData, {
6
+ getFilterKeys,
7
+ sanitizeFilterData,
8
+ } from "../../../UI/Components/ModelTable/FilterDataToQuery";
9
+ import FieldType from "../../../UI/Components/Types/FieldType";
10
+ import EqualTo from "../../../Types/BaseDatabase/EqualTo";
11
+ import NotEqual from "../../../Types/BaseDatabase/NotEqual";
12
+ import Query from "../../../Types/BaseDatabase/Query";
13
+ import Search from "../../../Types/BaseDatabase/Search";
14
+ import StartsWith from "../../../Types/BaseDatabase/StartsWith";
15
+ import { JSONObject } from "../../../Types/JSON";
16
+ import JSONFunctions from "../../../Types/JSONFunctions";
17
+ import User from "../../../Models/DatabaseModels/User";
18
+ import {
19
+ cleanup,
20
+ fireEvent,
21
+ render,
22
+ screen,
23
+ within,
24
+ } from "@testing-library/react";
25
+ import React from "react";
26
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
27
+
28
+ /*
29
+ * A master admin pastes a user id into the Users table's "User ID" filter and
30
+ * presses Apply. Between that keystroke and the rows coming back, the value
31
+ * crosses four separate pieces of machinery, each of which can drop it without
32
+ * anything visibly breaking:
33
+ *
34
+ * 1. FiltersForm picks a filter component by FieldType. ObjectID has to land
35
+ * on the text input; a type it does not recognise renders no control at
36
+ * all, and the modal shows a label with empty space beside it.
37
+ * 2. That component encodes the operator into a typed query value.
38
+ * 3. sanitizeFilterData drops every key the table did not declare - the
39
+ * trust boundary for URL-supplied filters - so `_id` has to be declared.
40
+ * 4. buildQueryFromFilterData copies recognised values into the query.
41
+ *
42
+ * These tests walk the whole path with the exact filter declaration the admin
43
+ * Users and Projects tables ship, rather than asserting on any one stage in
44
+ * isolation.
45
+ */
46
+
47
+ const USER_ID: string = "5f0e8e3a-1f24-4a2f-9e4c-2b1d3c4e5f60";
48
+
49
+ // Exactly what App/FeatureSet/AdminDashboard/src/Pages/Users/Index.tsx declares.
50
+ const ID_FILTER: ModelFilter<User> = {
51
+ field: { _id: true },
52
+ title: "User ID",
53
+ type: FieldType.ObjectID,
54
+ };
55
+
56
+ const NAME_FILTER: ModelFilter<User> = {
57
+ field: { name: true },
58
+ title: "Full Name",
59
+ type: FieldType.Text,
60
+ };
61
+
62
+ const MODEL_FILTERS: Array<ModelFilter<User>> = [ID_FILTER, NAME_FILTER];
63
+
64
+ /*
65
+ * BaseModelTable converts its `field`-shaped filters into the `key`-shaped
66
+ * ones the filter form consumes. Mirror that conversion here so the form is
67
+ * driven by the page's real declaration.
68
+ */
69
+ type ToClassicFunction = (filter: ModelFilter<User>) => ClassicFilter<User>;
70
+
71
+ const toClassic: ToClassicFunction = (
72
+ filter: ModelFilter<User>,
73
+ ): ClassicFilter<User> => {
74
+ return {
75
+ title: filter.title,
76
+ key: Object.keys(filter.field)[0] as keyof User,
77
+ type: filter.type,
78
+ } as ClassicFilter<User>;
79
+ };
80
+
81
+ /**
82
+ * The filter form is controlled by its parent - the real one being the filter
83
+ * modal, which holds the selections until Apply. This stands in for that
84
+ * parent, and publishes the current selections so assertions can read them.
85
+ */
86
+ let filterData: FilterData<User> = {};
87
+
88
+ const FilterModalStandIn: React.FunctionComponent<{
89
+ initial: FilterData<User>;
90
+ onChange: (data: FilterData<User>) => void;
91
+ }> = (props: {
92
+ initial: FilterData<User>;
93
+ onChange: (data: FilterData<User>) => void;
94
+ }): React.ReactElement => {
95
+ const [data, setData] = React.useState<FilterData<User>>(props.initial);
96
+
97
+ return (
98
+ <FiltersForm<User>
99
+ id="users-table-filter-form"
100
+ showFilter={true}
101
+ filters={MODEL_FILTERS.map(toClassic)}
102
+ filterData={data}
103
+ onFilterChanged={(next: FilterData<User>) => {
104
+ setData(next);
105
+ props.onChange(next);
106
+ }}
107
+ />
108
+ );
109
+ };
110
+
111
+ type RenderFormFunction = (initial?: FilterData<User>) => void;
112
+
113
+ const renderForm: RenderFormFunction = (initial?: FilterData<User>): void => {
114
+ filterData = initial || {};
115
+
116
+ render(
117
+ <FilterModalStandIn
118
+ initial={filterData}
119
+ onChange={(next: FilterData<User>) => {
120
+ filterData = next;
121
+ }}
122
+ />,
123
+ );
124
+ };
125
+
126
+ /**
127
+ * The text input belonging to the User ID row, found by the placeholder the
128
+ * filter builds from its own title.
129
+ */
130
+ type IdInputFunction = () => HTMLInputElement;
131
+
132
+ const idInput: IdInputFunction = (): HTMLInputElement => {
133
+ return screen.getByPlaceholderText("Filter by User ID") as HTMLInputElement;
134
+ };
135
+
136
+ /**
137
+ * The operator trigger on the User ID row. Rows are `grid` wrappers, so scope
138
+ * the lookup to the one whose label is "User ID" - otherwise the Full Name
139
+ * row's trigger is just as good a match.
140
+ */
141
+ type IdOperatorTriggerFunction = () => HTMLButtonElement;
142
+
143
+ const idOperatorTrigger: IdOperatorTriggerFunction = (): HTMLButtonElement => {
144
+ const label: HTMLElement = screen.getByText("User ID");
145
+ const row: HTMLElement = label.closest("div.grid") as HTMLElement;
146
+
147
+ return within(row).getByRole("button", {
148
+ name: /contains|equals|starts with|ends with|does not/i,
149
+ }) as HTMLButtonElement;
150
+ };
151
+
152
+ type ChooseOperatorFunction = (label: string) => void;
153
+
154
+ const chooseOperator: ChooseOperatorFunction = (label: string): void => {
155
+ const trigger: HTMLButtonElement = idOperatorTrigger();
156
+
157
+ fireEvent.mouseDown(trigger);
158
+ fireEvent.click(trigger);
159
+
160
+ const menuId: string = trigger.getAttribute("aria-controls") as string;
161
+ const menu: HTMLElement = document.getElementById(menuId) as HTMLElement;
162
+
163
+ fireEvent.click(within(menu).getByText(label));
164
+ };
165
+
166
+ type QueryFromFilterDataFunction = () => Query<User>;
167
+
168
+ /**
169
+ * The two stages that sit between the filter modal and the request: strip
170
+ * anything undeclared, then translate what is left into a query.
171
+ */
172
+ const queryFromFilterData: QueryFromFilterDataFunction = (): Query<User> => {
173
+ return buildQueryFromFilterData<User>({
174
+ filterData: sanitizeFilterData<User>({
175
+ filterData: filterData,
176
+ filters: MODEL_FILTERS,
177
+ }),
178
+ filters: MODEL_FILTERS,
179
+ });
180
+ };
181
+
182
+ describe("filtering a model by its ObjectID", () => {
183
+ beforeEach(() => {
184
+ filterData = {};
185
+ });
186
+
187
+ afterEach(() => {
188
+ cleanup();
189
+ });
190
+
191
+ describe("the control the admin actually gets", () => {
192
+ /*
193
+ * FiltersForm renders every filter component and each one returns an
194
+ * empty fragment for types it does not own. If ObjectID were missing from
195
+ * the text filter's list, the row would render its label and nothing
196
+ * else - a filter that looks present and cannot be used.
197
+ */
198
+ test("renders a text input to paste an id into", () => {
199
+ renderForm();
200
+
201
+ expect(idInput()).toBeTruthy();
202
+ expect(idInput().tagName).toBe("INPUT");
203
+ });
204
+
205
+ test("defaults to matching on part of an id", () => {
206
+ renderForm();
207
+
208
+ expect(idOperatorTrigger().textContent).toContain("contains");
209
+ });
210
+
211
+ /*
212
+ * An id is usually copied whole, so exact match has to be reachable -
213
+ * and it is the one operator where a wrong encoding is invisible, since
214
+ * "contains <whole id>" and "equals <whole id>" return the same row.
215
+ */
216
+ test("offers exact match as well as partial", () => {
217
+ renderForm();
218
+
219
+ const trigger: HTMLButtonElement = idOperatorTrigger();
220
+
221
+ fireEvent.mouseDown(trigger);
222
+ fireEvent.click(trigger);
223
+
224
+ const menu: HTMLElement = document.getElementById(
225
+ trigger.getAttribute("aria-controls") as string,
226
+ ) as HTMLElement;
227
+
228
+ expect(
229
+ Array.from(menu.querySelectorAll("[role='option']")).map(
230
+ (option: Element) => {
231
+ return (option.textContent || "").trim();
232
+ },
233
+ ),
234
+ ).toEqual([
235
+ "contains",
236
+ "does not contain",
237
+ "equals",
238
+ "does not equal",
239
+ "starts with",
240
+ "ends with",
241
+ "is empty",
242
+ "is not empty",
243
+ ]);
244
+ });
245
+ });
246
+
247
+ describe("what a typed id becomes", () => {
248
+ test("a partial id becomes a Search on _id", () => {
249
+ renderForm();
250
+
251
+ fireEvent.change(idInput(), { target: { value: "5f0e8e3a" } });
252
+
253
+ const query: Query<User> = queryFromFilterData();
254
+
255
+ expect(query._id).toBeInstanceOf(Search);
256
+ expect((query._id as Search<string>).toString()).toBe("5f0e8e3a");
257
+ });
258
+
259
+ test("a whole id under 'equals' becomes an EqualTo on _id", () => {
260
+ renderForm();
261
+
262
+ fireEvent.change(idInput(), { target: { value: USER_ID } });
263
+ chooseOperator("equals");
264
+
265
+ const query: Query<User> = queryFromFilterData();
266
+
267
+ expect(query._id).toBeInstanceOf(EqualTo);
268
+ expect((query._id as EqualTo<string>).toString()).toBe(USER_ID);
269
+ });
270
+
271
+ test("'does not equal' becomes a NotEqual on _id", () => {
272
+ renderForm();
273
+
274
+ fireEvent.change(idInput(), { target: { value: USER_ID } });
275
+ chooseOperator("does not equal");
276
+
277
+ expect(queryFromFilterData()._id).toBeInstanceOf(NotEqual);
278
+ });
279
+
280
+ test("'starts with' becomes a StartsWith on _id", () => {
281
+ renderForm();
282
+
283
+ fireEvent.change(idInput(), { target: { value: "5f0e8e3a" } });
284
+ chooseOperator("starts with");
285
+
286
+ expect(queryFromFilterData()._id).toBeInstanceOf(StartsWith);
287
+ });
288
+
289
+ /*
290
+ * Clearing the box has to clear the filter, not send an empty match that
291
+ * every row satisfies - or, worse, an `_id = ''` the database rejects.
292
+ */
293
+ test("clearing the box removes the filter entirely", () => {
294
+ renderForm();
295
+
296
+ fireEvent.change(idInput(), { target: { value: USER_ID } });
297
+ expect(queryFromFilterData()._id).toBeDefined();
298
+
299
+ fireEvent.change(idInput(), { target: { value: "" } });
300
+ expect(queryFromFilterData()._id).toBeUndefined();
301
+ });
302
+
303
+ test("filtering by id leaves the other filters alone", () => {
304
+ renderForm();
305
+
306
+ fireEvent.change(idInput(), { target: { value: USER_ID } });
307
+
308
+ expect(Object.keys(queryFromFilterData())).toEqual(["_id"]);
309
+ });
310
+ });
311
+
312
+ describe("the sanitizer that stands between the modal and the request", () => {
313
+ /*
314
+ * `sanitizeFilterData` keeps only keys the table declared a filter for.
315
+ * Before the page declared one, an `_id` in the filter data - restored
316
+ * from a URL, say - was dropped here and the table came back unfiltered.
317
+ */
318
+ test("keeps _id now that the page declares a filter for it", () => {
319
+ expect(getFilterKeys<User>(MODEL_FILTERS)).toContain("_id");
320
+
321
+ expect(
322
+ sanitizeFilterData<User>({
323
+ filterData: { _id: new Search(USER_ID) } as FilterData<User>,
324
+ filters: MODEL_FILTERS,
325
+ })._id,
326
+ ).toBeInstanceOf(Search);
327
+ });
328
+
329
+ test("still drops a column the page does not offer a filter for", () => {
330
+ expect(
331
+ sanitizeFilterData<User>({
332
+ filterData: {
333
+ _id: new Search(USER_ID),
334
+ isMasterAdmin: true,
335
+ } as unknown as FilterData<User>,
336
+ filters: MODEL_FILTERS,
337
+ }),
338
+ ).toEqual({ _id: new Search(USER_ID) });
339
+ });
340
+ });
341
+
342
+ /*
343
+ * Table filters are mirrored into the query string so a filtered view can be
344
+ * bookmarked and shared - which for an ID filter is the whole point, since
345
+ * "here is the user I mean" is exactly the link an admin wants to send.
346
+ * The values are class instances, so they only survive the round trip
347
+ * through JSONFunctions' typed (de)serialization.
348
+ */
349
+ describe("sharing a filtered view by URL", () => {
350
+ type RoundTripFunction = (value: unknown) => unknown;
351
+
352
+ const roundTrip: RoundTripFunction = (value: unknown): unknown => {
353
+ const serialized: string = JSON.stringify(
354
+ JSONFunctions.serialize({ _id: value } as JSONObject),
355
+ );
356
+
357
+ return (
358
+ JSONFunctions.deserialize(JSON.parse(serialized) as JSONObject) as {
359
+ _id: unknown;
360
+ }
361
+ )._id;
362
+ };
363
+
364
+ test("a 'contains' id filter survives the round trip", () => {
365
+ const restored: unknown = roundTrip(new Search(USER_ID));
366
+
367
+ expect(restored).toBeInstanceOf(Search);
368
+ expect((restored as Search<string>).toString()).toBe(USER_ID);
369
+ });
370
+
371
+ test("an 'equals' id filter survives the round trip", () => {
372
+ const restored: unknown = roundTrip(new EqualTo(USER_ID));
373
+
374
+ expect(restored).toBeInstanceOf(EqualTo);
375
+ expect((restored as EqualTo<string>).toString()).toBe(USER_ID);
376
+ });
377
+
378
+ /*
379
+ * A restored filter also has to read back as the operator that produced
380
+ * it, or the modal reopens showing "contains" over an equality filter.
381
+ */
382
+ test("a restored 'equals' filter reopens as 'equals'", () => {
383
+ renderForm({ _id: new EqualTo(USER_ID) } as FilterData<User>);
384
+
385
+ expect(idOperatorTrigger().textContent).toContain("equals");
386
+ expect(idInput().value).toBe(USER_ID);
387
+ });
388
+
389
+ test("a restored 'contains' filter reopens as 'contains'", () => {
390
+ renderForm({ _id: new Search("5f0e8e3a") } as FilterData<User>);
391
+
392
+ expect(idOperatorTrigger().textContent).toContain("contains");
393
+ expect(idInput().value).toBe("5f0e8e3a");
394
+ });
395
+ });
396
+ });