@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,284 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen, waitFor } from "@testing-library/react";
3
+ import React, { ReactElement } from "react";
4
+ import {
5
+ afterEach,
6
+ beforeEach,
7
+ describe,
8
+ expect,
9
+ jest,
10
+ test,
11
+ } from "@jest/globals";
12
+
13
+ /*
14
+ * UserElement can only show an email the caller actually asked the API for,
15
+ * and an avatar it was given an id for. Two families of call sites failed one
16
+ * of those and would have kept rendering a bare name after the component
17
+ * change:
18
+ *
19
+ * - the notification-log tables passed `selectMoreFields={{user: {name}}}`.
20
+ * BaseModelTable's getSelectFields ASSIGNS selectMoreFields over the
21
+ * select the columns built, rather than merging into it, so the narrower
22
+ * one won and only the name was ever fetched.
23
+ * - the team compliance table hand-builds its user object and left out the
24
+ * id, so the avatar route could not be built at all.
25
+ *
26
+ * Both are invisible in a screenshot of a seeded dev project (short names, a
27
+ * default avatar) and silent in every other suite, so they are pinned here.
28
+ */
29
+
30
+ const BLANK_PROFILE_PIC: string = "/blank-profile.svg";
31
+
32
+ jest.mock("../../../UI/Images/users/blank-profile.svg", () => {
33
+ return BLANK_PROFILE_PIC;
34
+ });
35
+
36
+ jest.mock("../../../UI/Utils/Permission", () => {
37
+ return {
38
+ __esModule: true,
39
+ default: {
40
+ getAllPermissions: () => {
41
+ return [];
42
+ },
43
+ getProjectPermissions: () => {
44
+ return [];
45
+ },
46
+ getGlobalPermissions: () => {
47
+ return [];
48
+ },
49
+ },
50
+ };
51
+ });
52
+
53
+ /*
54
+ * The real ModelTable would drag in the facet bar, URL state and the pager.
55
+ * What matters here is the select it was handed and what its User column does
56
+ * with a row, so it is replaced by a recorder.
57
+ */
58
+ type CapturedTableProps = {
59
+ selectMoreFields?: Record<string, unknown> | undefined;
60
+ columns: Array<{
61
+ title?: string | undefined;
62
+ field?: Record<string, unknown> | undefined;
63
+ getElement?: ((item: Record<string, unknown>) => ReactElement) | undefined;
64
+ }>;
65
+ };
66
+
67
+ let capturedTableProps: CapturedTableProps | null = null;
68
+
69
+ jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
70
+ return {
71
+ __esModule: true,
72
+ default: (props: CapturedTableProps) => {
73
+ capturedTableProps = props;
74
+ return null;
75
+ },
76
+ };
77
+ });
78
+
79
+ import CallLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/CallLogsTable";
80
+ import EmailLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/EmailLogsTable";
81
+ import PushLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/PushLogsTable";
82
+ import SmsLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/SmsLogsTable";
83
+ import TelegramLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/TelegramLogsTable";
84
+ import WebhookLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/WebhookLogsTable";
85
+ import WhatsAppLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/WhatsAppLogsTable";
86
+ import WorkspaceLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/WorkspaceLogsTable";
87
+ import TeamComplianceStatusTable from "../../../../App/FeatureSet/Dashboard/src/Components/Team/TeamComplianceStatusTable";
88
+ import API from "../../../UI/Utils/API/API";
89
+ import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
90
+ import ProjectUtil from "../../../UI/Utils/Project";
91
+ import ObjectID from "../../../Types/ObjectID";
92
+
93
+ const PROJECT_ID: ObjectID = new ObjectID(
94
+ "00000000-0000-4000-8000-000000000001",
95
+ );
96
+ const TEAM_ID: ObjectID = new ObjectID("00000000-0000-4000-8000-000000000002");
97
+ const USER_ID: string = "00000000-0000-4000-8000-000000000003";
98
+
99
+ const NOTIFICATION_LOG_TABLES: Array<{
100
+ name: string;
101
+ Component: React.FunctionComponent<any>;
102
+ }> = [
103
+ { name: "CallLogsTable", Component: CallLogsTable },
104
+ { name: "EmailLogsTable", Component: EmailLogsTable },
105
+ { name: "PushLogsTable", Component: PushLogsTable },
106
+ { name: "SmsLogsTable", Component: SmsLogsTable },
107
+ { name: "TelegramLogsTable", Component: TelegramLogsTable },
108
+ { name: "WebhookLogsTable", Component: WebhookLogsTable },
109
+ { name: "WhatsAppLogsTable", Component: WhatsAppLogsTable },
110
+ { name: "WorkspaceLogsTable", Component: WorkspaceLogsTable },
111
+ ];
112
+
113
+ type UserColumnFunction = () => CapturedTableProps["columns"][0];
114
+
115
+ const userColumn: UserColumnFunction = (): CapturedTableProps["columns"][0] => {
116
+ const column: CapturedTableProps["columns"][0] | undefined =
117
+ capturedTableProps?.columns.find(
118
+ (candidate: CapturedTableProps["columns"][0]) => {
119
+ return candidate.title === "User";
120
+ },
121
+ );
122
+
123
+ if (!column) {
124
+ throw new Error(
125
+ `No "User" column. Columns: ${(capturedTableProps?.columns || [])
126
+ .map((candidate: CapturedTableProps["columns"][0]) => {
127
+ return candidate.title;
128
+ })
129
+ .join(", ")}`,
130
+ );
131
+ }
132
+
133
+ return column;
134
+ };
135
+
136
+ describe("notification log tables", () => {
137
+ beforeEach(() => {
138
+ capturedTableProps = null;
139
+ jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
140
+ });
141
+
142
+ afterEach(() => {
143
+ jest.restoreAllMocks();
144
+ });
145
+
146
+ describe.each(NOTIFICATION_LOG_TABLES)(
147
+ "$name",
148
+ ({ Component }: { Component: React.FunctionComponent<any> }) => {
149
+ beforeEach(() => {
150
+ render(<Component />);
151
+ });
152
+
153
+ /*
154
+ * selectMoreFields overwrites the column's user select rather than
155
+ * merging with it. Narrowing it back to { name } silently drops the
156
+ * email off the wire and the row goes back to a bare name, with the
157
+ * column definition still (misleadingly) asking for the email.
158
+ */
159
+ test("asks the API for the email, not just the name", () => {
160
+ expect(capturedTableProps!.selectMoreFields!["user"]).toEqual(
161
+ expect.objectContaining({ email: true }),
162
+ );
163
+ });
164
+
165
+ test("asks the API for the profile picture too", () => {
166
+ expect(capturedTableProps!.selectMoreFields!["user"]).toEqual(
167
+ expect.objectContaining({ profilePictureId: true }),
168
+ );
169
+ });
170
+
171
+ /*
172
+ * The two selects have to agree: whichever one wins, the column's
173
+ * getElement must have the fields it renders.
174
+ */
175
+ test("selects the same user fields in the column and in selectMoreFields", () => {
176
+ expect(capturedTableProps!.selectMoreFields!["user"]).toEqual(
177
+ userColumn().field!["user"],
178
+ );
179
+ });
180
+
181
+ test("renders the name, the email and the avatar for a log row", () => {
182
+ render(
183
+ userColumn().getElement!({
184
+ user: {
185
+ _id: USER_ID,
186
+ name: "Jane Doe",
187
+ email: "jane@acme.com",
188
+ },
189
+ }),
190
+ );
191
+
192
+ expect(screen.getByText("Jane Doe")).toBeInTheDocument();
193
+ expect(screen.getByTestId("user-email")).toHaveTextContent(
194
+ "jane@acme.com",
195
+ );
196
+ expect(screen.getAllByRole("img")[0]).toHaveAttribute(
197
+ "src",
198
+ `/api/user/profile-picture/${USER_ID}`,
199
+ );
200
+ });
201
+
202
+ test("still renders a dash for a log row with no user", () => {
203
+ render(userColumn().getElement!({}));
204
+
205
+ expect(screen.getByText("-")).toBeInTheDocument();
206
+ });
207
+ },
208
+ );
209
+ });
210
+
211
+ describe("team compliance status table", () => {
212
+ const complianceResponse: {
213
+ teamId: string;
214
+ teamName: string;
215
+ complianceSettings: Array<{ ruleType: string; enabled: boolean }>;
216
+ userComplianceStatuses: Array<{
217
+ userId: string;
218
+ userName: string;
219
+ userEmail: string;
220
+ isCompliant: boolean;
221
+ nonCompliantRules: Array<{ ruleType: string; reason: string }>;
222
+ }>;
223
+ } = {
224
+ teamId: TEAM_ID.toString(),
225
+ teamName: "On-Call",
226
+ complianceSettings: [
227
+ { ruleType: "HasNotificationEmailMethod", enabled: true },
228
+ ],
229
+ userComplianceStatuses: [
230
+ {
231
+ userId: USER_ID,
232
+ userName: "Jane Doe",
233
+ userEmail: "jane@acme.com",
234
+ isCompliant: false,
235
+ nonCompliantRules: [
236
+ {
237
+ ruleType: "HasNotificationEmailMethod",
238
+ reason: "No email notification method",
239
+ },
240
+ ],
241
+ },
242
+ ],
243
+ };
244
+
245
+ beforeEach(async () => {
246
+ jest.spyOn(ModelAPI, "getCommonHeaders").mockReturnValue({});
247
+ jest
248
+ .spyOn(API, "get")
249
+ .mockResolvedValue({ data: complianceResponse } as never);
250
+
251
+ render(<TeamComplianceStatusTable teamId={TEAM_ID} />);
252
+
253
+ await waitFor(() => {
254
+ expect(screen.getByText("Jane Doe")).toBeInTheDocument();
255
+ });
256
+ });
257
+
258
+ afterEach(() => {
259
+ jest.restoreAllMocks();
260
+ });
261
+
262
+ test("shows the member's email beside their name", () => {
263
+ expect(screen.getByTestId("user-email")).toHaveTextContent("jane@acme.com");
264
+ });
265
+
266
+ /*
267
+ * The row is hand-built from the compliance payload. Without the id the
268
+ * avatar route cannot be built and every member falls back to the blank
269
+ * picture, which reads as "nobody has a photo" rather than as a bug.
270
+ */
271
+ test("builds the avatar from the member's own id", () => {
272
+ expect(screen.getAllByRole("img")[0]).toHaveAttribute(
273
+ "src",
274
+ `/api/user/profile-picture/${USER_ID}`,
275
+ );
276
+ });
277
+
278
+ test("still shows why the member is non-compliant", () => {
279
+ expect(screen.getByText("Non-Compliant")).toBeInTheDocument();
280
+ expect(
281
+ screen.getByText(/No email notification method/),
282
+ ).toBeInTheDocument();
283
+ });
284
+ });
@@ -0,0 +1,312 @@
1
+ import CommonModel from "../../../Models/AnalyticsModels/AnalyticsBaseModel/CommonModel";
2
+ import AnalyticsTableColumn from "../../../Types/AnalyticsDatabase/TableColumn";
3
+ import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
4
+ import BadDataException from "../../../Types/Exception/BadDataException";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+
7
+ /*
8
+ * Unit tests for CommonModel — the base every analytics (ClickHouse) model
9
+ * extends. It owns the column-aware value coercion (setColumnValue) and the
10
+ * JSON (de)serialization that the analytics query/ingest layer relies on.
11
+ *
12
+ * Nothing imports CommonModel directly in the existing test suite, so its
13
+ * per-type string coercion and its static/instance JSON helpers were
14
+ * uncovered. The logic is pure: it reads column metadata off the instance,
15
+ * so no ClickHouse connection is required.
16
+ */
17
+
18
+ // Build a column of a given type. Only key + type matter for these tests.
19
+ const column: (key: string, type: TableColumnType) => AnalyticsTableColumn = (
20
+ key: string,
21
+ type: TableColumnType,
22
+ ): AnalyticsTableColumn => {
23
+ return new AnalyticsTableColumn({
24
+ key,
25
+ title: key,
26
+ description: `${key} column`,
27
+ required: false,
28
+ type,
29
+ });
30
+ };
31
+
32
+ const COLUMNS: Array<AnalyticsTableColumn> = [
33
+ column("name", TableColumnType.Text),
34
+ column("resourceId", TableColumnType.ObjectID),
35
+ column("startsAt", TableColumnType.Date),
36
+ column("observedAt", TableColumnType.DateTime64),
37
+ column("attributes", TableColumnType.JSON),
38
+ column("tags", TableColumnType.JSONArray),
39
+ column("count", TableColumnType.Number),
40
+ column("ratio", TableColumnType.Decimal),
41
+ column("bigCount", TableColumnType.LongNumber),
42
+ ];
43
+
44
+ /*
45
+ * A concrete model with a no-arg constructor so the static new type() paths
46
+ * (fromJSON / fromJSONArray) work.
47
+ */
48
+ class TestModel extends CommonModel {
49
+ public constructor() {
50
+ super({ tableColumns: COLUMNS });
51
+ }
52
+ }
53
+
54
+ const newModel: () => TestModel = (): TestModel => {
55
+ return new TestModel();
56
+ };
57
+
58
+ describe("CommonModel.getTableColumn", () => {
59
+ test("returns the column when it exists", () => {
60
+ const model: TestModel = newModel();
61
+ const col: AnalyticsTableColumn | null = model.getTableColumn("name");
62
+ expect(col).not.toBeNull();
63
+ expect(col!.key).toBe("name");
64
+ expect(col!.type).toBe(TableColumnType.Text);
65
+ });
66
+
67
+ test("returns null for an unknown column", () => {
68
+ expect(newModel().getTableColumn("doesNotExist")).toBeNull();
69
+ });
70
+
71
+ test("getTableColumns returns the full column set", () => {
72
+ expect(newModel().getTableColumns()).toHaveLength(COLUMNS.length);
73
+ });
74
+ });
75
+
76
+ describe("CommonModel.setColumnValue coercion by column type", () => {
77
+ test("stores a plain text value unchanged", () => {
78
+ const model: TestModel = newModel();
79
+ model.setColumnValue("name", "hello");
80
+ expect(model.getColumnValue("name")).toBe("hello");
81
+ });
82
+
83
+ test("wraps a string into an ObjectID for an ObjectID column", () => {
84
+ const model: TestModel = newModel();
85
+ const id: ObjectID = ObjectID.generate();
86
+ model.setColumnValue("resourceId", id.toString());
87
+ const stored: unknown = model.getColumnValue("resourceId");
88
+ expect(stored).toBeInstanceOf(ObjectID);
89
+ expect((stored as ObjectID).toString()).toBe(id.toString());
90
+ });
91
+
92
+ test("parses a string into a Date for Date and DateTime64 columns", () => {
93
+ const model: TestModel = newModel();
94
+ model.setColumnValue("startsAt", "2023-01-15T12:30:00.000Z");
95
+ model.setColumnValue("observedAt", "2023-06-01T00:00:00.000Z");
96
+ expect(model.getColumnValue("startsAt")).toBeInstanceOf(Date);
97
+ expect(model.getColumnValue("observedAt")).toBeInstanceOf(Date);
98
+ });
99
+
100
+ test("parses a JSON string for a JSON column", () => {
101
+ const model: TestModel = newModel();
102
+ model.setColumnValue("attributes", '{"env":"prod","n":5}');
103
+ expect(model.getColumnValue("attributes")).toEqual({
104
+ env: "prod",
105
+ n: 5,
106
+ });
107
+ });
108
+
109
+ test("falls back to an empty object when the JSON string is invalid", () => {
110
+ const model: TestModel = newModel();
111
+ model.setColumnValue("attributes", "not json {");
112
+ expect(model.getColumnValue("attributes")).toEqual({});
113
+ });
114
+
115
+ test("parses a JSON array string for a JSONArray column", () => {
116
+ const model: TestModel = newModel();
117
+ model.setColumnValue("tags", "[1, 2, 3]");
118
+ expect(model.getColumnValue("tags")).toEqual([1, 2, 3]);
119
+ });
120
+
121
+ test("falls back to an empty array when the JSONArray string is invalid", () => {
122
+ const model: TestModel = newModel();
123
+ model.setColumnValue("tags", "definitely not an array");
124
+ expect(model.getColumnValue("tags")).toEqual([]);
125
+ });
126
+
127
+ test("parses a string into an integer for a Number column", () => {
128
+ const model: TestModel = newModel();
129
+ model.setColumnValue("count", "42");
130
+ expect(model.getColumnValue("count")).toBe(42);
131
+ });
132
+
133
+ test("parses a string into a float for a Decimal column", () => {
134
+ const model: TestModel = newModel();
135
+ model.setColumnValue("ratio", "3.14");
136
+ expect(model.getColumnValue("ratio")).toBe(3.14);
137
+ });
138
+
139
+ test("parses a string into an integer for a LongNumber column", () => {
140
+ const model: TestModel = newModel();
141
+ model.setColumnValue("bigCount", "1000");
142
+ expect(model.getColumnValue("bigCount")).toBe(1000);
143
+ });
144
+
145
+ test("leaves an already-numeric value on a Number column unchanged", () => {
146
+ const model: TestModel = newModel();
147
+ model.setColumnValue("count", 7);
148
+ expect(model.getColumnValue("count")).toBe(7);
149
+ });
150
+
151
+ test("throws BadDataException when setting an unknown column", () => {
152
+ const model: TestModel = newModel();
153
+ expect(() => {
154
+ model.setColumnValue("nope", "x");
155
+ }).toThrow(BadDataException);
156
+ });
157
+ });
158
+
159
+ describe("CommonModel.getColumnValue", () => {
160
+ test("returns undefined for an unknown column", () => {
161
+ expect(newModel().getColumnValue("nope")).toBeUndefined();
162
+ });
163
+
164
+ test("returns undefined for a known-but-unset column", () => {
165
+ expect(newModel().getColumnValue("name")).toBeUndefined();
166
+ });
167
+ });
168
+
169
+ describe("CommonModel.fromJSON (instance) and toJSON", () => {
170
+ test("fromJSON populates every provided column with coercion", () => {
171
+ const model: CommonModel = newModel().fromJSON({
172
+ name: "widget",
173
+ count: "10",
174
+ ratio: "2.5",
175
+ });
176
+
177
+ expect(model.getColumnValue("name")).toBe("widget");
178
+ expect(model.getColumnValue("count")).toBe(10);
179
+ expect(model.getColumnValue("ratio")).toBe(2.5);
180
+ });
181
+
182
+ test("toJSON emits set columns and omits unset ones", () => {
183
+ const model: TestModel = newModel();
184
+ model.setColumnValue("name", "widget");
185
+ model.setColumnValue("count", 3);
186
+
187
+ const json: Record<string, unknown> = model.toJSON();
188
+
189
+ expect(json["name"]).toBe("widget");
190
+ expect(json["count"]).toBe(3);
191
+ // Unset columns serialize to undefined, which JSONFunctions.serialize drops.
192
+ expect(Object.prototype.hasOwnProperty.call(json, "ratio")).toBe(false);
193
+ expect(json["ratio"]).toBeUndefined();
194
+ });
195
+
196
+ test("toJSON recursively serializes a nested CommonModel value", () => {
197
+ const parent: TestModel = newModel();
198
+ const child: TestModel = newModel();
199
+ child.setColumnValue("name", "child");
200
+
201
+ /*
202
+ * A non-string value on a column is stored as-is, so a nested model
203
+ * survives to toJSON where it is recursively serialized.
204
+ */
205
+ parent.setColumnValue("attributes", child as unknown as never);
206
+
207
+ const json: Record<string, unknown> = parent.toJSON();
208
+ expect((json["attributes"] as Record<string, unknown>)["name"]).toBe(
209
+ "child",
210
+ );
211
+ });
212
+ });
213
+
214
+ describe("CommonModel static fromJSON / toJSON", () => {
215
+ test("builds a model instance from a plain JSON object", () => {
216
+ const model: CommonModel = CommonModel.fromJSON(
217
+ { name: "from-json" },
218
+ TestModel,
219
+ ) as CommonModel;
220
+
221
+ expect(model).toBeInstanceOf(TestModel);
222
+ expect(model.getColumnValue("name")).toBe("from-json");
223
+ });
224
+
225
+ test("returns an already-constructed model unchanged", () => {
226
+ const existing: TestModel = newModel();
227
+ existing.setColumnValue("name", "existing");
228
+
229
+ const result: CommonModel = CommonModel.fromJSON(
230
+ existing,
231
+ TestModel,
232
+ ) as CommonModel;
233
+
234
+ expect(result).toBe(existing);
235
+ });
236
+
237
+ test("builds an array of models, passing through existing instances", () => {
238
+ const existing: TestModel = newModel();
239
+ existing.setColumnValue("name", "kept");
240
+
241
+ const result: Array<CommonModel> = CommonModel.fromJSON(
242
+ [{ name: "a" }, existing, { name: "b" }] as never,
243
+ TestModel,
244
+ ) as Array<CommonModel>;
245
+
246
+ expect(result).toHaveLength(3);
247
+ expect(result[0]!.getColumnValue("name")).toBe("a");
248
+ expect(result[1]).toBe(existing);
249
+ expect(result[2]!.getColumnValue("name")).toBe("b");
250
+ });
251
+
252
+ test("static toJSON delegates to the instance toJSON", () => {
253
+ const model: TestModel = newModel();
254
+ model.setColumnValue("name", "delegated");
255
+
256
+ const json: Record<string, unknown> = CommonModel.toJSON(model, TestModel);
257
+ expect(json["name"]).toBe("delegated");
258
+ });
259
+ });
260
+
261
+ describe("CommonModel static fromJSONArray / toJSONArray", () => {
262
+ test("fromJSONArray builds instances and passes through existing ones", () => {
263
+ const existing: TestModel = newModel();
264
+ existing.setColumnValue("name", "kept");
265
+
266
+ const models: Array<CommonModel> = CommonModel.fromJSONArray(
267
+ [{ name: "first" }, existing],
268
+ TestModel,
269
+ );
270
+
271
+ expect(models).toHaveLength(2);
272
+ expect(models[0]!.getColumnValue("name")).toBe("first");
273
+ expect(models[1]).toBe(existing);
274
+ });
275
+
276
+ test("toJSONArray round-trips a list of models to JSON objects", () => {
277
+ const a: TestModel = newModel();
278
+ a.setColumnValue("name", "a");
279
+ const b: TestModel = newModel();
280
+ b.setColumnValue("name", "b");
281
+
282
+ const jsonArray: Array<Record<string, unknown>> = CommonModel.toJSONArray(
283
+ [a, b],
284
+ TestModel,
285
+ );
286
+
287
+ expect(jsonArray).toHaveLength(2);
288
+ expect(jsonArray[0]!["name"]).toBe("a");
289
+ expect(jsonArray[1]!["name"]).toBe("b");
290
+ });
291
+
292
+ test("fromJSONArray then toJSONArray preserves scalar values", () => {
293
+ const input: Array<Record<string, unknown>> = [
294
+ { name: "x", count: 1 },
295
+ { name: "y", count: 2 },
296
+ ];
297
+
298
+ const models: Array<CommonModel> = CommonModel.fromJSONArray(
299
+ input as never,
300
+ TestModel,
301
+ );
302
+ const output: Array<Record<string, unknown>> = CommonModel.toJSONArray(
303
+ models,
304
+ TestModel,
305
+ );
306
+
307
+ expect(output[0]!["name"]).toBe("x");
308
+ expect(output[0]!["count"]).toBe(1);
309
+ expect(output[1]!["name"]).toBe("y");
310
+ expect(output[1]!["count"]).toBe(2);
311
+ });
312
+ });