@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
@@ -0,0 +1,412 @@
1
+ import RunnerJobService from "../../../Server/Services/RunnerJobService";
2
+ import RunnerJob from "../../../Models/DatabaseModels/RunnerJob";
3
+ import RunbookStepType, {
4
+ RUNNER_EXECUTED_STEP_TYPES,
5
+ isPayloadCarryingStepType,
6
+ } from "../../../Types/Runbook/RunbookStepType";
7
+ import RunnerJobOrigin from "../../../Types/Runbook/RunnerJobOrigin";
8
+ import RunnerJobStatus from "../../../Types/Runbook/RunnerJobStatus";
9
+ import BadDataException from "../../../Types/Exception/BadDataException";
10
+ import ObjectID from "../../../Types/ObjectID";
11
+ import logger from "../../../Server/Utils/Logger";
12
+ import { JSONObject } from "../../../Types/JSON";
13
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
14
+
15
+ /*
16
+ * ---------------------------------------------------------------------------
17
+ * Contract under test — RunnerJobService.enqueue, and the RunnerJob column
18
+ * declarations it has to satisfy.
19
+ *
20
+ * Background (issue #3209): RunnerJob.script was declared `required: true`.
21
+ * DatabaseService.checkRequiredFields rejects any falsy value on a required
22
+ * column, and an empty string is falsy — so every SSH and Kubernetes job,
23
+ * which carries its instruction in `payload` and an empty script BY DESIGN,
24
+ * died at create() with "script is required". Two whole step types were
25
+ * unusable, and nothing caught it because every test stubbed enqueue.
26
+ *
27
+ * Two things are pinned here, and they are deliberately different in kind:
28
+ *
29
+ * 1. the column declaration itself — `script` must not be a required
30
+ * column. This is the direct tripwire on the change that caused the bug.
31
+ *
32
+ * 2. the rule that `required: true` was standing in for, now enforced
33
+ * per-type where it can actually be expressed: a script-carrying type
34
+ * must bring a script, a payload-carrying type must bring a payload, and
35
+ * the row that comes out passes the real validator either way.
36
+ *
37
+ * No database: create is stubbed at the boundary, and everything above it is
38
+ * the real implementation.
39
+ * ---------------------------------------------------------------------------
40
+ */
41
+
42
+ const PROJECT_ID: ObjectID = new ObjectID(
43
+ "22222222-2222-4222-8222-222222222222",
44
+ );
45
+ const EXECUTION_ID: ObjectID = new ObjectID(
46
+ "33333333-3333-4333-8333-333333333333",
47
+ );
48
+ const AGENT_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
49
+
50
+ const SSH_PAYLOAD: JSONObject = {
51
+ credentialId: "55555555-5555-4555-8555-555555555555",
52
+ command: "systemctl restart nginx",
53
+ };
54
+
55
+ /*
56
+ * The production validator, bound off the live service rather than
57
+ * reimplemented — the point is to fail if the model's declarations change,
58
+ * not if a copy of the rule drifts.
59
+ */
60
+ type CreatePathInternals = {
61
+ generateDefaultValues: (model: RunnerJob) => RunnerJob;
62
+ checkRequiredFields: (model: RunnerJob) => RunnerJob;
63
+ };
64
+
65
+ function runRealCreateValidation(row: RunnerJob): RunnerJob {
66
+ const internals: CreatePathInternals =
67
+ RunnerJobService as unknown as CreatePathInternals;
68
+
69
+ return internals.checkRequiredFields.call(
70
+ RunnerJobService,
71
+ internals.generateDefaultValues.call(RunnerJobService, row),
72
+ );
73
+ }
74
+
75
+ type EnqueueArgs = Parameters<typeof RunnerJobService.enqueue>[0];
76
+
77
+ function enqueueArgs(overrides: Partial<EnqueueArgs> = {}): EnqueueArgs {
78
+ return {
79
+ projectId: PROJECT_ID,
80
+ runbookExecutionId: EXECUTION_ID,
81
+ stepId: "step-1",
82
+ stepType: RunbookStepType.Bash,
83
+ targetAgentId: AGENT_ID,
84
+ script: "uptime",
85
+ timeoutInMs: 30_000,
86
+ ...overrides,
87
+ } as EnqueueArgs;
88
+ }
89
+
90
+ /*
91
+ * Arguments that are valid for the given step type — a script for the script
92
+ * types, a payload and an empty script for the payload types. Used by the
93
+ * table-driven tests so every runner-executed type is exercised through the
94
+ * same assertions.
95
+ */
96
+ function validArgsFor(stepType: RunbookStepType): EnqueueArgs {
97
+ if (isPayloadCarryingStepType(stepType)) {
98
+ return enqueueArgs({ stepType, script: "", payload: SSH_PAYLOAD });
99
+ }
100
+ return enqueueArgs({ stepType, script: "echo hello" });
101
+ }
102
+
103
+ let createSpy: jest.SpyInstance;
104
+
105
+ function createdRow(): RunnerJob {
106
+ expect(createSpy).toHaveBeenCalledTimes(1);
107
+ return (createSpy.mock.calls[0]![0] as { data: RunnerJob }).data;
108
+ }
109
+
110
+ beforeEach(() => {
111
+ /*
112
+ * enqueue is wrapped in @CaptureSpan, which records a thrown exception at
113
+ * error level. The rejection tests expect those throws, so keep the output
114
+ * readable.
115
+ */
116
+ jest.spyOn(logger, "error").mockImplementation((): void => {
117
+ return undefined;
118
+ });
119
+
120
+ createSpy = jest
121
+ .spyOn(RunnerJobService, "create")
122
+ .mockImplementation((args: { data: RunnerJob }): Promise<RunnerJob> => {
123
+ const row: RunnerJob = args.data;
124
+ row._id = "66666666-6666-4666-8666-666666666666";
125
+ return Promise.resolve(row);
126
+ });
127
+ });
128
+
129
+ afterEach(() => {
130
+ jest.restoreAllMocks();
131
+ });
132
+
133
+ describe("RunnerJob column declarations", () => {
134
+ test("script is NOT a required column — the declaration that caused #3209", () => {
135
+ /*
136
+ * The single assertion that would have caught the bug. Flipping this back
137
+ * to `required: true` makes every SSH and Kubernetes job unpersistable,
138
+ * because their script is legitimately an empty string.
139
+ */
140
+ const requiredColumns: Array<string> = new RunnerJob().getRequiredColumns()
141
+ .columns;
142
+
143
+ expect(requiredColumns).not.toContain("script");
144
+ });
145
+
146
+ test("the columns that genuinely cannot be empty are still required", () => {
147
+ /*
148
+ * The fix must not have relaxed anything else. Each of these is written by
149
+ * enqueue on every job and has no meaningful empty value, so a missing one
150
+ * should still be refused before it reaches Postgres.
151
+ */
152
+ const requiredColumns: Array<string> = new RunnerJob().getRequiredColumns()
153
+ .columns;
154
+
155
+ for (const column of [
156
+ "projectId",
157
+ "origin",
158
+ "stepId",
159
+ "stepType",
160
+ "status",
161
+ "timeoutInMs",
162
+ "claimDeadlineAt",
163
+ ]) {
164
+ expect(requiredColumns).toContain(column);
165
+ }
166
+ });
167
+
168
+ test("payload is optional, since script-carrying jobs have none", () => {
169
+ expect(new RunnerJob().getRequiredColumns().columns).not.toContain(
170
+ "payload",
171
+ );
172
+ });
173
+
174
+ test("an empty script is rejected by the required-column check, which is why script is not required", () => {
175
+ /*
176
+ * Documents the mechanism rather than assuming it: checkRequiredFields
177
+ * tests truthiness, so "" fails exactly like undefined. Any future column
178
+ * that can legitimately hold an empty string has the same constraint.
179
+ */
180
+ const row: RunnerJob = new RunnerJob();
181
+ row.projectId = PROJECT_ID;
182
+ row.origin = RunnerJobOrigin.Runbook;
183
+ row.stepId = "step-1";
184
+ row.stepType = RunbookStepType.SSH;
185
+ row.status = RunnerJobStatus.Pending;
186
+ row.timeoutInMs = 30_000;
187
+ row.claimDeadlineAt = new Date();
188
+ row.script = "";
189
+ row.payload = SSH_PAYLOAD;
190
+
191
+ // Passes only because `script` is not in the required set.
192
+ expect(() => {
193
+ return runRealCreateValidation(row);
194
+ }).not.toThrow();
195
+
196
+ const stillRequired: RunnerJob = Object.assign(new RunnerJob(), row);
197
+ stillRequired.stepId = "";
198
+
199
+ // Same falsy value on a column that IS required — still refused.
200
+ expect(() => {
201
+ return runRealCreateValidation(stillRequired);
202
+ }).toThrow(/stepId is required/);
203
+ });
204
+ });
205
+
206
+ describe("RunnerJobService.enqueue — what may be dispatched", () => {
207
+ test.each([
208
+ RunbookStepType.Manual,
209
+ RunbookStepType.HttpRequest,
210
+ RunbookStepType.AI,
211
+ ])(
212
+ "refuses %s, which no Runner can execute",
213
+ async (type: RunbookStepType) => {
214
+ await expect(
215
+ RunnerJobService.enqueue(enqueueArgs({ stepType: type })),
216
+ ).rejects.toThrow(BadDataException);
217
+
218
+ expect(createSpy).not.toHaveBeenCalled();
219
+ },
220
+ );
221
+
222
+ test("refuses a job with no target Runner", async () => {
223
+ await expect(
224
+ RunnerJobService.enqueue(
225
+ enqueueArgs({ targetAgentId: undefined as unknown as ObjectID }),
226
+ ),
227
+ ).rejects.toThrow(/targetAgentId is required/);
228
+
229
+ expect(createSpy).not.toHaveBeenCalled();
230
+ });
231
+
232
+ test.each([RunbookStepType.SSH, RunbookStepType.Kubernetes])(
233
+ "refuses a %s job that arrives without structured instructions",
234
+ async (type: RunbookStepType) => {
235
+ /*
236
+ * The invariant that replaced the required column. A payload-carrying
237
+ * job with neither script nor payload would reach a Runner with nothing
238
+ * to do; the Runner's own guard would fail it, but only after a claim,
239
+ * a lease and a round trip. Refuse it at the source, with a message that
240
+ * names what is missing.
241
+ */
242
+ await expect(
243
+ RunnerJobService.enqueue(enqueueArgs({ stepType: type, script: "" })),
244
+ ).rejects.toThrow(/structured instructions/);
245
+
246
+ expect(createSpy).not.toHaveBeenCalled();
247
+ },
248
+ );
249
+
250
+ test.each([RunbookStepType.Bash, RunbookStepType.JavaScript])(
251
+ "refuses a %s job with an empty script",
252
+ async (type: RunbookStepType) => {
253
+ /*
254
+ * The mirror image. The step executors already short-circuit an empty
255
+ * script as a no-op before dispatching, so this is a backstop for any
256
+ * other caller — and it is the check that used to be (accidentally)
257
+ * provided by the required column.
258
+ */
259
+ await expect(
260
+ RunnerJobService.enqueue(enqueueArgs({ stepType: type, script: "" })),
261
+ ).rejects.toThrow(/needs a script/);
262
+
263
+ expect(createSpy).not.toHaveBeenCalled();
264
+ },
265
+ );
266
+
267
+ test("a payload alone does not satisfy a script-carrying step type", async () => {
268
+ await expect(
269
+ RunnerJobService.enqueue(
270
+ enqueueArgs({
271
+ stepType: RunbookStepType.Bash,
272
+ script: "",
273
+ payload: SSH_PAYLOAD,
274
+ }),
275
+ ),
276
+ ).rejects.toThrow(/needs a script/);
277
+
278
+ expect(createSpy).not.toHaveBeenCalled();
279
+ });
280
+ });
281
+
282
+ describe("RunnerJobService.enqueue — the row that is written", () => {
283
+ test.each(RUNNER_EXECUTED_STEP_TYPES)(
284
+ "a %s job passes the model's real required-field validation",
285
+ async (type: RunbookStepType) => {
286
+ /*
287
+ * The regression test for #3209, run across every type a Runner can be
288
+ * handed. With `script` required again, the two payload-carrying types
289
+ * fail here exactly as they failed in production.
290
+ */
291
+ await RunnerJobService.enqueue(validArgsFor(type));
292
+
293
+ const row: RunnerJob = createdRow();
294
+
295
+ expect(() => {
296
+ return runRealCreateValidation(row);
297
+ }).not.toThrow();
298
+ },
299
+ );
300
+
301
+ test.each([RunbookStepType.SSH, RunbookStepType.Kubernetes])(
302
+ "a %s job is written with an empty script and its payload",
303
+ async (type: RunbookStepType) => {
304
+ await RunnerJobService.enqueue(validArgsFor(type));
305
+
306
+ const row: RunnerJob = createdRow();
307
+
308
+ expect(row.script).toBe("");
309
+ expect(row.payload).toEqual(SSH_PAYLOAD);
310
+ expect(row.stepType).toBe(type);
311
+ },
312
+ );
313
+
314
+ test("an undefined script becomes an empty string, never undefined", async () => {
315
+ /*
316
+ * The column is NOT NULL in Postgres. Relaxing the application-level check
317
+ * must not let an undefined value through to the database, where it would
318
+ * surface as a driver error instead of a readable one.
319
+ */
320
+ await RunnerJobService.enqueue(
321
+ enqueueArgs({
322
+ stepType: RunbookStepType.SSH,
323
+ script: undefined as unknown as string,
324
+ payload: SSH_PAYLOAD,
325
+ }),
326
+ );
327
+
328
+ const row: RunnerJob = createdRow();
329
+
330
+ expect(row.script).toBe("");
331
+ expect(typeof row.script).toBe("string");
332
+ });
333
+
334
+ test("a script-carrying job keeps its script verbatim and gets no payload key", async () => {
335
+ const script: string = "pg_dump -Fc app | gzip > /backup/$(date +%F).gz";
336
+
337
+ await RunnerJobService.enqueue(
338
+ enqueueArgs({ stepType: RunbookStepType.Bash, script }),
339
+ );
340
+
341
+ const row: RunnerJob = createdRow();
342
+
343
+ expect(row.script).toBe(script);
344
+ expect(row.payload).toBeUndefined();
345
+ });
346
+
347
+ test("a runbook job carries Runbook provenance and starts Pending", async () => {
348
+ await RunnerJobService.enqueue(validArgsFor(RunbookStepType.SSH));
349
+
350
+ const row: RunnerJob = createdRow();
351
+
352
+ expect(row.origin).toBe(RunnerJobOrigin.Runbook);
353
+ expect(row.status).toBe(RunnerJobStatus.Pending);
354
+ expect(row.projectId?.toString()).toBe(PROJECT_ID.toString());
355
+ expect(row.runbookExecutionId?.toString()).toBe(EXECUTION_ID.toString());
356
+ expect(row.targetAgentId?.toString()).toBe(AGENT_ID.toString());
357
+ expect(row.stepId).toBe("step-1");
358
+ expect(row.timeoutInMs).toBe(30_000);
359
+ // Never pre-assigned: a job belongs to whoever wins the claim.
360
+ expect(row.assignedAgentId).toBeUndefined();
361
+ });
362
+
363
+ test("the claim deadline honours the configured claim timeout", async () => {
364
+ const before: number = Date.now();
365
+
366
+ await RunnerJobService.enqueue(
367
+ enqueueArgs({
368
+ stepType: RunbookStepType.SSH,
369
+ script: "",
370
+ payload: SSH_PAYLOAD,
371
+ claimTimeoutInMs: 300_000,
372
+ }),
373
+ );
374
+
375
+ const after: number = Date.now();
376
+ const deadlineMs: number = createdRow().claimDeadlineAt!.getTime();
377
+
378
+ expect(deadlineMs).toBeGreaterThanOrEqual(before + 300_000);
379
+ expect(deadlineMs).toBeLessThanOrEqual(after + 300_000);
380
+ });
381
+
382
+ test("an SSH job row never carries credential material", async () => {
383
+ /*
384
+ * The reason the payload exists at all: the row is readable by anyone who
385
+ * can read the execution, so it references a credential by id and the
386
+ * secret is resolved at claim time, scoped to the claiming Runner.
387
+ */
388
+ await RunnerJobService.enqueue(validArgsFor(RunbookStepType.SSH));
389
+
390
+ const serialized: string = JSON.stringify(createdRow());
391
+
392
+ for (const marker of [
393
+ "privateKey",
394
+ "passphrase",
395
+ "password",
396
+ "hostname",
397
+ "username",
398
+ "BEGIN OPENSSH PRIVATE KEY",
399
+ ]) {
400
+ expect(serialized).not.toContain(marker);
401
+ }
402
+ });
403
+
404
+ test("the create is performed as root, since no user owns a dispatched job", async () => {
405
+ await RunnerJobService.enqueue(validArgsFor(RunbookStepType.SSH));
406
+
407
+ const args: { props: Record<string, unknown> } = createSpy.mock
408
+ .calls[0]![0] as { props: Record<string, unknown> };
409
+
410
+ expect(args.props).toEqual(expect.objectContaining({ isRoot: true }));
411
+ });
412
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import {
3
+ SESSION_REPLAY_PRICE_IN_USD_PER_GB,
4
+ TELEMETRY_PRICE_IN_USD_PER_GB,
5
+ TELEMETRY_PRICE_RETENTION_IN_DAYS,
6
+ } from "../../../../Types/Billing/PayAsYouGoPricing";
7
+ import {
8
+ LogDataIngestMeteredPlan,
9
+ MetricsDataIngestMeteredPlan,
10
+ ProfilesDataIngestMeteredPlan,
11
+ SessionReplayDataIngestMeteredPlan,
12
+ TracesDataIngestMetredPlan,
13
+ } from "../../../../Server/Types/Billing/MeteredPlan/AllMeteredPlans";
14
+ import TelemetryMeteredPlan from "../../../../Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan";
15
+
16
+ /*
17
+ * The dashboard now quotes the telemetry rate to Free plan users before they
18
+ * create an ingestion key. A quoted price that does not match the metered one
19
+ * is a promise we break on the invoice, so the two are pinned together here:
20
+ * the metered plans must charge exactly the advertised per-GB rate at the
21
+ * advertised retention.
22
+ */
23
+
24
+ /*
25
+ * Session replay rides on the same ingestion key at its own, much higher rate,
26
+ * so it is listed here with its own expected figure rather than left out - a
27
+ * plan missing from this table is a plan whose price the UI can misquote.
28
+ */
29
+ const TELEMETRY_PLANS: Array<{
30
+ name: string;
31
+ plan: TelemetryMeteredPlan;
32
+ pricePerGB: number;
33
+ }> = [
34
+ {
35
+ name: "logs",
36
+ plan: LogDataIngestMeteredPlan,
37
+ pricePerGB: TELEMETRY_PRICE_IN_USD_PER_GB,
38
+ },
39
+ {
40
+ name: "metrics",
41
+ plan: MetricsDataIngestMeteredPlan,
42
+ pricePerGB: TELEMETRY_PRICE_IN_USD_PER_GB,
43
+ },
44
+ {
45
+ name: "traces",
46
+ plan: TracesDataIngestMetredPlan,
47
+ pricePerGB: TELEMETRY_PRICE_IN_USD_PER_GB,
48
+ },
49
+ {
50
+ name: "profiles",
51
+ plan: ProfilesDataIngestMeteredPlan,
52
+ pricePerGB: TELEMETRY_PRICE_IN_USD_PER_GB,
53
+ },
54
+ {
55
+ name: "session replay",
56
+ plan: SessionReplayDataIngestMeteredPlan,
57
+ pricePerGB: SESSION_REPLAY_PRICE_IN_USD_PER_GB,
58
+ },
59
+ ];
60
+
61
+ describe("Telemetry metered plans charge the advertised pay as you go rate", () => {
62
+ it.each(TELEMETRY_PLANS)(
63
+ "$name: 1 GB retained for the advertised window costs the advertised per-GB price",
64
+ ({
65
+ plan,
66
+ pricePerGB,
67
+ }: {
68
+ plan: TelemetryMeteredPlan;
69
+ pricePerGB: number;
70
+ }) => {
71
+ expect(
72
+ plan.getTotalCostInUSD({
73
+ dataIngestedInGB: 1,
74
+ retentionInDays: TELEMETRY_PRICE_RETENTION_IN_DAYS,
75
+ }),
76
+ ).toBeCloseTo(pricePerGB, 10);
77
+ },
78
+ );
79
+
80
+ it("prices session replay well above the other pillars, as intended", () => {
81
+ /*
82
+ * Not incidental: the gap is why in-app copy cannot quote one rate for
83
+ * "data sent with this ingestion key".
84
+ */
85
+ expect(SESSION_REPLAY_PRICE_IN_USD_PER_GB).toBeGreaterThan(
86
+ TELEMETRY_PRICE_IN_USD_PER_GB,
87
+ );
88
+ expect(SESSION_REPLAY_PRICE_IN_USD_PER_GB).toBe(2);
89
+ });
90
+
91
+ it.each(TELEMETRY_PLANS)(
92
+ "$name: cost scales linearly with volume and retention",
93
+ ({
94
+ plan,
95
+ pricePerGB,
96
+ }: {
97
+ plan: TelemetryMeteredPlan;
98
+ pricePerGB: number;
99
+ }) => {
100
+ expect(
101
+ plan.getTotalCostInUSD({
102
+ dataIngestedInGB: 10,
103
+ retentionInDays: TELEMETRY_PRICE_RETENTION_IN_DAYS,
104
+ }),
105
+ ).toBeCloseTo(pricePerGB * 10, 10);
106
+
107
+ // Twice the advertised retention is twice the advertised price.
108
+ expect(
109
+ plan.getTotalCostInUSD({
110
+ dataIngestedInGB: 1,
111
+ retentionInDays: TELEMETRY_PRICE_RETENTION_IN_DAYS * 2,
112
+ }),
113
+ ).toBeCloseTo(pricePerGB * 2, 10);
114
+ },
115
+ );
116
+
117
+ it("charges nothing for no data", () => {
118
+ expect(
119
+ LogDataIngestMeteredPlan.getTotalCostInUSD({
120
+ dataIngestedInGB: 0,
121
+ retentionInDays: TELEMETRY_PRICE_RETENTION_IN_DAYS,
122
+ }),
123
+ ).toBe(0);
124
+ });
125
+ });