@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,585 @@
1
+ import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
2
+
3
+ /*
4
+ * Extensive tests for SlackUtil.getAllWorkspaceChannels — the API behind the
5
+ * new GET /slack/channels "browse channels" endpoint:
6
+ *
7
+ * - Cursor pagination over https://slack.com/api/conversations.list
8
+ * (POST form-urlencoded, limit 999, public+private non-archived channels,
9
+ * hard cap of 100 pages).
10
+ * - Result dictionary keyed by ORIGINAL-case channel name.
11
+ * - BadRequestException on ok !== true, HTTPErrorResponse rethrown as-is.
12
+ * - Bulk write-through of miscData.channelCache (LOWERCASED keys) via
13
+ * WorkspaceProjectAuthTokenService.getProjectAuth + refreshAuthToken,
14
+ * with every cache-write failure swallowed.
15
+ */
16
+
17
+ import SlackUtil from "../../../../Server/Utils/Workspace/Slack/Slack";
18
+ import WorkspaceProjectAuthTokenService from "../../../../Server/Services/WorkspaceProjectAuthTokenService";
19
+ import WorkspaceProjectAuthToken, {
20
+ SlackMiscData,
21
+ } from "../../../../Models/DatabaseModels/WorkspaceProjectAuthToken";
22
+ import WorkspaceType from "../../../../Types/Workspace/WorkspaceType";
23
+ import ObjectID from "../../../../Types/ObjectID";
24
+ import API from "../../../../Utils/API";
25
+ import HTTPResponse from "../../../../Types/API/HTTPResponse";
26
+ import HTTPErrorResponse from "../../../../Types/API/HTTPErrorResponse";
27
+ import BadRequestException from "../../../../Types/Exception/BadRequestException";
28
+ import { JSONObject } from "../../../../Types/JSON";
29
+ import Dictionary from "../../../../Types/Dictionary";
30
+ import { WorkspaceChannel } from "../../../../Server/Utils/Workspace/WorkspaceBase";
31
+ import URL from "../../../../Types/API/URL";
32
+
33
+ const AUTH_TOKEN: string = "xoxb-test-token";
34
+ const CONVERSATIONS_LIST_URL: string =
35
+ "https://slack.com/api/conversations.list";
36
+
37
+ interface PostCallArgs {
38
+ url: URL;
39
+ data: JSONObject;
40
+ headers: Dictionary<string>;
41
+ options: {
42
+ retries: number;
43
+ exponentialBackoff: boolean;
44
+ };
45
+ }
46
+
47
+ interface RefreshAuthTokenArgs {
48
+ projectId: ObjectID;
49
+ workspaceType: WorkspaceType;
50
+ authToken: string;
51
+ workspaceProjectId: string;
52
+ miscData: SlackMiscData;
53
+ }
54
+
55
+ interface CachedChannelEntry {
56
+ id: string;
57
+ name: string;
58
+ workspaceType: WorkspaceType;
59
+ lastUpdated: string;
60
+ }
61
+
62
+ function slackChannel(id: string, name: string): JSONObject {
63
+ return {
64
+ id: id,
65
+ name: name,
66
+ is_archived: false,
67
+ };
68
+ }
69
+
70
+ /*
71
+ * The source only reads `.jsonData` off a successful response and only
72
+ * branches on `instanceof HTTPErrorResponse` for failures, so a plain object
73
+ * with jsonData pins exactly what the code depends on.
74
+ */
75
+ function buildPage(data: {
76
+ channels: Array<JSONObject>;
77
+ nextCursor?: string | undefined;
78
+ }): HTTPResponse<JSONObject> {
79
+ const jsonData: JSONObject = {
80
+ ok: true,
81
+ channels: data.channels,
82
+ };
83
+
84
+ if (data.nextCursor !== undefined) {
85
+ jsonData["response_metadata"] = {
86
+ next_cursor: data.nextCursor,
87
+ };
88
+ }
89
+
90
+ return { jsonData: jsonData } as unknown as HTTPResponse<JSONObject>;
91
+ }
92
+
93
+ function buildRawResponse(jsonData: JSONObject): HTTPResponse<JSONObject> {
94
+ return { jsonData: jsonData } as unknown as HTTPResponse<JSONObject>;
95
+ }
96
+
97
+ function buildAuthRow(data: {
98
+ authToken?: string | undefined;
99
+ workspaceProjectId?: string | undefined;
100
+ miscData?: SlackMiscData | undefined;
101
+ }): WorkspaceProjectAuthToken {
102
+ return {
103
+ authToken: data.authToken ?? "existing-auth-token",
104
+ workspaceProjectId: data.workspaceProjectId ?? "T0001",
105
+ miscData: data.miscData,
106
+ } as unknown as WorkspaceProjectAuthToken;
107
+ }
108
+
109
+ describe("SlackUtil.getAllWorkspaceChannels", () => {
110
+ let projectId: ObjectID;
111
+ let postSpy: jest.SpyInstance;
112
+ let getProjectAuthSpy: jest.SpyInstance;
113
+ let refreshAuthTokenSpy: jest.SpyInstance;
114
+
115
+ beforeEach(() => {
116
+ projectId = ObjectID.generate();
117
+ postSpy = jest.spyOn(API, "post");
118
+ getProjectAuthSpy = jest
119
+ .spyOn(WorkspaceProjectAuthTokenService, "getProjectAuth")
120
+ .mockResolvedValue(null);
121
+ refreshAuthTokenSpy = jest
122
+ .spyOn(WorkspaceProjectAuthTokenService, "refreshAuthToken")
123
+ .mockResolvedValue(undefined as never);
124
+ });
125
+
126
+ afterEach(() => {
127
+ jest.restoreAllMocks();
128
+ });
129
+
130
+ function getAllChannels(): Promise<Dictionary<WorkspaceChannel>> {
131
+ return SlackUtil.getAllWorkspaceChannels({
132
+ authToken: AUTH_TOKEN,
133
+ projectId: projectId,
134
+ });
135
+ }
136
+
137
+ describe("pagination and response parsing", () => {
138
+ test("single page: returns a dictionary keyed by ORIGINAL-case channel name", async () => {
139
+ postSpy.mockResolvedValueOnce(
140
+ buildPage({
141
+ channels: [
142
+ slackChannel("C001", "General"),
143
+ slackChannel("C002", "Alerts-PROD"),
144
+ ],
145
+ }),
146
+ );
147
+
148
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
149
+
150
+ expect(channels).toEqual({
151
+ General: {
152
+ id: "C001",
153
+ name: "General",
154
+ workspaceType: WorkspaceType.Slack,
155
+ },
156
+ "Alerts-PROD": {
157
+ id: "C002",
158
+ name: "Alerts-PROD",
159
+ workspaceType: WorkspaceType.Slack,
160
+ },
161
+ });
162
+ expect(postSpy).toHaveBeenCalledTimes(1);
163
+ });
164
+
165
+ test("sends the documented Slack request shape (url, bearer + form headers, retries, limit/types/exclude_archived)", async () => {
166
+ postSpy.mockResolvedValueOnce(buildPage({ channels: [] }));
167
+
168
+ await getAllChannels();
169
+
170
+ expect(postSpy).toHaveBeenCalledTimes(1);
171
+ const callArgs: PostCallArgs = postSpy.mock.calls[0]?.[0] as PostCallArgs;
172
+ expect(callArgs.url.toString()).toBe(CONVERSATIONS_LIST_URL);
173
+ expect(callArgs.data).toEqual({
174
+ limit: 999,
175
+ types: "public_channel,private_channel",
176
+ exclude_archived: true,
177
+ });
178
+ expect(callArgs.headers).toEqual({
179
+ Authorization: `Bearer ${AUTH_TOKEN}`,
180
+ "Content-Type": "application/x-www-form-urlencoded",
181
+ });
182
+ expect(callArgs.options).toEqual({
183
+ retries: 3,
184
+ exponentialBackoff: true,
185
+ });
186
+ });
187
+
188
+ test("two pages: follows next_cursor, merges results, and passes the cursor on the second call only", async () => {
189
+ postSpy.mockResolvedValueOnce(
190
+ buildPage({
191
+ channels: [slackChannel("C001", "general")],
192
+ nextCursor: "cursor-page-2",
193
+ }),
194
+ );
195
+ postSpy.mockResolvedValueOnce(
196
+ buildPage({
197
+ channels: [slackChannel("C002", "random")],
198
+ nextCursor: "",
199
+ }),
200
+ );
201
+
202
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
203
+
204
+ expect(postSpy).toHaveBeenCalledTimes(2);
205
+ const firstCallArgs: PostCallArgs = postSpy.mock
206
+ .calls[0]?.[0] as PostCallArgs;
207
+ const secondCallArgs: PostCallArgs = postSpy.mock
208
+ .calls[1]?.[0] as PostCallArgs;
209
+ expect(firstCallArgs.data["cursor"]).toBeUndefined();
210
+ expect(secondCallArgs.data).toEqual({
211
+ limit: 999,
212
+ types: "public_channel,private_channel",
213
+ exclude_archived: true,
214
+ cursor: "cursor-page-2",
215
+ });
216
+ expect(Object.keys(channels).sort()).toEqual(["general", "random"]);
217
+ expect(channels["general"]).toEqual({
218
+ id: "C001",
219
+ name: "general",
220
+ workspaceType: WorkspaceType.Slack,
221
+ });
222
+ expect(channels["random"]).toEqual({
223
+ id: "C002",
224
+ name: "random",
225
+ workspaceType: WorkspaceType.Slack,
226
+ });
227
+ });
228
+
229
+ test("an empty-string next_cursor terminates pagination after one call", async () => {
230
+ postSpy.mockResolvedValueOnce(
231
+ buildPage({
232
+ channels: [slackChannel("C001", "general")],
233
+ nextCursor: "",
234
+ }),
235
+ );
236
+
237
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
238
+
239
+ expect(postSpy).toHaveBeenCalledTimes(1);
240
+ expect(Object.keys(channels)).toEqual(["general"]);
241
+ });
242
+
243
+ test("a missing response_metadata terminates pagination after one call", async () => {
244
+ postSpy.mockResolvedValueOnce(
245
+ buildPage({ channels: [slackChannel("C001", "general")] }),
246
+ );
247
+
248
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
249
+
250
+ expect(postSpy).toHaveBeenCalledTimes(1);
251
+ expect(Object.keys(channels)).toEqual(["general"]);
252
+ });
253
+
254
+ test("empty channels array resolves to an empty dictionary", async () => {
255
+ postSpy.mockResolvedValueOnce(buildPage({ channels: [] }));
256
+
257
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
258
+
259
+ expect(channels).toEqual({});
260
+ });
261
+
262
+ test("channels missing id or name are SKIPPED (pinned current behavior, falsy check)", async () => {
263
+ postSpy.mockResolvedValueOnce(
264
+ buildPage({
265
+ channels: [
266
+ slackChannel("C001", "good-channel"),
267
+ { name: "channel-without-id" },
268
+ { id: "C-NO-NAME" },
269
+ { id: "", name: "channel-with-empty-id" },
270
+ { id: "C005", name: "" },
271
+ ],
272
+ }),
273
+ );
274
+
275
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
276
+
277
+ expect(channels).toEqual({
278
+ "good-channel": {
279
+ id: "C001",
280
+ name: "good-channel",
281
+ workspaceType: WorkspaceType.Slack,
282
+ },
283
+ });
284
+ });
285
+
286
+ test("a duplicate channel name on a later page overwrites the earlier one (last page wins)", async () => {
287
+ postSpy.mockResolvedValueOnce(
288
+ buildPage({
289
+ channels: [slackChannel("C-FIRST", "dupe")],
290
+ nextCursor: "cursor-page-2",
291
+ }),
292
+ );
293
+ postSpy.mockResolvedValueOnce(
294
+ buildPage({ channels: [slackChannel("C-SECOND", "dupe")] }),
295
+ );
296
+
297
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
298
+
299
+ expect(channels).toEqual({
300
+ dupe: {
301
+ id: "C-SECOND",
302
+ name: "dupe",
303
+ workspaceType: WorkspaceType.Slack,
304
+ },
305
+ });
306
+ });
307
+
308
+ test("stops at exactly 100 POST calls (maxPages cap) even if Slack returns a cursor forever", async () => {
309
+ postSpy.mockResolvedValue(
310
+ buildPage({
311
+ channels: [slackChannel("C001", "general")],
312
+ nextCursor: "there-is-always-more",
313
+ }),
314
+ );
315
+
316
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
317
+
318
+ expect(postSpy).toHaveBeenCalledTimes(100);
319
+ expect(Object.keys(channels)).toEqual(["general"]);
320
+ });
321
+ });
322
+
323
+ describe("error handling", () => {
324
+ test("ok:false → throws BadRequestException containing the Slack error string, and no cache write is attempted", async () => {
325
+ postSpy.mockResolvedValueOnce(
326
+ buildRawResponse({ ok: false, error: "invalid_auth" }),
327
+ );
328
+
329
+ let caught: unknown = undefined;
330
+ try {
331
+ await getAllChannels();
332
+ } catch (error) {
333
+ caught = error;
334
+ }
335
+
336
+ expect(caught).toBeInstanceOf(BadRequestException);
337
+ expect((caught as BadRequestException).message).toBe(
338
+ "Error from Slack invalid_auth",
339
+ );
340
+ expect(getProjectAuthSpy).not.toHaveBeenCalled();
341
+ expect(refreshAuthTokenSpy).not.toHaveBeenCalled();
342
+ });
343
+
344
+ test("a missing ok field is treated as failure (ok !== true, pinned 'undefined' message)", async () => {
345
+ postSpy.mockResolvedValueOnce(buildRawResponse({ channels: [] }));
346
+
347
+ let caught: unknown = undefined;
348
+ try {
349
+ await getAllChannels();
350
+ } catch (error) {
351
+ caught = error;
352
+ }
353
+
354
+ expect(caught).toBeInstanceOf(BadRequestException);
355
+ expect((caught as BadRequestException).message).toBe(
356
+ "Error from Slack undefined",
357
+ );
358
+ });
359
+
360
+ test("HTTPErrorResponse from the API layer is rethrown as-is", async () => {
361
+ const errorResponse: HTTPErrorResponse = new HTTPErrorResponse(
362
+ 500,
363
+ { error: "slack_is_down" },
364
+ {},
365
+ );
366
+ postSpy.mockResolvedValueOnce(errorResponse);
367
+
368
+ await expect(getAllChannels()).rejects.toBe(errorResponse);
369
+ expect(refreshAuthTokenSpy).not.toHaveBeenCalled();
370
+ });
371
+
372
+ test("ok:false on the SECOND page still throws — no partial results are returned", async () => {
373
+ postSpy.mockResolvedValueOnce(
374
+ buildPage({
375
+ channels: [slackChannel("C001", "general")],
376
+ nextCursor: "cursor-page-2",
377
+ }),
378
+ );
379
+ postSpy.mockResolvedValueOnce(
380
+ buildRawResponse({ ok: false, error: "ratelimited" }),
381
+ );
382
+
383
+ let caught: unknown = undefined;
384
+ try {
385
+ await getAllChannels();
386
+ } catch (error) {
387
+ caught = error;
388
+ }
389
+
390
+ expect(caught).toBeInstanceOf(BadRequestException);
391
+ expect((caught as BadRequestException).message).toBe(
392
+ "Error from Slack ratelimited",
393
+ );
394
+ expect(postSpy).toHaveBeenCalledTimes(2);
395
+ expect(refreshAuthTokenSpy).not.toHaveBeenCalled();
396
+ });
397
+ });
398
+
399
+ describe("channel cache write-through", () => {
400
+ test("refreshAuthToken receives LOWERCASED keys merged over the existing cache, with authToken/workspaceProjectId passed through", async () => {
401
+ const existingMiscData: SlackMiscData = {
402
+ teamId: "T-team",
403
+ teamName: "Acme",
404
+ botUserId: "B001",
405
+ channelCache: {
406
+ "old-channel": {
407
+ id: "C-OLD",
408
+ name: "old-channel",
409
+ lastUpdated: "2026-01-01T00:00:00.000Z",
410
+ },
411
+ },
412
+ };
413
+ getProjectAuthSpy.mockResolvedValue(
414
+ buildAuthRow({
415
+ authToken: "existing-auth-token",
416
+ workspaceProjectId: "T0001",
417
+ miscData: existingMiscData,
418
+ }),
419
+ );
420
+ postSpy.mockResolvedValueOnce(
421
+ buildPage({ channels: [slackChannel("C-NEW", "New-Channel")] }),
422
+ );
423
+
424
+ await getAllChannels();
425
+
426
+ expect(getProjectAuthSpy).toHaveBeenCalledTimes(1);
427
+ expect(getProjectAuthSpy).toHaveBeenCalledWith({
428
+ projectId: projectId,
429
+ workspaceType: WorkspaceType.Slack,
430
+ });
431
+ expect(refreshAuthTokenSpy).toHaveBeenCalledTimes(1);
432
+ const refreshArgs: RefreshAuthTokenArgs = refreshAuthTokenSpy.mock
433
+ .calls[0]?.[0] as RefreshAuthTokenArgs;
434
+ expect(refreshArgs.projectId).toBe(projectId);
435
+ expect(refreshArgs.workspaceType).toBe(WorkspaceType.Slack);
436
+ expect(refreshArgs.authToken).toBe("existing-auth-token");
437
+ expect(refreshArgs.workspaceProjectId).toBe("T0001");
438
+ /*
439
+ * Existing miscData fields are preserved and the cache is a merge —
440
+ * the new entry is keyed LOWERCASE while its `name` keeps the
441
+ * original case.
442
+ */
443
+ expect(refreshArgs.miscData.teamId).toBe("T-team");
444
+ expect(refreshArgs.miscData.teamName).toBe("Acme");
445
+ expect(refreshArgs.miscData.botUserId).toBe("B001");
446
+ expect(
447
+ Object.keys(refreshArgs.miscData.channelCache || {}).sort(),
448
+ ).toEqual(["new-channel", "old-channel"]);
449
+ const newEntry: CachedChannelEntry = (refreshArgs.miscData.channelCache ||
450
+ {})["new-channel"] as unknown as CachedChannelEntry;
451
+ expect(newEntry.id).toBe("C-NEW");
452
+ expect(newEntry.name).toBe("New-Channel");
453
+ expect(newEntry.workspaceType).toBe(WorkspaceType.Slack);
454
+ expect(typeof newEntry.lastUpdated).toBe("string");
455
+ expect(Number.isNaN(new Date(newEntry.lastUpdated).getTime())).toBe(
456
+ false,
457
+ );
458
+ expect((refreshArgs.miscData.channelCache || {})["old-channel"]).toEqual({
459
+ id: "C-OLD",
460
+ name: "old-channel",
461
+ lastUpdated: "2026-01-01T00:00:00.000Z",
462
+ });
463
+ });
464
+
465
+ test("mixed-case names: returned dict key keeps ORIGINAL case while the cache key is lowercased", async () => {
466
+ getProjectAuthSpy.mockResolvedValue(
467
+ buildAuthRow({ miscData: undefined }),
468
+ );
469
+ postSpy.mockResolvedValueOnce(
470
+ buildPage({ channels: [slackChannel("C010", "UPPER-Case")] }),
471
+ );
472
+
473
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
474
+
475
+ expect(Object.keys(channels)).toEqual(["UPPER-Case"]);
476
+ const refreshArgs: RefreshAuthTokenArgs = refreshAuthTokenSpy.mock
477
+ .calls[0]?.[0] as RefreshAuthTokenArgs;
478
+ expect(Object.keys(refreshArgs.miscData.channelCache || {})).toEqual([
479
+ "upper-case",
480
+ ]);
481
+ });
482
+
483
+ test("row with no miscData at all: cache write still works via the || {} fallbacks", async () => {
484
+ getProjectAuthSpy.mockResolvedValue(
485
+ buildAuthRow({ miscData: undefined }),
486
+ );
487
+ postSpy.mockResolvedValueOnce(
488
+ buildPage({
489
+ channels: [
490
+ slackChannel("C001", "good-channel"),
491
+ { name: "channel-without-id" },
492
+ ],
493
+ }),
494
+ );
495
+
496
+ await getAllChannels();
497
+
498
+ expect(refreshAuthTokenSpy).toHaveBeenCalledTimes(1);
499
+ const refreshArgs: RefreshAuthTokenArgs = refreshAuthTokenSpy.mock
500
+ .calls[0]?.[0] as RefreshAuthTokenArgs;
501
+ /*
502
+ * Channels skipped in the result (missing id/name) are excluded from
503
+ * the cache write too.
504
+ */
505
+ expect(Object.keys(refreshArgs.miscData.channelCache || {})).toEqual([
506
+ "good-channel",
507
+ ]);
508
+ });
509
+
510
+ test("no project auth row → refreshAuthToken is never called and the call still succeeds", async () => {
511
+ getProjectAuthSpy.mockResolvedValue(null);
512
+ postSpy.mockResolvedValueOnce(
513
+ buildPage({ channels: [slackChannel("C001", "general")] }),
514
+ );
515
+
516
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
517
+
518
+ expect(Object.keys(channels)).toEqual(["general"]);
519
+ expect(getProjectAuthSpy).toHaveBeenCalledTimes(1);
520
+ expect(refreshAuthTokenSpy).not.toHaveBeenCalled();
521
+ });
522
+
523
+ test("refreshAuthToken rejecting does NOT fail the overall call (errors swallowed)", async () => {
524
+ getProjectAuthSpy.mockResolvedValue(
525
+ buildAuthRow({ miscData: undefined }),
526
+ );
527
+ refreshAuthTokenSpy.mockRejectedValue(new Error("db write failed"));
528
+ postSpy.mockResolvedValueOnce(
529
+ buildPage({ channels: [slackChannel("C001", "general")] }),
530
+ );
531
+
532
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
533
+
534
+ expect(channels).toEqual({
535
+ general: {
536
+ id: "C001",
537
+ name: "general",
538
+ workspaceType: WorkspaceType.Slack,
539
+ },
540
+ });
541
+ expect(refreshAuthTokenSpy).toHaveBeenCalledTimes(1);
542
+ });
543
+
544
+ test("getProjectAuth rejecting is swallowed too", async () => {
545
+ getProjectAuthSpy.mockRejectedValue(new Error("db read failed"));
546
+ postSpy.mockResolvedValueOnce(
547
+ buildPage({ channels: [slackChannel("C001", "general")] }),
548
+ );
549
+
550
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
551
+
552
+ expect(Object.keys(channels)).toEqual(["general"]);
553
+ expect(refreshAuthTokenSpy).not.toHaveBeenCalled();
554
+ });
555
+
556
+ test("zero channels still triggers the cache write attempt, preserving the existing cache", async () => {
557
+ const existingMiscData: SlackMiscData = {
558
+ teamId: "T-team",
559
+ teamName: "Acme",
560
+ botUserId: "B001",
561
+ channelCache: {
562
+ "kept-channel": {
563
+ id: "C-KEPT",
564
+ name: "kept-channel",
565
+ lastUpdated: "2026-01-01T00:00:00.000Z",
566
+ },
567
+ },
568
+ };
569
+ getProjectAuthSpy.mockResolvedValue(
570
+ buildAuthRow({ miscData: existingMiscData }),
571
+ );
572
+ postSpy.mockResolvedValueOnce(buildPage({ channels: [] }));
573
+
574
+ const channels: Dictionary<WorkspaceChannel> = await getAllChannels();
575
+
576
+ expect(channels).toEqual({});
577
+ expect(refreshAuthTokenSpy).toHaveBeenCalledTimes(1);
578
+ const refreshArgs: RefreshAuthTokenArgs = refreshAuthTokenSpy.mock
579
+ .calls[0]?.[0] as RefreshAuthTokenArgs;
580
+ expect(Object.keys(refreshArgs.miscData.channelCache || {})).toEqual([
581
+ "kept-channel",
582
+ ]);
583
+ });
584
+ });
585
+ });
@@ -0,0 +1,46 @@
1
+ import MeteredPlan from "../../../Types/Billing/MeteredPlan";
2
+
3
+ describe("MeteredPlan", () => {
4
+ test("exposes the values passed to the constructor", () => {
5
+ const plan: MeteredPlan = new MeteredPlan({
6
+ priceId: "price_active_monitors",
7
+ pricePerUnitInUSD: 1.5,
8
+ unitName: "Active Monitor",
9
+ });
10
+
11
+ expect(plan.getPriceId()).toBe("price_active_monitors");
12
+ expect(plan.getPricePerUnit()).toBe(1.5);
13
+ expect(plan.getUnitName()).toBe("Active Monitor");
14
+ });
15
+
16
+ test("supports a zero price per unit", () => {
17
+ const plan: MeteredPlan = new MeteredPlan({
18
+ priceId: "price_free",
19
+ pricePerUnitInUSD: 0,
20
+ unitName: "Log Ingestion",
21
+ });
22
+
23
+ expect(plan.getPricePerUnit()).toBe(0);
24
+ });
25
+
26
+ test("keeps each instance's data independent", () => {
27
+ const planA: MeteredPlan = new MeteredPlan({
28
+ priceId: "price_a",
29
+ pricePerUnitInUSD: 2,
30
+ unitName: "Unit A",
31
+ });
32
+ const planB: MeteredPlan = new MeteredPlan({
33
+ priceId: "price_b",
34
+ pricePerUnitInUSD: 7,
35
+ unitName: "Unit B",
36
+ });
37
+
38
+ expect(planA.getPriceId()).toBe("price_a");
39
+ expect(planA.getPricePerUnit()).toBe(2);
40
+ expect(planA.getUnitName()).toBe("Unit A");
41
+
42
+ expect(planB.getPriceId()).toBe("price_b");
43
+ expect(planB.getPricePerUnit()).toBe(7);
44
+ expect(planB.getUnitName()).toBe("Unit B");
45
+ });
46
+ });