@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,215 +0,0 @@
1
- import { AxiosError } from "axios";
2
- import Attribution from "../Attribution";
3
- import { JSONObject } from "../../../Types/JSON";
4
- import {
5
- AdUploadableMarketingConversionTypes,
6
- LeadMarketingConversionTypes,
7
- MarketingConversionType,
8
- } from "../../../Types/Marketing/MarketingConversion";
9
- import MarketingConversion from "../../../Models/DatabaseModels/MarketingConversion";
10
-
11
- /*
12
- * One implementation per ad platform. The MarketingConversions worker job
13
- * iterates all configured providers and uploads every pending conversion to
14
- * each of them, tracking per-provider status in
15
- * MarketingConversion.uploadState (keyed by `key`).
16
- *
17
- * Contract:
18
- * - getSkipReason returns a human-readable reason when this conversion can
19
- * never be uploaded to this platform (conversion type with no ad-platform
20
- * mapping, no identifier this platform can match on, outside the platform's
21
- * upload window, provider not configured for this conversion type). Null
22
- * means uploadable. It is implemented here and must not be overridden —
23
- * providers implement getProviderSkipReason instead, which only runs for
24
- * conversion types that are ad-uploadable at all.
25
- * - upload() sends one batch. Per-conversion PERMANENT rejections are
26
- * returned in the result keyed by array index; they will not be retried.
27
- * Transport/auth-level failures must THROW — the whole batch stays pending
28
- * and is retried on the next run (bounded by the job's attempt cap), so
29
- * providers should be safe against duplicate delivery where the platform
30
- * supports an idempotency/dedup key.
31
- *
32
- * IDENTIFIERS
33
- *
34
- * A conversion can be matched to an ad click two ways, and most platforms
35
- * accept either:
36
- *
37
- * - the click id the visitor carried (gclid, fbclid, msclkid, ...), which is
38
- * exact but only survives as far as the browser storage that held it; and
39
- * - the SHA-256 of the person's email, which every platform calls "enhanced
40
- * conversions"/"enhanced matching" and which survives a change of device,
41
- * a cleared browser, and the weeks between a demo and the deal it led to.
42
- *
43
- * Providers must consider a conversion uploadable when EITHER is present.
44
- * Requiring the click id — which is what every provider here used to do — threw
45
- * away exactly the sales-led conversions the enhanced-matching mechanism was
46
- * designed for.
47
- */
48
-
49
- export interface ConversionUploadBatchResult {
50
- // Index into the submitted batch -> permanent failure message.
51
- permanentFailures: Map<number, string>;
52
- }
53
-
54
- export interface ConversionSkip {
55
- reason: string;
56
- /*
57
- * Permanent skips (no usable identifier, outside the platform's upload
58
- * window) are recorded as Skipped and never revisited. Non-permanent skips
59
- * (missing provider configuration such as a conversion action id) leave
60
- * the conversion pending so it uploads once the configuration is added.
61
- */
62
- isPermanent: boolean;
63
- }
64
-
65
- /*
66
- * A per-conversion-type mapping. Written as a Record over the enum rather than
67
- * a switch or a two-way branch on purpose: the compiler refuses a Record that
68
- * omits a member, so adding a MarketingConversionType is a build error in every
69
- * provider until that provider states what the new type means on its platform.
70
- *
71
- * That is the guarantee Docs/analytics/enterprise-conversion-tracking.md asks
72
- * for. Before it existed, providers chose their conversion action with
73
- * `isSignUp(conversion) ? signUp : paidSubscription`, so a booked meeting added
74
- * to the uploadable list would have been reported to every ad platform as a
75
- * purchase.
76
- */
77
- export type ConversionTypeMapping<T> = Record<MarketingConversionType, T>;
78
-
79
- export default abstract class ConversionUploadProvider {
80
- // Stable key used in MarketingConversion.uploadState. Never change it.
81
- public abstract readonly key: string;
82
- public abstract readonly displayName: string;
83
-
84
- /*
85
- * Max conversions this provider accepts in one upload() call. Providers
86
- * that issue one HTTP request per conversion must keep this small so a
87
- * single run finishes well inside the worker job timeout.
88
- */
89
- public readonly maxBatchSize: number = 500;
90
-
91
- public abstract isConfigured(): boolean;
92
-
93
- /*
94
- * Screens conversion types the ledger records but no ad platform has a
95
- * mapping for, before any provider sees them.
96
- */
97
- public getSkipReason(conversion: MarketingConversion): ConversionSkip | null {
98
- if (
99
- !AdUploadableMarketingConversionTypes.includes(
100
- conversion.conversionType as MarketingConversionType,
101
- )
102
- ) {
103
- return {
104
- reason: `Conversion type ${
105
- conversion.conversionType || "unknown"
106
- } has no ad platform conversion mapping`,
107
- isPermanent: true,
108
- };
109
- }
110
-
111
- return this.getProviderSkipReason(conversion);
112
- }
113
-
114
- protected abstract getProviderSkipReason(
115
- conversion: MarketingConversion,
116
- ): ConversionSkip | null;
117
-
118
- public abstract upload(
119
- conversions: Array<MarketingConversion>,
120
- ): Promise<ConversionUploadBatchResult>;
121
-
122
- protected getClickId(
123
- conversion: MarketingConversion,
124
- clickIdKey: string,
125
- ): string | undefined {
126
- const clickIds: JSONObject = conversion.clickIds || {};
127
- const value: unknown = clickIds[clickIdKey];
128
- return typeof value === "string" && value ? value : undefined;
129
- }
130
-
131
- /*
132
- * The hashed email this conversion can be matched on, or undefined.
133
- *
134
- * Prefers the stored emailHash and falls back to hashing the email column,
135
- * so rows written before emailHash existed still match. Both paths go
136
- * through Attribution so the digest is identical whichever one produced it.
137
- */
138
- protected getHashedEmail(
139
- conversion: MarketingConversion,
140
- ): string | undefined {
141
- if (conversion.emailHash) {
142
- return conversion.emailHash;
143
- }
144
-
145
- return Attribution.hashEmail(conversion.email) || undefined;
146
- }
147
-
148
- /*
149
- * Resolve a per-type value, or undefined when the column holds a string that
150
- * is not a known conversion type. The column is a plain varchar, so this
151
- * cannot be assumed away even though the mapping itself is exhaustive.
152
- */
153
- protected resolveByConversionType<T>(
154
- conversion: MarketingConversion,
155
- mapping: ConversionTypeMapping<T>,
156
- ): T | undefined {
157
- const conversionType: MarketingConversionType =
158
- conversion.conversionType as MarketingConversionType;
159
-
160
- if (!Object.values(MarketingConversionType).includes(conversionType)) {
161
- return undefined;
162
- }
163
-
164
- return mapping[conversionType];
165
- }
166
-
167
- /*
168
- * A sales-led lead (booked meeting, enterprise licence request) rather than
169
- * a completed transaction.
170
- */
171
- protected isLead(conversion: MarketingConversion): boolean {
172
- return LeadMarketingConversionTypes.includes(
173
- conversion.conversionType as MarketingConversionType,
174
- );
175
- }
176
-
177
- /*
178
- * The revenue to report for this conversion, in USD.
179
- *
180
- * Leads return undefined regardless of what the row holds. A booked meeting
181
- * is not money, and reporting one as revenue would let a bidding algorithm
182
- * optimise towards booking meetings that never close.
183
- */
184
- protected getValueInUSD(conversion: MarketingConversion): number | undefined {
185
- if (this.isLead(conversion)) {
186
- return undefined;
187
- }
188
-
189
- if (
190
- conversion.conversionValueInUSDCents === undefined ||
191
- conversion.conversionValueInUSDCents === null ||
192
- conversion.conversionValueInUSDCents <= 0
193
- ) {
194
- return undefined;
195
- }
196
- return conversion.conversionValueInUSDCents / 100;
197
- }
198
-
199
- protected isOlderThanDays(
200
- conversion: MarketingConversion,
201
- days: number,
202
- ): boolean {
203
- const conversionAt: Date = conversion.conversionAt || new Date();
204
- return Date.now() - conversionAt.getTime() > days * 24 * 60 * 60 * 1000;
205
- }
206
-
207
- public static getErrorMessage(err: unknown): string {
208
- if (err instanceof AxiosError) {
209
- return `HTTP ${err.response?.status || "?"}: ${JSON.stringify(
210
- err.response?.data || err.message,
211
- ).slice(0, 900)}`;
212
- }
213
- return (err as Error)?.message || "Unknown error";
214
- }
215
- }
@@ -1,22 +0,0 @@
1
- import ConversionUploadProvider from "./ConversionUploadProvider";
2
- import GoogleAdsProvider from "./Providers/GoogleAds";
3
- import LinkedInProvider from "./Providers/LinkedIn";
4
- import MetaProvider from "./Providers/Meta";
5
- import MicrosoftAdsProvider from "./Providers/MicrosoftAds";
6
- import RedditProvider from "./Providers/Reddit";
7
-
8
- /*
9
- * All ad platforms OneUptime can upload offline conversions to. To add a
10
- * platform: implement ConversionUploadProvider (one file under Providers/),
11
- * add its env config to EnvironmentConfig, and list it here — no schema or
12
- * worker changes needed. Unconfigured providers are ignored at runtime.
13
- */
14
- const AllConversionUploadProviders: Array<ConversionUploadProvider> = [
15
- new GoogleAdsProvider(),
16
- new MetaProvider(),
17
- new MicrosoftAdsProvider(),
18
- new LinkedInProvider(),
19
- new RedditProvider(),
20
- ];
21
-
22
- export default AllConversionUploadProviders;
@@ -1,333 +0,0 @@
1
- import axios, { AxiosResponse } from "axios";
2
- import {
3
- GoogleAdsApiVersion,
4
- GoogleAdsCustomerId,
5
- GoogleAdsDeveloperToken,
6
- GoogleAdsEnhancedConversionsForLeadsEnabled,
7
- GoogleAdsLoginCustomerId,
8
- GoogleAdsMeetingBookedConversionActionId,
9
- GoogleAdsOAuthClientId,
10
- GoogleAdsOAuthClientSecret,
11
- GoogleAdsOAuthRefreshToken,
12
- GoogleAdsPaidSubscriptionConversionActionId,
13
- GoogleAdsSignUpConversionActionId,
14
- } from "../../../EnvironmentConfig";
15
- import ConversionUploadProvider, {
16
- ConversionSkip,
17
- ConversionTypeMapping,
18
- ConversionUploadBatchResult,
19
- } from "../ConversionUploadProvider";
20
- import { JSONObject } from "../../../../Types/JSON";
21
- import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
22
- import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
23
- import logger from "../../Logger";
24
-
25
- const REQUEST_TIMEOUT_MS: number = 30000;
26
-
27
- // Google Ads accepts click conversions up to 90 days after the click.
28
- const GOOGLE_MAX_CONVERSION_AGE_IN_DAYS: number = 90;
29
-
30
- /*
31
- * Google Ads offline click conversions
32
- * (customers/{id}:uploadClickConversions) with OAuth2 refresh-token auth.
33
- * Supports gclid, wbraid and gbraid click ids, plus enhanced conversions — a
34
- * SHA-256 email in userIdentifiers. Uses partialFailure so valid conversions
35
- * succeed even when others in the batch fail.
36
- *
37
- * A hashed email is attached to EVERY upload that has one, which only improves
38
- * matching. Uploading a conversion that has ONLY a hashed email — enhanced
39
- * conversions for leads, and the only way a sales-led deal months after the
40
- * click is ever attributed — additionally requires the account to be set up
41
- * for it, so it is gated on
42
- * GOOGLE_ADS_ENHANCED_CONVERSIONS_FOR_LEADS_ENABLED.
43
- */
44
- export default class GoogleAdsProvider extends ConversionUploadProvider {
45
- public override readonly key: string = "google";
46
- public override readonly displayName: string = "Google Ads";
47
-
48
- private cachedAccessToken: string | null = null;
49
- private cachedAccessTokenExpiresAt: number = 0;
50
-
51
- public override isConfigured(): boolean {
52
- return Boolean(
53
- GoogleAdsDeveloperToken &&
54
- GoogleAdsOAuthClientId &&
55
- GoogleAdsOAuthClientSecret &&
56
- GoogleAdsOAuthRefreshToken &&
57
- GoogleAdsCustomerId,
58
- );
59
- }
60
-
61
- protected override getProviderSkipReason(
62
- conversion: MarketingConversion,
63
- ): ConversionSkip | null {
64
- if (
65
- !this.getGoogleClickId(conversion) &&
66
- !this.canUploadAsLead(conversion)
67
- ) {
68
- return {
69
- reason: GoogleAdsEnhancedConversionsForLeadsEnabled
70
- ? "No Google click id (gclid/wbraid/gbraid) and no email to match on"
71
- : "No Google click id (gclid/wbraid/gbraid)",
72
- isPermanent: true,
73
- };
74
- }
75
-
76
- if (!this.getConversionActionId(conversion)) {
77
- // Config gap — leave pending so it uploads once the id is configured.
78
- return {
79
- reason:
80
- "No Google Ads conversion action id configured for this conversion type",
81
- isPermanent: false,
82
- };
83
- }
84
-
85
- if (this.isOlderThanDays(conversion, GOOGLE_MAX_CONVERSION_AGE_IN_DAYS)) {
86
- return {
87
- reason: "Conversion older than Google's 90-day click window",
88
- isPermanent: true,
89
- };
90
- }
91
-
92
- return null;
93
- }
94
-
95
- private getGoogleClickId(conversion: MarketingConversion): JSONObject | null {
96
- const gclid: string | undefined = this.getClickId(conversion, "gclid");
97
- if (gclid) {
98
- return { gclid: gclid };
99
- }
100
-
101
- const wbraid: string | undefined = this.getClickId(conversion, "wbraid");
102
- if (wbraid) {
103
- return { wbraid: wbraid };
104
- }
105
-
106
- const gbraid: string | undefined = this.getClickId(conversion, "gbraid");
107
- if (gbraid) {
108
- return { gbraid: gbraid };
109
- }
110
-
111
- return null;
112
- }
113
-
114
- /*
115
- * Enhanced conversions for leads: this conversion has no click id, but it
116
- * has an email and the account is configured to match on one.
117
- */
118
- private canUploadAsLead(conversion: MarketingConversion): boolean {
119
- return Boolean(
120
- GoogleAdsEnhancedConversionsForLeadsEnabled &&
121
- this.getHashedEmail(conversion),
122
- );
123
- }
124
-
125
- /*
126
- * userIdentifiers per the enhanced-conversions spec. FIRST_PARTY is the
127
- * correct source: the address was given to OneUptime directly, not bought or
128
- * inferred.
129
- */
130
- private getUserIdentifiers(
131
- conversion: MarketingConversion,
132
- ): Array<JSONObject> {
133
- const hashedEmail: string | undefined = this.getHashedEmail(conversion);
134
-
135
- if (!hashedEmail) {
136
- return [];
137
- }
138
-
139
- return [
140
- {
141
- hashedEmail: hashedEmail,
142
- userIdentifierSource: "FIRST_PARTY",
143
- },
144
- ];
145
- }
146
-
147
- private getConversionActionId(conversion: MarketingConversion): string {
148
- const mapping: ConversionTypeMapping<string> = {
149
- [MarketingConversionType.SignUp]: GoogleAdsSignUpConversionActionId,
150
- [MarketingConversionType.MeetingBooked]:
151
- GoogleAdsMeetingBookedConversionActionId,
152
- [MarketingConversionType.PaidSubscription]:
153
- GoogleAdsPaidSubscriptionConversionActionId,
154
- };
155
-
156
- return this.resolveByConversionType(conversion, mapping) || "";
157
- }
158
-
159
- private async getAccessToken(): Promise<string> {
160
- if (
161
- this.cachedAccessToken &&
162
- Date.now() < this.cachedAccessTokenExpiresAt
163
- ) {
164
- return this.cachedAccessToken;
165
- }
166
-
167
- const params: URLSearchParams = new URLSearchParams();
168
- params.append("client_id", GoogleAdsOAuthClientId);
169
- params.append("client_secret", GoogleAdsOAuthClientSecret);
170
- params.append("refresh_token", GoogleAdsOAuthRefreshToken);
171
- params.append("grant_type", "refresh_token");
172
-
173
- const response: AxiosResponse<{
174
- access_token?: string;
175
- expires_in?: number;
176
- }> = await axios.post(
177
- "https://oauth2.googleapis.com/token",
178
- params.toString(),
179
- {
180
- headers: {
181
- "content-type": "application/x-www-form-urlencoded",
182
- },
183
- timeout: REQUEST_TIMEOUT_MS,
184
- },
185
- );
186
-
187
- const accessToken: string | undefined = response.data?.access_token;
188
-
189
- if (!accessToken) {
190
- throw new Error("Google OAuth token response had no access_token");
191
- }
192
-
193
- this.cachedAccessToken = accessToken;
194
- // Refresh one minute before actual expiry.
195
- this.cachedAccessTokenExpiresAt =
196
- Date.now() + ((response.data?.expires_in || 3600) - 60) * 1000;
197
-
198
- return accessToken;
199
- }
200
-
201
- // Format Google Ads expects: "yyyy-mm-dd hh:mm:ss+00:00".
202
- private formatConversionDateTime(date: Date): string {
203
- const iso: string = date.toISOString(); // 2026-07-18T12:34:56.789Z
204
- return `${iso.slice(0, 10)} ${iso.slice(11, 19)}+00:00`;
205
- }
206
-
207
- public override async upload(
208
- conversions: Array<MarketingConversion>,
209
- ): Promise<ConversionUploadBatchResult> {
210
- const accessToken: string = await this.getAccessToken();
211
-
212
- const headers: JSONObject = {
213
- Authorization: `Bearer ${accessToken}`,
214
- "developer-token": GoogleAdsDeveloperToken,
215
- };
216
-
217
- if (GoogleAdsLoginCustomerId) {
218
- headers["login-customer-id"] = GoogleAdsLoginCustomerId;
219
- }
220
-
221
- const body: JSONObject = {
222
- conversions: conversions.map((conversion: MarketingConversion) => {
223
- const payload: JSONObject = {
224
- ...(this.getGoogleClickId(conversion) || {}),
225
- conversionAction: `customers/${GoogleAdsCustomerId}/conversionActions/${this.getConversionActionId(
226
- conversion,
227
- )}`,
228
- conversionDateTime: this.formatConversionDateTime(
229
- conversion.conversionAt || new Date(),
230
- ),
231
- };
232
-
233
- const userIdentifiers: Array<JSONObject> =
234
- this.getUserIdentifiers(conversion);
235
-
236
- if (userIdentifiers.length > 0) {
237
- payload["userIdentifiers"] = userIdentifiers;
238
- }
239
-
240
- const valueInUSD: number | undefined = this.getValueInUSD(conversion);
241
-
242
- if (valueInUSD !== undefined) {
243
- payload["conversionValue"] = valueInUSD;
244
- payload["currencyCode"] = "USD";
245
- }
246
-
247
- return payload;
248
- }),
249
- partialFailure: true,
250
- };
251
-
252
- const url: string = `https://googleads.googleapis.com/${GoogleAdsApiVersion}/customers/${GoogleAdsCustomerId}:uploadClickConversions`;
253
-
254
- const response: AxiosResponse<JSONObject> = await axios.post(url, body, {
255
- headers: headers as Record<string, string>,
256
- timeout: REQUEST_TIMEOUT_MS,
257
- });
258
-
259
- return this.parsePartialFailure(response.data);
260
- }
261
-
262
- /*
263
- * partialFailureError.details[].errors[] entries carry the index of the
264
- * failing operation in location.fieldPathElements ({fieldName:
265
- * "conversions", index: N}).
266
- */
267
- private parsePartialFailure(
268
- responseData: JSONObject,
269
- ): ConversionUploadBatchResult {
270
- const permanentFailures: Map<number, string> = new Map<number, string>();
271
-
272
- const partialFailureError: JSONObject | undefined = responseData?.[
273
- "partialFailureError"
274
- ] as JSONObject | undefined;
275
-
276
- if (!partialFailureError) {
277
- return { permanentFailures };
278
- }
279
-
280
- const details: Array<JSONObject> =
281
- (partialFailureError["details"] as Array<JSONObject>) || [];
282
-
283
- let alreadyExistsCount: number = 0;
284
-
285
- for (const detail of details) {
286
- const errors: Array<JSONObject> =
287
- (detail["errors"] as Array<JSONObject>) || [];
288
-
289
- for (const error of errors) {
290
- const message: string =
291
- (error["message"] as string) || "Unknown Google Ads error";
292
-
293
- /*
294
- * A duplicate rejection means the conversion WAS counted by a prior
295
- * run (e.g. crash between upload and status write) — that is a
296
- * success, not a failure.
297
- */
298
- if (JSON.stringify(error).includes("ALREADY_EXISTS")) {
299
- alreadyExistsCount++;
300
- continue;
301
- }
302
-
303
- const fieldPathElements: Array<JSONObject> =
304
- ((error["location"] as JSONObject)?.[
305
- "fieldPathElements"
306
- ] as Array<JSONObject>) || [];
307
-
308
- for (const element of fieldPathElements) {
309
- if (
310
- element["fieldName"] === "conversions" &&
311
- typeof element["index"] === "number"
312
- ) {
313
- permanentFailures.set(element["index"] as number, message);
314
- }
315
- }
316
- }
317
- }
318
-
319
- /*
320
- * A partial failure with no parseable indexes should not be silently
321
- * treated as full success.
322
- */
323
- if (permanentFailures.size === 0 && alreadyExistsCount === 0) {
324
- logger.error(
325
- `Google Ads partial failure with unparseable indexes: ${JSON.stringify(
326
- partialFailureError,
327
- ).slice(0, 1000)}`,
328
- );
329
- }
330
-
331
- return { permanentFailures };
332
- }
333
- }