@oneuptime/common 12.0.6 → 12.0.7

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 (424) hide show
  1. package/Models/DatabaseModels/AIInsight.ts +1 -1
  2. package/Models/DatabaseModels/DatabaseBaseModel/FileModel.ts +2 -3
  3. package/Models/DatabaseModels/DeletedProject.ts +595 -0
  4. package/Models/DatabaseModels/Index.ts +2 -0
  5. package/Models/DatabaseModels/Monitor.ts +111 -0
  6. package/Models/DatabaseModels/Project.ts +3 -3
  7. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -1
  8. package/Server/API/FileAPI.ts +15 -2
  9. package/Server/API/MicrosoftTeamsAPI.ts +6 -1
  10. package/Server/API/ProjectAPI.ts +214 -0
  11. package/Server/API/StatusPageAPI.ts +39 -6
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.ts +50 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.ts +67 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.ts +39 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  16. package/Server/Services/AIAgentService.ts +20 -0
  17. package/Server/Services/DashboardDomainService.ts +73 -28
  18. package/Server/Services/DatabaseService.ts +1 -0
  19. package/Server/Services/DeletedProjectService.ts +138 -0
  20. package/Server/Services/FileService.ts +61 -0
  21. package/Server/Services/IncidentEpisodePublicNoteService.ts +18 -0
  22. package/Server/Services/IncidentPublicNoteService.ts +18 -0
  23. package/Server/Services/Index.ts +2 -0
  24. package/Server/Services/MonitorService.ts +311 -0
  25. package/Server/Services/ProbeService.ts +20 -0
  26. package/Server/Services/ProjectService.ts +297 -1
  27. package/Server/Services/PushNotificationService.ts +71 -0
  28. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +18 -0
  29. package/Server/Services/StatusPageAnnouncementService.ts +53 -0
  30. package/Server/Services/StatusPageDomainService.ts +73 -28
  31. package/Server/Services/TeamMemberService.ts +117 -12
  32. package/Server/Services/TelemetryIngestionKeyService.ts +1 -0
  33. package/Server/Services/UserWebhookService.ts +34 -109
  34. package/Server/Services/WorkspaceNotificationRuleService.ts +69 -0
  35. package/Server/Services/WorkspaceProjectAuthTokenService.ts +37 -1
  36. package/Server/Types/Database/DeleteById.ts +2 -0
  37. package/Server/Types/Database/DeleteOneBy.ts +6 -0
  38. package/Server/Types/Domain.ts +41 -0
  39. package/Server/Types/Workflow/Components/API/Delete.ts +2 -1
  40. package/Server/Types/Workflow/Components/API/Get.ts +2 -1
  41. package/Server/Types/Workflow/Components/API/Patch.ts +2 -1
  42. package/Server/Types/Workflow/Components/API/Post.ts +2 -1
  43. package/Server/Types/Workflow/Components/API/Put.ts +2 -1
  44. package/Server/Types/Workflow/Components/API/Utils.ts +41 -2
  45. package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +25 -19
  46. package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +6 -5
  47. package/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts +32 -14
  48. package/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts +31 -13
  49. package/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts +29 -20
  50. package/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts +28 -21
  51. package/Server/Types/Workflow/Components/BaseModel/LogComponentError.ts +89 -0
  52. package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +208 -0
  53. package/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts +11 -0
  54. package/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts +41 -18
  55. package/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts +45 -19
  56. package/Server/Types/Workflow/Components/Discord/SendMessageToChannel.ts +16 -9
  57. package/Server/Types/Workflow/Components/IncomingWebhookUtils.ts +66 -0
  58. package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +15 -9
  59. package/Server/Types/Workflow/Components/Slack/SendMessageToChannel.ts +17 -0
  60. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  61. package/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.ts +171 -0
  62. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +3 -3
  63. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +3 -2
  64. package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +1 -1
  65. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +6 -6
  66. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +43 -5
  67. package/Server/Utils/DataSource/EgressGuard.ts +176 -16
  68. package/Server/Utils/DataSource/HttpFetch.ts +6 -45
  69. package/Server/Utils/Execute.ts +8 -0
  70. package/Server/Utils/InlineImageAccessTokenSync.ts +23 -0
  71. package/Server/Utils/LLM/LLMService.ts +57 -12
  72. package/Server/Utils/Monitor/MonitorAlert.ts +40 -0
  73. package/Server/Utils/Monitor/MonitorDependencySuppression.ts +192 -0
  74. package/Server/Utils/Monitor/MonitorIncident.ts +40 -0
  75. package/Server/Utils/Monitor/MonitorResource.ts +36 -0
  76. package/Server/Utils/Response.ts +142 -6
  77. package/Server/Utils/SSRFProtection.ts +273 -60
  78. package/Server/Utils/StartServer.ts +8 -0
  79. package/Server/Utils/VM/VMRunner.ts +88 -1
  80. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +555 -49
  81. package/Server/Utils/Workspace/Slack/Slack.ts +25 -2
  82. package/Server/Utils/Workspace/WorkspaceBase.ts +6 -0
  83. package/Tests/App/Dashboard/DangerZone.test.tsx +334 -0
  84. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +539 -0
  85. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +678 -0
  86. package/Tests/App/Dashboard/DashboardVariablesDiscard.test.tsx +706 -0
  87. package/Tests/App/Dashboard/DashboardVariablesModal.test.tsx +508 -0
  88. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +470 -0
  89. package/Tests/App/StatusPage/StatusPageDetailPagesNotFound.test.tsx +308 -0
  90. package/Tests/Models/DatabaseModels/DeletedProjectModel.test.ts +251 -0
  91. package/Tests/Models/DatabaseModels/SavedViewJSONColumnTuples.test.ts +115 -0
  92. package/Tests/Server/API/FileAPIAccessControl.test.ts +316 -0
  93. package/Tests/Server/API/ProjectAPI.test.ts +299 -1
  94. package/Tests/Server/API/ProjectAPIAdjustBalance.test.ts +459 -0
  95. package/Tests/Server/API/ProjectAPIAdjustBalanceBillingDisabled.test.ts +177 -0
  96. package/Tests/Server/Services/DatabaseServiceDeletionReason.test.ts +123 -0
  97. package/Tests/Server/Services/DeletedProjectService.test.ts +415 -0
  98. package/Tests/Server/Services/DeletedProjectServiceSelfHosted.test.ts +70 -0
  99. package/Tests/Server/Services/DomainSubdomainHooks.test.ts +94 -0
  100. package/Tests/Server/Services/FileIsPublicBooleanMigration.test.ts +244 -0
  101. package/Tests/Server/Services/FileServiceMimeType.test.ts +89 -0
  102. package/Tests/Server/Services/MonitorServiceDependencyValidation.test.ts +702 -0
  103. package/Tests/Server/Services/ProjectServiceAdjustBalance.test.ts +615 -0
  104. package/Tests/Server/Services/ProjectServiceDeleteAudit.test.ts +406 -0
  105. package/Tests/Server/Services/PublishTimeImageVisibility.test.ts +450 -0
  106. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +669 -0
  107. package/Tests/Server/Services/UserWebhookSSRF.test.ts +174 -0
  108. package/Tests/Server/Services/WebPushEndpointAllowlist.test.ts +133 -0
  109. package/Tests/Server/Services/WorkspaceProjectAuthTokenRepoint.test.ts +618 -0
  110. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +26 -1
  111. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +215 -0
  112. package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +742 -0
  113. package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +256 -0
  114. package/Tests/Server/Types/Workflow/Components/ModelArguments.test.ts +371 -0
  115. package/Tests/Server/Utils/AI/CodeAgentCommandGuard.test.ts +204 -0
  116. package/Tests/Server/Utils/AI/CodeAgentWriteLimits.test.ts +213 -0
  117. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +52 -5
  118. package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +3 -0
  119. package/Tests/Server/Utils/AI/LLMServiceEgressGuard.test.ts +315 -0
  120. package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +3 -0
  121. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +17 -5
  122. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +14 -1
  123. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +35 -0
  124. package/Tests/Server/Utils/AI/StubLLMEgressGuard.ts +25 -0
  125. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +62 -7
  126. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +69 -0
  127. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +330 -0
  128. package/Tests/Server/Utils/GitHubCreatePullRequestWithToken.test.ts +393 -0
  129. package/Tests/Server/Utils/InlineImageAccessTokenSync.test.ts +202 -0
  130. package/Tests/Server/Utils/Monitor/MonitorDependencySuppression.test.ts +811 -0
  131. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +604 -0
  132. package/Tests/Server/Utils/ResponseSendFileResponse.test.ts +192 -0
  133. package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +406 -0
  134. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +194 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +1169 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +2 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +1605 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTenantResolution.test.ts +1218 -0
  139. package/Tests/Types/AI/AIChatMessageStatus.test.ts +79 -0
  140. package/Tests/Types/AI/AIInsightStatus.test.ts +40 -0
  141. package/Tests/Types/API/HostnameAuthorityParsing.test.ts +157 -0
  142. package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +27 -0
  143. package/Tests/Types/DataSource/DataSourceLimits.test.ts +71 -0
  144. package/Tests/Types/DataSource/DataSourceType.test.ts +181 -0
  145. package/Tests/Types/DomainSubdomainValidation.test.ts +90 -0
  146. package/Tests/Types/JSONFunctions.test.ts +252 -1
  147. package/Tests/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.test.ts +61 -0
  148. package/Tests/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.test.ts +62 -0
  149. package/Tests/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.test.ts +68 -0
  150. package/Tests/Types/Monitor/SnmpMonitor/SnmpVersion.test.ts +51 -0
  151. package/Tests/UI/Components/404.test.tsx +1 -1
  152. package/Tests/UI/Components/Alert.test.tsx +1 -1
  153. package/Tests/UI/Components/Badge.test.tsx +1 -1
  154. package/Tests/UI/Components/BasicForm.test.tsx +39 -10
  155. package/Tests/UI/Components/Button.test.tsx +1 -1
  156. package/Tests/UI/Components/Card.test.tsx +1 -1
  157. package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +1 -1
  158. package/Tests/UI/Components/ColorViewer.test.tsx +1 -1
  159. package/Tests/UI/Components/ComponentsModal.test.tsx +7 -1
  160. package/Tests/UI/Components/DictionaryOfStrings.test.tsx +1 -1
  161. package/Tests/UI/Components/Dropdown.test.tsx +1 -1
  162. package/Tests/UI/Components/FilePicker.test.tsx +1 -1
  163. package/Tests/UI/Components/HiddenText.test.tsx +1 -1
  164. package/Tests/UI/Components/Input.test.tsx +1 -1
  165. package/Tests/UI/Components/Loader.test.tsx +1 -1
  166. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +1 -1
  167. package/Tests/UI/Components/MasterPage.test.tsx +1 -1
  168. package/Tests/UI/Components/ModalPortalDismissal.test.tsx +489 -0
  169. package/Tests/UI/Components/ModelDelete.test.tsx +214 -0
  170. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1 -1
  171. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +1 -1
  172. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +1 -1
  173. package/Tests/UI/Components/ModelTable/BaseModelTableUrlState.test.tsx +1 -1
  174. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1 -1
  175. package/Tests/UI/Components/NavBar.test.tsx +1 -1
  176. package/Tests/UI/Components/OperatorSelectorKeyboard.test.tsx +1 -1
  177. package/Tests/UI/Components/Pagination.test.tsx +1 -1
  178. package/Tests/UI/Components/Pill.test.tsx +1 -1
  179. package/Tests/UI/Components/Probe.test.tsx +1 -1
  180. package/Tests/UI/Components/ProgressBar.test.tsx +1 -1
  181. package/Tests/UI/Components/RadioButtons.test.tsx +1 -1
  182. package/Tests/UI/Components/SideMenuItem.test.tsx +1 -1
  183. package/Tests/UI/Components/SideOver.test.tsx +500 -6
  184. package/Tests/UI/Components/Tabs.test.tsx +1 -1
  185. package/Tests/UI/Components/TelemetrySearchBar.test.tsx +1 -1
  186. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +1 -1
  187. package/Tests/UI/Components/TextArea.test.tsx +1 -1
  188. package/Tests/UI/Components/Toast.test.tsx +1 -1
  189. package/Tests/UI/Components/Toggle.test.tsx +1 -1
  190. package/Tests/UI/Components/TopSection.test.tsx +1 -1
  191. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +1 -1
  192. package/Tests/UI/Rum/ReplayScrubber.test.tsx +1 -1
  193. package/Tests/UI/Rum/ReplayStage.test.tsx +1 -1
  194. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +1 -1
  195. package/Tests/UI/Utils/LocalStorageArrayRoundTrip.test.ts +72 -0
  196. package/Tests/Utils/Dashboard/HtmlWidgetDocument.test.ts +19 -2
  197. package/Tests/Utils/Dashboard/ModelQueryVariableInterpolation.test.ts +44 -0
  198. package/Tests/Utils/Dashboard/VariableInterpolation.test.ts +288 -5
  199. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +42 -0
  200. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +27 -0
  201. package/Tests/Utils/LegacySerializedArray.test.ts +139 -0
  202. package/Tests/Utils/Monitor/MonitorDependencyRule.test.ts +190 -0
  203. package/Tests/Utils/Realtime.test.ts +78 -0
  204. package/Tests/Utils/Telemetry/SavedViewFilters.test.ts +246 -0
  205. package/Tests/__mocks__/i18next-browser-languagedetector.js +21 -0
  206. package/Types/AI/FixPullRequestCiStatus.ts +1 -1
  207. package/Types/AI/FixVerificationStatus.ts +2 -2
  208. package/Types/API/Hostname.ts +92 -4
  209. package/Types/API/URL.ts +43 -25
  210. package/Types/Billing/BalanceAdjustmentType.ts +14 -0
  211. package/Types/Billing/ProjectBalanceType.ts +13 -0
  212. package/Types/Domain.ts +26 -0
  213. package/Types/File/MimeType.ts +11 -0
  214. package/Types/JSONFunctions.ts +15 -0
  215. package/Types/Telemetry/TelemetrySavedViewState.ts +9 -1
  216. package/Types/Workflow/Components/BaseModel.ts +92 -28
  217. package/Typings/Index.d.ts +12 -0
  218. package/UI/Components/GanttChart/ChartContainer.tsx +4 -3
  219. package/UI/Components/Modal/ConfirmModal.tsx +10 -5
  220. package/UI/Components/Modal/Modal.tsx +27 -47
  221. package/UI/Components/ModelDelete/ModelDelete.tsx +23 -6
  222. package/UI/Components/SideOver/SideOver.tsx +195 -68
  223. package/UI/Utils/PageScrollLock.ts +66 -0
  224. package/Utils/Dashboard/HtmlWidgetDocument.ts +4 -2
  225. package/Utils/Dashboard/VariableInterpolation.ts +19 -1
  226. package/Utils/LegacySerializedArray.ts +60 -0
  227. package/Utils/Monitor/MonitorDependencyRule.ts +88 -0
  228. package/Utils/Telemetry/SavedViewFilters.ts +88 -0
  229. package/build/dist/Models/DatabaseModels/AIInsight.js +1 -1
  230. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
  231. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js +2 -3
  232. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js.map +1 -1
  233. package/build/dist/Models/DatabaseModels/DeletedProject.js +640 -0
  234. package/build/dist/Models/DatabaseModels/DeletedProject.js.map +1 -0
  235. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  236. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  237. package/build/dist/Models/DatabaseModels/Monitor.js +106 -0
  238. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  239. package/build/dist/Models/DatabaseModels/Project.js +3 -3
  240. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  241. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  242. package/build/dist/Server/API/FileAPI.js +14 -2
  243. package/build/dist/Server/API/FileAPI.js.map +1 -1
  244. package/build/dist/Server/API/MicrosoftTeamsAPI.js +6 -1
  245. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  246. package/build/dist/Server/API/ProjectAPI.js +157 -4
  247. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  248. package/build/dist/Server/API/StatusPageAPI.js +27 -3
  249. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js +34 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js.map +1 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js +30 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js.map +1 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js +20 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js.map +1 -0
  256. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  257. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  258. package/build/dist/Server/Services/AIAgentService.js +17 -0
  259. package/build/dist/Server/Services/AIAgentService.js.map +1 -1
  260. package/build/dist/Server/Services/DashboardDomainService.js +55 -18
  261. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  262. package/build/dist/Server/Services/DatabaseService.js +1 -0
  263. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  264. package/build/dist/Server/Services/DeletedProjectService.js +124 -0
  265. package/build/dist/Server/Services/DeletedProjectService.js.map +1 -0
  266. package/build/dist/Server/Services/FileService.js +52 -0
  267. package/build/dist/Server/Services/FileService.js.map +1 -1
  268. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +10 -1
  269. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  270. package/build/dist/Server/Services/IncidentPublicNoteService.js +10 -1
  271. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  272. package/build/dist/Server/Services/Index.js +2 -0
  273. package/build/dist/Server/Services/Index.js.map +1 -1
  274. package/build/dist/Server/Services/MonitorService.js +232 -0
  275. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  276. package/build/dist/Server/Services/ProbeService.js +17 -0
  277. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  278. package/build/dist/Server/Services/ProjectService.js +228 -6
  279. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  280. package/build/dist/Server/Services/PushNotificationService.js +56 -0
  281. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  282. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +10 -1
  283. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  284. package/build/dist/Server/Services/StatusPageAnnouncementService.js +54 -0
  285. package/build/dist/Server/Services/StatusPageAnnouncementService.js.map +1 -1
  286. package/build/dist/Server/Services/StatusPageDomainService.js +55 -18
  287. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  288. package/build/dist/Server/Services/TeamMemberService.js +97 -6
  289. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  290. package/build/dist/Server/Services/TelemetryIngestionKeyService.js +1 -0
  291. package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
  292. package/build/dist/Server/Services/UserWebhookService.js +31 -87
  293. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  294. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +65 -0
  295. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  296. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +23 -0
  297. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  298. package/build/dist/Server/Types/Domain.js +37 -0
  299. package/build/dist/Server/Types/Domain.js.map +1 -1
  300. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +2 -1
  301. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  302. package/build/dist/Server/Types/Workflow/Components/API/Get.js +2 -1
  303. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  304. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +2 -1
  305. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  306. package/build/dist/Server/Types/Workflow/Components/API/Post.js +2 -1
  307. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  308. package/build/dist/Server/Types/Workflow/Components/API/Put.js +2 -1
  309. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  310. package/build/dist/Server/Types/Workflow/Components/API/Utils.js +35 -2
  311. package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
  312. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +15 -11
  313. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
  314. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +2 -3
  315. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
  316. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js +21 -10
  317. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js.map +1 -1
  318. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js +21 -10
  319. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js.map +1 -1
  320. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js +18 -13
  321. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js.map +1 -1
  322. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js +18 -13
  323. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js.map +1 -1
  324. package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js +43 -0
  325. package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js.map +1 -0
  326. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +116 -0
  327. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -0
  328. package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js +7 -0
  329. package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js.map +1 -1
  330. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js +24 -13
  331. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js.map +1 -1
  332. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js +27 -13
  333. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js.map +1 -1
  334. package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js +14 -6
  335. package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js.map +1 -1
  336. package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js +42 -0
  337. package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js.map +1 -0
  338. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +15 -6
  339. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
  340. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js +10 -2
  341. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js.map +1 -1
  342. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  343. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js +141 -0
  344. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js.map +1 -1
  345. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
  346. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +1 -1
  347. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +6 -6
  348. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -1
  349. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +39 -5
  350. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  351. package/build/dist/Server/Utils/DataSource/EgressGuard.js +94 -10
  352. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  353. package/build/dist/Server/Utils/DataSource/HttpFetch.js +1 -19
  354. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
  355. package/build/dist/Server/Utils/Execute.js +2 -2
  356. package/build/dist/Server/Utils/Execute.js.map +1 -1
  357. package/build/dist/Server/Utils/InlineImageAccessTokenSync.js +13 -0
  358. package/build/dist/Server/Utils/InlineImageAccessTokenSync.js.map +1 -1
  359. package/build/dist/Server/Utils/LLM/LLMService.js +43 -12
  360. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  361. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +44 -24
  362. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  363. package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js +158 -0
  364. package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js.map +1 -0
  365. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +50 -30
  366. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  367. package/build/dist/Server/Utils/Monitor/MonitorResource.js +32 -0
  368. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  369. package/build/dist/Server/Utils/Response.js +112 -5
  370. package/build/dist/Server/Utils/Response.js.map +1 -1
  371. package/build/dist/Server/Utils/SSRFProtection.js +219 -53
  372. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  373. package/build/dist/Server/Utils/StartServer.js +7 -0
  374. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  375. package/build/dist/Server/Utils/VM/VMRunner.js +73 -1
  376. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  377. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +445 -43
  378. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  379. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +18 -1
  380. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  381. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
  382. package/build/dist/Types/AI/FixPullRequestCiStatus.js +1 -1
  383. package/build/dist/Types/AI/FixVerificationStatus.js +2 -2
  384. package/build/dist/Types/API/Hostname.js +73 -4
  385. package/build/dist/Types/API/Hostname.js.map +1 -1
  386. package/build/dist/Types/API/URL.js +42 -24
  387. package/build/dist/Types/API/URL.js.map +1 -1
  388. package/build/dist/Types/Billing/BalanceAdjustmentType.js +15 -0
  389. package/build/dist/Types/Billing/BalanceAdjustmentType.js.map +1 -0
  390. package/build/dist/Types/Billing/ProjectBalanceType.js +14 -0
  391. package/build/dist/Types/Billing/ProjectBalanceType.js.map +1 -0
  392. package/build/dist/Types/Domain.js +22 -0
  393. package/build/dist/Types/Domain.js.map +1 -1
  394. package/build/dist/Types/File/MimeType.js +11 -0
  395. package/build/dist/Types/File/MimeType.js.map +1 -1
  396. package/build/dist/Types/JSONFunctions.js +14 -0
  397. package/build/dist/Types/JSONFunctions.js.map +1 -1
  398. package/build/dist/Types/Workflow/Components/BaseModel.js +83 -28
  399. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  400. package/build/dist/UI/Components/GanttChart/ChartContainer.js +4 -3
  401. package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +1 -1
  402. package/build/dist/UI/Components/Modal/ConfirmModal.js +3 -1
  403. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  404. package/build/dist/UI/Components/Modal/Modal.js +24 -34
  405. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  406. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +11 -6
  407. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  408. package/build/dist/UI/Components/SideOver/SideOver.js +105 -30
  409. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  410. package/build/dist/UI/Utils/PageScrollLock.js +52 -0
  411. package/build/dist/UI/Utils/PageScrollLock.js.map +1 -0
  412. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js +4 -2
  413. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js.map +1 -1
  414. package/build/dist/Utils/Dashboard/VariableInterpolation.js +19 -1
  415. package/build/dist/Utils/Dashboard/VariableInterpolation.js.map +1 -1
  416. package/build/dist/Utils/LegacySerializedArray.js +51 -0
  417. package/build/dist/Utils/LegacySerializedArray.js.map +1 -0
  418. package/build/dist/Utils/Monitor/MonitorDependencyRule.js +47 -0
  419. package/build/dist/Utils/Monitor/MonitorDependencyRule.js.map +1 -0
  420. package/build/dist/Utils/Telemetry/SavedViewFilters.js +52 -0
  421. package/build/dist/Utils/Telemetry/SavedViewFilters.js.map +1 -0
  422. package/jest.config.json +3 -0
  423. package/package.json +1 -1
  424. package/tsconfig.json +3 -1
@@ -0,0 +1,706 @@
1
+ import {
2
+ cleanup,
3
+ fireEvent,
4
+ render,
5
+ screen,
6
+ waitFor,
7
+ } from "@testing-library/react";
8
+ import "@testing-library/jest-dom";
9
+ import {
10
+ afterEach,
11
+ beforeEach,
12
+ describe,
13
+ expect,
14
+ jest,
15
+ test,
16
+ } from "@jest/globals";
17
+ import React, { act } from "react";
18
+ import { JSONObject, ObjectType } from "../../../Types/JSON";
19
+ import ObjectID from "../../../Types/ObjectID";
20
+ import DashboardVariable, {
21
+ DashboardVariableType,
22
+ } from "../../../Types/Dashboard/DashboardVariable";
23
+ import DashboardViewConfig from "../../../Types/Dashboard/DashboardViewConfig";
24
+ import DefaultDashboardSize from "../../../Types/Dashboard/DashboardSize";
25
+ import DashboardTextComponentUtil from "../../../Utils/Dashboard/Components/DashboardTextComponent";
26
+
27
+ /*
28
+ * "Discard Changes" on a dashboard has to put the board back exactly as the
29
+ * server has it, and dashboard variables were the one part of it that survived
30
+ * the discard.
31
+ *
32
+ * The restore lived behind `if (config.variables)`. A dashboard that had never
33
+ * saved a variable therefore hit no branch at all on discard: a variable added
34
+ * in edit mode stayed in React state, kept its selector on the toolbar, kept
35
+ * being handed to every widget as a filter, kept its `var-` param in the URL,
36
+ * and — because the Variables modal seeds itself from the live list — came
37
+ * back the next time the modal was opened. Only a page reload cleared it, and
38
+ * in the meantime the board was scoped by a variable that did not exist
39
+ * server-side.
40
+ *
41
+ * These tests drive the real DashboardViewer against a fake dashboard API, so
42
+ * they assert what a person actually ends up with after the discard: what the
43
+ * canvas is filtered by, what the toolbar renders, what the URL says, and what
44
+ * the Variables modal shows when it is opened again.
45
+ *
46
+ * The canvas is stubbed — it is ~60 widget implementations deep and none of
47
+ * them are under test here — but it publishes the `variables` prop it received
48
+ * so the runtime list is directly observable.
49
+ */
50
+
51
+ /*
52
+ * Mounting the real DashboardViewer costs a few hundred milliseconds of module
53
+ * work per test on an idle machine and several seconds on a loaded CI box. The
54
+ * default 5s budget is a machine-speed check, not an assertion about the code.
55
+ */
56
+ jest.setTimeout(120000);
57
+
58
+ const DASHBOARD_ID: string = "22222222-2222-4222-8222-222222222222";
59
+
60
+ /**
61
+ * The fake server's copy of the dashboard. `updateById` writes to it and
62
+ * `getItem` reads from it, so a save followed by a re-fetch behaves the way
63
+ * the real API does.
64
+ */
65
+ const serverState: { dashboardViewConfig: JSONObject | null } = {
66
+ dashboardViewConfig: null,
67
+ };
68
+
69
+ const getItemMock: jest.Mock<any, any> = jest.fn() as jest.Mock<any, any>;
70
+ const updateByIdMock: jest.Mock<any, any> = jest.fn() as jest.Mock<any, any>;
71
+
72
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
73
+ return {
74
+ __esModule: true,
75
+ default: {
76
+ getItem: (...args: Array<any>) => {
77
+ return getItemMock(...args);
78
+ },
79
+ updateById: (...args: Array<any>) => {
80
+ return updateByIdMock(...args);
81
+ },
82
+ },
83
+ };
84
+ });
85
+
86
+ jest.mock("../../../UI/Utils/API/API", () => {
87
+ return {
88
+ __esModule: true,
89
+ default: {
90
+ getFriendlyErrorMessage: (err: Error) => {
91
+ return err.message;
92
+ },
93
+ getFriendlyMessage: (err: Error) => {
94
+ return err.message;
95
+ },
96
+ },
97
+ };
98
+ });
99
+
100
+ /*
101
+ * The attribute values the telemetry-backed selector offers. Fixed here so a
102
+ * <select> in the toolbar has real options to choose between.
103
+ */
104
+ const ATTRIBUTE_VALUE: string = "prod";
105
+
106
+ jest.mock(
107
+ "../../../../App/FeatureSet/Dashboard/src/Components/Metrics/Utils/Metrics",
108
+ () => {
109
+ return {
110
+ __esModule: true,
111
+ default: {
112
+ loadAllMetricsTypes: () => {
113
+ return Promise.resolve({ metricTypes: [], telemetryAttributes: [] });
114
+ },
115
+ getTelemetryAttributes: () => {
116
+ return Promise.resolve([]);
117
+ },
118
+ getTelemetryAttributeValues: () => {
119
+ return Promise.resolve(["prod", "staging"]);
120
+ },
121
+ },
122
+ };
123
+ },
124
+ );
125
+
126
+ /*
127
+ * The canvas stub. It renders nothing visible and publishes the variable names
128
+ * it was handed on a data attribute, so asserting on it never collides with
129
+ * the same name rendered by the toolbar selector.
130
+ */
131
+ jest.mock(
132
+ "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Canvas/Index",
133
+ () => {
134
+ const reactModule: typeof React = jest.requireActual(
135
+ "react",
136
+ ) as typeof React;
137
+ return {
138
+ __esModule: true,
139
+ default: (props: {
140
+ variables?: Array<DashboardVariable> | undefined;
141
+ }) => {
142
+ return reactModule.createElement("div", {
143
+ "data-testid": "canvas-stub",
144
+ "data-variable-names": (props.variables || [])
145
+ .map((variable: DashboardVariable) => {
146
+ return variable.name;
147
+ })
148
+ .join(","),
149
+ "data-variable-selections": (props.variables || [])
150
+ .map((variable: DashboardVariable) => {
151
+ return `${variable.name}=${
152
+ variable.selectedValues
153
+ ? variable.selectedValues.join("|")
154
+ : variable.selectedValue || ""
155
+ }`;
156
+ })
157
+ .join(","),
158
+ });
159
+ },
160
+ };
161
+ },
162
+ );
163
+
164
+ import DashboardViewer from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/DashboardView";
165
+
166
+ type MakeVariableFunction = (
167
+ data: Partial<DashboardVariable>,
168
+ ) => DashboardVariable;
169
+
170
+ const makeVariable: MakeVariableFunction = (
171
+ data: Partial<DashboardVariable>,
172
+ ): DashboardVariable => {
173
+ return {
174
+ id: data.id || `var-id-${data.name || "unnamed"}`,
175
+ name: data.name || "cluster",
176
+ type: data.type || DashboardVariableType.TelemetryAttribute,
177
+ attributeKey: data.attributeKey || "k8s.cluster.name",
178
+ ...data,
179
+ };
180
+ };
181
+
182
+ type MakeConfigFunction = (
183
+ variables?: Array<DashboardVariable> | undefined,
184
+ ) => DashboardViewConfig;
185
+
186
+ /**
187
+ * A saved dashboard with exactly one widget on it. The widget matters: the
188
+ * toolbar only renders variable selectors for a board that has components.
189
+ */
190
+ const makeConfig: MakeConfigFunction = (
191
+ variables?: Array<DashboardVariable> | undefined,
192
+ ): DashboardViewConfig => {
193
+ const config: DashboardViewConfig = {
194
+ _type: ObjectType.DashboardViewConfig,
195
+ components: [DashboardTextComponentUtil.getDefaultComponent()],
196
+ heightInDashboardUnits: DefaultDashboardSize.heightInDashboardUnits,
197
+ };
198
+
199
+ if (variables) {
200
+ config.variables = variables;
201
+ }
202
+
203
+ return config;
204
+ };
205
+
206
+ type SeedServerFunction = (config: DashboardViewConfig) => void;
207
+
208
+ const seedServer: SeedServerFunction = (config: DashboardViewConfig): void => {
209
+ serverState.dashboardViewConfig = config as unknown as JSONObject;
210
+ };
211
+
212
+ type SetUrlFunction = (search: string) => void;
213
+
214
+ const setUrl: SetUrlFunction = (search: string): void => {
215
+ window.history.replaceState({}, "", `/dashboard/${DASHBOARD_ID}${search}`);
216
+ };
217
+
218
+ type CurrentVarParamsFunction = () => Record<string, string>;
219
+
220
+ /** Every `var-*` param currently in the browser URL. */
221
+ const currentVarParams: CurrentVarParamsFunction = (): Record<
222
+ string,
223
+ string
224
+ > => {
225
+ const params: URLSearchParams = new URLSearchParams(window.location.search);
226
+ const out: Record<string, string> = {};
227
+ params.forEach((value: string, key: string) => {
228
+ if (key.startsWith("var-")) {
229
+ out[key] = value;
230
+ }
231
+ });
232
+ return out;
233
+ };
234
+
235
+ type CanvasVariableNamesFunction = () => Array<string>;
236
+
237
+ /** The variable names the canvas — i.e. every widget — is scoped by. */
238
+ const canvasVariableNames: CanvasVariableNamesFunction = (): Array<string> => {
239
+ const raw: string =
240
+ screen.getByTestId("canvas-stub").getAttribute("data-variable-names") || "";
241
+ return raw === "" ? [] : raw.split(",");
242
+ };
243
+
244
+ type CanvasVariableSelectionsFunction = () => Array<string>;
245
+
246
+ const canvasVariableSelections: CanvasVariableSelectionsFunction =
247
+ (): Array<string> => {
248
+ const raw: string =
249
+ screen
250
+ .getByTestId("canvas-stub")
251
+ .getAttribute("data-variable-selections") || "";
252
+ return raw === "" ? [] : raw.split(",");
253
+ };
254
+
255
+ type SettleFunction = () => Promise<void>;
256
+
257
+ /*
258
+ * Drain the promise chain a fetch kicked off and flush the React updates it
259
+ * schedules. A bare `act` only drains the microtasks queued at the moment it
260
+ * runs, which is not enough for `loadPage` — it awaits `Promise.all` and then
261
+ * sets state, so the updates land several turns later. Awaiting real macrotask
262
+ * turns is what makes that deterministic instead of a race the test wins only
263
+ * on an idle machine.
264
+ */
265
+ const settle: SettleFunction = async (): Promise<void> => {
266
+ await act(async () => {
267
+ for (let turn: number = 0; turn < 5; turn++) {
268
+ await new Promise<void>((resolve: () => void) => {
269
+ setTimeout(resolve, 0);
270
+ });
271
+ }
272
+ });
273
+ };
274
+
275
+ type RenderDashboardFunction = () => Promise<void>;
276
+
277
+ const renderDashboard: RenderDashboardFunction = async (): Promise<void> => {
278
+ render(<DashboardViewer dashboardId={new ObjectID(DASHBOARD_ID)} />);
279
+ /*
280
+ * The canvas only mounts once `isLoading` flips false, which happens after
281
+ * the whole load chain — including the variable restore — has run. Waiting
282
+ * for it is therefore the gate for "the dashboard is ready to assert on".
283
+ */
284
+ await waitFor(() => {
285
+ expect(screen.getByTestId("canvas-stub")).toBeInTheDocument();
286
+ });
287
+ };
288
+
289
+ type EnterEditModeFunction = () => Promise<void>;
290
+
291
+ const enterEditMode: EnterEditModeFunction = async (): Promise<void> => {
292
+ await act(async () => {
293
+ fireEvent.click(screen.getByLabelText("More dashboard options"));
294
+ });
295
+ await act(async () => {
296
+ fireEvent.click(screen.getByText("Edit Dashboard"));
297
+ });
298
+ };
299
+
300
+ type OpenVariablesModalFunction = () => Promise<void>;
301
+
302
+ const openVariablesModal: OpenVariablesModalFunction =
303
+ async (): Promise<void> => {
304
+ await act(async () => {
305
+ fireEvent.click(screen.getByTitle("Variables"));
306
+ });
307
+ };
308
+
309
+ type VariableNameInputsFunction = () => Array<HTMLInputElement>;
310
+
311
+ /** The Name field of every row in the open Variables modal, in DOM order. */
312
+ const variableNameInputs: VariableNameInputsFunction =
313
+ (): Array<HTMLInputElement> => {
314
+ return screen.queryAllByPlaceholderText(
315
+ "cluster",
316
+ ) as Array<HTMLInputElement>;
317
+ };
318
+
319
+ type AddVariableInModalFunction = (name: string) => Promise<void>;
320
+
321
+ const addVariableInModal: AddVariableInModalFunction = async (
322
+ name: string,
323
+ ): Promise<void> => {
324
+ await act(async () => {
325
+ fireEvent.click(screen.getByText("Add Variable"));
326
+ });
327
+ const inputs: Array<HTMLInputElement> = variableNameInputs();
328
+ const newRow: HTMLInputElement = inputs[
329
+ inputs.length - 1
330
+ ] as HTMLInputElement;
331
+ await act(async () => {
332
+ fireEvent.change(newRow, { target: { value: name } });
333
+ });
334
+ };
335
+
336
+ type SaveVariablesModalFunction = () => Promise<void>;
337
+
338
+ const saveVariablesModal: SaveVariablesModalFunction =
339
+ async (): Promise<void> => {
340
+ await act(async () => {
341
+ fireEvent.click(screen.getByText("Save Variables"));
342
+ });
343
+ };
344
+
345
+ type DiscardEditsFunction = () => Promise<void>;
346
+
347
+ const discardEdits: DiscardEditsFunction = async (): Promise<void> => {
348
+ await act(async () => {
349
+ fireEvent.click(screen.getByTitle("Cancel"));
350
+ });
351
+ await act(async () => {
352
+ fireEvent.click(screen.getByText("Discard Changes"));
353
+ });
354
+ /*
355
+ * `onCancelEditClick` re-fetches and applies the result asynchronously, and
356
+ * nothing visible flips once it lands, so there is no element to wait on.
357
+ */
358
+ await settle();
359
+ };
360
+
361
+ type KeepEditingFunction = () => Promise<void>;
362
+
363
+ /** Open the cancel confirmation and then back out of it. */
364
+ const keepEditing: KeepEditingFunction = async (): Promise<void> => {
365
+ await act(async () => {
366
+ fireEvent.click(screen.getByTitle("Cancel"));
367
+ });
368
+ await act(async () => {
369
+ fireEvent.click(screen.getByText("Keep Editing"));
370
+ });
371
+ };
372
+
373
+ type SaveDashboardFunction = () => Promise<void>;
374
+
375
+ const saveDashboard: SaveDashboardFunction = async (): Promise<void> => {
376
+ await act(async () => {
377
+ fireEvent.click(screen.getByText("Save Changes"));
378
+ });
379
+ await settle();
380
+ };
381
+
382
+ beforeEach(() => {
383
+ serverState.dashboardViewConfig = null;
384
+ setUrl("");
385
+
386
+ getItemMock.mockImplementation(() => {
387
+ return Promise.resolve({
388
+ dashboardViewConfig: serverState.dashboardViewConfig,
389
+ name: "Test Dashboard",
390
+ description: "A dashboard",
391
+ pageTitle: null,
392
+ pageDescription: null,
393
+ });
394
+ });
395
+
396
+ updateByIdMock.mockImplementation((args: any) => {
397
+ serverState.dashboardViewConfig = args.data
398
+ .dashboardViewConfig as JSONObject;
399
+ return Promise.resolve();
400
+ });
401
+ });
402
+
403
+ afterEach(() => {
404
+ cleanup();
405
+ jest.clearAllMocks();
406
+ });
407
+
408
+ describe("DashboardView — discarding edits reconciles dashboard variables", () => {
409
+ describe("a dashboard that saved no variables", () => {
410
+ beforeEach(() => {
411
+ seedServer(makeConfig());
412
+ });
413
+
414
+ test("drops a variable that was added and then discarded", async () => {
415
+ await renderDashboard();
416
+ expect(canvasVariableNames()).toEqual([]);
417
+
418
+ await enterEditMode();
419
+ await openVariablesModal();
420
+ await addVariableInModal("cluster");
421
+ await saveVariablesModal();
422
+
423
+ // The variable is live while the edit is still in progress.
424
+ expect(canvasVariableNames()).toEqual(["cluster"]);
425
+
426
+ await discardEdits();
427
+
428
+ expect(canvasVariableNames()).toEqual([]);
429
+ });
430
+
431
+ test("strips the discarded variable's var- param from the URL", async () => {
432
+ await renderDashboard();
433
+ await enterEditMode();
434
+ await openVariablesModal();
435
+ await addVariableInModal("cluster");
436
+ await saveVariablesModal();
437
+ await discardEdits();
438
+
439
+ expect(currentVarParams()).toEqual({});
440
+ expect(window.location.search).not.toContain("var-");
441
+ });
442
+
443
+ test("stops rendering a toolbar selector for the discarded variable", async () => {
444
+ await renderDashboard();
445
+ await enterEditMode();
446
+ await openVariablesModal();
447
+ await addVariableInModal("cluster");
448
+ await saveVariablesModal();
449
+ await discardEdits();
450
+
451
+ /*
452
+ * Back in view mode the selector's label is the only place the name
453
+ * would appear — the canvas stub keeps it in a data attribute.
454
+ */
455
+ expect(screen.queryByText("cluster")).not.toBeInTheDocument();
456
+ expect(screen.queryByRole("combobox")).not.toBeInTheDocument();
457
+ });
458
+
459
+ test("does not resurrect the discarded definition when the Variables modal is reopened", async () => {
460
+ await renderDashboard();
461
+ await enterEditMode();
462
+ await openVariablesModal();
463
+ await addVariableInModal("cluster");
464
+ await saveVariablesModal();
465
+ await discardEdits();
466
+
467
+ await enterEditMode();
468
+ await openVariablesModal();
469
+
470
+ expect(screen.getByText("No variables yet")).toBeInTheDocument();
471
+ expect(variableNameInputs()).toHaveLength(0);
472
+ });
473
+
474
+ test("keeps the new variable when the user backs out with Keep Editing", async () => {
475
+ /*
476
+ * The re-fetch has to hang off the confirmation's submit, not off
477
+ * leaving edit mode. If it ever moves to a `dashboardMode` effect,
478
+ * every mode transition would silently discard — and every other
479
+ * test here would still pass.
480
+ */
481
+ await renderDashboard();
482
+ expect(getItemMock).toHaveBeenCalledTimes(1);
483
+
484
+ await enterEditMode();
485
+ await openVariablesModal();
486
+ await addVariableInModal("cluster");
487
+ await saveVariablesModal();
488
+
489
+ await keepEditing();
490
+
491
+ expect(getItemMock).toHaveBeenCalledTimes(1);
492
+ expect(canvasVariableNames()).toEqual(["cluster"]);
493
+ // Still editing: the edit-mode action bar is up.
494
+ expect(screen.getByText("Save Changes")).toBeInTheDocument();
495
+ });
496
+
497
+ test("keeps the new variable when the edit is saved rather than discarded", async () => {
498
+ await renderDashboard();
499
+ await enterEditMode();
500
+ await openVariablesModal();
501
+ await addVariableInModal("cluster");
502
+ await saveVariablesModal();
503
+ await saveDashboard();
504
+
505
+ expect(updateByIdMock).toHaveBeenCalledTimes(1);
506
+ expect(canvasVariableNames()).toEqual(["cluster"]);
507
+
508
+ /*
509
+ * And the save actually persisted the definition: a fresh mount of the
510
+ * same dashboard finds it on the server.
511
+ */
512
+ cleanup();
513
+ await renderDashboard();
514
+ expect(canvasVariableNames()).toEqual(["cluster"]);
515
+ });
516
+
517
+ test("clears the runtime list when the saved config has an empty variables array", async () => {
518
+ seedServer(makeConfig([]));
519
+
520
+ await renderDashboard();
521
+ await enterEditMode();
522
+ await openVariablesModal();
523
+ await addVariableInModal("cluster");
524
+ await saveVariablesModal();
525
+ await discardEdits();
526
+
527
+ expect(canvasVariableNames()).toEqual([]);
528
+ });
529
+ });
530
+
531
+ describe("a dashboard that already saved variables", () => {
532
+ beforeEach(() => {
533
+ seedServer(
534
+ makeConfig([makeVariable({ id: "saved-cluster", name: "cluster" })]),
535
+ );
536
+ });
537
+
538
+ test("keeps the saved variable and drops only the newly added one", async () => {
539
+ await renderDashboard();
540
+ expect(canvasVariableNames()).toEqual(["cluster"]);
541
+
542
+ await enterEditMode();
543
+ await openVariablesModal();
544
+ await addVariableInModal("namespace");
545
+ await saveVariablesModal();
546
+ expect(canvasVariableNames()).toEqual(["cluster", "namespace"]);
547
+
548
+ await discardEdits();
549
+
550
+ expect(canvasVariableNames()).toEqual(["cluster"]);
551
+ });
552
+
553
+ test("strips the discarded variable's var- param while preserving the saved one's selection", async () => {
554
+ setUrl("?var-cluster=prod&team=platform");
555
+
556
+ await renderDashboard();
557
+ expect(canvasVariableSelections()).toEqual(["cluster=prod"]);
558
+
559
+ await enterEditMode();
560
+ await openVariablesModal();
561
+ await addVariableInModal("namespace");
562
+ await saveVariablesModal();
563
+ await discardEdits();
564
+
565
+ expect(currentVarParams()).toEqual({ "var-cluster": "prod" });
566
+ // Query state that has nothing to do with variables is left alone.
567
+ expect(new URLSearchParams(window.location.search).get("team")).toBe(
568
+ "platform",
569
+ );
570
+ });
571
+
572
+ test("restores a variable that was deleted in edit mode", async () => {
573
+ await renderDashboard();
574
+ await enterEditMode();
575
+ await openVariablesModal();
576
+
577
+ await act(async () => {
578
+ fireEvent.click(screen.getByTitle("Remove variable"));
579
+ });
580
+ await saveVariablesModal();
581
+ expect(canvasVariableNames()).toEqual([]);
582
+
583
+ await discardEdits();
584
+
585
+ expect(canvasVariableNames()).toEqual(["cluster"]);
586
+ });
587
+
588
+ test("restores the saved name after a rename in edit mode", async () => {
589
+ await renderDashboard();
590
+ await enterEditMode();
591
+ await openVariablesModal();
592
+
593
+ const nameInput: HTMLInputElement =
594
+ variableNameInputs()[0] as HTMLInputElement;
595
+ await act(async () => {
596
+ fireEvent.change(nameInput, { target: { value: "region" } });
597
+ });
598
+ await saveVariablesModal();
599
+ expect(canvasVariableNames()).toEqual(["region"]);
600
+
601
+ await discardEdits();
602
+
603
+ expect(canvasVariableNames()).toEqual(["cluster"]);
604
+ expect(currentVarParams()).toEqual({});
605
+ });
606
+
607
+ test("strips the URL param a discarded rename left pointing at a variable that no longer exists", async () => {
608
+ /*
609
+ * Renaming a variable carries its selection over to the new name and
610
+ * rewrites the URL to match. Discarding the rename puts the definition
611
+ * back, so the `var-region` param has nothing left to address — leaving
612
+ * it would hand anyone the link a selection for a variable that is not
613
+ * on the dashboard.
614
+ */
615
+ setUrl("?var-cluster=prod");
616
+
617
+ await renderDashboard();
618
+ expect(canvasVariableSelections()).toEqual(["cluster=prod"]);
619
+
620
+ await enterEditMode();
621
+ await openVariablesModal();
622
+ const nameInput: HTMLInputElement =
623
+ variableNameInputs()[0] as HTMLInputElement;
624
+ await act(async () => {
625
+ fireEvent.change(nameInput, { target: { value: "region" } });
626
+ });
627
+ await saveVariablesModal();
628
+ expect(currentVarParams()).toEqual({ "var-region": "prod" });
629
+
630
+ await discardEdits();
631
+
632
+ expect(canvasVariableNames()).toEqual(["cluster"]);
633
+ expect(currentVarParams()).toEqual({});
634
+ /*
635
+ * The selection itself does not come back. `var-` params are keyed by
636
+ * name (VariableUrlState.ts:6-9), so the rename moved the only record
637
+ * of it and the discard removed that record. Restoring the definition
638
+ * is the contract; the selection is view state the rename destroyed.
639
+ */
640
+ expect(canvasVariableSelections()).toEqual(["cluster="]);
641
+ });
642
+
643
+ test("a selection made in view mode survives an unrelated edit-and-discard cycle", async () => {
644
+ await renderDashboard();
645
+
646
+ /*
647
+ * The selector fetches its options, and a <select> silently ignores a
648
+ * change to a value it has no <option> for — so picking before they
649
+ * arrive would assert nothing.
650
+ */
651
+ await screen.findByRole("option", { name: ATTRIBUTE_VALUE });
652
+ await act(async () => {
653
+ fireEvent.change(screen.getByRole("combobox"), {
654
+ target: { value: ATTRIBUTE_VALUE },
655
+ });
656
+ });
657
+ expect(canvasVariableSelections()).toEqual(["cluster=prod"]);
658
+
659
+ await enterEditMode();
660
+ await discardEdits();
661
+
662
+ expect(canvasVariableNames()).toEqual(["cluster"]);
663
+ expect(canvasVariableSelections()).toEqual(["cluster=prod"]);
664
+ expect(currentVarParams()).toEqual({ "var-cluster": "prod" });
665
+ });
666
+ });
667
+
668
+ describe("shared links", () => {
669
+ test("applies a var- selection from the URL to the saved variable on load", async () => {
670
+ seedServer(
671
+ makeConfig([makeVariable({ id: "saved-cluster", name: "cluster" })]),
672
+ );
673
+ setUrl("?var-cluster=staging");
674
+
675
+ await renderDashboard();
676
+
677
+ expect(canvasVariableSelections()).toEqual(["cluster=staging"]);
678
+ expect(currentVarParams()).toEqual({ "var-cluster": "staging" });
679
+ });
680
+
681
+ test("strips a var- param naming a variable the dashboard does not have", async () => {
682
+ seedServer(
683
+ makeConfig([makeVariable({ id: "saved-cluster", name: "cluster" })]),
684
+ );
685
+ setUrl("?var-cluster=prod&var-ghost=whatever");
686
+
687
+ await renderDashboard();
688
+
689
+ expect(canvasVariableNames()).toEqual(["cluster"]);
690
+ expect(currentVarParams()).toEqual({ "var-cluster": "prod" });
691
+ });
692
+
693
+ test("strips every var- param on a dashboard that has no variables at all", async () => {
694
+ seedServer(makeConfig());
695
+ setUrl("?var-ghost=whatever&keep=me");
696
+
697
+ await renderDashboard();
698
+
699
+ expect(canvasVariableNames()).toEqual([]);
700
+ expect(currentVarParams()).toEqual({});
701
+ expect(new URLSearchParams(window.location.search).get("keep")).toBe(
702
+ "me",
703
+ );
704
+ });
705
+ });
706
+ });