@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
@@ -1,208 +0,0 @@
1
- import axios, { AxiosResponse } from "axios";
2
- import {
3
- RedditAdsAccountId,
4
- RedditAdsOAuthClientId,
5
- RedditAdsOAuthClientSecret,
6
- RedditAdsOAuthRefreshToken,
7
- } from "../../../EnvironmentConfig";
8
- import ConversionUploadProvider, {
9
- ConversionSkip,
10
- ConversionTypeMapping,
11
- ConversionUploadBatchResult,
12
- } from "../ConversionUploadProvider";
13
- import { JSONObject } from "../../../../Types/JSON";
14
- import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
15
- import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
16
-
17
- const REQUEST_TIMEOUT_MS: number = 30000;
18
-
19
- // Reddit accepts conversion events up to 7 days old.
20
- const REDDIT_MAX_EVENT_AGE_IN_DAYS: number = 7;
21
-
22
- /*
23
- * Reddit Conversions API
24
- * (ads-api.reddit.com/api/v2.0/conversions/events/{accountId}) using the
25
- * rdt_cid click id or a SHA-256 email, whichever the conversion carries.
26
- * conversion_id (the conversion row id) makes retried uploads idempotent on
27
- * Reddit's side. OAuth2 refresh-token flow with HTTP basic auth against
28
- * reddit.com.
29
- */
30
- export default class RedditProvider extends ConversionUploadProvider {
31
- public override readonly key: string = "reddit";
32
- public override readonly displayName: string = "Reddit";
33
-
34
- private cachedAccessToken: string | null = null;
35
- private cachedAccessTokenExpiresAt: number = 0;
36
-
37
- public override isConfigured(): boolean {
38
- return Boolean(
39
- RedditAdsOAuthClientId &&
40
- RedditAdsOAuthClientSecret &&
41
- RedditAdsOAuthRefreshToken &&
42
- RedditAdsAccountId,
43
- );
44
- }
45
-
46
- protected override getProviderSkipReason(
47
- conversion: MarketingConversion,
48
- ): ConversionSkip | null {
49
- if (
50
- !this.getClickId(conversion, "rdt_cid") &&
51
- !this.getHashedEmail(conversion)
52
- ) {
53
- return {
54
- reason: "No Reddit click id (rdt_cid) and no email to match on",
55
- isPermanent: true,
56
- };
57
- }
58
-
59
- if (!this.getTrackingType(conversion)) {
60
- return {
61
- reason: "No Reddit tracking type mapped for this conversion type",
62
- isPermanent: true,
63
- };
64
- }
65
-
66
- if (this.isOlderThanDays(conversion, REDDIT_MAX_EVENT_AGE_IN_DAYS)) {
67
- return {
68
- reason: "Conversion older than Reddit's 7-day upload window",
69
- isPermanent: true,
70
- };
71
- }
72
-
73
- return null;
74
- }
75
-
76
- /*
77
- * Reddit standard tracking types. A booked meeting is `Lead`: Reddit has no
78
- * distinct "meeting booked" type, and reporting it as Purchase would put a
79
- * demo into the same optimisation pool as revenue.
80
- */
81
- private getTrackingType(conversion: MarketingConversion): string | undefined {
82
- const mapping: ConversionTypeMapping<string> = {
83
- [MarketingConversionType.SignUp]: "SignUp",
84
- [MarketingConversionType.MeetingBooked]: "Lead",
85
- [MarketingConversionType.PaidSubscription]: "Purchase",
86
- };
87
-
88
- return this.resolveByConversionType(conversion, mapping);
89
- }
90
-
91
- private async getAccessToken(): Promise<string> {
92
- if (
93
- this.cachedAccessToken &&
94
- Date.now() < this.cachedAccessTokenExpiresAt
95
- ) {
96
- return this.cachedAccessToken;
97
- }
98
-
99
- const params: URLSearchParams = new URLSearchParams();
100
- params.append("grant_type", "refresh_token");
101
- params.append("refresh_token", RedditAdsOAuthRefreshToken);
102
-
103
- const response: AxiosResponse<{
104
- access_token?: string;
105
- expires_in?: number;
106
- }> = await axios.post(
107
- "https://www.reddit.com/api/v1/access_token",
108
- params.toString(),
109
- {
110
- auth: {
111
- username: RedditAdsOAuthClientId,
112
- password: RedditAdsOAuthClientSecret,
113
- },
114
- headers: {
115
- "content-type": "application/x-www-form-urlencoded",
116
- // Reddit throttles/blocks requests without a descriptive User-Agent.
117
- "User-Agent": "OneUptime-ConversionsUpload/1.0",
118
- },
119
- timeout: REQUEST_TIMEOUT_MS,
120
- },
121
- );
122
-
123
- const accessToken: string | undefined = response.data?.access_token;
124
-
125
- if (!accessToken) {
126
- throw new Error("Reddit OAuth token response had no access_token");
127
- }
128
-
129
- this.cachedAccessToken = accessToken;
130
- this.cachedAccessTokenExpiresAt =
131
- Date.now() + ((response.data?.expires_in || 3600) - 60) * 1000;
132
-
133
- return accessToken;
134
- }
135
-
136
- public override async upload(
137
- conversions: Array<MarketingConversion>,
138
- ): Promise<ConversionUploadBatchResult> {
139
- const accessToken: string = await this.getAccessToken();
140
-
141
- const events: Array<JSONObject> = conversions.map(
142
- (conversion: MarketingConversion) => {
143
- const eventAt: Date = conversion.conversionAt || new Date();
144
-
145
- const eventMetadata: JSONObject = {
146
- // Dedup key: retried uploads are idempotent on Reddit's side.
147
- conversion_id: conversion.id!.toString(),
148
- };
149
-
150
- const valueInUSD: number | undefined = this.getValueInUSD(conversion);
151
-
152
- if (valueInUSD !== undefined) {
153
- eventMetadata["value_decimal"] = valueInUSD;
154
- eventMetadata["currency"] = "USD";
155
- }
156
-
157
- const payload: JSONObject = {
158
- event_at: eventAt.toISOString(),
159
- event_type: {
160
- tracking_type: this.getTrackingType(conversion) || "Lead",
161
- },
162
- event_metadata: eventMetadata,
163
- };
164
-
165
- const clickId: string | undefined = this.getClickId(
166
- conversion,
167
- "rdt_cid",
168
- );
169
-
170
- if (clickId) {
171
- payload["click_id"] = clickId;
172
- }
173
-
174
- const hashedEmail: string | undefined = this.getHashedEmail(conversion);
175
-
176
- if (hashedEmail) {
177
- payload["user"] = {
178
- email: hashedEmail,
179
- };
180
- }
181
-
182
- return payload;
183
- },
184
- );
185
-
186
- await axios.post(
187
- `https://ads-api.reddit.com/api/v2.0/conversions/events/${RedditAdsAccountId}`,
188
- {
189
- test_mode: false,
190
- events: events,
191
- },
192
- {
193
- headers: {
194
- Authorization: `Bearer ${accessToken}`,
195
- "Content-Type": "application/json",
196
- },
197
- timeout: REQUEST_TIMEOUT_MS,
198
- },
199
- );
200
-
201
- /*
202
- * Reddit accepts or rejects the request as a whole; request-level
203
- * failures throw and the batch is retried (idempotent via
204
- * conversion_id).
205
- */
206
- return { permanentFailures: new Map<number, string>() };
207
- }
208
- }
@@ -1,315 +0,0 @@
1
- import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
2
- import ConversionUploadProvider, {
3
- ConversionSkip,
4
- ConversionUploadBatchResult,
5
- } from "../../../../Server/Utils/Marketing/ConversionUploadProvider";
6
- import GoogleAdsProvider from "../../../../Server/Utils/Marketing/Providers/GoogleAds";
7
- import LinkedInProvider from "../../../../Server/Utils/Marketing/Providers/LinkedIn";
8
- import MetaProvider from "../../../../Server/Utils/Marketing/Providers/Meta";
9
- import MicrosoftAdsProvider from "../../../../Server/Utils/Marketing/Providers/MicrosoftAds";
10
- import RedditAdsProvider from "../../../../Server/Utils/Marketing/Providers/Reddit";
11
- import {
12
- AdUploadableMarketingConversionTypes,
13
- LeadMarketingConversionTypes,
14
- MarketingConversionType,
15
- } from "../../../../Types/Marketing/MarketingConversion";
16
- import { beforeEach, describe, expect, test } from "@jest/globals";
17
-
18
- /*
19
- * ---------------------------------------------------------------------------
20
- * Which conversion types may be uploaded to an ad platform, and as what.
21
- *
22
- * THE HAZARD THIS GUARDS
23
- *
24
- * Providers used to choose their platform conversion action with a two-way
25
- * branch:
26
- *
27
- * this.isSignUp(conversion) ? signUpAction : paidSubscriptionAction
28
- *
29
- * There was no third arm, so a conversion type nobody had written a mapping
30
- * for did not get skipped by that branch — it was uploaded to Google, Meta,
31
- * Microsoft, LinkedIn and Reddit as a PURCHASE carrying whatever revenue the
32
- * row happened to hold. The old defence was to keep such types out of the
33
- * uploadable allowlist entirely, which kept sales-led conversions out of the
34
- * ad platforms altogether.
35
- *
36
- * They are in the allowlist now, and the defence is structural instead: every
37
- * provider resolves its action through a Record over the conversion-type enum,
38
- * which the compiler will not accept unless every member is named. What is
39
- * left for these tests is everything the type system cannot see:
40
- *
41
- * - the column is a plain varchar, so a value that is not a known type at
42
- * all must still be refused, permanently, before any provider sees it;
43
- * - an unconfigured mapping must leave the row PENDING, not discard it —
44
- * the difference between "we have not set this up yet" and "this can never
45
- * be uploaded";
46
- * - a lead (a booked meeting, a licence request) must never carry revenue to
47
- * any platform, whatever the row says, or bid models optimise towards
48
- * demos that buy nothing.
49
- * ---------------------------------------------------------------------------
50
- */
51
-
52
- class TestProvider extends ConversionUploadProvider {
53
- public override readonly key: string = "test";
54
- public override readonly displayName: string = "Test Provider";
55
- public providerSkipCalls: number = 0;
56
- public providerSkip: ConversionSkip | null = null;
57
-
58
- public override isConfigured(): boolean {
59
- return true;
60
- }
61
-
62
- protected override getProviderSkipReason(
63
- _conversion: MarketingConversion,
64
- ): ConversionSkip | null {
65
- this.providerSkipCalls++;
66
- return this.providerSkip;
67
- }
68
-
69
- public override async upload(
70
- _conversions: Array<MarketingConversion>,
71
- ): Promise<ConversionUploadBatchResult> {
72
- return { permanentFailures: new Map<number, string>() };
73
- }
74
-
75
- public readValueInUSD(conversion: MarketingConversion): number | undefined {
76
- return this.getValueInUSD(conversion);
77
- }
78
- }
79
-
80
- type MakeConversionFunction = (
81
- data?: Partial<MarketingConversion>,
82
- ) => MarketingConversion;
83
-
84
- const makeConversion: MakeConversionFunction = (
85
- data: Partial<MarketingConversion> = {},
86
- ): MarketingConversion => {
87
- return Object.assign(new MarketingConversion(), {
88
- conversionType: MarketingConversionType.SignUp,
89
- conversionAt: new Date("2026-08-19T10:00:00.000Z"),
90
- // One click id per platform, so nothing is skipped for lack of one.
91
- clickIds: {
92
- gclid: "google-click",
93
- fbclid: "meta-click",
94
- msclkid: "microsoft-click",
95
- li_fat_id: "linkedin-click",
96
- rdt_cid: "reddit-click",
97
- },
98
- ...data,
99
- });
100
- };
101
-
102
- describe("ad-uploadable conversion types", () => {
103
- describe("the allowlist itself", () => {
104
- /*
105
- * Every type is uploadable now, which is only safe because every provider
106
- * maps every type. If a type is ever removed from this list again, that is
107
- * a deliberate "no platform should hear about this" decision and should be
108
- * spelled out here.
109
- */
110
- test("covers every conversion type the ledger records", () => {
111
- expect([...AdUploadableMarketingConversionTypes].sort()).toEqual(
112
- [...Object.values(MarketingConversionType)].sort(),
113
- );
114
- });
115
-
116
- /*
117
- * A booked meeting is the ONLY sales-led conversion. Asking about an
118
- * enterprise licence and booking an architecture assessment are the same
119
- * conversation reached through the same Cal embed, so they are the same
120
- * conversion — not two types that would split the signal in reporting and
121
- * need separate conversion actions on five platforms.
122
- */
123
- test("classifies a booked meeting as the only sales-led step", () => {
124
- expect(LeadMarketingConversionTypes).toEqual([
125
- MarketingConversionType.MeetingBooked,
126
- ]);
127
- });
128
-
129
- test("never classifies a completed transaction as a lead", () => {
130
- expect(LeadMarketingConversionTypes).not.toContain(
131
- MarketingConversionType.SignUp,
132
- );
133
- expect(LeadMarketingConversionTypes).not.toContain(
134
- MarketingConversionType.PaidSubscription,
135
- );
136
- });
137
- });
138
-
139
- describe("the base class screen", () => {
140
- let provider: TestProvider;
141
-
142
- beforeEach(() => {
143
- provider = new TestProvider();
144
- });
145
-
146
- test.each([
147
- ["an unknown type", "SomethingElse"],
148
- ["an empty type", ""],
149
- ["a lookalike with different casing", "meetingbooked"],
150
- ["a lookalike with different casing", "signup"],
151
- ["a lookalike with different casing", "enterpriselicenserequested"],
152
- ])("permanently skips %s", (_label: string, conversionType: string) => {
153
- const skip: ConversionSkip | null = provider.getSkipReason(
154
- makeConversion({ conversionType: conversionType }),
155
- );
156
-
157
- expect(skip?.isPermanent).toBe(true);
158
- // The provider is never consulted about a type the ledger cannot name.
159
- expect(provider.providerSkipCalls).toBe(0);
160
- });
161
-
162
- test("names an absent conversion type as unknown rather than blank", () => {
163
- const conversion: MarketingConversion = makeConversion();
164
- delete conversion.conversionType;
165
-
166
- expect(provider.getSkipReason(conversion)).toEqual({
167
- reason: "Conversion type unknown has no ad platform conversion mapping",
168
- isPermanent: true,
169
- });
170
- });
171
-
172
- test.each(Object.values(MarketingConversionType))(
173
- "delegates %s to the provider",
174
- (conversionType: MarketingConversionType) => {
175
- expect(
176
- provider.getSkipReason(
177
- makeConversion({ conversionType: conversionType }),
178
- ),
179
- ).toBeNull();
180
- expect(provider.providerSkipCalls).toBe(1);
181
- },
182
- );
183
-
184
- test("returns the provider's own skip verbatim for a mapped type", () => {
185
- provider.providerSkip = {
186
- reason: "No Google click id",
187
- isPermanent: true,
188
- };
189
-
190
- expect(provider.getSkipReason(makeConversion())).toEqual({
191
- reason: "No Google click id",
192
- isPermanent: true,
193
- });
194
- });
195
- });
196
-
197
- describe("lead conversions never carry revenue", () => {
198
- const provider: TestProvider = new TestProvider();
199
-
200
- test.each(LeadMarketingConversionTypes)(
201
- "%s reports no value even when the row holds one",
202
- (conversionType: MarketingConversionType) => {
203
- /*
204
- * A row can legitimately hold a value here — nothing stops an operator
205
- * assigning an estimated pipeline value to a booked meeting — and the
206
- * platform still must not be told it as revenue.
207
- */
208
- expect(
209
- provider.readValueInUSD(
210
- makeConversion({
211
- conversionType: conversionType,
212
- conversionValueInUSDCents: 500000,
213
- }),
214
- ),
215
- ).toBeUndefined();
216
- },
217
- );
218
-
219
- test("a paid subscription still reports its value", () => {
220
- expect(
221
- provider.readValueInUSD(
222
- makeConversion({
223
- conversionType: MarketingConversionType.PaidSubscription,
224
- conversionValueInUSDCents: 500000,
225
- }),
226
- ),
227
- ).toBe(5000);
228
- });
229
- });
230
-
231
- /*
232
- * Exercised through the real providers too: a structural guarantee in the
233
- * base class is only worth something if no provider has quietly overridden
234
- * getSkipReason back.
235
- *
236
- * These run with no ad-platform environment configured, which is the state a
237
- * fresh checkout and CI are in — so what they assert is the behaviour of an
238
- * UNCONFIGURED deployment, which is where the dangerous failure mode lives:
239
- * a row silently thrown away is unrecoverable, a row left pending is not.
240
- */
241
- describe("every shipped provider, unconfigured", () => {
242
- const providers: Array<[string, ConversionUploadProvider]> = [
243
- ["Google Ads", new GoogleAdsProvider()],
244
- ["Meta", new MetaProvider()],
245
- ["Microsoft Ads", new MicrosoftAdsProvider()],
246
- ["LinkedIn", new LinkedInProvider()],
247
- ["Reddit Ads", new RedditAdsProvider()],
248
- ];
249
-
250
- test.each(providers)(
251
- "%s still permanently refuses an unknown conversion type",
252
- (_name: string, provider: ConversionUploadProvider) => {
253
- expect(
254
- provider.getSkipReason(
255
- makeConversion({ conversionType: "NotARealType" }),
256
- ),
257
- ).toEqual({
258
- reason:
259
- "Conversion type NotARealType has no ad platform conversion mapping",
260
- isPermanent: true,
261
- });
262
- },
263
- );
264
-
265
- /*
266
- * Google, Microsoft and LinkedIn each need an operator-supplied conversion
267
- * action per type. Unset, that is a config gap: the row waits.
268
- */
269
- const configuredByOperator: Array<[string, ConversionUploadProvider]> = [
270
- ["Google Ads", new GoogleAdsProvider()],
271
- ["Microsoft Ads", new MicrosoftAdsProvider()],
272
- ["LinkedIn", new LinkedInProvider()],
273
- ];
274
-
275
- test.each(configuredByOperator)(
276
- "%s leaves a booked meeting pending rather than discarding it",
277
- (_name: string, provider: ConversionUploadProvider) => {
278
- const skip: ConversionSkip | null = provider.getSkipReason(
279
- makeConversion({
280
- conversionType: MarketingConversionType.MeetingBooked,
281
- }),
282
- );
283
-
284
- expect(skip).not.toBeNull();
285
- expect(skip?.isPermanent).toBe(false);
286
- expect(skip?.reason).toMatch(/conversion (action id|name|rule id)/i);
287
- },
288
- );
289
-
290
- /*
291
- * Meta and Reddit name their events with platform constants rather than an
292
- * operator-configured id, so there is nothing to be missing: a booked
293
- * meeting is uploadable as soon as it has an identifier.
294
- */
295
- const selfDescribing: Array<[string, ConversionUploadProvider]> = [
296
- ["Meta", new MetaProvider()],
297
- ["Reddit Ads", new RedditAdsProvider()],
298
- ];
299
-
300
- test.each(selfDescribing)(
301
- "%s accepts a booked meeting outright",
302
- (_name: string, provider: ConversionUploadProvider) => {
303
- expect(
304
- provider.getSkipReason(
305
- makeConversion({
306
- conversionType: MarketingConversionType.MeetingBooked,
307
- // Inside Meta's and Reddit's 7-day window.
308
- conversionAt: new Date(),
309
- }),
310
- ),
311
- ).toBeNull();
312
- },
313
- );
314
- });
315
- });