@oneuptime/common 12.0.18 → 12.0.19
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.
- package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
- package/Models/DatabaseModels/Index.ts +4 -2
- package/Models/DatabaseModels/Monitor.ts +39 -9
- package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
- package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
- package/Models/DatabaseModels/UserSlack.ts +341 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
- package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
- package/Server/API/UserSlackAPI.ts +125 -0
- package/Server/EnvironmentConfig.ts +12 -107
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/Queue.ts +7 -0
- package/Server/Services/EnterpriseLicenseService.ts +60 -0
- package/Server/Services/Index.ts +6 -2
- package/Server/Services/LlmLogService.ts +25 -9
- package/Server/Services/OnCallReadinessService.ts +86 -2
- package/Server/Services/ProjectService.ts +67 -0
- package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
- package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
- package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
- package/Server/Services/UserNotificationRuleService.ts +589 -11
- package/Server/Services/UserNotificationSettingService.ts +138 -0
- package/Server/Services/UserService.ts +28 -0
- package/Server/Services/UserSlackService.ts +218 -0
- package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
- package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
- package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
- package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
- package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
- package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
- package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
- package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
- package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
- package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
- package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
- package/Tests/Server/Services/UserSlackService.test.ts +407 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
- package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
- package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
- package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
- package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
- package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
- package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
- package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
- package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
- package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Types/Marketing/MarketingEvent.ts +97 -0
- package/UI/Utils/NotificationMethodUtil.ts +41 -1
- package/UI/Utils/PermissionGate.ts +56 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -2
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
- package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
- package/build/dist/Server/API/UserSlackAPI.js +81 -0
- package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +10 -70
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +7 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +6 -2
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +25 -9
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +56 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +26 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSlackService.js +195 -0
- package/build/dist/Server/Services/UserSlackService.js.map +1 -0
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
- package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +32 -0
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/package.json +1 -1
- package/Models/DatabaseModels/MarketingConversion.ts +0 -410
- package/Server/Services/MarketingConversionService.ts +0 -10
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
- package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
- package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
- package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
- package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
- package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
- package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
- package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
- package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
- package/Types/Marketing/MarketingConversion.ts +0 -53
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
- package/build/dist/Server/Services/MarketingConversionService.js +0 -9
- package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
- package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
- package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import Attribution from "../../../../Server/Utils/Attribution";
|
|
2
|
-
import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
|
|
3
|
-
import MicrosoftAdsProvider from "../../../../Server/Utils/Marketing/Providers/MicrosoftAds";
|
|
4
|
-
import { ConversionUploadBatchResult } from "../../../../Server/Utils/Marketing/ConversionUploadProvider";
|
|
5
|
-
import { JSONObject } from "../../../../Types/JSON";
|
|
6
|
-
import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
|
|
7
|
-
import axios, { AxiosResponse } from "axios";
|
|
8
|
-
import {
|
|
9
|
-
afterEach,
|
|
10
|
-
beforeEach,
|
|
11
|
-
describe,
|
|
12
|
-
expect,
|
|
13
|
-
jest,
|
|
14
|
-
test,
|
|
15
|
-
} from "@jest/globals";
|
|
16
|
-
import { SpyInstance } from "jest-mock";
|
|
17
|
-
|
|
18
|
-
/*
|
|
19
|
-
* ---------------------------------------------------------------------------
|
|
20
|
-
* Microsoft Advertising offline conversions.
|
|
21
|
-
*
|
|
22
|
-
* Microsoft matches on msclkid or on a hashed email (its enhanced conversions),
|
|
23
|
-
* and names its goals by string rather than by id — so a typo in a conversion
|
|
24
|
-
* name is a silently unmatched upload, and an unset one must leave the row
|
|
25
|
-
* pending rather than upload it under another goal's name.
|
|
26
|
-
* ---------------------------------------------------------------------------
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
jest.mock("../../../../Server/EnvironmentConfig", () => {
|
|
30
|
-
return {
|
|
31
|
-
MicrosoftAdsDeveloperToken: "developer-token",
|
|
32
|
-
MicrosoftAdsOAuthClientId: "ms-client-id",
|
|
33
|
-
MicrosoftAdsOAuthClientSecret: "ms-client-secret",
|
|
34
|
-
MicrosoftAdsOAuthRefreshToken: "ms-refresh-token",
|
|
35
|
-
MicrosoftAdsCustomerId: "ms-customer",
|
|
36
|
-
MicrosoftAdsAccountId: "ms-account",
|
|
37
|
-
MicrosoftAdsSignUpConversionName: "OneUptime Signup",
|
|
38
|
-
MicrosoftAdsPaidSubscriptionConversionName: "OneUptime Paid",
|
|
39
|
-
MicrosoftAdsMeetingBookedConversionName: "OneUptime Demo Booked",
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
type MakeConversionFunction = (
|
|
44
|
-
data?: Partial<MarketingConversion>,
|
|
45
|
-
) => MarketingConversion;
|
|
46
|
-
|
|
47
|
-
const makeConversion: MakeConversionFunction = (
|
|
48
|
-
data: Partial<MarketingConversion> = {},
|
|
49
|
-
): MarketingConversion => {
|
|
50
|
-
return Object.assign(new MarketingConversion(), {
|
|
51
|
-
conversionType: MarketingConversionType.SignUp,
|
|
52
|
-
conversionAt: new Date("2026-07-22T10:11:12.345Z"),
|
|
53
|
-
clickIds: { msclkid: "microsoft-click" },
|
|
54
|
-
...data,
|
|
55
|
-
});
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type ResponseFunction = (data: JSONObject) => AxiosResponse<JSONObject>;
|
|
59
|
-
|
|
60
|
-
const response: ResponseFunction = (
|
|
61
|
-
data: JSONObject,
|
|
62
|
-
): AxiosResponse<JSONObject> => {
|
|
63
|
-
return { data } as AxiosResponse<JSONObject>;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
type MockUploadFunction = (
|
|
67
|
-
postSpy: SpyInstance<any>,
|
|
68
|
-
data?: JSONObject,
|
|
69
|
-
) => void;
|
|
70
|
-
|
|
71
|
-
// Call 0 is the OAuth token exchange; call 1 is the conversion upload.
|
|
72
|
-
const mockUpload: MockUploadFunction = (
|
|
73
|
-
postSpy: SpyInstance<any>,
|
|
74
|
-
data: JSONObject = {},
|
|
75
|
-
): void => {
|
|
76
|
-
postSpy
|
|
77
|
-
.mockResolvedValueOnce(
|
|
78
|
-
response({ access_token: "access-token", expires_in: 3600 }) as never,
|
|
79
|
-
)
|
|
80
|
-
.mockResolvedValueOnce(response(data) as never);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
type UploadedConversionFunction = (postSpy: SpyInstance<any>) => JSONObject;
|
|
84
|
-
|
|
85
|
-
const uploadedConversion: UploadedConversionFunction = (
|
|
86
|
-
postSpy: SpyInstance<any>,
|
|
87
|
-
): JSONObject => {
|
|
88
|
-
const body: JSONObject = postSpy.mock.calls[1]?.[1] as JSONObject;
|
|
89
|
-
return (body["OfflineConversions"] as Array<JSONObject>)[0]!;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
describe("MicrosoftAdsProvider", () => {
|
|
93
|
-
let provider: MicrosoftAdsProvider;
|
|
94
|
-
let postSpy: SpyInstance<any>;
|
|
95
|
-
|
|
96
|
-
beforeEach(() => {
|
|
97
|
-
provider = new MicrosoftAdsProvider();
|
|
98
|
-
postSpy = jest.spyOn(axios, "post") as SpyInstance<any>;
|
|
99
|
-
jest
|
|
100
|
-
.spyOn(Date, "now")
|
|
101
|
-
.mockReturnValue(new Date("2026-07-23T10:11:12.345Z").getTime());
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
afterEach(() => {
|
|
105
|
-
jest.restoreAllMocks();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("reports configured only when the OAuth and account settings exist", () => {
|
|
109
|
-
expect(provider.isConfigured()).toBe(true);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe("identifiers", () => {
|
|
113
|
-
test("accepts a conversion with only a click id", () => {
|
|
114
|
-
expect(provider.getSkipReason(makeConversion())).toBeNull();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("accepts a conversion with only an email", () => {
|
|
118
|
-
expect(
|
|
119
|
-
provider.getSkipReason(
|
|
120
|
-
makeConversion({ clickIds: {}, email: "ada@example.com" }),
|
|
121
|
-
),
|
|
122
|
-
).toBeNull();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("permanently skips a conversion with neither", () => {
|
|
126
|
-
expect(provider.getSkipReason(makeConversion({ clickIds: {} }))).toEqual({
|
|
127
|
-
reason: "No Microsoft click id (msclkid) and no email to match on",
|
|
128
|
-
isPermanent: true,
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("permanently skips a conversion outside Microsoft's 90-day window", () => {
|
|
133
|
-
expect(
|
|
134
|
-
provider.getSkipReason(
|
|
135
|
-
makeConversion({
|
|
136
|
-
conversionAt: new Date("2026-01-01T00:00:00.000Z"),
|
|
137
|
-
}),
|
|
138
|
-
),
|
|
139
|
-
).toEqual({
|
|
140
|
-
reason: "Conversion older than Microsoft's 90-day click window",
|
|
141
|
-
isPermanent: true,
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("sends the click id and the hashed email when both are known", async () => {
|
|
146
|
-
mockUpload(postSpy);
|
|
147
|
-
|
|
148
|
-
await provider.upload([makeConversion({ email: "Ada@Example.com" })]);
|
|
149
|
-
|
|
150
|
-
expect(uploadedConversion(postSpy)).toMatchObject({
|
|
151
|
-
MicrosoftClickId: "microsoft-click",
|
|
152
|
-
HashedEmailAddress: Attribution.hashEmail("ada@example.com"),
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("omits MicrosoftClickId entirely when there is no click id", async () => {
|
|
157
|
-
mockUpload(postSpy);
|
|
158
|
-
|
|
159
|
-
await provider.upload([
|
|
160
|
-
makeConversion({ clickIds: {}, email: "ada@example.com" }),
|
|
161
|
-
]);
|
|
162
|
-
|
|
163
|
-
const uploaded: JSONObject = uploadedConversion(postSpy);
|
|
164
|
-
|
|
165
|
-
expect(uploaded["MicrosoftClickId"]).toBeUndefined();
|
|
166
|
-
expect(uploaded["HashedEmailAddress"]).toBe(
|
|
167
|
-
Attribution.hashEmail("ada@example.com"),
|
|
168
|
-
);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("never sends the address in the clear", async () => {
|
|
172
|
-
mockUpload(postSpy);
|
|
173
|
-
|
|
174
|
-
await provider.upload([makeConversion({ email: "ada@example.com" })]);
|
|
175
|
-
|
|
176
|
-
expect(JSON.stringify(postSpy.mock.calls[1]?.[1])).not.toContain(
|
|
177
|
-
"ada@example.com",
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe("conversion goals", () => {
|
|
183
|
-
test.each([
|
|
184
|
-
[MarketingConversionType.SignUp, "OneUptime Signup"],
|
|
185
|
-
[MarketingConversionType.PaidSubscription, "OneUptime Paid"],
|
|
186
|
-
[MarketingConversionType.MeetingBooked, "OneUptime Demo Booked"],
|
|
187
|
-
])(
|
|
188
|
-
"reports %s against its own goal name",
|
|
189
|
-
async (conversionType: MarketingConversionType, expectedName: string) => {
|
|
190
|
-
mockUpload(postSpy);
|
|
191
|
-
|
|
192
|
-
await provider.upload([
|
|
193
|
-
makeConversion({ conversionType: conversionType }),
|
|
194
|
-
]);
|
|
195
|
-
|
|
196
|
-
expect(uploadedConversion(postSpy)["ConversionName"]).toBe(
|
|
197
|
-
expectedName,
|
|
198
|
-
);
|
|
199
|
-
},
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
test.each([MarketingConversionType.MeetingBooked])(
|
|
203
|
-
"attaches no ConversionValue to %s even when the row holds one",
|
|
204
|
-
async (conversionType: MarketingConversionType) => {
|
|
205
|
-
mockUpload(postSpy);
|
|
206
|
-
|
|
207
|
-
await provider.upload([
|
|
208
|
-
makeConversion({
|
|
209
|
-
conversionType: conversionType,
|
|
210
|
-
conversionValueInUSDCents: 500000,
|
|
211
|
-
}),
|
|
212
|
-
]);
|
|
213
|
-
|
|
214
|
-
const uploaded: JSONObject = uploadedConversion(postSpy);
|
|
215
|
-
|
|
216
|
-
expect(uploaded["ConversionValue"]).toBeUndefined();
|
|
217
|
-
expect(uploaded["ConversionCurrencyCode"]).toBeUndefined();
|
|
218
|
-
},
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
test("attaches ConversionValue to a paid subscription", async () => {
|
|
222
|
-
mockUpload(postSpy);
|
|
223
|
-
|
|
224
|
-
await provider.upload([
|
|
225
|
-
makeConversion({
|
|
226
|
-
conversionType: MarketingConversionType.PaidSubscription,
|
|
227
|
-
conversionValueInUSDCents: 12999,
|
|
228
|
-
}),
|
|
229
|
-
]);
|
|
230
|
-
|
|
231
|
-
expect(uploadedConversion(postSpy)).toMatchObject({
|
|
232
|
-
ConversionValue: 129.99,
|
|
233
|
-
ConversionCurrencyCode: "USD",
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("sends the conversion time as second-precision ISO 8601 UTC", async () => {
|
|
239
|
-
mockUpload(postSpy);
|
|
240
|
-
|
|
241
|
-
await provider.upload([makeConversion()]);
|
|
242
|
-
|
|
243
|
-
expect(uploadedConversion(postSpy)["ConversionTime"]).toBe(
|
|
244
|
-
"2026-07-22T10:11:12Z",
|
|
245
|
-
);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test("records per-item PartialErrors against the right batch index", async () => {
|
|
249
|
-
mockUpload(postSpy, {
|
|
250
|
-
PartialErrors: [
|
|
251
|
-
{ Index: 1, ErrorCode: "InvalidClickId", Message: "not found" },
|
|
252
|
-
],
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
const result: ConversionUploadBatchResult = await provider.upload([
|
|
256
|
-
makeConversion(),
|
|
257
|
-
makeConversion({ clickIds: { msclkid: "expired" } }),
|
|
258
|
-
]);
|
|
259
|
-
|
|
260
|
-
expect(result.permanentFailures.size).toBe(1);
|
|
261
|
-
expect(result.permanentFailures.get(1)).toBe("InvalidClickId not found");
|
|
262
|
-
});
|
|
263
|
-
});
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import Attribution from "../../../../Server/Utils/Attribution";
|
|
2
|
-
import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
|
|
3
|
-
import RedditProvider from "../../../../Server/Utils/Marketing/Providers/Reddit";
|
|
4
|
-
import { JSONObject } from "../../../../Types/JSON";
|
|
5
|
-
import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
|
|
6
|
-
import ObjectID from "../../../../Types/ObjectID";
|
|
7
|
-
import axios, { AxiosResponse } from "axios";
|
|
8
|
-
import {
|
|
9
|
-
afterEach,
|
|
10
|
-
beforeEach,
|
|
11
|
-
describe,
|
|
12
|
-
expect,
|
|
13
|
-
jest,
|
|
14
|
-
test,
|
|
15
|
-
} from "@jest/globals";
|
|
16
|
-
import { SpyInstance } from "jest-mock";
|
|
17
|
-
|
|
18
|
-
/*
|
|
19
|
-
* ---------------------------------------------------------------------------
|
|
20
|
-
* Reddit Conversions API.
|
|
21
|
-
*
|
|
22
|
-
* Reddit's tracking types are the same hazard as Meta's event names: `Purchase`
|
|
23
|
-
* is the revenue type, and the provider used to reach it for anything that was
|
|
24
|
-
* not a signup. Reddit has no "meeting booked" type, so both sales-led steps
|
|
25
|
-
* are `Lead` — which is the honest mapping, not a placeholder.
|
|
26
|
-
*
|
|
27
|
-
* Reddit also matches on a hashed email, so a conversion without rdt_cid is
|
|
28
|
-
* still uploadable.
|
|
29
|
-
* ---------------------------------------------------------------------------
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
jest.mock("../../../../Server/EnvironmentConfig", () => {
|
|
33
|
-
return {
|
|
34
|
-
RedditAdsOAuthClientId: "reddit-client-id",
|
|
35
|
-
RedditAdsOAuthClientSecret: "reddit-client-secret",
|
|
36
|
-
RedditAdsOAuthRefreshToken: "reddit-refresh-token",
|
|
37
|
-
RedditAdsAccountId: "reddit-account",
|
|
38
|
-
};
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
type MakeConversionFunction = (
|
|
42
|
-
data?: Partial<MarketingConversion>,
|
|
43
|
-
) => MarketingConversion;
|
|
44
|
-
|
|
45
|
-
const makeConversion: MakeConversionFunction = (
|
|
46
|
-
data: Partial<MarketingConversion> = {},
|
|
47
|
-
): MarketingConversion => {
|
|
48
|
-
return Object.assign(new MarketingConversion(), {
|
|
49
|
-
id: new ObjectID("22222222-2222-4222-8222-222222222222"),
|
|
50
|
-
conversionType: MarketingConversionType.SignUp,
|
|
51
|
-
conversionAt: new Date("2026-07-22T10:11:12.345Z"),
|
|
52
|
-
clickIds: { rdt_cid: "reddit-click" },
|
|
53
|
-
...data,
|
|
54
|
-
});
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
type ResponseFunction = (data: JSONObject) => AxiosResponse<JSONObject>;
|
|
58
|
-
|
|
59
|
-
const response: ResponseFunction = (
|
|
60
|
-
data: JSONObject,
|
|
61
|
-
): AxiosResponse<JSONObject> => {
|
|
62
|
-
return { data } as AxiosResponse<JSONObject>;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
type UploadedEventFunction = (postSpy: SpyInstance<any>) => JSONObject;
|
|
66
|
-
|
|
67
|
-
// Call 0 is the OAuth token exchange; call 1 is the conversion upload.
|
|
68
|
-
const uploadedEvent: UploadedEventFunction = (
|
|
69
|
-
postSpy: SpyInstance<any>,
|
|
70
|
-
): JSONObject => {
|
|
71
|
-
const body: JSONObject = postSpy.mock.calls[1]?.[1] as JSONObject;
|
|
72
|
-
return (body["events"] as Array<JSONObject>)[0]!;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
type MockUploadFunction = (postSpy: SpyInstance<any>) => void;
|
|
76
|
-
|
|
77
|
-
const mockUpload: MockUploadFunction = (postSpy: SpyInstance<any>): void => {
|
|
78
|
-
postSpy
|
|
79
|
-
.mockResolvedValueOnce(
|
|
80
|
-
response({ access_token: "access-token", expires_in: 3600 }) as never,
|
|
81
|
-
)
|
|
82
|
-
.mockResolvedValueOnce(response({}) as never);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
describe("RedditProvider", () => {
|
|
86
|
-
let provider: RedditProvider;
|
|
87
|
-
let postSpy: SpyInstance<any>;
|
|
88
|
-
|
|
89
|
-
beforeEach(() => {
|
|
90
|
-
provider = new RedditProvider();
|
|
91
|
-
postSpy = jest.spyOn(axios, "post") as SpyInstance<any>;
|
|
92
|
-
// Reddit only accepts events up to 7 days old; freeze inside that window.
|
|
93
|
-
jest
|
|
94
|
-
.spyOn(Date, "now")
|
|
95
|
-
.mockReturnValue(new Date("2026-07-23T10:11:12.345Z").getTime());
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
afterEach(() => {
|
|
99
|
-
jest.restoreAllMocks();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("reports configured only when the OAuth and account settings exist", () => {
|
|
103
|
-
expect(provider.isConfigured()).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe("identifiers", () => {
|
|
107
|
-
test("accepts a conversion with only a click id", () => {
|
|
108
|
-
expect(provider.getSkipReason(makeConversion())).toBeNull();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("accepts a conversion with only an email", () => {
|
|
112
|
-
expect(
|
|
113
|
-
provider.getSkipReason(
|
|
114
|
-
makeConversion({ clickIds: {}, email: "ada@example.com" }),
|
|
115
|
-
),
|
|
116
|
-
).toBeNull();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("permanently skips a conversion with neither", () => {
|
|
120
|
-
expect(provider.getSkipReason(makeConversion({ clickIds: {} }))).toEqual({
|
|
121
|
-
reason: "No Reddit click id (rdt_cid) and no email to match on",
|
|
122
|
-
isPermanent: true,
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("permanently skips a conversion outside Reddit's 7-day window", () => {
|
|
127
|
-
expect(
|
|
128
|
-
provider.getSkipReason(
|
|
129
|
-
makeConversion({
|
|
130
|
-
conversionAt: new Date("2026-07-01T00:00:00.000Z"),
|
|
131
|
-
}),
|
|
132
|
-
),
|
|
133
|
-
).toEqual({
|
|
134
|
-
reason: "Conversion older than Reddit's 7-day upload window",
|
|
135
|
-
isPermanent: true,
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test("sends the click id and the hashed email when both are known", async () => {
|
|
140
|
-
mockUpload(postSpy);
|
|
141
|
-
|
|
142
|
-
await provider.upload([makeConversion({ email: "Ada@Example.com" })]);
|
|
143
|
-
|
|
144
|
-
const event: JSONObject = uploadedEvent(postSpy);
|
|
145
|
-
|
|
146
|
-
expect(event["click_id"]).toBe("reddit-click");
|
|
147
|
-
expect(event["user"]).toEqual({
|
|
148
|
-
email: Attribution.hashEmail("ada@example.com"),
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
/*
|
|
153
|
-
* The click id used to be sent as `click_id: "" ` when absent. An empty
|
|
154
|
-
* string is not "no identifier" to an API, it is an identifier that cannot
|
|
155
|
-
* match anything — omit the field instead.
|
|
156
|
-
*/
|
|
157
|
-
test("omits click_id entirely when there is no click id", async () => {
|
|
158
|
-
mockUpload(postSpy);
|
|
159
|
-
|
|
160
|
-
await provider.upload([
|
|
161
|
-
makeConversion({ clickIds: {}, email: "ada@example.com" }),
|
|
162
|
-
]);
|
|
163
|
-
|
|
164
|
-
expect(uploadedEvent(postSpy)["click_id"]).toBeUndefined();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("never sends the address in the clear", async () => {
|
|
168
|
-
mockUpload(postSpy);
|
|
169
|
-
|
|
170
|
-
await provider.upload([makeConversion({ email: "ada@example.com" })]);
|
|
171
|
-
|
|
172
|
-
expect(JSON.stringify(postSpy.mock.calls[1]?.[1])).not.toContain(
|
|
173
|
-
"ada@example.com",
|
|
174
|
-
);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe("tracking types", () => {
|
|
179
|
-
test.each([
|
|
180
|
-
[MarketingConversionType.SignUp, "SignUp"],
|
|
181
|
-
[MarketingConversionType.MeetingBooked, "Lead"],
|
|
182
|
-
[MarketingConversionType.PaidSubscription, "Purchase"],
|
|
183
|
-
])(
|
|
184
|
-
"sends %s as %s",
|
|
185
|
-
async (
|
|
186
|
-
conversionType: MarketingConversionType,
|
|
187
|
-
expectedTrackingType: string,
|
|
188
|
-
) => {
|
|
189
|
-
mockUpload(postSpy);
|
|
190
|
-
|
|
191
|
-
await provider.upload([
|
|
192
|
-
makeConversion({ conversionType: conversionType }),
|
|
193
|
-
]);
|
|
194
|
-
|
|
195
|
-
expect(uploadedEvent(postSpy)["event_type"]).toEqual({
|
|
196
|
-
tracking_type: expectedTrackingType,
|
|
197
|
-
});
|
|
198
|
-
},
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
test.each([MarketingConversionType.MeetingBooked])(
|
|
202
|
-
"attaches no revenue to %s even when the row holds a value",
|
|
203
|
-
async (conversionType: MarketingConversionType) => {
|
|
204
|
-
mockUpload(postSpy);
|
|
205
|
-
|
|
206
|
-
await provider.upload([
|
|
207
|
-
makeConversion({
|
|
208
|
-
conversionType: conversionType,
|
|
209
|
-
conversionValueInUSDCents: 500000,
|
|
210
|
-
}),
|
|
211
|
-
]);
|
|
212
|
-
|
|
213
|
-
const metadata: JSONObject = uploadedEvent(postSpy)[
|
|
214
|
-
"event_metadata"
|
|
215
|
-
] as JSONObject;
|
|
216
|
-
|
|
217
|
-
expect(metadata["value_decimal"]).toBeUndefined();
|
|
218
|
-
expect(metadata["currency"]).toBeUndefined();
|
|
219
|
-
},
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
test("attaches revenue to a paid subscription", async () => {
|
|
223
|
-
mockUpload(postSpy);
|
|
224
|
-
|
|
225
|
-
await provider.upload([
|
|
226
|
-
makeConversion({
|
|
227
|
-
conversionType: MarketingConversionType.PaidSubscription,
|
|
228
|
-
conversionValueInUSDCents: 12999,
|
|
229
|
-
}),
|
|
230
|
-
]);
|
|
231
|
-
|
|
232
|
-
expect(uploadedEvent(postSpy)["event_metadata"]).toEqual({
|
|
233
|
-
conversion_id: "22222222-2222-4222-8222-222222222222",
|
|
234
|
-
value_decimal: 129.99,
|
|
235
|
-
currency: "USD",
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("keys the event on the conversion row id so retries dedupe", async () => {
|
|
241
|
-
mockUpload(postSpy);
|
|
242
|
-
|
|
243
|
-
await provider.upload([makeConversion()]);
|
|
244
|
-
|
|
245
|
-
expect(
|
|
246
|
-
(uploadedEvent(postSpy)["event_metadata"] as JSONObject)["conversion_id"],
|
|
247
|
-
).toBe("22222222-2222-4222-8222-222222222222");
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("posts to the configured ad account", async () => {
|
|
251
|
-
mockUpload(postSpy);
|
|
252
|
-
|
|
253
|
-
await provider.upload([makeConversion()]);
|
|
254
|
-
|
|
255
|
-
expect(postSpy.mock.calls[1]?.[0]).toBe(
|
|
256
|
-
"https://ads-api.reddit.com/api/v2.0/conversions/events/reddit-account",
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
export enum MarketingConversionType {
|
|
2
|
-
SignUp = "SignUp",
|
|
3
|
-
// A verified Cal.com booking. See Docs/analytics/enterprise-conversion-tracking.md.
|
|
4
|
-
MeetingBooked = "MeetingBooked",
|
|
5
|
-
PaidSubscription = "PaidSubscription",
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/*
|
|
9
|
-
* Conversion types ad platforms may be told about.
|
|
10
|
-
*
|
|
11
|
-
* This list used to hold only SignUp and PaidSubscription because every
|
|
12
|
-
* provider chose its platform conversion action with a two-way branch on
|
|
13
|
-
* isSignUp(), so any third type would have been uploaded as a *purchase*
|
|
14
|
-
* carrying whatever value the row held. That branch is gone: providers now
|
|
15
|
-
* declare a Record<MarketingConversionType, ...> mapping, which the compiler
|
|
16
|
-
* refuses to accept unless every member of the enum above is named. Adding a
|
|
17
|
-
* conversion type is therefore a type error in each provider until that
|
|
18
|
-
* provider says what the type means on its platform.
|
|
19
|
-
*
|
|
20
|
-
* A mapping may legitimately be an empty string, meaning "this deployment has
|
|
21
|
-
* not configured a conversion action for this type yet". That is a config gap,
|
|
22
|
-
* not a modelling gap: the worker leaves such rows pending rather than
|
|
23
|
-
* skipping them, so they upload once the id is set.
|
|
24
|
-
*/
|
|
25
|
-
export const AdUploadableMarketingConversionTypes: Array<MarketingConversionType> =
|
|
26
|
-
[
|
|
27
|
-
MarketingConversionType.SignUp,
|
|
28
|
-
MarketingConversionType.MeetingBooked,
|
|
29
|
-
MarketingConversionType.PaidSubscription,
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
/*
|
|
33
|
-
* Types that represent a sales-led lead rather than a completed self-serve
|
|
34
|
-
* transaction. They carry no revenue of their own — a booked meeting is not
|
|
35
|
-
* money — so providers must not attach a conversion value to them even when
|
|
36
|
-
* the row happens to have one, and platforms that demand a value for their
|
|
37
|
-
* purchase-shaped events must not be handed these as purchases.
|
|
38
|
-
*
|
|
39
|
-
* Every sales-led step is a booked meeting. The enterprise licence request is
|
|
40
|
-
* not a separate type: asking about a licence and booking an architecture
|
|
41
|
-
* assessment are the same conversation, reached through the same Cal embed, so
|
|
42
|
-
* they are the same conversion.
|
|
43
|
-
*/
|
|
44
|
-
export const LeadMarketingConversionTypes: Array<MarketingConversionType> = [
|
|
45
|
-
MarketingConversionType.MeetingBooked,
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
export enum MarketingConversionUploadStatus {
|
|
49
|
-
Uploaded = "Uploaded",
|
|
50
|
-
Failed = "Failed",
|
|
51
|
-
// No identifier relevant to this ad platform — nothing to upload.
|
|
52
|
-
Skipped = "Skipped",
|
|
53
|
-
}
|