@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,536 @@
1
+ /**
2
+ * @timezone UTC
3
+ */
4
+ import "@testing-library/jest-dom";
5
+ import {
6
+ afterEach,
7
+ beforeEach,
8
+ describe,
9
+ expect,
10
+ jest,
11
+ test,
12
+ } from "@jest/globals";
13
+ import {
14
+ RenderResult,
15
+ cleanup,
16
+ render,
17
+ screen,
18
+ waitFor,
19
+ } from "@testing-library/react";
20
+ import * as React from "react";
21
+ import getJestMockFunction, { MockFunction } from "../../MockType";
22
+
23
+ /*
24
+ * The SLO widget's Chart display renders a real recharts line — and it is
25
+ * exactly there that it broke. Every sibling suite stubs LineChart out and
26
+ * asserts on the props handed down, which is why the bug survived: the props
27
+ * were right, the REQUEST was right, the data was right, and recharts still
28
+ * drew nothing, because the widget labelled its x-axis "" and the chart rows
29
+ * were keyed off that label while recharts was told to read "Time".
30
+ *
31
+ * So this suite deliberately renders the real chart and asserts on the SVG
32
+ * the viewer actually gets. It covers both surfaces the widget runs on: the
33
+ * anonymous public dashboard (where the bug was reported) and the
34
+ * authenticated one (where it was equally broken, just unnoticed).
35
+ *
36
+ * Pinned to UTC: the x-axis bucket labels are produced by local-time
37
+ * formatters.
38
+ */
39
+
40
+ const getItemMock: MockFunction = getJestMockFunction();
41
+ const getListMock: MockFunction = getJestMockFunction();
42
+ const aggregateMock: MockFunction = getJestMockFunction();
43
+ const getCurrentProjectIdMock: MockFunction = getJestMockFunction();
44
+
45
+ /*
46
+ * The arrow wrappers are load bearing: jest.mock is hoisted above the compiled
47
+ * requires, so the mock variables above are still unassigned when the factory
48
+ * runs. Dereferencing them lazily, at call time, is what makes this work.
49
+ */
50
+ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
51
+ return {
52
+ __esModule: true,
53
+ default: {
54
+ getItem: (...args: Array<any>) => {
55
+ return getItemMock(...args);
56
+ },
57
+ getList: (...args: Array<any>) => {
58
+ return getListMock(...args);
59
+ },
60
+ },
61
+ };
62
+ });
63
+
64
+ jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
65
+ return {
66
+ __esModule: true,
67
+ default: {
68
+ aggregate: (...args: Array<any>) => {
69
+ return aggregateMock(...args);
70
+ },
71
+ },
72
+ };
73
+ });
74
+
75
+ jest.mock("../../../UI/Utils/Project", () => {
76
+ return {
77
+ __esModule: true,
78
+ default: {
79
+ getCurrentProjectId: (...args: Array<any>) => {
80
+ return getCurrentProjectIdMock(...args);
81
+ },
82
+ },
83
+ };
84
+ });
85
+
86
+ /*
87
+ * The ONLY thing stubbed in the chart stack. ResponsiveContainer measures its
88
+ * parent, which is always 0x0 in jsdom, and a 0x0 recharts chart draws no
89
+ * marks — which would make every assertion below pass for the wrong reason.
90
+ */
91
+ jest.mock("recharts", () => {
92
+ const actual: Record<string, any> = jest.requireActual("recharts") as Record<
93
+ string,
94
+ any
95
+ >;
96
+ return {
97
+ ...actual,
98
+ ResponsiveContainer: ({ children }: { children: React.ReactElement }) => {
99
+ return React.cloneElement(children, { width: 600, height: 300 });
100
+ },
101
+ };
102
+ });
103
+
104
+ import DashboardSloComponentElement from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardSloComponent";
105
+ import { DashboardBaseComponentProps } from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardBaseComponent";
106
+ import {
107
+ PublicDashboardContext,
108
+ setPublicDashboardContext,
109
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Utils/PublicDashboardContext";
110
+ import ServiceLevelObjective from "../../../Models/DatabaseModels/ServiceLevelObjective";
111
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
112
+ import AggregatedResult from "../../../Types/BaseDatabase/AggregatedResult";
113
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
114
+ import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
115
+ import DashboardSloComponent, {
116
+ SloWidgetDisplayType,
117
+ SloWidgetMetric,
118
+ } from "../../../Types/Dashboard/DashboardComponents/DashboardSloComponent";
119
+ import DashboardViewConfig from "../../../Types/Dashboard/DashboardViewConfig";
120
+ import { JSONObject, ObjectType } from "../../../Types/JSON";
121
+ import ObjectID from "../../../Types/ObjectID";
122
+ import SloStatus from "../../../Types/ServiceLevelObjective/SloStatus";
123
+ import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
124
+ import TimeRange from "../../../Types/Time/TimeRange";
125
+
126
+ const COMPONENT_ID: ObjectID = new ObjectID(
127
+ "11111111-1111-4111-8111-111111111111",
128
+ );
129
+ const DASHBOARD_ID: ObjectID = new ObjectID(
130
+ "22222222-2222-4222-8222-222222222222",
131
+ );
132
+ const SLO_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
133
+ const PROJECT_ID: ObjectID = new ObjectID(
134
+ "44444444-4444-4444-8444-444444444444",
135
+ );
136
+
137
+ /*
138
+ * A CUSTOM range pins the window to fixed instants; a relative range would
139
+ * resolve against the wall clock. Six hours buckets at five minutes, which
140
+ * is the cadence the evaluation worker writes at.
141
+ */
142
+ const START_DATE: Date = new Date("2026-08-10T00:00:00.000Z");
143
+ const END_DATE: Date = new Date("2026-08-10T06:00:00.000Z");
144
+
145
+ const DASHBOARD_RANGE: RangeStartAndEndDateTime = {
146
+ range: TimeRange.CUSTOM,
147
+ startAndEndDate: new InBetween<Date>(START_DATE, END_DATE),
148
+ };
149
+
150
+ const DASHBOARD_VIEW_CONFIG: DashboardViewConfig = {
151
+ _type: ObjectType.DashboardViewConfig,
152
+ components: [],
153
+ heightInDashboardUnits: 60,
154
+ };
155
+
156
+ /** SVG the viewer sees only when recharts actually plotted the series. */
157
+ const LINE_SELECTOR: string = "path.recharts-line-curve";
158
+
159
+ let publicResponse: JSONObject = { data: [] };
160
+
161
+ const PUBLIC_DASHBOARD_CONTEXT: PublicDashboardContext = {
162
+ dashboardId: DASHBOARD_ID,
163
+ apiUrl: {
164
+ toString: (): string => {
165
+ return "http://localhost/public-dashboard-api";
166
+ },
167
+ },
168
+ postJSON: () => {
169
+ return Promise.resolve({
170
+ data: publicResponse,
171
+ } as unknown as HTTPResponse<JSONObject>);
172
+ },
173
+ } as unknown as PublicDashboardContext;
174
+
175
+ /** One row every five minutes across the window, oldest first. */
176
+ type BuildHistoryFunction = (data?: {
177
+ rowCount?: number | undefined;
178
+ valueAt?: ((index: number) => number | string) | undefined;
179
+ }) => Array<JSONObject>;
180
+
181
+ const buildHistory: BuildHistoryFunction = (
182
+ data: {
183
+ rowCount?: number | undefined;
184
+ valueAt?: ((index: number) => number | string) | undefined;
185
+ } = {},
186
+ ): Array<JSONObject> => {
187
+ const rowCount: number = data.rowCount ?? 12;
188
+ const rows: Array<JSONObject> = [];
189
+
190
+ for (let index: number = 0; index < rowCount; index++) {
191
+ rows.push({
192
+ timestamp: new Date(
193
+ START_DATE.getTime() + index * 5 * 60 * 1000,
194
+ ).toISOString(),
195
+ value: data.valueAt ? data.valueAt(index) : 99.9 - index * 0.01,
196
+ });
197
+ }
198
+
199
+ return rows;
200
+ };
201
+
202
+ type BuildSloFunction = (
203
+ overrides?: Partial<ServiceLevelObjective>,
204
+ ) => ServiceLevelObjective;
205
+
206
+ const buildSlo: BuildSloFunction = (
207
+ overrides: Partial<ServiceLevelObjective> = {},
208
+ ): ServiceLevelObjective => {
209
+ const slo: ServiceLevelObjective = new ServiceLevelObjective();
210
+ slo.name = "Checkout availability";
211
+ slo.targetPercentage = 99.9;
212
+ slo.currentSliPercentage = 99.95;
213
+ slo.errorBudgetRemainingPercentage = 42.5;
214
+ slo.errorBudgetRemainingSeconds = 3600;
215
+ slo.currentBurnRate = 1.25;
216
+ slo.sloStatus = SloStatus.Healthy;
217
+ return Object.assign(slo, overrides);
218
+ };
219
+
220
+ type BuildPropsFunction = (
221
+ overrides?: Partial<DashboardBaseComponentProps>,
222
+ ) => DashboardBaseComponentProps;
223
+
224
+ const buildBaseProps: BuildPropsFunction = (
225
+ overrides: Partial<DashboardBaseComponentProps> = {},
226
+ ): DashboardBaseComponentProps => {
227
+ return {
228
+ componentId: COMPONENT_ID,
229
+ isEditMode: false,
230
+ isSelected: false,
231
+ key: "slo-widget",
232
+ onComponentUpdate: (): void => {
233
+ // The widget never writes back through this.
234
+ },
235
+ totalCurrentDashboardWidthInPx: 1200,
236
+ dashboardCanvasTopInPx: 0,
237
+ dashboardCanvasLeftInPx: 0,
238
+ dashboardCanvasWidthInPx: 1200,
239
+ dashboardCanvasHeightInPx: 800,
240
+ dashboardComponentHeightInPx: 320,
241
+ dashboardComponentWidthInPx: 480,
242
+ dashboardViewConfig: DASHBOARD_VIEW_CONFIG,
243
+ dashboardStartAndEndDate: DASHBOARD_RANGE,
244
+ metricTypes: [],
245
+ refreshTick: 0,
246
+ variables: undefined,
247
+ ...overrides,
248
+ } as unknown as DashboardBaseComponentProps;
249
+ };
250
+
251
+ type RenderWidgetFunction = (
252
+ args: DashboardSloComponent["arguments"],
253
+ propOverrides?: Partial<DashboardBaseComponentProps>,
254
+ ) => RenderResult;
255
+
256
+ const renderWidget: RenderWidgetFunction = (
257
+ args: DashboardSloComponent["arguments"],
258
+ propOverrides: Partial<DashboardBaseComponentProps> = {},
259
+ ): RenderResult => {
260
+ const component: DashboardSloComponent = {
261
+ _type: ObjectType.DashboardComponent,
262
+ componentId: COMPONENT_ID,
263
+ componentType: DashboardComponentType.Slo,
264
+ topInDashboardUnits: 0,
265
+ leftInDashboardUnits: 0,
266
+ widthInDashboardUnits: 3,
267
+ heightInDashboardUnits: 3,
268
+ minWidthInDashboardUnits: 2,
269
+ minHeightInDashboardUnits: 2,
270
+ arguments: args,
271
+ } as unknown as DashboardSloComponent;
272
+
273
+ return render(
274
+ <DashboardSloComponentElement
275
+ {...buildBaseProps(propOverrides)}
276
+ component={component}
277
+ />,
278
+ );
279
+ };
280
+
281
+ /*
282
+ * Settle in two stages.
283
+ *
284
+ * First the chart FRAME, which appears as soon as the widget's fetch resolves
285
+ * and it switches to its Chart branch. Under the bug the frame — surface,
286
+ * axes, grid, tooltip — rendered perfectly and only the series was missing,
287
+ * so reaching this point proves the failure below is about the LINE and not
288
+ * about a request that never came back.
289
+ *
290
+ * Then the series itself, on a short leash: the chart fills its rows in an
291
+ * effect one tick after mounting, so it is never there on the first paint,
292
+ * and a regression should fail in a second rather than sitting out the
293
+ * default five.
294
+ */
295
+ const LINE_TIMEOUT_IN_MS: number = 1500;
296
+
297
+ type FindLinePathsFunction = (
298
+ container: HTMLElement,
299
+ ) => Promise<Array<Element>>;
300
+
301
+ const findLinePaths: FindLinePathsFunction = async (
302
+ container: HTMLElement,
303
+ ): Promise<Array<Element>> => {
304
+ await waitFor((): void => {
305
+ expect(container.querySelector("svg.recharts-surface")).not.toBeNull();
306
+ });
307
+
308
+ await waitFor(
309
+ (): void => {
310
+ expect(container.querySelector(LINE_SELECTOR)).not.toBeNull();
311
+ },
312
+ { timeout: LINE_TIMEOUT_IN_MS },
313
+ );
314
+
315
+ return Array.from(container.querySelectorAll(LINE_SELECTOR));
316
+ };
317
+
318
+ /*
319
+ * recharts emits the curve more than once per series (the drawn stroke plus
320
+ * its hit-target twin), so assert on the GEOMETRY they all share rather than
321
+ * on how many nodes carry it.
322
+ */
323
+ type ExpectPlottedFunction = (paths: Array<Element>) => string;
324
+
325
+ const expectPlotted: ExpectPlottedFunction = (
326
+ paths: Array<Element>,
327
+ ): string => {
328
+ expect(paths.length).toBeGreaterThan(0);
329
+
330
+ const geometries: Array<string> = paths.map((path: Element): string => {
331
+ return path.getAttribute("d") || "";
332
+ });
333
+
334
+ for (const geometry of geometries) {
335
+ /*
336
+ * The reported bug produced a chart frame with NO curve at all; a curve
337
+ * present but empty (or full of NaN) would be the same blank widget, so
338
+ * pin the path data itself.
339
+ */
340
+ expect(geometry.length).toBeGreaterThan(0);
341
+ expect(geometry.startsWith("M")).toBe(true);
342
+ expect(geometry).not.toContain("NaN");
343
+ }
344
+
345
+ // Every copy is the same series, so they must agree.
346
+ expect(new Set(geometries).size).toBe(1);
347
+
348
+ return geometries[0]!;
349
+ };
350
+
351
+ beforeEach((): void => {
352
+ jest.clearAllMocks();
353
+ publicResponse = { data: buildHistory() };
354
+ getCurrentProjectIdMock.mockReturnValue(PROJECT_ID);
355
+ getItemMock.mockResolvedValue(buildSlo());
356
+ getListMock.mockResolvedValue({
357
+ data: [buildSlo()],
358
+ count: 1,
359
+ skip: 0,
360
+ limit: 1,
361
+ });
362
+ aggregateMock.mockResolvedValue({
363
+ data: buildHistory(),
364
+ } as unknown as AggregatedResult);
365
+ });
366
+
367
+ afterEach((): void => {
368
+ cleanup();
369
+ setPublicDashboardContext(null);
370
+ });
371
+
372
+ describe("SLO chart widget on a public dashboard", () => {
373
+ beforeEach((): void => {
374
+ setPublicDashboardContext(PUBLIC_DASHBOARD_CONTEXT);
375
+ // A public dashboard has no session, so no current project.
376
+ getCurrentProjectIdMock.mockReturnValue(null);
377
+ });
378
+
379
+ test("plots a line an anonymous viewer can actually see", async () => {
380
+ const { container }: RenderResult = renderWidget({
381
+ serviceLevelObjectiveId: SLO_ID.toString(),
382
+ sloMetric: SloWidgetMetric.Sli,
383
+ displayType: SloWidgetDisplayType.Chart,
384
+ });
385
+
386
+ const geometry: string = expectPlotted(await findLinePaths(container));
387
+
388
+ /*
389
+ * A drawn line visits every history row, so it carries one curve command
390
+ * per plotted point rather than a single degenerate move-to.
391
+ */
392
+ expect(geometry.split("C").length).toBeGreaterThan(1);
393
+ });
394
+
395
+ test.each([
396
+ [SloWidgetMetric.Sli],
397
+ [SloWidgetMetric.ErrorBudgetRemaining],
398
+ [SloWidgetMetric.BurnRate],
399
+ ])("plots %s", async (sloMetric: SloWidgetMetric) => {
400
+ publicResponse = {
401
+ data: buildHistory({
402
+ valueAt: (index: number): number => {
403
+ return sloMetric === SloWidgetMetric.BurnRate
404
+ ? 1 + index * 0.1
405
+ : 99 - index * 0.1;
406
+ },
407
+ }),
408
+ };
409
+
410
+ const { container }: RenderResult = renderWidget({
411
+ serviceLevelObjectiveId: SLO_ID.toString(),
412
+ sloMetric: sloMetric,
413
+ displayType: SloWidgetDisplayType.Chart,
414
+ });
415
+
416
+ expectPlotted(await findLinePaths(container));
417
+ });
418
+
419
+ test("draws the SLO's target as a reference line on the SLI chart", async () => {
420
+ const { container }: RenderResult = renderWidget({
421
+ serviceLevelObjectiveId: SLO_ID.toString(),
422
+ sloMetric: SloWidgetMetric.Sli,
423
+ displayType: SloWidgetDisplayType.Chart,
424
+ });
425
+
426
+ await findLinePaths(container);
427
+
428
+ expect(
429
+ container.querySelectorAll("line.recharts-reference-line-line").length,
430
+ ).toBeGreaterThan(0);
431
+ expect(screen.getByText("Target 99.9%")).toBeInTheDocument();
432
+ });
433
+
434
+ test("plots decimal strings, which is how ClickHouse returns the value", async () => {
435
+ publicResponse = {
436
+ data: buildHistory({
437
+ valueAt: (index: number): string => {
438
+ return `${99.9 - index * 0.05}`;
439
+ },
440
+ }),
441
+ };
442
+
443
+ const { container }: RenderResult = renderWidget({
444
+ serviceLevelObjectiveId: SLO_ID.toString(),
445
+ displayType: SloWidgetDisplayType.Chart,
446
+ });
447
+
448
+ expectPlotted(await findLinePaths(container));
449
+ });
450
+
451
+ test("shows the empty-history state instead of a blank chart frame", async () => {
452
+ publicResponse = { data: [] };
453
+
454
+ const { container }: RenderResult = renderWidget({
455
+ serviceLevelObjectiveId: SLO_ID.toString(),
456
+ displayType: SloWidgetDisplayType.Chart,
457
+ });
458
+
459
+ expect(
460
+ await screen.findByText(/No history for the selected/i),
461
+ ).toBeInTheDocument();
462
+ expect(container.querySelectorAll(LINE_SELECTOR).length).toBe(0);
463
+ });
464
+
465
+ test("keeps the tile display working alongside the chart fix", async () => {
466
+ const { container }: RenderResult = renderWidget({
467
+ serviceLevelObjectiveId: SLO_ID.toString(),
468
+ sloMetric: SloWidgetMetric.Sli,
469
+ displayType: SloWidgetDisplayType.Tile,
470
+ });
471
+
472
+ expect(await screen.findByText("99.95%")).toBeInTheDocument();
473
+ expect(screen.getByText("target 99.9%")).toBeInTheDocument();
474
+ // A Tile never asks for history, so it never draws a series.
475
+ expect(container.querySelectorAll(LINE_SELECTOR).length).toBe(0);
476
+ });
477
+
478
+ test("still plots when the widget tile is small enough to clamp its height", async () => {
479
+ const { container }: RenderResult = renderWidget(
480
+ {
481
+ serviceLevelObjectiveId: SLO_ID.toString(),
482
+ displayType: SloWidgetDisplayType.Chart,
483
+ },
484
+ { dashboardComponentHeightInPx: 100, dashboardComponentWidthInPx: 140 },
485
+ );
486
+
487
+ expectPlotted(await findLinePaths(container));
488
+ });
489
+ });
490
+
491
+ describe("SLO chart widget on an authenticated dashboard", () => {
492
+ test("plots a line through the private analytics route too", async () => {
493
+ const { container }: RenderResult = renderWidget({
494
+ serviceLevelObjectiveId: SLO_ID.toString(),
495
+ sloMetric: SloWidgetMetric.Sli,
496
+ displayType: SloWidgetDisplayType.Chart,
497
+ });
498
+
499
+ expectPlotted(await findLinePaths(container));
500
+
501
+ expect(aggregateMock).toHaveBeenCalledTimes(1);
502
+ });
503
+
504
+ test("renders the same geometry as the public dashboard for the same series", async () => {
505
+ const authenticated: RenderResult = renderWidget({
506
+ serviceLevelObjectiveId: SLO_ID.toString(),
507
+ sloMetric: SloWidgetMetric.Sli,
508
+ displayType: SloWidgetDisplayType.Chart,
509
+ });
510
+
511
+ const authenticatedGeometry: string = expectPlotted(
512
+ await findLinePaths(authenticated.container),
513
+ );
514
+
515
+ cleanup();
516
+
517
+ setPublicDashboardContext(PUBLIC_DASHBOARD_CONTEXT);
518
+ getCurrentProjectIdMock.mockReturnValue(null);
519
+
520
+ const anonymous: RenderResult = renderWidget({
521
+ serviceLevelObjectiveId: SLO_ID.toString(),
522
+ sloMetric: SloWidgetMetric.Sli,
523
+ displayType: SloWidgetDisplayType.Chart,
524
+ });
525
+
526
+ const anonymousGeometry: string = expectPlotted(
527
+ await findLinePaths(anonymous.container),
528
+ );
529
+
530
+ /*
531
+ * Same SLO, same window, same rows — the two surfaces differ only in
532
+ * which endpoint served them, so the drawn line must be identical.
533
+ */
534
+ expect(anonymousGeometry).toBe(authenticatedGeometry);
535
+ });
536
+ });