@oneuptime/common 12.0.9 → 12.0.11

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 (262) hide show
  1. package/Models/DatabaseModels/NetworkDevice.ts +44 -0
  2. package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
  3. package/Models/DatabaseModels/RunnerJob.ts +17 -2
  4. package/Server/API/LlmProviderAPI.ts +8 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
  8. package/Server/Services/NetworkDeviceLinkService.ts +338 -2
  9. package/Server/Services/NetworkDeviceService.ts +265 -12
  10. package/Server/Services/RunnerJobService.ts +24 -1
  11. package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
  12. package/Server/Types/Database/JSONColumnQuery.ts +42 -4
  13. package/Server/Utils/LLM/LLMService.ts +108 -19
  14. package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
  15. package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
  16. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
  17. package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
  18. package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
  19. package/Server/Utils/SSRFProtection.ts +5 -4
  20. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
  21. package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
  22. package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
  23. package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
  24. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
  25. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
  26. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
  27. package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
  28. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
  29. package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
  30. package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
  31. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
  32. package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
  33. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
  34. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
  35. package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
  36. package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
  37. package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
  38. package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
  39. package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
  40. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
  41. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
  42. package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
  43. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
  44. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
  45. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
  46. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
  47. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
  48. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
  49. package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
  50. package/Tests/Types/API/URLTelScheme.test.ts +261 -0
  51. package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
  52. package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
  53. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
  54. package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
  55. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
  56. package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
  57. package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
  58. package/Tests/UI/Components/Button.test.tsx +279 -0
  59. package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
  60. package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
  61. package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
  62. package/Tests/UI/Components/Icon.test.tsx +57 -0
  63. package/Tests/UI/Components/List.test.tsx +20 -3
  64. package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
  65. package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
  66. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
  67. package/Tests/UI/Components/Pagination.test.tsx +721 -146
  68. package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
  69. package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
  70. package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
  71. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
  72. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
  73. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
  74. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
  75. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
  76. package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
  77. package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
  78. package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
  79. package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
  80. package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
  81. package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
  82. package/Types/API/Protocol.ts +9 -0
  83. package/Types/API/URL.ts +286 -20
  84. package/Types/Billing/PayAsYouGoPricing.ts +47 -0
  85. package/Types/CustomField/CustomFieldType.ts +28 -0
  86. package/Types/Monitor/MonitorType.ts +11 -0
  87. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  88. package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
  89. package/Types/Runbook/RunbookStepType.ts +23 -0
  90. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
  91. package/UI/Components/Button/Button.tsx +14 -22
  92. package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
  93. package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
  94. package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
  95. package/UI/Components/Charts/Line/LineChart.tsx +4 -2
  96. package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
  97. package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
  98. package/UI/Components/Dictionary/Dictionary.tsx +4 -39
  99. package/UI/Components/Icon/Icon.tsx +1 -1
  100. package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
  101. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
  102. package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
  103. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
  104. package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
  105. package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
  106. package/UI/Components/Pagination/Pagination.tsx +373 -328
  107. package/UI/Components/Pagination/PaginationUtil.ts +263 -0
  108. package/UI/Components/Pagination/Screenshots/README.md +15 -0
  109. package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
  110. package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
  111. package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
  112. package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
  113. package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
  114. package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
  115. package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
  116. package/UI/Components/TextArea/TextArea.tsx +3 -0
  117. package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
  118. package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
  119. package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
  120. package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
  121. package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
  122. package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
  123. package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
  124. package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
  125. package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
  126. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
  127. package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
  128. package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
  129. package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
  130. package/Utils/API.ts +243 -36
  131. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
  132. package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
  133. package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
  134. package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
  135. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
  137. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
  139. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  140. package/build/dist/Server/API/LlmProviderAPI.js +9 -1
  141. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  142. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  146. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
  147. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  148. package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
  149. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
  150. package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
  151. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  152. package/build/dist/Server/Services/RunnerJobService.js +20 -2
  153. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  154. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
  155. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
  156. package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
  157. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
  158. package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
  159. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  160. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
  161. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  162. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
  163. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  164. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
  165. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  166. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
  167. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  168. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
  169. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
  170. package/build/dist/Server/Utils/SSRFProtection.js +5 -4
  171. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  172. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
  173. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  174. package/build/dist/Types/API/Protocol.js +9 -0
  175. package/build/dist/Types/API/Protocol.js.map +1 -1
  176. package/build/dist/Types/API/URL.js +241 -18
  177. package/build/dist/Types/API/URL.js.map +1 -1
  178. package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
  179. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
  180. package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
  181. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  182. package/build/dist/Types/Monitor/MonitorType.js +10 -0
  183. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  184. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
  185. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
  186. package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
  187. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  188. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  189. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  190. package/build/dist/UI/Components/Button/Button.js +10 -16
  191. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
  193. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  194. package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
  195. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  196. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
  197. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
  198. package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
  199. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  200. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
  201. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  202. package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
  203. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  204. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  205. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  206. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
  207. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  208. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
  209. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  210. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
  211. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
  212. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  213. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  214. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
  215. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
  216. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
  217. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  218. package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
  219. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  220. package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
  221. package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
  222. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
  223. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
  224. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
  225. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
  226. package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
  227. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  228. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
  229. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  230. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
  231. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
  232. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
  233. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
  234. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
  235. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
  236. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
  237. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
  238. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
  239. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
  240. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
  241. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
  242. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
  243. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
  244. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
  245. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
  246. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
  247. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
  248. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
  249. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
  250. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
  251. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
  252. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
  253. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
  254. package/build/dist/Utils/API.js +175 -34
  255. package/build/dist/Utils/API.js.map +1 -1
  256. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
  257. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
  258. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
  259. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  260. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
  261. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
  262. package/package.json +1 -1
@@ -519,6 +519,24 @@ type SummaryJsonFunction = (
519
519
  isFallbackEnabled?: boolean | undefined,
520
520
  ) => JSONObject;
521
521
 
522
+ /*
523
+ * The login addresses the fixture responders sign in with, read off the
524
+ * fixtures rather than retyped so a renamed responder cannot quietly drop out
525
+ * of the masking guard's exemption list and start passing for the wrong
526
+ * reason. These are NOT notification-method identifiers: those all live in
527
+ * ALL_RAW_IDENTIFIERS and must never render.
528
+ */
529
+ const ALL_LOGIN_EMAILS: Array<string> = [
530
+ READY_USER,
531
+ PARTIAL_USER,
532
+ UNREACHABLE_USER,
533
+ TWO_TEAM_USER,
534
+ CHANNELS_OFF_USER,
535
+ DISTINCT_KIND_USER,
536
+ ].map((user: JSONObject) => {
537
+ return user["userEmail"] as string;
538
+ });
539
+
522
540
  /*
523
541
  * `isFallbackEnabled` defaults to true because that is the product default
524
542
  * (disableOnCallNotificationFallback is false unless an admin sets it), and every
@@ -3130,11 +3148,18 @@ describe("On-call readiness page", () => {
3130
3148
  test("the options are the teams that actually page somebody", async () => {
3131
3149
  await openTeamFilter();
3132
3150
 
3133
- const options: Array<string> = (await screen.findAllByRole("option")).map(
3134
- (option: HTMLElement): string => {
3151
+ /*
3152
+ * Scoped to the open menu rather than the document: the table's
3153
+ * pagination bar carries a page-size select, whose native options are
3154
+ * `option`s too and are there whether a menu is open or not.
3155
+ */
3156
+ const menu: HTMLElement = await screen.findByRole("listbox");
3157
+
3158
+ const options: Array<string> = within(menu)
3159
+ .getAllByRole("option")
3160
+ .map((option: HTMLElement): string => {
3135
3161
  return option.textContent || "";
3136
- },
3137
- );
3162
+ });
3138
3163
 
3139
3164
  expect(options).toEqual(["Payments", "Platform"]);
3140
3165
  });
@@ -3527,11 +3552,61 @@ describe("no unmasked identifier reaches the DOM", () => {
3527
3552
  /*
3528
3553
  * The scan above catches the planted values by name; these two catch the
3529
3554
  * shape, so an identifier this file never thought of is caught too.
3555
+ *
3556
+ * Two things come out of the text before the shape is scanned.
3557
+ *
3558
+ * The pagination bar, because `textContent` joins siblings with no
3559
+ * separator: its page-size options ("10", "20", "25", "50", "100") fuse
3560
+ * into "10202550100", a digit run long enough to read as a phone number.
3561
+ *
3562
+ * Login addresses, because a responder row shows the person's login
3563
+ * email under their name, and that address is legitimate here for the
3564
+ * same reason the mail draft below may carry it: an admin already reads
3565
+ * it on Settings > Users and on the team page.
3566
+ *
3567
+ * What must never appear is a notification-method identifier, and
3568
+ * neither exclusion hides one: substituting a method identifier for one
3569
+ * of these login addresses still fails the shape scan, and one rendered
3570
+ * inside the pagination bar still fails the by-name scan above, which
3571
+ * reads the whole container.
3530
3572
  */
3531
- expect(container.textContent || "").not.toMatch(UNMASKED_EMAIL_PATTERN);
3532
- expect(container.textContent || "").not.toMatch(UNMASKED_PHONE_PATTERN);
3573
+ const withoutPagination: HTMLElement = container.cloneNode(
3574
+ true,
3575
+ ) as HTMLElement;
3576
+
3577
+ withoutPagination
3578
+ .querySelectorAll('nav[aria-label^="Pagination"]')
3579
+ .forEach((paginationBar: Element): void => {
3580
+ paginationBar.remove();
3581
+ });
3582
+
3583
+ let text: string = withoutPagination.textContent || "";
3584
+
3585
+ for (const loginEmail of ALL_LOGIN_EMAILS) {
3586
+ text = text.split(loginEmail).join("");
3587
+ }
3588
+
3589
+ expect(text).not.toMatch(UNMASKED_EMAIL_PATTERN);
3590
+ expect(text).not.toMatch(UNMASKED_PHONE_PATTERN);
3533
3591
  };
3534
3592
 
3593
+ /*
3594
+ * The other half of the exemption above: the login email is not merely
3595
+ * tolerated on these surfaces, it is meant to be there, so that two
3596
+ * responders who share a name are still tellable apart. If this stops
3597
+ * holding, the exemption is protecting nothing and should go.
3598
+ */
3599
+ test("the responder row names the person by their login email", async () => {
3600
+ respondWith(summaryJson(ALL_THREE_USERS));
3601
+
3602
+ const container: HTMLElement = await renderPolicyCard();
3603
+
3604
+ await screen.findByText("Zed Unreachable");
3605
+
3606
+ expect(container.textContent).toContain("zed.unreachable@example.com");
3607
+ expect(container.textContent).toContain("jane.partial@example.com");
3608
+ });
3609
+
3535
3610
  test("the policy card renders masked identifiers only", async () => {
3536
3611
  respondWith(summaryJson(ALL_THREE_USERS));
3537
3612
 
@@ -0,0 +1,543 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen, waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import * as React from "react";
5
+ import { beforeEach, describe, expect, it } from "@jest/globals";
6
+ import { PlanType } from "../../../Types/Billing/SubscriptionPlan";
7
+ import MonitorType from "../../../Types/Monitor/MonitorType";
8
+ import Permission, { UserPermission } from "../../../Types/Permission";
9
+ import Monitor from "../../../Models/DatabaseModels/Monitor";
10
+ import TelemetryIngestionKey from "../../../Models/DatabaseModels/TelemetryIngestionKey";
11
+ import FormFieldSchemaType from "../../../UI/Components/Forms/Types/FormFieldSchemaType";
12
+ import ProjectUtil from "../../../UI/Utils/Project";
13
+ import getJestMockFunction, { MockFunction } from "../../MockType";
14
+ import { getJestSpyOn } from "../../Spy";
15
+
16
+ /*
17
+ * The consent checkbox is the whole point of the change: on the Free plan a
18
+ * user must not be able to start a metered charge without saying they
19
+ * understand it. This suite drives the real ModelForm rather than the field
20
+ * definitions, because the mechanism is subtle - the form's own `required`
21
+ * check stringifies values, so an unticked box reads as the non-empty string
22
+ * "false" and sails straight through it. The gate is a customValidation, and
23
+ * it only runs at all because getDefaultValue seeds the key.
24
+ */
25
+
26
+ const createOrUpdateMock: MockFunction = getJestMockFunction();
27
+ const getItemMock: MockFunction = getJestMockFunction();
28
+
29
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
30
+ return {
31
+ __esModule: true,
32
+ default: {
33
+ getItem: (...args: Array<any>) => {
34
+ return getItemMock(...args);
35
+ },
36
+ createOrUpdate: (...args: Array<any>) => {
37
+ return createOrUpdateMock(...args);
38
+ },
39
+ },
40
+ };
41
+ });
42
+
43
+ const OWNER_PERMISSIONS: Array<Permission> = [
44
+ Permission.Public,
45
+ Permission.User,
46
+ Permission.CurrentUser,
47
+ Permission.ProjectOwner,
48
+ ];
49
+
50
+ jest.mock("../../../UI/Utils/Permission", () => {
51
+ return {
52
+ __esModule: true,
53
+ default: {
54
+ getAllPermissions: () => {
55
+ return OWNER_PERMISSIONS;
56
+ },
57
+ getProjectPermissions: () => {
58
+ return {
59
+ permissions: OWNER_PERMISSIONS.map((permission: Permission) => {
60
+ return {
61
+ permission,
62
+ labelIds: [],
63
+ _type: "UserPermission",
64
+ } as UserPermission;
65
+ }),
66
+ };
67
+ },
68
+ getGlobalPermissions: () => {
69
+ return { globalPermissions: OWNER_PERMISSIONS };
70
+ },
71
+ },
72
+ };
73
+ });
74
+
75
+ jest.mock("../../../UI/Utils/User", () => {
76
+ return {
77
+ __esModule: true,
78
+ default: {
79
+ isMasterAdmin: () => {
80
+ return false;
81
+ },
82
+ },
83
+ };
84
+ });
85
+
86
+ interface MutableConfig {
87
+ billingEnabled: boolean;
88
+ }
89
+
90
+ const config: MutableConfig = { billingEnabled: true };
91
+
92
+ (
93
+ globalThis as unknown as { __payAsYouGoGateConfig: MutableConfig }
94
+ ).__payAsYouGoGateConfig = config;
95
+
96
+ jest.mock("../../../UI/Config", () => {
97
+ const mocked: Record<string, unknown> = {
98
+ ...jest.requireActual("../../../UI/Config"),
99
+ };
100
+
101
+ Object.defineProperty(mocked, "BILLING_ENABLED", {
102
+ get: (): boolean => {
103
+ return Boolean(
104
+ (
105
+ globalThis as unknown as {
106
+ __payAsYouGoGateConfig: MutableConfig | undefined;
107
+ }
108
+ ).__payAsYouGoGateConfig?.billingEnabled,
109
+ );
110
+ },
111
+ });
112
+
113
+ return mocked;
114
+ });
115
+
116
+ // Imported after the mocks above.
117
+ import ModelForm, { FormType } from "../../../UI/Components/Forms/ModelForm";
118
+ import {
119
+ MONITOR_CONSENT_ERROR,
120
+ MONITOR_CONSENT_FIELD_KEY,
121
+ TELEMETRY_CONSENT_ERROR,
122
+ TELEMETRY_CONSENT_FIELD_KEY,
123
+ getMonitorPayAsYouGoFormFields,
124
+ getTelemetryPayAsYouGoFormFields,
125
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/Billing/PayAsYouGo";
126
+
127
+ /*
128
+ * These render real forms that validate on every keystroke, so give the waits
129
+ * room to survive a loaded CI box.
130
+ */
131
+ const WAIT_TIMEOUT: number = 20000;
132
+
133
+ type RenderIngestionKeyFormFunction = () => void;
134
+
135
+ const renderIngestionKeyForm: RenderIngestionKeyFormFunction = (): void => {
136
+ render(
137
+ <ModelForm<TelemetryIngestionKey>
138
+ modelType={TelemetryIngestionKey}
139
+ id="create-ingestion-key-form"
140
+ name="Create Ingestion Key"
141
+ formType={FormType.Create}
142
+ submitButtonText="Create Ingestion Key"
143
+ fields={[
144
+ ...getTelemetryPayAsYouGoFormFields(),
145
+ {
146
+ field: { name: true },
147
+ title: "Name",
148
+ fieldType: FormFieldSchemaType.Text,
149
+ required: true,
150
+ dataTestId: "ingestion-key-name",
151
+ },
152
+ ]}
153
+ />,
154
+ );
155
+ };
156
+
157
+ type RenderMonitorFormFunction = (monitorType: MonitorType) => void;
158
+
159
+ const renderMonitorForm: RenderMonitorFormFunction = (
160
+ monitorType: MonitorType,
161
+ ): void => {
162
+ render(
163
+ <ModelForm<Monitor>
164
+ modelType={Monitor}
165
+ id="create-monitor-form"
166
+ name="Create New Monitor"
167
+ formType={FormType.Create}
168
+ submitButtonText="Create Monitor"
169
+ initialValues={{ monitorType: monitorType } as any}
170
+ steps={[{ title: "Monitor Info", id: "monitor-info" }]}
171
+ fields={[
172
+ {
173
+ field: { name: true },
174
+ stepId: "monitor-info",
175
+ title: "Name",
176
+ fieldType: FormFieldSchemaType.Text,
177
+ required: true,
178
+ dataTestId: "monitor-name",
179
+ },
180
+ ...getMonitorPayAsYouGoFormFields({ stepId: "monitor-info" }),
181
+ ]}
182
+ />,
183
+ );
184
+ };
185
+
186
+ describe("Pay as you go consent gate", () => {
187
+ beforeEach(() => {
188
+ jest.restoreAllMocks();
189
+ config.billingEnabled = true;
190
+ createOrUpdateMock.mockReset();
191
+ getItemMock.mockReset();
192
+ createOrUpdateMock.mockResolvedValue({ data: {} });
193
+ getJestSpyOn(ProjectUtil, "getCurrentPlan").mockReturnValue(PlanType.Free);
194
+ });
195
+
196
+ describe("creating a telemetry ingestion key on the Free plan", () => {
197
+ it("shows the pay as you go notice and the consent box in the form", async () => {
198
+ renderIngestionKeyForm();
199
+
200
+ expect(
201
+ await screen.findByText(
202
+ "Telemetry is a pay as you go feature",
203
+ {},
204
+ { timeout: WAIT_TIMEOUT },
205
+ ),
206
+ ).toBeInTheDocument();
207
+ expect(
208
+ screen.getByTestId("telemetry-pay-as-you-go-consent"),
209
+ ).toBeInTheDocument();
210
+ });
211
+
212
+ it("refuses to create the key until the charge is acknowledged", async () => {
213
+ renderIngestionKeyForm();
214
+
215
+ await userEvent.type(
216
+ await screen.findByTestId(
217
+ "ingestion-key-name",
218
+ {},
219
+ { timeout: WAIT_TIMEOUT },
220
+ ),
221
+ "Production",
222
+ );
223
+
224
+ await userEvent.click(screen.getByText("Create Ingestion Key"));
225
+
226
+ await waitFor(
227
+ () => {
228
+ expect(screen.getByText(TELEMETRY_CONSENT_ERROR)).toBeInTheDocument();
229
+ },
230
+ { timeout: WAIT_TIMEOUT },
231
+ );
232
+
233
+ expect(createOrUpdateMock).not.toHaveBeenCalled();
234
+ });
235
+
236
+ it("creates the key once the box is ticked", async () => {
237
+ renderIngestionKeyForm();
238
+
239
+ await userEvent.type(
240
+ await screen.findByTestId(
241
+ "ingestion-key-name",
242
+ {},
243
+ { timeout: WAIT_TIMEOUT },
244
+ ),
245
+ "Production",
246
+ );
247
+ await userEvent.click(
248
+ screen.getByTestId("telemetry-pay-as-you-go-consent"),
249
+ );
250
+ await userEvent.click(screen.getByText("Create Ingestion Key"));
251
+
252
+ await waitFor(
253
+ () => {
254
+ expect(createOrUpdateMock).toHaveBeenCalled();
255
+ },
256
+ { timeout: WAIT_TIMEOUT },
257
+ );
258
+ });
259
+
260
+ it("blocks again if the box is ticked and then unticked", async () => {
261
+ /*
262
+ * The regression this guards: an unticked box that has been touched
263
+ * holds boolean false, which the form's `required` check would accept.
264
+ */
265
+ renderIngestionKeyForm();
266
+
267
+ await userEvent.type(
268
+ await screen.findByTestId(
269
+ "ingestion-key-name",
270
+ {},
271
+ { timeout: WAIT_TIMEOUT },
272
+ ),
273
+ "Production",
274
+ );
275
+
276
+ const consent: HTMLElement = screen.getByTestId(
277
+ "telemetry-pay-as-you-go-consent",
278
+ );
279
+
280
+ await userEvent.click(consent);
281
+ await userEvent.click(consent);
282
+ await userEvent.click(screen.getByText("Create Ingestion Key"));
283
+
284
+ await waitFor(
285
+ () => {
286
+ expect(screen.getByText(TELEMETRY_CONSENT_ERROR)).toBeInTheDocument();
287
+ },
288
+ { timeout: WAIT_TIMEOUT },
289
+ );
290
+
291
+ expect(createOrUpdateMock).not.toHaveBeenCalled();
292
+ });
293
+
294
+ it("does not send the acknowledgement to the API", async () => {
295
+ renderIngestionKeyForm();
296
+
297
+ await userEvent.type(
298
+ await screen.findByTestId(
299
+ "ingestion-key-name",
300
+ {},
301
+ { timeout: WAIT_TIMEOUT },
302
+ ),
303
+ "Production",
304
+ );
305
+ await userEvent.click(
306
+ screen.getByTestId("telemetry-pay-as-you-go-consent"),
307
+ );
308
+ await userEvent.click(screen.getByText("Create Ingestion Key"));
309
+
310
+ await waitFor(
311
+ () => {
312
+ expect(createOrUpdateMock).toHaveBeenCalled();
313
+ },
314
+ { timeout: WAIT_TIMEOUT },
315
+ );
316
+
317
+ const call: any = createOrUpdateMock.mock.calls[0]?.[0];
318
+
319
+ /*
320
+ * The consent is a UI gate, not data. It has no column, so sending it
321
+ * would be an unknown property on the create payload.
322
+ */
323
+ expect(call?.miscDataProps ?? {}).not.toHaveProperty(
324
+ TELEMETRY_CONSENT_FIELD_KEY,
325
+ );
326
+ expect(JSON.stringify(call?.model ?? {})).not.toContain(
327
+ TELEMETRY_CONSENT_FIELD_KEY,
328
+ );
329
+ });
330
+ });
331
+
332
+ describe("creating a telemetry ingestion key off the Free plan", () => {
333
+ it("asks for nothing extra on a paid plan", async () => {
334
+ getJestSpyOn(ProjectUtil, "getCurrentPlan").mockReturnValue(
335
+ PlanType.Growth,
336
+ );
337
+
338
+ renderIngestionKeyForm();
339
+
340
+ await userEvent.type(
341
+ await screen.findByTestId(
342
+ "ingestion-key-name",
343
+ {},
344
+ { timeout: WAIT_TIMEOUT },
345
+ ),
346
+ "Production",
347
+ );
348
+
349
+ expect(
350
+ screen.queryByTestId("telemetry-pay-as-you-go-consent"),
351
+ ).not.toBeInTheDocument();
352
+ expect(
353
+ screen.queryByText("Telemetry is a pay as you go feature"),
354
+ ).not.toBeInTheDocument();
355
+
356
+ await userEvent.click(screen.getByText("Create Ingestion Key"));
357
+
358
+ await waitFor(
359
+ () => {
360
+ expect(createOrUpdateMock).toHaveBeenCalled();
361
+ },
362
+ { timeout: WAIT_TIMEOUT },
363
+ );
364
+ });
365
+
366
+ it("asks for nothing extra on a self-hosted install", async () => {
367
+ config.billingEnabled = false;
368
+
369
+ renderIngestionKeyForm();
370
+
371
+ await userEvent.type(
372
+ await screen.findByTestId(
373
+ "ingestion-key-name",
374
+ {},
375
+ { timeout: WAIT_TIMEOUT },
376
+ ),
377
+ "Production",
378
+ );
379
+
380
+ expect(
381
+ screen.queryByTestId("telemetry-pay-as-you-go-consent"),
382
+ ).not.toBeInTheDocument();
383
+
384
+ await userEvent.click(screen.getByText("Create Ingestion Key"));
385
+
386
+ await waitFor(
387
+ () => {
388
+ expect(createOrUpdateMock).toHaveBeenCalled();
389
+ },
390
+ { timeout: WAIT_TIMEOUT },
391
+ );
392
+ });
393
+ });
394
+
395
+ describe("creating a monitor on the Free plan", () => {
396
+ it("refuses to create a billed monitor until the charge is acknowledged", async () => {
397
+ renderMonitorForm(MonitorType.Website);
398
+
399
+ await userEvent.type(
400
+ await screen.findByTestId(
401
+ "monitor-name",
402
+ {},
403
+ { timeout: WAIT_TIMEOUT },
404
+ ),
405
+ "Marketing site",
406
+ );
407
+
408
+ expect(
409
+ screen.getByTestId("monitor-pay-as-you-go-consent"),
410
+ ).toBeInTheDocument();
411
+
412
+ await userEvent.click(screen.getByText("Create Monitor"));
413
+
414
+ await waitFor(
415
+ () => {
416
+ expect(screen.getByText(MONITOR_CONSENT_ERROR)).toBeInTheDocument();
417
+ },
418
+ { timeout: WAIT_TIMEOUT },
419
+ );
420
+
421
+ expect(createOrUpdateMock).not.toHaveBeenCalled();
422
+ });
423
+
424
+ it("creates the billed monitor once the box is ticked", async () => {
425
+ renderMonitorForm(MonitorType.Website);
426
+
427
+ await userEvent.type(
428
+ await screen.findByTestId(
429
+ "monitor-name",
430
+ {},
431
+ { timeout: WAIT_TIMEOUT },
432
+ ),
433
+ "Marketing site",
434
+ );
435
+ await userEvent.click(
436
+ screen.getByTestId("monitor-pay-as-you-go-consent"),
437
+ );
438
+ await userEvent.click(screen.getByText("Create Monitor"));
439
+
440
+ await waitFor(
441
+ () => {
442
+ expect(createOrUpdateMock).toHaveBeenCalled();
443
+ },
444
+ { timeout: WAIT_TIMEOUT },
445
+ );
446
+
447
+ const call: any = createOrUpdateMock.mock.calls[0]?.[0];
448
+
449
+ expect(call?.miscDataProps ?? {}).not.toHaveProperty(
450
+ MONITOR_CONSENT_FIELD_KEY,
451
+ );
452
+ });
453
+
454
+ it("asks nothing of a Manual monitor - those are free", async () => {
455
+ renderMonitorForm(MonitorType.Manual);
456
+
457
+ await userEvent.type(
458
+ await screen.findByTestId(
459
+ "monitor-name",
460
+ {},
461
+ { timeout: WAIT_TIMEOUT },
462
+ ),
463
+ "Runbook step",
464
+ );
465
+
466
+ expect(
467
+ screen.queryByTestId("monitor-pay-as-you-go-consent"),
468
+ ).not.toBeInTheDocument();
469
+
470
+ await userEvent.click(screen.getByText("Create Monitor"));
471
+
472
+ await waitFor(
473
+ () => {
474
+ expect(createOrUpdateMock).toHaveBeenCalled();
475
+ },
476
+ { timeout: WAIT_TIMEOUT },
477
+ );
478
+ });
479
+
480
+ it.each([
481
+ MonitorType.API,
482
+ MonitorType.Logs,
483
+ MonitorType.IncomingRequest,
484
+ MonitorType.SSLCertificate,
485
+ ])(
486
+ "blocks %s too - every non-Manual type is billed",
487
+ async (monitorType: MonitorType) => {
488
+ renderMonitorForm(monitorType);
489
+
490
+ await userEvent.type(
491
+ await screen.findByTestId(
492
+ "monitor-name",
493
+ {},
494
+ { timeout: WAIT_TIMEOUT },
495
+ ),
496
+ "Something",
497
+ );
498
+ await userEvent.click(screen.getByText("Create Monitor"));
499
+
500
+ await waitFor(
501
+ () => {
502
+ expect(screen.getByText(MONITOR_CONSENT_ERROR)).toBeInTheDocument();
503
+ },
504
+ { timeout: WAIT_TIMEOUT },
505
+ );
506
+
507
+ expect(createOrUpdateMock).not.toHaveBeenCalled();
508
+ },
509
+ );
510
+ });
511
+
512
+ describe("creating a monitor off the Free plan", () => {
513
+ it("asks for nothing extra on a paid plan", async () => {
514
+ getJestSpyOn(ProjectUtil, "getCurrentPlan").mockReturnValue(
515
+ PlanType.Growth,
516
+ );
517
+
518
+ renderMonitorForm(MonitorType.Website);
519
+
520
+ await userEvent.type(
521
+ await screen.findByTestId(
522
+ "monitor-name",
523
+ {},
524
+ { timeout: WAIT_TIMEOUT },
525
+ ),
526
+ "Marketing site",
527
+ );
528
+
529
+ expect(
530
+ screen.queryByTestId("monitor-pay-as-you-go-consent"),
531
+ ).not.toBeInTheDocument();
532
+
533
+ await userEvent.click(screen.getByText("Create Monitor"));
534
+
535
+ await waitFor(
536
+ () => {
537
+ expect(createOrUpdateMock).toHaveBeenCalled();
538
+ },
539
+ { timeout: WAIT_TIMEOUT },
540
+ );
541
+ });
542
+ });
543
+ });