@oneuptime/common 12.0.18 → 12.0.20

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/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/EnterpriseLicenseAPI.ts +94 -19
  11. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  12. package/Server/API/UserSlackAPI.ts +125 -0
  13. package/Server/EnvironmentConfig.ts +29 -107
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  19. package/Server/Infrastructure/Queue.ts +7 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +171 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Response.ts +2 -2
  40. package/Server/Utils/StartServer.ts +2 -2
  41. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  42. package/Server/Views/Partials/AnalyticsConsent.ejs +533 -0
  43. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  44. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  45. package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +465 -0
  46. package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +302 -0
  47. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +408 -0
  48. package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +474 -0
  49. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +368 -0
  50. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  51. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  52. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  53. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  54. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +763 -0
  55. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  56. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  57. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  58. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  59. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  60. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  61. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  62. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  63. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  64. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  65. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  66. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +138 -0
  67. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +516 -0
  68. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  69. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  70. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  71. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  72. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  73. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  74. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  75. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  76. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  77. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  78. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  79. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  80. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  81. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  82. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  83. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  84. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  85. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  86. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  87. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  88. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  89. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +53 -0
  90. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  91. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  92. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  93. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  94. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  95. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  96. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +241 -0
  97. package/Tests/UI/Components/Charts/ChartTooltipSorted.test.tsx +129 -0
  98. package/Tests/UI/Components/Charts/TooltipEntries.test.ts +155 -0
  99. package/Tests/UI/Components/Detail/DetailFieldErrors.test.tsx +347 -0
  100. package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +392 -0
  101. package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +549 -0
  102. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  103. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  104. package/Tests/Utils/EnterpriseLicenseSync.test.ts +562 -0
  105. package/Types/Analytics/RevenueEvent.ts +1 -0
  106. package/Types/Marketing/MarketingEvent.ts +140 -0
  107. package/UI/Components/Charts/Area/AreaChart.tsx +8 -1
  108. package/UI/Components/Charts/Bar/BarChart.tsx +8 -1
  109. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +19 -4
  110. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +20 -2
  111. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +19 -4
  112. package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +105 -0
  113. package/UI/Components/Charts/Line/LineChart.tsx +8 -1
  114. package/UI/Components/Detail/Detail.tsx +18 -1
  115. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  116. package/UI/Utils/PermissionGate.ts +56 -0
  117. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +244 -0
  118. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  119. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  120. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  121. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  123. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  125. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  126. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  127. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  129. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  131. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  133. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  134. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  135. package/build/dist/Server/API/EnterpriseLicenseAPI.js +66 -12
  136. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  137. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  138. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  139. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  140. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  141. package/build/dist/Server/EnvironmentConfig.js +25 -70
  142. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  151. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  152. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  153. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  154. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  155. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  156. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  157. package/build/dist/Server/Services/Index.js +6 -2
  158. package/build/dist/Server/Services/Index.js.map +1 -1
  159. package/build/dist/Server/Services/LlmLogService.js +25 -9
  160. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  161. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  162. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  163. package/build/dist/Server/Services/ProjectService.js +144 -0
  164. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  165. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  166. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  167. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  168. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  169. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  170. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  171. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  172. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  173. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  174. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  175. package/build/dist/Server/Services/UserService.js +26 -1
  176. package/build/dist/Server/Services/UserService.js.map +1 -1
  177. package/build/dist/Server/Services/UserSlackService.js +195 -0
  178. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  179. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  180. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  181. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  182. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  183. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  184. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  185. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  186. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  187. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  188. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  189. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  190. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  192. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  193. package/build/dist/Server/Utils/Response.js +2 -2
  194. package/build/dist/Server/Utils/Response.js.map +1 -1
  195. package/build/dist/Server/Utils/StartServer.js +2 -2
  196. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  197. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  198. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  199. package/build/dist/Types/Analytics/RevenueEvent.js +1 -0
  200. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -1
  201. package/build/dist/Types/Marketing/MarketingEvent.js +82 -0
  202. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  203. package/build/dist/UI/Components/Charts/Area/AreaChart.js +9 -1
  204. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  205. package/build/dist/UI/Components/Charts/Bar/BarChart.js +9 -1
  206. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  207. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +22 -13
  208. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  209. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +27 -15
  210. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  211. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +27 -18
  212. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  213. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +71 -0
  214. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -0
  215. package/build/dist/UI/Components/Charts/Line/LineChart.js +9 -1
  216. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  217. package/build/dist/UI/Components/Detail/Detail.js +17 -1
  218. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  219. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  220. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  221. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  222. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  223. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +174 -0
  224. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +1 -0
  225. package/package.json +1 -1
  226. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  227. package/Server/Services/MarketingConversionService.ts +0 -10
  228. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  229. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  230. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  231. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  232. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  233. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  234. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  235. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  236. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  237. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  238. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  239. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  240. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  241. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  242. package/Types/Marketing/MarketingConversion.ts +0 -53
  243. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  244. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  245. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  246. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  247. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  248. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  249. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  250. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  251. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  252. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  253. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  254. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  255. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  256. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  257. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  258. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  259. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  260. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  261. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  262. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -0,0 +1,129 @@
1
+ import "@testing-library/jest-dom";
2
+ import { afterEach, describe, expect, test } from "@jest/globals";
3
+ import { cleanup, render, screen } from "@testing-library/react";
4
+ import * as React from "react";
5
+ import { ChartTooltip as AreaChartTooltip } from "../../../../UI/Components/Charts/ChartLibrary/AreaChart/AreaChart";
6
+ import { ChartTooltip as BarChartTooltip } from "../../../../UI/Components/Charts/ChartLibrary/BarChart/BarChart";
7
+ import { ChartTooltip as LineChartTooltip } from "../../../../UI/Components/Charts/ChartLibrary/LineChart/LineChart";
8
+
9
+ /*
10
+ * The rendered tooltip is where "which host is this spike?" gets answered:
11
+ * with a 20-series group-by chart the entries must arrive highest-value
12
+ * first (recharts hands them over in series-mount order, effectively
13
+ * alphabetical) and the tail past the cap must be summarized, not
14
+ * silently dropped. All three chart types ship their own ChartTooltip, so
15
+ * all three are pinned to the same contract here.
16
+ */
17
+
18
+ type TooltipComponent =
19
+ | typeof AreaChartTooltip
20
+ | typeof LineChartTooltip
21
+ | typeof BarChartTooltip;
22
+
23
+ interface TooltipCase {
24
+ name: string;
25
+ Tooltip: TooltipComponent;
26
+ }
27
+
28
+ const TOOLTIP_CASES: Array<TooltipCase> = [
29
+ { name: "AreaChart", Tooltip: AreaChartTooltip },
30
+ { name: "LineChart", Tooltip: LineChartTooltip },
31
+ { name: "BarChart", Tooltip: BarChartTooltip },
32
+ ];
33
+
34
+ function buildPayload(count: number): Array<{
35
+ category: string;
36
+ value: number;
37
+ index: string;
38
+ color: string;
39
+ payload: Record<string, unknown>;
40
+ }> {
41
+ return Array.from({ length: count }, (_: unknown, index: number) => {
42
+ return {
43
+ /*
44
+ * Alphabetical categories with ASCENDING values — exactly the shape
45
+ * where mount order (host-00 first) and value order (host-NN first)
46
+ * disagree, so a pass proves the sort actually ran.
47
+ */
48
+ category: `host-${String(index).padStart(2, "0")}`,
49
+ value: index,
50
+ index: "10:00",
51
+ color: "blue",
52
+ payload: {},
53
+ };
54
+ });
55
+ }
56
+
57
+ afterEach(() => {
58
+ cleanup();
59
+ });
60
+
61
+ describe.each(TOOLTIP_CASES)("$name tooltip", ({ Tooltip }: TooltipCase) => {
62
+ test("renders entries highest-value first with an overflow summary", () => {
63
+ const { container } = render(
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ <Tooltip
66
+ active={true}
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ payload={buildPayload(14) as any}
69
+ label="10:00"
70
+ valueFormatter={(value: number): string => {
71
+ return `${value}%`;
72
+ }}
73
+ />,
74
+ );
75
+
76
+ const text: string = container.textContent || "";
77
+
78
+ // The top-valued series leads; the lowest-valued ones are elided.
79
+ expect(text.indexOf("host-13")).toBeGreaterThan(-1);
80
+ expect(text.indexOf("host-13")).toBeLessThan(text.indexOf("host-12"));
81
+ expect(text.indexOf("host-12")).toBeLessThan(text.indexOf("host-04"));
82
+ expect(text).not.toContain("host-03");
83
+ expect(text).not.toContain("host-00");
84
+
85
+ // 14 series, 10 shown → 4 summarized.
86
+ expect(
87
+ screen.getByText(/\+4 more series — highest values shown/),
88
+ ).toBeInTheDocument();
89
+
90
+ // Values run through the caller's formatter.
91
+ expect(text).toContain("13%");
92
+ });
93
+
94
+ test("shows no overflow line when everything fits", () => {
95
+ const { container } = render(
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ <Tooltip
98
+ active={true}
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ payload={buildPayload(3) as any}
101
+ label="10:00"
102
+ valueFormatter={(value: number): string => {
103
+ return String(value);
104
+ }}
105
+ />,
106
+ );
107
+
108
+ expect(container.textContent).not.toContain("more series");
109
+ expect(container.textContent).toContain("host-02");
110
+ expect(container.textContent).toContain("host-00");
111
+ });
112
+
113
+ test("renders nothing when inactive", () => {
114
+ const { container } = render(
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ <Tooltip
117
+ active={false}
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
+ payload={buildPayload(3) as any}
120
+ label="10:00"
121
+ valueFormatter={(value: number): string => {
122
+ return String(value);
123
+ }}
124
+ />,
125
+ );
126
+
127
+ expect(container.firstChild).toBeNull();
128
+ });
129
+ });
@@ -0,0 +1,155 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import {
3
+ DEFAULT_TOOLTIP_MAX_ENTRIES,
4
+ PreparedTooltipEntries,
5
+ SortableTooltipItem,
6
+ prepareTooltipEntries,
7
+ } from "../../../../UI/Components/Charts/ChartLibrary/Utils/TooltipEntries";
8
+
9
+ /*
10
+ * The tooltip ordering contract: on a grouped chart the series that is
11
+ * spiking AT THE HOVERED TIMESTAMP must be the first tooltip line, the
12
+ * list must stay readable at high cardinality (cap + "+N more"), and
13
+ * non-data entries (hidden series, band fills) must never displace a real
14
+ * reading. The chips already rank by peak; the tooltip is the only place
15
+ * the user reads per-timestamp values, so its order is what answers
16
+ * "which host is this spike?".
17
+ */
18
+
19
+ interface TestItem extends SortableTooltipItem {
20
+ color?: string;
21
+ }
22
+
23
+ function item(category: string, value: number, type?: string): TestItem {
24
+ return { category, value, ...(type ? { type } : {}) };
25
+ }
26
+
27
+ function categories(prepared: PreparedTooltipEntries<TestItem>): Array<string> {
28
+ return prepared.entries.map((entry: TestItem) => {
29
+ return entry.category;
30
+ });
31
+ }
32
+
33
+ describe("prepareTooltipEntries", () => {
34
+ test("orders entries by value at the hovered point, highest first", () => {
35
+ const prepared: PreparedTooltipEntries<TestItem> = prepareTooltipEntries([
36
+ item("host-b", 10),
37
+ item("host-a", 90),
38
+ item("host-c", 40),
39
+ ]);
40
+
41
+ expect(categories(prepared)).toEqual(["host-a", "host-c", "host-b"]);
42
+ expect(prepared.overflowCount).toBe(0);
43
+ expect(prepared.totalCount).toBe(3);
44
+ });
45
+
46
+ test("breaks value ties by natural name order (cpu2 before cpu10)", () => {
47
+ const prepared: PreparedTooltipEntries<TestItem> = prepareTooltipEntries([
48
+ item("cpu10", 5),
49
+ item("cpu2", 5),
50
+ item("cpu1", 5),
51
+ ]);
52
+
53
+ expect(categories(prepared)).toEqual(["cpu1", "cpu2", "cpu10"]);
54
+ });
55
+
56
+ test("sorts non-finite values last without dropping them", () => {
57
+ const prepared: PreparedTooltipEntries<TestItem> = prepareTooltipEntries([
58
+ item("nan-series", NaN),
59
+ item("real-low", 1),
60
+ item("infinite", Infinity),
61
+ item("real-high", 7),
62
+ // The anomaly band's dataKey yields a [low, high] tuple at runtime.
63
+ item("band", [2, 9] as unknown as number),
64
+ ]);
65
+
66
+ expect(categories(prepared).slice(0, 2)).toEqual(["real-high", "real-low"]);
67
+ // Non-finite tail keeps natural name order among itself.
68
+ expect(categories(prepared).slice(2)).toEqual([
69
+ "band",
70
+ "infinite",
71
+ "nan-series",
72
+ ]);
73
+ expect(prepared.totalCount).toBe(5);
74
+ });
75
+
76
+ test('filters out type "none" entries (hidden click-target lines, band fills)', () => {
77
+ const prepared: PreparedTooltipEntries<TestItem> = prepareTooltipEntries([
78
+ item("visible", 3),
79
+ item("hidden", 99, "none"),
80
+ ]);
81
+
82
+ expect(categories(prepared)).toEqual(["visible"]);
83
+ expect(prepared.totalCount).toBe(1);
84
+ expect(prepared.overflowCount).toBe(0);
85
+ });
86
+
87
+ test("caps at the default and reports the overflow", () => {
88
+ const payload: Array<TestItem> = Array.from(
89
+ { length: 25 },
90
+ (_: unknown, index: number) => {
91
+ return item(`series-${index}`, index);
92
+ },
93
+ );
94
+
95
+ const prepared: PreparedTooltipEntries<TestItem> =
96
+ prepareTooltipEntries(payload);
97
+
98
+ expect(prepared.entries).toHaveLength(DEFAULT_TOOLTIP_MAX_ENTRIES);
99
+ expect(prepared.overflowCount).toBe(25 - DEFAULT_TOOLTIP_MAX_ENTRIES);
100
+ expect(prepared.totalCount).toBe(25);
101
+ // The cap keeps the HIGHEST values, not the first-mounted series.
102
+ expect(categories(prepared)[0]).toBe("series-24");
103
+ expect(categories(prepared)[DEFAULT_TOOLTIP_MAX_ENTRIES - 1]).toBe(
104
+ `series-${25 - DEFAULT_TOOLTIP_MAX_ENTRIES}`,
105
+ );
106
+ });
107
+
108
+ test("exactly at the cap means no overflow", () => {
109
+ const payload: Array<TestItem> = Array.from(
110
+ { length: DEFAULT_TOOLTIP_MAX_ENTRIES },
111
+ (_: unknown, index: number) => {
112
+ return item(`series-${index}`, index);
113
+ },
114
+ );
115
+
116
+ const prepared: PreparedTooltipEntries<TestItem> =
117
+ prepareTooltipEntries(payload);
118
+
119
+ expect(prepared.entries).toHaveLength(DEFAULT_TOOLTIP_MAX_ENTRIES);
120
+ expect(prepared.overflowCount).toBe(0);
121
+ });
122
+
123
+ test("honors a custom cap and rejects nonsense caps", () => {
124
+ const payload: Array<TestItem> = [item("a", 1), item("b", 2), item("c", 3)];
125
+
126
+ expect(prepareTooltipEntries(payload, 2).entries).toHaveLength(2);
127
+ expect(prepareTooltipEntries(payload, 2).overflowCount).toBe(1);
128
+
129
+ // Zero, negative, and fractional caps fall back to the default.
130
+ expect(prepareTooltipEntries(payload, 0).entries).toHaveLength(3);
131
+ expect(prepareTooltipEntries(payload, -5).entries).toHaveLength(3);
132
+ expect(prepareTooltipEntries(payload, 1.5).entries).toHaveLength(3);
133
+ });
134
+
135
+ test("tolerates empty, null, and undefined payloads", () => {
136
+ for (const payload of [[], null, undefined]) {
137
+ const prepared: PreparedTooltipEntries<TestItem> = prepareTooltipEntries(
138
+ payload as Array<TestItem>,
139
+ );
140
+ expect(prepared.entries).toEqual([]);
141
+ expect(prepared.overflowCount).toBe(0);
142
+ expect(prepared.totalCount).toBe(0);
143
+ }
144
+ });
145
+
146
+ test("does not mutate the input payload", () => {
147
+ const payload: Array<TestItem> = [item("low", 1), item("high", 2)];
148
+ prepareTooltipEntries(payload);
149
+ expect(
150
+ payload.map((entry: TestItem) => {
151
+ return entry.category;
152
+ }),
153
+ ).toEqual(["low", "high"]);
154
+ });
155
+ });
@@ -0,0 +1,347 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ jest,
8
+ test,
9
+ } from "@jest/globals";
10
+ import { cleanup, render } from "@testing-library/react";
11
+ import React, { ReactElement } from "react";
12
+ import getJestMockFunction, { MockFunction } from "../../../MockType";
13
+
14
+ const loggerErrorMock: MockFunction = getJestMockFunction();
15
+
16
+ /*
17
+ * The arrow wrapper is load bearing: jest.mock is hoisted above the compiled
18
+ * requires, so loggerErrorMock is still unassigned when the factory runs.
19
+ * Dereferencing it lazily, at call time, is what makes this work.
20
+ */
21
+ jest.mock("../../../../UI/Utils/Logger", () => {
22
+ return {
23
+ __esModule: true,
24
+ Logger: {
25
+ error: (...args: Array<any>) => {
26
+ return loggerErrorMock(...args);
27
+ },
28
+ warn: () => {
29
+ return undefined;
30
+ },
31
+ log: () => {
32
+ return undefined;
33
+ },
34
+ info: () => {
35
+ return undefined;
36
+ },
37
+ },
38
+ };
39
+ });
40
+
41
+ import Detail from "../../../../UI/Components/Detail/Detail";
42
+ import Field from "../../../../UI/Components/Detail/Field";
43
+ import FieldType from "../../../../UI/Components/Types/FieldType";
44
+ import IncidentSeverity from "../../../../Models/DatabaseModels/IncidentSeverity";
45
+ import BadDataException from "../../../../Types/Exception/BadDataException";
46
+ import Color from "../../../../Types/Color";
47
+ import ObjectID from "../../../../Types/ObjectID";
48
+
49
+ /*
50
+ * Detail's getField is a plain closure called from Detail's own render, so a
51
+ * throw out of a field's getElement used to unwind the entire route up to the
52
+ * app level ErrorBoundary - replacing the whole page with "Something went
53
+ * wrong" over one bad cell. That is issue #3374: an Incident Episode with no
54
+ * severity threw "Episode Severity not found" from the severity renderer and
55
+ * took the detail page with it.
56
+ *
57
+ * The two call sites in that issue are fixed at source, but the same shape
58
+ * exists in ~20 other getElement callbacks across the dashboard. These tests
59
+ * pin the framework level guard: a throwing field renderer degrades to
60
+ * whatever the field type already rendered, logs the misconfiguration, and
61
+ * costs exactly one field - never the page.
62
+ */
63
+
64
+ interface DetailItem {
65
+ incidentSeverity?: IncidentSeverity | undefined;
66
+ title?: string | undefined;
67
+ description?: string | undefined;
68
+ }
69
+
70
+ type RenderDetailFunction = (
71
+ item: DetailItem,
72
+ fields: Array<Field<DetailItem>>,
73
+ ) => HTMLElement;
74
+
75
+ const renderDetail: RenderDetailFunction = (
76
+ item: DetailItem,
77
+ fields: Array<Field<DetailItem>>,
78
+ ): HTMLElement => {
79
+ const { container } = render(
80
+ <Detail<DetailItem>
81
+ item={item}
82
+ fields={fields}
83
+ showDetailsInNumberOfColumns={1}
84
+ />,
85
+ );
86
+
87
+ return container;
88
+ };
89
+
90
+ type BuildSeverityFunction = (name: string, color?: Color) => IncidentSeverity;
91
+
92
+ const buildSeverity: BuildSeverityFunction = (
93
+ name: string,
94
+ color?: Color,
95
+ ): IncidentSeverity => {
96
+ const severity: IncidentSeverity = new IncidentSeverity();
97
+ severity._id = new ObjectID("severity-id").toString();
98
+ severity.name = name;
99
+
100
+ if (color) {
101
+ severity.color = color;
102
+ }
103
+
104
+ return severity;
105
+ };
106
+
107
+ type ThrowingFieldFunction = (
108
+ key: keyof DetailItem,
109
+ title: string,
110
+ error: unknown,
111
+ ) => Field<DetailItem>;
112
+
113
+ const throwingField: ThrowingFieldFunction = (
114
+ key: keyof DetailItem,
115
+ title: string,
116
+ error: unknown,
117
+ ): Field<DetailItem> => {
118
+ return {
119
+ key: key,
120
+ title: title,
121
+ fieldType: FieldType.Element,
122
+ getElement: (): ReactElement => {
123
+ throw error;
124
+ },
125
+ };
126
+ };
127
+
128
+ const badgeSelector: string = '[data-dropdown-value-badge="true"]';
129
+
130
+ beforeEach(() => {
131
+ loggerErrorMock.mockClear();
132
+ });
133
+
134
+ afterEach(() => {
135
+ cleanup();
136
+ });
137
+
138
+ describe("Detail: a throwing field renderer never takes down the page", () => {
139
+ test("does not rethrow out of render when getElement throws", () => {
140
+ expect(() => {
141
+ renderDetail({ title: "Episode one" }, [
142
+ throwingField("title", "Episode Title", new Error("boom")),
143
+ ]);
144
+ }).not.toThrow();
145
+ });
146
+
147
+ test("renders the surrounding grid, so the route survives", () => {
148
+ const container: HTMLElement = renderDetail({ title: "Episode one" }, [
149
+ throwingField("title", "Episode Title", new Error("boom")),
150
+ ]);
151
+
152
+ // The label is still rendered - the row exists, only its value is missing.
153
+ expect(container.textContent).toContain("Episode Title");
154
+ });
155
+
156
+ test("costs exactly one field - the siblings still render their values", () => {
157
+ const container: HTMLElement = renderDetail(
158
+ { title: "Episode one", description: "A long running episode" },
159
+ [
160
+ throwingField("title", "Episode Title", new Error("boom")),
161
+ {
162
+ key: "description",
163
+ title: "Description",
164
+ fieldType: FieldType.Text,
165
+ },
166
+ ],
167
+ );
168
+
169
+ expect(container.textContent).toContain("Description");
170
+ expect(container.textContent).toContain("A long running episode");
171
+ });
172
+
173
+ test("logs the misconfiguration once, naming the field and the message", () => {
174
+ renderDetail({ title: "Episode one" }, [
175
+ throwingField(
176
+ "title",
177
+ "Episode Title",
178
+ new BadDataException("Episode Severity not found"),
179
+ ),
180
+ ]);
181
+
182
+ expect(loggerErrorMock).toHaveBeenCalledTimes(1);
183
+
184
+ const logged: string = String(loggerErrorMock.mock.calls[0]?.[0]);
185
+
186
+ expect(logged).toContain("title");
187
+ expect(logged).toContain("Episode Severity not found");
188
+ });
189
+
190
+ test("logs once per throwing field when several fields throw", () => {
191
+ const container: HTMLElement = renderDetail(
192
+ { title: "Episode one", description: "A long running episode" },
193
+ [
194
+ throwingField("title", "Episode Title", new Error("first")),
195
+ throwingField("description", "Description", new Error("second")),
196
+ ],
197
+ );
198
+
199
+ expect(loggerErrorMock).toHaveBeenCalledTimes(2);
200
+ expect(container.textContent).toContain("Episode Title");
201
+ expect(container.textContent).toContain("Description");
202
+ });
203
+
204
+ test("catches a thrown non Error value and still logs it", () => {
205
+ expect(() => {
206
+ renderDetail({ title: "Episode one" }, [
207
+ throwingField("title", "Episode Title", "a bare string"),
208
+ ]);
209
+ }).not.toThrow();
210
+
211
+ expect(loggerErrorMock).toHaveBeenCalledTimes(1);
212
+ expect(String(loggerErrorMock.mock.calls[0]?.[0])).toContain(
213
+ "a bare string",
214
+ );
215
+ });
216
+
217
+ test("keeps the entity badge the field type already rendered", () => {
218
+ /*
219
+ * data is computed from the field key and transformed by the Entity
220
+ * branch before getElement runs, so swallowing the throw leaves the
221
+ * framework's own rendering of the relation in place.
222
+ */
223
+ const container: HTMLElement = renderDetail(
224
+ { incidentSeverity: buildSeverity("Critical", new Color("#ef4444")) },
225
+ [
226
+ {
227
+ key: "incidentSeverity",
228
+ title: "Episode Severity",
229
+ fieldType: FieldType.Entity,
230
+ getElement: (): ReactElement => {
231
+ throw new BadDataException("Episode Severity not found");
232
+ },
233
+ },
234
+ ],
235
+ );
236
+
237
+ const badge: HTMLElement | null =
238
+ container.querySelector<HTMLElement>(badgeSelector);
239
+
240
+ expect(badge).not.toBeNull();
241
+ expect(badge?.textContent).toContain("Critical");
242
+ });
243
+
244
+ test("falls back to the field placeholder when the relation is absent", () => {
245
+ const container: HTMLElement = renderDetail({}, [
246
+ {
247
+ key: "incidentSeverity",
248
+ title: "Episode Severity",
249
+ fieldType: FieldType.Entity,
250
+ placeholder: "No severity",
251
+ getElement: (): ReactElement => {
252
+ throw new BadDataException("Episode Severity not found");
253
+ },
254
+ },
255
+ ]);
256
+
257
+ expect(container.textContent).toContain("No severity");
258
+ expect(container.querySelector(badgeSelector)).toBeNull();
259
+ });
260
+
261
+ test("never shows the app level error boundary copy", () => {
262
+ const container: HTMLElement = renderDetail({ title: "Episode one" }, [
263
+ throwingField("title", "Episode Title", new Error("boom")),
264
+ ]);
265
+
266
+ expect(container.textContent).not.toContain("Something went wrong");
267
+ expect(container.textContent).not.toContain(
268
+ "An unexpected error has occurred",
269
+ );
270
+ });
271
+ });
272
+
273
+ describe("Detail: fields that do not throw are untouched by the guard", () => {
274
+ test("renders the element a healthy getElement returns", () => {
275
+ const container: HTMLElement = renderDetail({ title: "Episode one" }, [
276
+ {
277
+ key: "title",
278
+ title: "Episode Title",
279
+ fieldType: FieldType.Element,
280
+ getElement: (item: DetailItem): ReactElement => {
281
+ return <span data-testid="custom">{item.title}</span>;
282
+ },
283
+ },
284
+ ]);
285
+
286
+ expect(
287
+ container.querySelector<HTMLElement>('[data-testid="custom"]')
288
+ ?.textContent,
289
+ ).toEqual("Episode one");
290
+ });
291
+
292
+ test("does not log anything when no field throws", () => {
293
+ renderDetail({ title: "Episode one" }, [
294
+ {
295
+ key: "title",
296
+ title: "Episode Title",
297
+ fieldType: FieldType.Text,
298
+ },
299
+ ]);
300
+
301
+ expect(loggerErrorMock).not.toHaveBeenCalled();
302
+ });
303
+
304
+ test("getElement still wins over the value the field type computed", () => {
305
+ const container: HTMLElement = renderDetail(
306
+ { incidentSeverity: buildSeverity("Critical", new Color("#ef4444")) },
307
+ [
308
+ {
309
+ key: "incidentSeverity",
310
+ title: "Episode Severity",
311
+ fieldType: FieldType.Entity,
312
+ getElement: (): ReactElement => {
313
+ return <span data-testid="custom">Handled</span>;
314
+ },
315
+ },
316
+ ],
317
+ );
318
+
319
+ expect(container.querySelector('[data-testid="custom"]')).not.toBeNull();
320
+ expect(container.querySelector(badgeSelector)).toBeNull();
321
+ });
322
+
323
+ test("a getElement that returns a placeholder dash renders the dash", () => {
324
+ /*
325
+ * The shape both episode detail pages now use for an absent severity or
326
+ * state. It must survive the guard untouched and must not log.
327
+ */
328
+ const container: HTMLElement = renderDetail({}, [
329
+ {
330
+ key: "incidentSeverity",
331
+ title: "Episode Severity",
332
+ fieldType: FieldType.Entity,
333
+ getElement: (item: DetailItem): ReactElement => {
334
+ if (!item["incidentSeverity"]) {
335
+ return <>-</>;
336
+ }
337
+
338
+ return <span data-testid="custom">present</span>;
339
+ },
340
+ },
341
+ ]);
342
+
343
+ expect(container.textContent).toContain("Episode Severity");
344
+ expect(container.textContent).toContain("-");
345
+ expect(loggerErrorMock).not.toHaveBeenCalled();
346
+ });
347
+ });