@oneuptime/common 11.7.1 → 11.7.3

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 (285) hide show
  1. package/Models/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
  2. package/Models/DatabaseModels/DashboardDomain.ts +1 -0
  3. package/Models/DatabaseModels/Incident.ts +2 -0
  4. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  5. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  6. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  7. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  8. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  9. package/Models/DatabaseModels/Monitor.ts +1 -0
  10. package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
  11. package/Models/DatabaseModels/NetworkSite.ts +1 -0
  12. package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
  13. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
  14. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
  15. package/Models/DatabaseModels/Probe.ts +13 -13
  16. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  17. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  18. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  19. package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
  20. package/Models/DatabaseModels/StatusPage.ts +1 -0
  21. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  22. package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
  23. package/Models/DatabaseModels/TableView.ts +40 -0
  24. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
  25. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
  26. package/Server/API/BaseAPI.ts +17 -1
  27. package/Server/API/CommonAPI.ts +32 -0
  28. package/Server/API/MicrosoftTeamsAPI.ts +99 -2
  29. package/Server/API/ProbeAPI.ts +2 -0
  30. package/Server/API/SlackAPI.ts +62 -0
  31. package/Server/API/StatusPageAPI.ts +157 -192
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  37. package/Server/Services/DatabaseService.ts +11 -3
  38. package/Server/Services/MonitorGroupService.ts +145 -0
  39. package/Server/Services/MonitorService.ts +231 -30
  40. package/Server/Services/ProbeService.ts +319 -16
  41. package/Server/Services/StatusPageService.ts +72 -42
  42. package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
  43. package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
  44. package/Server/Utils/Monitor/MonitorResource.ts +88 -35
  45. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
  46. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
  47. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
  48. package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
  49. package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
  50. package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
  51. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
  52. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
  53. package/Tests/Server/API/ProbeAPI.test.ts +6 -0
  54. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
  55. package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
  56. package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
  57. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
  58. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
  59. package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
  60. package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
  61. package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
  62. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
  63. package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
  64. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
  65. package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
  66. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
  67. package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
  68. package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
  69. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
  70. package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
  71. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
  72. package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
  73. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
  74. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
  75. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
  76. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
  77. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
  78. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
  79. package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
  80. package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
  81. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
  82. package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
  83. package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
  84. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
  85. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
  86. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
  87. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
  88. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
  89. package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
  90. package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
  91. package/Tests/Types/JSONFunctions.test.ts +103 -1
  92. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
  93. package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
  94. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
  95. package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
  96. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
  97. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
  98. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +572 -0
  99. package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
  100. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
  101. package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
  102. package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
  103. package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
  104. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
  105. package/Tests/UI/Utils/Project.test.ts +809 -0
  106. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
  107. package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
  108. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
  109. package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
  110. package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
  111. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
  112. package/Tests/Utils/UserPreferences.test.ts +563 -0
  113. package/Types/JSONFunctions.ts +7 -2
  114. package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
  115. package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
  116. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
  117. package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
  118. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
  119. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  120. package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
  121. package/UI/Components/Forms/ModelForm.tsx +41 -18
  122. package/UI/Components/MasterPage/MasterPage.tsx +65 -0
  123. package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
  124. package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
  125. package/UI/Components/ModelTable/BaseModelTable.tsx +687 -34
  126. package/UI/Components/ModelTable/Column.ts +29 -0
  127. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +420 -0
  128. package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
  129. package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
  130. package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
  131. package/UI/Components/ModelTable/TableView.tsx +24 -2
  132. package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
  133. package/UI/Components/Navbar/NavBar.tsx +66 -1
  134. package/UI/Components/SideMenu/SideMenu.tsx +24 -4
  135. package/UI/Components/Table/Table.tsx +66 -14
  136. package/UI/Components/Table/TableRow.tsx +180 -175
  137. package/UI/Components/Table/Types/Column.ts +20 -0
  138. package/UI/Utils/Project.ts +214 -18
  139. package/UI/Utils/TableColumnsToCsv.ts +86 -6
  140. package/UI/esbuild-config.js +34 -0
  141. package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
  142. package/Utils/UserPreferences.ts +53 -0
  143. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
  144. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
  145. package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
  146. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  147. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  148. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  149. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  150. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  152. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  154. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  155. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  156. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  157. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  158. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  159. package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
  160. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  161. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
  162. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  163. package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
  164. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  165. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
  166. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
  167. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
  168. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  169. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
  170. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  171. package/build/dist/Models/DatabaseModels/Probe.js +13 -13
  172. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  173. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  174. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  175. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  176. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  177. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  178. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  179. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
  180. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
  181. package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
  182. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  184. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
  186. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  187. package/build/dist/Models/DatabaseModels/TableView.js +40 -0
  188. package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
  189. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
  190. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
  191. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  192. package/build/dist/Server/API/BaseAPI.js +12 -1
  193. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  194. package/build/dist/Server/API/CommonAPI.js +21 -0
  195. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  196. package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
  197. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  198. package/build/dist/Server/API/ProbeAPI.js +2 -0
  199. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  200. package/build/dist/Server/API/SlackAPI.js +44 -0
  201. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  202. package/build/dist/Server/API/StatusPageAPI.js +98 -152
  203. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  204. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
  205. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
  206. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
  207. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
  208. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
  209. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
  210. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
  211. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
  212. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  213. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  214. package/build/dist/Server/Services/DatabaseService.js +10 -2
  215. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  216. package/build/dist/Server/Services/MonitorGroupService.js +118 -0
  217. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  218. package/build/dist/Server/Services/MonitorService.js +198 -24
  219. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  220. package/build/dist/Server/Services/ProbeService.js +238 -9
  221. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  222. package/build/dist/Server/Services/StatusPageService.js +46 -39
  223. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  224. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
  225. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  226. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
  227. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
  228. package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
  229. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  230. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
  231. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  232. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
  233. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
  234. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
  235. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  236. package/build/dist/Types/JSONFunctions.js +8 -3
  237. package/build/dist/Types/JSONFunctions.js.map +1 -1
  238. package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
  239. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
  240. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  241. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
  242. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  243. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
  244. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  245. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
  246. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
  247. package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
  248. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  249. package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
  250. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  251. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
  252. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  253. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
  254. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  255. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +472 -27
  256. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  257. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +189 -0
  258. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
  259. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
  260. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
  261. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
  262. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
  263. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
  264. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
  265. package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
  266. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  267. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
  268. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
  269. package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
  270. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  271. package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
  272. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  273. package/build/dist/UI/Components/Table/Table.js +46 -13
  274. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  275. package/build/dist/UI/Components/Table/TableRow.js +11 -6
  276. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  277. package/build/dist/UI/Utils/Project.js +161 -15
  278. package/build/dist/UI/Utils/Project.js.map +1 -1
  279. package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
  280. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
  281. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
  282. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
  283. package/build/dist/Utils/UserPreferences.js +33 -0
  284. package/build/dist/Utils/UserPreferences.js.map +1 -1
  285. package/package.json +4 -3
@@ -0,0 +1,679 @@
1
+ import "@testing-library/jest-dom/extend-expect";
2
+ import {
3
+ act,
4
+ cleanup,
5
+ configure,
6
+ fireEvent,
7
+ render,
8
+ screen,
9
+ waitFor,
10
+ } from "@testing-library/react";
11
+ import React from "react";
12
+
13
+ /*
14
+ * A real BaseModelTable mounts a card, a header, a full table and (in the
15
+ * picker tests) a modal on top of that. That comfortably outruns the 1s
16
+ * default when the whole Common suite is competing for the same box. A
17
+ * regression still fails, just later.
18
+ */
19
+ configure({ asyncUtilTimeout: 15000 });
20
+
21
+ import {
22
+ afterEach,
23
+ beforeEach,
24
+ describe,
25
+ expect,
26
+ jest,
27
+ test,
28
+ } from "@jest/globals";
29
+
30
+ /*
31
+ * BaseModelTable pulls in permissions, the current project and the i18n
32
+ * provider. None of those are what these tests are about, so they are stubbed
33
+ * to their permissive/no-op form and the tests focus on one thing: the column
34
+ * layout a viewer chose, and how it survives the round trip through
35
+ * localStorage into the rendered <th> cells.
36
+ */
37
+ jest.mock("../../../../UI/Utils/Permission", () => {
38
+ return {
39
+ __esModule: true,
40
+ default: {
41
+ getAllPermissions: () => {
42
+ return [];
43
+ },
44
+ getProjectPermissions: () => {
45
+ return [];
46
+ },
47
+ getGlobalPermissions: () => {
48
+ return [];
49
+ },
50
+ },
51
+ };
52
+ });
53
+
54
+ jest.mock("../../../../UI/Utils/User", () => {
55
+ return {
56
+ __esModule: true,
57
+ default: {
58
+ isMasterAdmin: () => {
59
+ return true;
60
+ },
61
+ getUserId: () => {
62
+ return null;
63
+ },
64
+ },
65
+ };
66
+ });
67
+
68
+ jest.mock("../../../../UI/Utils/Translation", () => {
69
+ return {
70
+ __esModule: true,
71
+ default: () => {
72
+ return {
73
+ translateString: (value: string | undefined) => {
74
+ return value;
75
+ },
76
+ translateValue: (value: unknown) => {
77
+ return value;
78
+ },
79
+ };
80
+ },
81
+ };
82
+ });
83
+
84
+ import BaseModelTable, {
85
+ BaseTableCallbacks,
86
+ ComponentProps as BaseModelTableProps,
87
+ } from "../../../../UI/Components/ModelTable/BaseModelTable";
88
+ import Columns from "../../../../UI/Components/ModelTable/Columns";
89
+ import TableFilterUrlState from "../../../../UI/Utils/TableFilterUrlState";
90
+ import Filter from "../../../../UI/Components/ModelFilter/Filter";
91
+ import FieldType from "../../../../UI/Components/Types/FieldType";
92
+ import Monitor from "../../../../Models/DatabaseModels/Monitor";
93
+ import Query from "../../../../Types/BaseDatabase/Query";
94
+ import ListResult from "../../../../Types/BaseDatabase/ListResult";
95
+ import UserPreferences, {
96
+ UserPreferenceType,
97
+ } from "../../../../Utils/UserPreferences";
98
+ import { JSONObject } from "../../../../Types/JSON";
99
+
100
+ const PREFS_KEY: string = "monitors-columns-table";
101
+
102
+ type GetListCall = {
103
+ query: Query<Monitor>;
104
+ select: JSONObject;
105
+ };
106
+
107
+ const FILTERS: Array<Filter<Monitor>> = [
108
+ { title: "Name", type: FieldType.Text, field: { name: true } },
109
+ ] as unknown as Array<Filter<Monitor>>;
110
+
111
+ type MakeColumnsOptions = {
112
+ // Adds a `isHiddenByDefault` column ("Monitor Type") at the end.
113
+ withHiddenByDefault?: boolean | undefined;
114
+ // Marks "Name" `isNotCustomizable`, i.e. pinned and kept out of the picker.
115
+ pinName?: boolean | undefined;
116
+ };
117
+
118
+ type MakeColumnsFunction = (options?: MakeColumnsOptions) => Columns<Monitor>;
119
+
120
+ /*
121
+ * Real Monitor columns, not placeholders: the ids the layout is keyed on are
122
+ * derived from the declared `field`, so a table of fake columns would not
123
+ * exercise the same id derivation the app does.
124
+ */
125
+ const makeColumns: MakeColumnsFunction = (
126
+ options: MakeColumnsOptions = {},
127
+ ): Columns<Monitor> => {
128
+ const columns: Array<unknown> = [
129
+ {
130
+ field: { name: true },
131
+ title: "Name",
132
+ type: FieldType.Text,
133
+ ...(options.pinName ? { isNotCustomizable: true } : {}),
134
+ },
135
+ {
136
+ field: { description: true },
137
+ title: "Description",
138
+ type: FieldType.LongText,
139
+ },
140
+ {
141
+ field: { currentMonitorStatus: { name: true } },
142
+ title: "Monitor Status",
143
+ type: FieldType.Entity,
144
+ },
145
+ {
146
+ field: { labels: { name: true } },
147
+ title: "Labels",
148
+ type: FieldType.EntityArray,
149
+ },
150
+ ];
151
+
152
+ if (options.withHiddenByDefault) {
153
+ columns.push({
154
+ field: { monitorType: true },
155
+ title: "Monitor Type",
156
+ type: FieldType.Text,
157
+ isHiddenByDefault: true,
158
+ });
159
+ }
160
+
161
+ return columns as unknown as Columns<Monitor>;
162
+ };
163
+
164
+ const ALL_TITLES: Array<string> = [
165
+ "Name",
166
+ "Description",
167
+ "Monitor Status",
168
+ "Labels",
169
+ ];
170
+
171
+ type SeedPreferenceFunction = (data: {
172
+ key: string;
173
+ order: Array<string>;
174
+ hidden: Array<string>;
175
+ }) => void;
176
+
177
+ /*
178
+ * Written through UserPreferences rather than localStorage directly, so the
179
+ * key derivation the table reads with is the same one the test writes with.
180
+ */
181
+ const seedPreference: SeedPreferenceFunction = (data: {
182
+ key: string;
183
+ order: Array<string>;
184
+ hidden: Array<string>;
185
+ }): void => {
186
+ UserPreferences.saveUserPreferenceByTypeAsJSON({
187
+ key: data.key,
188
+ userPreferenceType: UserPreferenceType.BaseModelTableColumns,
189
+ value: { order: data.order, hidden: data.hidden },
190
+ });
191
+ };
192
+
193
+ type ReadPreferenceFunction = (key: string) => JSONObject | null;
194
+
195
+ const readPreference: ReadPreferenceFunction = (
196
+ key: string,
197
+ ): JSONObject | null => {
198
+ return UserPreferences.getUserPreferenceByTypeAsJSON({
199
+ key: key,
200
+ userPreferenceType: UserPreferenceType.BaseModelTableColumns,
201
+ });
202
+ };
203
+
204
+ type GetHeadersFunction = () => Array<string>;
205
+
206
+ const getHeaders: GetHeadersFunction = (): Array<string> => {
207
+ return screen.getAllByRole("columnheader").map((header: HTMLElement) => {
208
+ return (header.textContent || "").trim();
209
+ });
210
+ };
211
+
212
+ type GetPickerRowIdsFunction = () => Array<string>;
213
+
214
+ /*
215
+ * One checkbox per row in the picker, each tagged with the column id the
216
+ * layout is stored under.
217
+ */
218
+ const getPickerRowIds: GetPickerRowIdsFunction = (): Array<string> => {
219
+ const modal: HTMLElement = screen.getByTestId("column-customization-modal");
220
+
221
+ return Array.from(
222
+ modal.querySelectorAll('[data-testid^="column-toggle-"]'),
223
+ ).map((element: Element) => {
224
+ return (element.getAttribute("data-testid") || "").replace(
225
+ "column-toggle-",
226
+ "",
227
+ );
228
+ });
229
+ };
230
+
231
+ describe("BaseModelTable column customization", () => {
232
+ let calls: Array<GetListCall> = [];
233
+
234
+ type MakeCallbacksFunction = () => BaseTableCallbacks<Monitor>;
235
+
236
+ const makeCallbacks: MakeCallbacksFunction =
237
+ (): BaseTableCallbacks<Monitor> => {
238
+ return {
239
+ deleteItem: async () => {
240
+ return undefined;
241
+ },
242
+ getModelFromJSON: (item: JSONObject) => {
243
+ return item as unknown as Monitor;
244
+ },
245
+ getJSONFromModel: (item: Monitor) => {
246
+ return item as unknown as JSONObject;
247
+ },
248
+ addSlugToSelect: (select: unknown) => {
249
+ return select;
250
+ },
251
+ getList: async (data: {
252
+ query: Query<Monitor>;
253
+ limit: number;
254
+ select: JSONObject;
255
+ }): Promise<ListResult<Monitor>> => {
256
+ calls.push({
257
+ query: data.query,
258
+ select: (data.select || {}) as unknown as JSONObject,
259
+ });
260
+ return { data: [], count: 0, skip: 0, limit: data.limit };
261
+ },
262
+ toJSONArray: () => {
263
+ return [];
264
+ },
265
+ updateById: async () => {
266
+ return undefined;
267
+ },
268
+ showCreateEditModal: () => {
269
+ return <></>;
270
+ },
271
+ } as unknown as BaseTableCallbacks<Monitor>;
272
+ };
273
+
274
+ type RenderTableOptions = {
275
+ userPreferencesKey?: string | undefined;
276
+ columns?: Columns<Monitor> | undefined;
277
+ isDeleteable?: boolean | undefined;
278
+ disableColumnCustomization?: boolean | undefined;
279
+ };
280
+
281
+ type RenderTableFunction = (
282
+ options?: RenderTableOptions,
283
+ ) => ReturnType<typeof render>;
284
+
285
+ const renderTable: RenderTableFunction = (
286
+ options: RenderTableOptions = {},
287
+ ): ReturnType<typeof render> => {
288
+ const props: BaseModelTableProps<Monitor> = {
289
+ modelType: Monitor,
290
+ id: "monitors-table",
291
+ name: "Monitors",
292
+ userPreferencesKey: options.userPreferencesKey || PREFS_KEY,
293
+ /*
294
+ * The Columns control lives in the card header, and BaseModelTable only
295
+ * renders a header at all when cardProps is set.
296
+ */
297
+ cardProps: { title: "Monitors" },
298
+ columns: options.columns || makeColumns(),
299
+ filters: FILTERS,
300
+ isDeleteable: Boolean(options.isDeleteable),
301
+ isCreateable: false,
302
+ isViewable: false,
303
+ isEditable: false,
304
+ // This suite is about columns; keep the URL out of it entirely.
305
+ disableUrlState: true,
306
+ callbacks: makeCallbacks(),
307
+ ...(options.disableColumnCustomization
308
+ ? { disableColumnCustomization: true }
309
+ : {}),
310
+ } as unknown as BaseModelTableProps<Monitor>;
311
+
312
+ return render(<BaseModelTable<Monitor> {...props} />);
313
+ };
314
+
315
+ type WaitForTableFunction = () => Promise<void>;
316
+
317
+ const waitForTable: WaitForTableFunction = async (): Promise<void> => {
318
+ await waitFor(() => {
319
+ expect(calls.length).toBeGreaterThan(0);
320
+ });
321
+
322
+ await waitFor(() => {
323
+ expect(screen.getAllByRole("columnheader").length).toBeGreaterThan(0);
324
+ });
325
+ };
326
+
327
+ type OpenPickerFunction = () => Promise<void>;
328
+
329
+ /*
330
+ * The real click path. The Columns control is an icon-only card button, and
331
+ * every non-primary card button is folded into the "⋯" overflow menu, where
332
+ * it is labelled from its icon — so the viewer reaches it in two clicks.
333
+ */
334
+ const openPicker: OpenPickerFunction = async (): Promise<void> => {
335
+ await act(async () => {
336
+ fireEvent.click(screen.getByRole("button", { name: "More options" }));
337
+ });
338
+
339
+ await act(async () => {
340
+ fireEvent.click(screen.getByText("Columns"));
341
+ });
342
+
343
+ await waitFor(() => {
344
+ expect(screen.queryByTestId("column-customization-modal")).not.toBeNull();
345
+ });
346
+ };
347
+
348
+ type ClickFunction = (element: HTMLElement) => Promise<void>;
349
+
350
+ const click: ClickFunction = async (element: HTMLElement): Promise<void> => {
351
+ await act(async () => {
352
+ fireEvent.click(element);
353
+ });
354
+ };
355
+
356
+ type SavePickerFunction = () => Promise<void>;
357
+
358
+ const savePicker: SavePickerFunction = async (): Promise<void> => {
359
+ await click(screen.getByTestId("modal-footer-submit-button"));
360
+
361
+ await waitFor(() => {
362
+ expect(screen.queryByTestId("column-customization-modal")).toBeNull();
363
+ });
364
+ };
365
+
366
+ beforeEach(() => {
367
+ calls = [];
368
+ window.history.replaceState(
369
+ window.history.state,
370
+ "",
371
+ "/dashboard/monitors",
372
+ );
373
+ TableFilterUrlState.resetClaimedKeys();
374
+ /*
375
+ * The whole point of this suite is what the table reads out of
376
+ * localStorage on mount, and the runner shares one jsdom across files.
377
+ */
378
+ window.localStorage.clear();
379
+ });
380
+
381
+ afterEach(() => {
382
+ cleanup();
383
+ jest.restoreAllMocks();
384
+ });
385
+
386
+ describe("rendering the declared layout", () => {
387
+ test("renders every declared column, in declared order", async () => {
388
+ renderTable({ columns: makeColumns({ withHiddenByDefault: true }) });
389
+
390
+ await waitForTable();
391
+
392
+ /*
393
+ * `isHiddenByDefault` is the table author saying "useful, but not worth
394
+ * the horizontal space" — it must not show up until someone asks for it
395
+ * in the picker.
396
+ */
397
+ expect(getHeaders()).toEqual(ALL_TITLES);
398
+ });
399
+
400
+ test("a stored layout is applied on the very first paint", async () => {
401
+ seedPreference({
402
+ key: PREFS_KEY,
403
+ order: ["labels", "currentMonitorStatus", "description", "name"],
404
+ hidden: ["description"],
405
+ });
406
+
407
+ renderTable();
408
+
409
+ await waitForTable();
410
+
411
+ /*
412
+ * Both halves of the layout at once: Description is gone, and what is
413
+ * left follows the stored order rather than the declared one. Read
414
+ * during render, not in an effect, so there is no flash of the default
415
+ * layout first.
416
+ */
417
+ expect(getHeaders()).toEqual(["Labels", "Monitor Status", "Name"]);
418
+ });
419
+
420
+ test("a layout stored for another table does not leak into this one", async () => {
421
+ seedPreference({
422
+ key: "some-other-table",
423
+ order: ["name"],
424
+ hidden: ["description", "labels"],
425
+ });
426
+
427
+ renderTable();
428
+
429
+ await waitForTable();
430
+
431
+ expect(getHeaders()).toEqual(ALL_TITLES);
432
+ });
433
+
434
+ test("a stale layout naming columns that no longer exist is ignored", async () => {
435
+ /*
436
+ * A stored layout outlives the release that wrote it. If a dropped
437
+ * column's id could still take part, a table could be left permanently
438
+ * mis-ordered - or, worse, with everything hidden - by a layout the
439
+ * viewer has no way to reason about.
440
+ */
441
+ seedPreference({
442
+ key: PREFS_KEY,
443
+ order: ["monitorSteps", "incomingRequestReceivedAt", "ghost-column"],
444
+ hidden: ["monitorSteps", "ghost-column"],
445
+ });
446
+
447
+ renderTable();
448
+
449
+ await waitForTable();
450
+
451
+ expect(getHeaders()).toEqual(ALL_TITLES);
452
+ });
453
+
454
+ test("disableColumnCustomization ignores a stored layout entirely", async () => {
455
+ seedPreference({
456
+ key: PREFS_KEY,
457
+ order: ["labels", "name"],
458
+ hidden: ["description", "currentMonitorStatus"],
459
+ });
460
+
461
+ renderTable({ disableColumnCustomization: true });
462
+
463
+ await waitForTable();
464
+
465
+ /*
466
+ * Tables opt out when their layout is load-bearing. Honouring a stored
467
+ * layout there would break the surrounding page, and the viewer would
468
+ * have no control to undo it with - the Columns button is gone too.
469
+ */
470
+ expect(getHeaders()).toEqual(ALL_TITLES);
471
+ expect(
472
+ screen.queryByRole("button", { name: "More options" }),
473
+ ).not.toBeNull();
474
+ expect(screen.queryByText("Columns")).toBeNull();
475
+ });
476
+ });
477
+
478
+ describe("what hiding a column does NOT do", () => {
479
+ test("a hidden column's field is still selected from the API", async () => {
480
+ seedPreference({
481
+ key: PREFS_KEY,
482
+ order: ["name", "description", "currentMonitorStatus", "labels"],
483
+ hidden: ["description"],
484
+ });
485
+
486
+ renderTable();
487
+
488
+ await waitForTable();
489
+
490
+ expect(getHeaders()).not.toContain("Description");
491
+
492
+ /*
493
+ * Deliberate: a *visible* column's getElement is arbitrary caller code
494
+ * that may read a hidden column's field, and nothing can detect that
495
+ * statically. Narrowing the select to what is on screen would therefore
496
+ * silently blank some other cell. The cost is a few unused fields on the
497
+ * wire; this test is the guard on that trade.
498
+ */
499
+ expect(calls[0]?.select["description"]).toBe(true);
500
+ expect(calls[0]?.select["name"]).toBe(true);
501
+ });
502
+ });
503
+
504
+ describe("the generated Actions column", () => {
505
+ test("stays last however the viewer reorders, and is not in the picker", async () => {
506
+ seedPreference({
507
+ key: PREFS_KEY,
508
+ order: ["labels", "currentMonitorStatus", "description", "name"],
509
+ hidden: [],
510
+ });
511
+
512
+ renderTable({ isDeleteable: true });
513
+
514
+ await waitForTable();
515
+
516
+ const headers: Array<string> = getHeaders();
517
+
518
+ expect(headers).toEqual([
519
+ "Labels",
520
+ "Monitor Status",
521
+ "Description",
522
+ "Name",
523
+ "Actions",
524
+ ]);
525
+
526
+ /*
527
+ * Actions is generated, not declared, and the row buttons it holds are
528
+ * right-aligned against the edge of the table. It is not the viewer's to
529
+ * move or switch off, so the layout is applied to the *input* of the
530
+ * column build rather than to the finished array.
531
+ */
532
+ expect(headers[headers.length - 1]).toBe("Actions");
533
+
534
+ await openPicker();
535
+
536
+ expect(getPickerRowIds()).toEqual([
537
+ "labels",
538
+ "currentMonitorStatus",
539
+ "description",
540
+ "name",
541
+ ]);
542
+ expect(
543
+ screen
544
+ .getByTestId("column-customization-modal")
545
+ .textContent?.includes("Actions"),
546
+ ).toBe(false);
547
+ });
548
+ });
549
+
550
+ describe("the picker", () => {
551
+ test("opens from the card header with a row per customizable column", async () => {
552
+ renderTable({
553
+ columns: makeColumns({ withHiddenByDefault: true, pinName: true }),
554
+ });
555
+
556
+ await waitForTable();
557
+
558
+ await openPicker();
559
+
560
+ /*
561
+ * Name is pinned (`isNotCustomizable`), so it is not offered: a table
562
+ * must never be customizable into anonymity. Monitor Type is offered
563
+ * even though it starts hidden - that is the only way to switch it on.
564
+ */
565
+ expect(getPickerRowIds()).toEqual([
566
+ "description",
567
+ "currentMonitorStatus",
568
+ "labels",
569
+ "monitorType",
570
+ ]);
571
+
572
+ expect(
573
+ screen.getByTestId("column-customization-count").textContent,
574
+ ).toContain("3 of 4 columns shown");
575
+ });
576
+
577
+ test("saving a change updates the table and stores the layout", async () => {
578
+ renderTable();
579
+
580
+ await waitForTable();
581
+ expect(getHeaders()).toEqual(ALL_TITLES);
582
+
583
+ await openPicker();
584
+ await click(screen.getByTestId("column-toggle-description"));
585
+ await savePicker();
586
+
587
+ await waitFor(() => {
588
+ expect(getHeaders()).toEqual(["Name", "Monitor Status", "Labels"]);
589
+ });
590
+
591
+ const stored: JSONObject | null = readPreference(PREFS_KEY);
592
+
593
+ expect(stored).not.toBeNull();
594
+ expect(stored?.["hidden"]).toEqual(["description"]);
595
+ expect(stored?.["order"]).toEqual([
596
+ "name",
597
+ "description",
598
+ "currentMonitorStatus",
599
+ "labels",
600
+ ]);
601
+ });
602
+
603
+ test("saving a layout identical to the default stores nothing", async () => {
604
+ renderTable({ columns: makeColumns({ withHiddenByDefault: true }) });
605
+
606
+ await waitForTable();
607
+
608
+ await openPicker();
609
+ await savePicker();
610
+
611
+ /*
612
+ * Otherwise every table anyone ever opened the picker on would be pinned
613
+ * to the column set of the release they opened it in, and a column
614
+ * shipped later would arrive already stale - present in the code, absent
615
+ * from every viewer's stored order.
616
+ */
617
+ expect(readPreference(PREFS_KEY)).toBeNull();
618
+ expect(
619
+ window.localStorage.getItem(
620
+ `${UserPreferenceType.BaseModelTableColumns}.${PREFS_KEY}`,
621
+ ),
622
+ ).toBeNull();
623
+
624
+ expect(getHeaders()).toEqual(ALL_TITLES);
625
+ });
626
+
627
+ test("Reset drops the stored layout and restores the declared columns", async () => {
628
+ seedPreference({
629
+ key: PREFS_KEY,
630
+ order: ["labels", "name", "currentMonitorStatus", "description"],
631
+ hidden: ["description"],
632
+ });
633
+
634
+ renderTable();
635
+
636
+ await waitForTable();
637
+ expect(getHeaders()).toEqual(["Labels", "Name", "Monitor Status"]);
638
+
639
+ await openPicker();
640
+ await click(screen.getByTestId("column-customization-reset"));
641
+
642
+ await waitFor(() => {
643
+ expect(screen.queryByTestId("column-customization-modal")).toBeNull();
644
+ });
645
+
646
+ await waitFor(() => {
647
+ expect(getHeaders()).toEqual(ALL_TITLES);
648
+ });
649
+
650
+ expect(readPreference(PREFS_KEY)).toBeNull();
651
+ });
652
+ });
653
+
654
+ describe("persistence across a remount", () => {
655
+ test("a layout saved from the picker is still there on the next visit", async () => {
656
+ const view: ReturnType<typeof render> = renderTable();
657
+
658
+ await waitForTable();
659
+
660
+ await openPicker();
661
+ await click(screen.getByTestId("column-toggle-labels"));
662
+ await savePicker();
663
+
664
+ await waitFor(() => {
665
+ expect(getHeaders()).toEqual(["Name", "Description", "Monitor Status"]);
666
+ });
667
+
668
+ view.unmount();
669
+ calls = [];
670
+
671
+ // Same userPreferencesKey: this is the viewer coming back to the page.
672
+ renderTable();
673
+
674
+ await waitForTable();
675
+
676
+ expect(getHeaders()).toEqual(["Name", "Description", "Monitor Status"]);
677
+ });
678
+ });
679
+ });