@prismatic-io/components-google-ads 0.0.0-stage → 0.0.1

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.
@@ -0,0 +1,2928 @@
1
+ let _prismatic_io_spectral = require("@prismatic-io/spectral");
2
+ let _prismatic_io_spectral_http = require("@prismatic-io/spectral/http");
3
+ let lodash = require("lodash");
4
+ //#region src/constants.ts
5
+ const GET_ACCOUNT_HIERARCHY_QUERY = `SELECT
6
+ customer_client.client_customer,
7
+ customer_client.level,
8
+ customer_client.manager,
9
+ customer_client.descriptive_name,
10
+ customer_client.id
11
+ FROM customer_client
12
+ WHERE customer_client.level <= `;
13
+ const MICROS_TO_DOLLARS_CONVERSION_FACTOR = 1e6;
14
+ const BUDGET_SEVERITY = {
15
+ INFO: "info",
16
+ WARNING: "warning",
17
+ CRITICAL: "critical"
18
+ };
19
+ const NO_DESCRIPTIVE_NAME = "(No descriptive name)";
20
+ const DESCRIPTIVE_NAME_UNAVAILABLE = "No Description";
21
+ const GOOGLE_ADS_OAUTH_SCOPE = "https://www.googleapis.com/auth/adwords";
22
+ const GOOGLE_DATA_MANAGER_OAUTH_SCOPE = "https://www.googleapis.com/auth/datamanager";
23
+ const GOOGLE_ADS_BASE_URL = "https://googleads.googleapis.com";
24
+ const GOOGLE_DATA_MANAGER_BASE_URL = "https://datamanager.googleapis.com";
25
+ const GOOGLE_LOCAL_SERVICES_BASE_URL = "https://localservices.googleapis.com";
26
+ const googleAdsSearchPath = (customerId) => `/customers/${customerId}/googleAds:search`;
27
+ const CHANGE_TYPE = {
28
+ STATUS: "status",
29
+ BUDGET: "budget",
30
+ BIDDING: "bidding",
31
+ SETTINGS: "settings",
32
+ ALL: "all"
33
+ };
34
+ const CHANGE_EVENT_ROW_LIMIT = 1e4;
35
+ const BATCHED_CHANGE_EVENT_ROW_LIMIT = 1e3;
36
+ const LOOK_BACK_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
37
+ const LOOK_BACK_DATE_START_TIME = "00:00:00";
38
+ const OPERATION_TO_CHANGE_TYPE = {
39
+ CREATE: "created",
40
+ UPDATE: "updated",
41
+ REMOVE: "removed"
42
+ };
43
+ const CAMPAIGN_CHANGE_RESOURCE_TYPE = {
44
+ CAMPAIGN: "CAMPAIGN",
45
+ CAMPAIGN_BUDGET: "CAMPAIGN_BUDGET"
46
+ };
47
+ const CHANGE_HISTORY_RESOURCE_TYPE_QUERY_VALUE = { KEYWORD: "AD_GROUP_CRITERION" };
48
+ const CHANGE_TYPES_MODEL = [
49
+ {
50
+ label: "Status Changes",
51
+ value: CHANGE_TYPE.STATUS
52
+ },
53
+ {
54
+ label: "Budget Changes",
55
+ value: CHANGE_TYPE.BUDGET
56
+ },
57
+ {
58
+ label: "Bid Strategy Changes",
59
+ value: CHANGE_TYPE.BIDDING
60
+ },
61
+ {
62
+ label: "Campaign Settings",
63
+ value: CHANGE_TYPE.SETTINGS
64
+ },
65
+ {
66
+ label: "All Changes",
67
+ value: CHANGE_TYPE.ALL
68
+ }
69
+ ];
70
+ //#endregion
71
+ //#region src/connections/dataManagerOAuth.ts
72
+ const dataManagerOAuth = (0, _prismatic_io_spectral.oauth2Connection)({
73
+ key: "dataManagerOAuth",
74
+ display: {
75
+ label: "Data Manager OAuth 2.0",
76
+ description: "Authenticate to the Google Data Manager API using OAuth 2.0. Use this connection for the Ingest Offline Conversions action. A Developer Token is not required for this API.",
77
+ icons: { oauth2ConnectionIconPath: "oauth-button.png" }
78
+ },
79
+ oauth2Type: _prismatic_io_spectral.OAuth2Type.AuthorizationCode,
80
+ inputs: {
81
+ authorizeUrl: {
82
+ label: "Authorize URL",
83
+ placeholder: "Enter authorize URL",
84
+ type: "string",
85
+ required: true,
86
+ shown: false,
87
+ comments: "The OAuth 2.0 Authorization URL for the Google Data Manager API. See [OAuth 2.0 documentation](https://developers.google.com/identity/protocols/oauth2).",
88
+ default: "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent"
89
+ },
90
+ tokenUrl: {
91
+ label: "Token URL",
92
+ placeholder: "Enter token URL",
93
+ type: "string",
94
+ required: true,
95
+ shown: false,
96
+ comments: "The OAuth 2.0 Token URL for the Google Data Manager API. See [OAuth 2.0 documentation](https://developers.google.com/identity/protocols/oauth2).",
97
+ default: "https://oauth2.googleapis.com/token"
98
+ },
99
+ scopes: {
100
+ label: "Scopes",
101
+ placeholder: "Enter OAuth scopes",
102
+ type: "string",
103
+ required: true,
104
+ shown: false,
105
+ comments: "Space-separated OAuth 2.0 permission scopes for the Google Data Manager API. See [OAuth scopes documentation](https://developers.google.com/identity/protocols/oauth2/scopes).",
106
+ default: GOOGLE_DATA_MANAGER_OAUTH_SCOPE
107
+ },
108
+ clientId: {
109
+ label: "Client ID",
110
+ placeholder: "Enter client ID",
111
+ type: "string",
112
+ required: true,
113
+ shown: true,
114
+ comments: "The Client ID for the Google Data Manager API application. Obtain from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).",
115
+ example: "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com"
116
+ },
117
+ clientSecret: {
118
+ label: "Client Secret",
119
+ placeholder: "Enter client secret",
120
+ type: "password",
121
+ required: true,
122
+ shown: true,
123
+ comments: "The Client Secret for the Google Data Manager API application. Obtain from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).",
124
+ example: "GOCSPX-abcdefghijklmnopqrstuvwxyz"
125
+ },
126
+ apiVersion: {
127
+ label: "API Version",
128
+ placeholder: "Enter API version",
129
+ type: "string",
130
+ required: false,
131
+ shown: true,
132
+ comments: `The version of the Google Data Manager API to use. Defaults to v1. See [API reference](https://developers.google.com/data-manager/api/reference/rest).`,
133
+ example: "v1",
134
+ default: "v1"
135
+ }
136
+ }
137
+ });
138
+ //#endregion
139
+ //#region src/connections/oauth.ts
140
+ const oauth = (0, _prismatic_io_spectral.oauth2Connection)({
141
+ key: "oauth",
142
+ display: {
143
+ label: "OAuth 2.0",
144
+ description: "Authenticate using OAuth 2.0.",
145
+ icons: { oauth2ConnectionIconPath: "oauth-button.png" }
146
+ },
147
+ oauth2Type: _prismatic_io_spectral.OAuth2Type.AuthorizationCode,
148
+ inputs: {
149
+ authorizeUrl: {
150
+ label: "Authorize URL",
151
+ placeholder: "Enter authorize URL",
152
+ type: "string",
153
+ required: true,
154
+ shown: false,
155
+ comments: "The OAuth 2.0 Authorization URL for the Google Ads API. See [OAuth 2.0 documentation](https://developers.google.com/identity/protocols/oauth2).",
156
+ default: "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent"
157
+ },
158
+ tokenUrl: {
159
+ label: "Token URL",
160
+ placeholder: "Enter token URL",
161
+ type: "string",
162
+ required: true,
163
+ shown: false,
164
+ comments: "The OAuth 2.0 Token URL for the Google Ads API. See [OAuth 2.0 documentation](https://developers.google.com/identity/protocols/oauth2).",
165
+ default: "https://oauth2.googleapis.com/token"
166
+ },
167
+ scopes: {
168
+ label: "Scopes",
169
+ placeholder: "Enter OAuth scopes",
170
+ type: "string",
171
+ required: true,
172
+ shown: true,
173
+ comments: "Space-separated OAuth 2.0 permission scopes for the Google Ads and Data Manager APIs. See [OAuth scopes documentation](https://developers.google.com/identity/protocols/oauth2/scopes).",
174
+ example: `${GOOGLE_ADS_OAUTH_SCOPE} ${GOOGLE_DATA_MANAGER_OAUTH_SCOPE}`,
175
+ default: GOOGLE_ADS_OAUTH_SCOPE
176
+ },
177
+ clientId: {
178
+ label: "Client ID",
179
+ placeholder: "Enter client ID",
180
+ type: "string",
181
+ required: true,
182
+ shown: true,
183
+ comments: "The Client ID for the Google Ads API application. Obtain from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).",
184
+ example: "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com"
185
+ },
186
+ clientSecret: {
187
+ label: "Client Secret",
188
+ placeholder: "Enter client secret",
189
+ type: "password",
190
+ required: true,
191
+ shown: true,
192
+ comments: "The Client Secret for the Google Ads API application. Obtain from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).",
193
+ example: "GOCSPX-abcdefghijklmnopqrstuvwxyz"
194
+ },
195
+ developerToken: {
196
+ label: "Developer Token",
197
+ placeholder: "Leave blank",
198
+ type: "password",
199
+ required: false,
200
+ shown: true,
201
+ comments: "Leave blank. Google [sunset developer tokens](https://developers.google.com/google-ads/api/docs/api-policy/developer-token) on September 9, 2026, and API access is now granted to the Google Cloud project that owns the OAuth credentials. A saved token is not sent to the Google Ads API.",
202
+ example: "ABcdeFGH93KL-NOPQ_RsTuv"
203
+ },
204
+ apiVersion: {
205
+ label: "API Version",
206
+ placeholder: "Enter API version",
207
+ type: "string",
208
+ required: false,
209
+ shown: true,
210
+ comments: `The version of the Google Ads API to use. Defaults to v25. Any version from v22 through v25 can be set explicitly; anything below v22 has already been sunset by Google and is automatically upgraded to v22. Note: v22 sunsets in October 2026. See [API versions documentation](https://developers.google.com/google-ads/api/docs/release-notes).`,
211
+ example: "v25",
212
+ default: "v25"
213
+ }
214
+ }
215
+ });
216
+ //#endregion
217
+ //#region src/connections/index.ts
218
+ var connections_default = [oauth, dataManagerOAuth];
219
+ //#endregion
220
+ //#region src/util/apiVersion.ts
221
+ const validateVersionAgainstFloor = (apiVersion, logger, options) => {
222
+ if (!apiVersion) return options.defaultVersion;
223
+ const match = apiVersion.match(/^v(\d+)$/);
224
+ if (!match) throw new Error(options.invalidFormatMessage);
225
+ if (_prismatic_io_spectral.util.types.toInt(match[1]) < _prismatic_io_spectral.util.types.toInt(options.minimumVersion.replace("v", ""))) {
226
+ logger.warn(options.belowMinimumMessage);
227
+ return options.minimumVersion;
228
+ }
229
+ return apiVersion;
230
+ };
231
+ const validateApiVersion = (apiVersion, logger) => validateVersionAgainstFloor(apiVersion, logger, {
232
+ defaultVersion: "v25",
233
+ minimumVersion: "v22",
234
+ invalidFormatMessage: `Invalid API version format: ${apiVersion}. Expected format: v{number} (e.g., v24, v25)`,
235
+ belowMinimumMessage: `The connection API version ${apiVersion} is configured below the minimum supported version. Version v22 will be used to prevent errors.`
236
+ });
237
+ const validateDataManagerApiVersion = (apiVersion, logger) => validateVersionAgainstFloor(apiVersion, logger, {
238
+ defaultVersion: "v1",
239
+ minimumVersion: "v1",
240
+ invalidFormatMessage: `Invalid Data Manager API version format: ${apiVersion}. Expected format: v{number} (e.g., v1)`,
241
+ belowMinimumMessage: `The Data Manager API version ${apiVersion} is configured below the minimum supported version. Version v1 will be used to prevent errors.`
242
+ });
243
+ //#endregion
244
+ //#region src/util/clean.ts
245
+ const cleanString = (value) => {
246
+ const str = _prismatic_io_spectral.util.types.toString(value);
247
+ return str ? str : void 0;
248
+ };
249
+ const cleanCustomerId = (value) => {
250
+ return _prismatic_io_spectral.util.types.toString(value).replace("customers/", "").replace(/-/g, "");
251
+ };
252
+ const toOptionalCustomerId = (value) => {
253
+ const id = _prismatic_io_spectral.util.types.toString(value);
254
+ return id ? cleanCustomerId(id) : void 0;
255
+ };
256
+ const valueListInputClean = (value) => {
257
+ if (Array.isArray(value) && value.length >= 1 && value[0] !== "000xxx") return value.map((v) => `customer_id:${cleanCustomerId(v)}`).toString().replaceAll(",", ";");
258
+ };
259
+ const toOptionalInt = (value) => value ? _prismatic_io_spectral.util.types.toInt(value) : void 0;
260
+ const toStringList = (value) => {
261
+ if (value && Array.isArray(value)) return value;
262
+ return [];
263
+ };
264
+ const lookBackDateClean = (value) => {
265
+ const raw = _prismatic_io_spectral.util.types.toString(value).trim();
266
+ if (raw === "") return "";
267
+ const match = raw.match(LOOK_BACK_DATE_PATTERN);
268
+ if (!match) throw new Error(`Look-back Date must be a date in YYYY-MM-DD format. Received: ${raw}`);
269
+ const [, yearStr, monthStr, dayStr] = match;
270
+ const year = _prismatic_io_spectral.util.types.toNumber(yearStr);
271
+ const month = _prismatic_io_spectral.util.types.toNumber(monthStr);
272
+ const day = _prismatic_io_spectral.util.types.toNumber(dayStr);
273
+ const parsed = new Date(Date.UTC(year, month - 1, day));
274
+ if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) throw new Error(`Look-back Date must be a date in YYYY-MM-DD format. Received: ${raw}`);
275
+ if (parsed.getTime() > Date.now()) throw new Error(`Look-back Date cannot be a future date. Received: ${raw}`);
276
+ return raw;
277
+ };
278
+ //#endregion
279
+ //#region src/client.ts
280
+ const extractAccessToken = (connection) => {
281
+ const accessToken = _prismatic_io_spectral.util.types.toString(connection.token?.access_token);
282
+ if (!accessToken) throw new _prismatic_io_spectral.ConnectionError(connection, "Did not receive an access token. Confirm your connection settings.");
283
+ return accessToken;
284
+ };
285
+ const validateConnection = (connection) => {
286
+ if (connection.key !== oauth.key) throw new _prismatic_io_spectral.ConnectionError(connection, `Unexpected connection type received: ${connection.key}`);
287
+ return { accessToken: extractAccessToken(connection) };
288
+ };
289
+ const createClient = ({ connection, debugEnabled, logger, loginCustomerId }) => {
290
+ const { accessToken } = validateConnection(connection);
291
+ const apiVersion = validateApiVersion(_prismatic_io_spectral.util.types.toString(connection.fields?.apiVersion), logger);
292
+ const headers = {
293
+ Accept: "application/json",
294
+ Authorization: `Bearer ${accessToken}`
295
+ };
296
+ if (loginCustomerId) headers["login-customer-id"] = cleanCustomerId(loginCustomerId);
297
+ return (0, _prismatic_io_spectral_http.createClient)({
298
+ baseUrl: `${GOOGLE_ADS_BASE_URL}/${apiVersion}`,
299
+ headers,
300
+ responseType: "json",
301
+ debug: debugEnabled
302
+ });
303
+ };
304
+ const validateDataManagerConnection = (connection) => {
305
+ if (connection.key !== dataManagerOAuth.key && connection.key !== oauth.key) throw new _prismatic_io_spectral.ConnectionError(connection, `Unexpected connection type received: ${connection.key}.`);
306
+ return { accessToken: extractAccessToken(connection) };
307
+ };
308
+ const createDataManagerClient = ({ connection, debugEnabled, logger }) => {
309
+ const { accessToken } = validateDataManagerConnection(connection);
310
+ return (0, _prismatic_io_spectral_http.createClient)({
311
+ baseUrl: `${GOOGLE_DATA_MANAGER_BASE_URL}/${connection.key === oauth.key ? "v1" : validateDataManagerApiVersion(_prismatic_io_spectral.util.types.toString(connection.fields?.apiVersion), logger)}`,
312
+ headers: {
313
+ Accept: "application/json",
314
+ Authorization: `Bearer ${accessToken}`
315
+ },
316
+ responseType: "json",
317
+ debug: debugEnabled
318
+ });
319
+ };
320
+ const createLocalServicesClient = ({ connection, debugEnabled, loginCustomerId }) => {
321
+ const { accessToken } = validateConnection(connection);
322
+ const headers = {
323
+ Accept: "application/json",
324
+ Authorization: `Bearer ${accessToken}`
325
+ };
326
+ if (loginCustomerId) headers["login-customer-id"] = cleanCustomerId(loginCustomerId);
327
+ return (0, _prismatic_io_spectral_http.createClient)({
328
+ baseUrl: `${GOOGLE_LOCAL_SERVICES_BASE_URL}/v1`,
329
+ headers,
330
+ responseType: "json",
331
+ debug: debugEnabled
332
+ });
333
+ };
334
+ const clickConversionsExampleInput = [{
335
+ gbraid: "CjwKCAjwABCDEF1234567890ghijklMNOPQRstuvwxyz",
336
+ wbraid: "CjwKCAjwZYXWVU0987654321LKJIHGfedcbaSTUVWXqr",
337
+ externalAttributionData: {
338
+ externalAttributionCredit: .75,
339
+ externalAttributionModel: "LINEAR"
340
+ },
341
+ customVariables: [{
342
+ conversionCustomVariable: "customers/1234567890/conversionCustomVariables/11111",
343
+ value: "premium_tier"
344
+ }],
345
+ cartData: {
346
+ merchantId: "1234567",
347
+ feedCountryCode: "US",
348
+ feedLanguageCode: "EN",
349
+ localTransactionCost: 5.99,
350
+ items: [{
351
+ productId: "SKU-12345",
352
+ quantity: 2,
353
+ unitPrice: 49.99
354
+ }]
355
+ },
356
+ userIdentifiers: [{
357
+ userIdentifierSource: "FIRST_PARTY",
358
+ hashedEmail: "a1159e9df3670d549d04524532629f5477ceb7deec9b45e47e8c009506ecb2c8",
359
+ hashedPhoneNumber: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
360
+ mobileId: "cdda802e-fb9c-47ad-0794d394c913",
361
+ thirdPartyUserId: "ext-user-98765",
362
+ addressInfo: {
363
+ hashedFirstName: "a8cfcd74832004951b4408cdb0a5dbcd8c7e52d43f7fe244bf720582e05241da",
364
+ hashedLastName: "2c624232cdd221771294dfbb310aca000a0df6ac8b166e8b32c4bdc2a5a5cc0a",
365
+ city: "San Francisco",
366
+ state: "CA",
367
+ countryCode: "US",
368
+ postalCode: "94105",
369
+ hashedStreetAddress: "d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35"
370
+ }
371
+ }],
372
+ conversionEnvironment: "WEB",
373
+ consent: {
374
+ adUserData: "GRANTED",
375
+ adPersonalization: "GRANTED"
376
+ },
377
+ gclid: "CjwKCAjw1234567890abcdefGHIJKLmnoPQRStuvwxyz",
378
+ conversionAction: "customers/1234567890/conversionActions/987654321",
379
+ conversionDateTime: "2026-01-15 10:30:00-05:00",
380
+ conversionValue: 149.99,
381
+ currencyCode: "USD",
382
+ orderId: "ORDER-2026-00123"
383
+ }];
384
+ const ingestOfflineConversionsExamplePayload = { data: {
385
+ requestId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
386
+ fieldWarnings: [{
387
+ reason: "WARNING_REASON_CUSTOM_VARIABLE_NOT_ENABLED",
388
+ description: "The custom variable is not enabled in the destination account.",
389
+ field: "events[0].custom_variables[0]"
390
+ }]
391
+ } };
392
+ const campaignOperationsExampleInput = [{
393
+ updateMask: "status,name",
394
+ create: {
395
+ name: "Example Campaign",
396
+ advertisingChannelType: "SEARCH",
397
+ status: "PAUSED",
398
+ campaignBudget: "customers/1234567890/campaignBudgets/9876543210"
399
+ },
400
+ update: {
401
+ resourceName: "customers/1234567890/campaigns/1122334455",
402
+ status: "ENABLED"
403
+ },
404
+ remove: "customers/1234567890/campaigns/1122334455"
405
+ }];
406
+ const campaignCriteriaOperationsExampleInput = [{
407
+ updateMask: "bidModifier",
408
+ create: {
409
+ campaign: "customers/1234567890/campaigns/1122334455",
410
+ location: { geoTargetConstant: "geoTargetConstants/1014044" }
411
+ },
412
+ update: {
413
+ resourceName: "customers/1234567890/campaignCriteria/1122334455~987654321",
414
+ bidModifier: 1.5
415
+ },
416
+ remove: "customers/1234567890/campaignCriteria/1122334455~987654321"
417
+ }];
418
+ const uploadConversionsPartialFailureError = {
419
+ code: 3,
420
+ message: "Multiple errors in 'conversions' field. Details are provided in the 'details' field.",
421
+ details: [{
422
+ "@type": "type.googleapis.com/google.ads.googleads.v25.errors.GoogleAdsFailure",
423
+ errors: [{
424
+ errorCode: { conversionUploadError: "INVALID_CONVERSION_ACTION" },
425
+ message: "The conversion action specified in the request is invalid.",
426
+ location: { fieldPathElements: [{
427
+ fieldName: "conversions",
428
+ index: 0
429
+ }, { fieldName: "conversionAction" }] }
430
+ }]
431
+ }]
432
+ };
433
+ const uploadCallConversionsExamplePayload = { data: {
434
+ partialFailureError: uploadConversionsPartialFailureError,
435
+ results: [{
436
+ callerId: "+18005551234",
437
+ callStartDateTime: "2026-01-15 09:30:00-05:00",
438
+ conversionAction: "customers/1234567890/conversionActions/987654321",
439
+ conversionDateTime: "2026-01-15 10:30:00-05:00"
440
+ }]
441
+ } };
442
+ const uploadClickConversionsExamplePayload = { data: {
443
+ partialFailureError: uploadConversionsPartialFailureError,
444
+ results: [{
445
+ gclid: "TeSter123.gClIdString_xYz",
446
+ gbraid: "gbraid_identifier_string",
447
+ wbraid: "wbraid_identifier_string",
448
+ conversionAction: "customers/1234567890/conversionActions/987654321",
449
+ conversionDateTime: "2026-01-15 10:30:00-05:00",
450
+ userIdentifiers: [{
451
+ userIdentifierSource: "FIRST_PARTY",
452
+ hashedEmail: "a1159e9df3670d549d04524532629f5477ceb7deec9b45e47e8c009506ecb2c8"
453
+ }]
454
+ }],
455
+ jobId: "1234567890123456789"
456
+ } };
457
+ const confirmClientLinkExamplePayload = { data: { results: [{ resourceName: "customers/2222222222/customerManagerLinks/1111111111~3333333333" }] } };
458
+ const getCustomerExamplePayload = { data: {
459
+ results: [{ customer: {
460
+ resourceName: "customers/1234567890",
461
+ id: "1234567890",
462
+ descriptiveName: "Example Customer Account",
463
+ status: "ENABLED",
464
+ testAccount: false,
465
+ manager: false
466
+ } }],
467
+ fieldMask: "customer.id,customer.descriptiveName,customer.status,customer.testAccount,customer.manager",
468
+ queryResourceConsumption: "80"
469
+ } };
470
+ const getConversionActionExamplePayload = { data: {
471
+ results: [{ conversionAction: {
472
+ resourceName: "customers/1234567890/conversionActions/987654321",
473
+ id: "987654321",
474
+ name: "Purchase Conversion"
475
+ } }],
476
+ fieldMask: "conversionAction.id,conversionAction.name,conversionAction.resourceName",
477
+ queryResourceConsumption: "702"
478
+ } };
479
+ const mutateCampaignExamplePayload = { data: { results: [{ resourceName: "customers/1234567890/campaigns/9876543210" }] } };
480
+ const mutateCampaignCriteriaExamplePayload = { data: { results: [{ resourceName: "customers/1234567890/campaignCriteria/9876543210~123456" }] } };
481
+ const searchAdsLocalServicesExamplePayload = { data: {
482
+ results: [{ campaign: {
483
+ resourceName: "customers/1234567890/campaigns/9876543210",
484
+ id: "9876543210",
485
+ name: "Local Services Campaign",
486
+ status: "ENABLED"
487
+ } }],
488
+ nextPageToken: "CJL5XLT2PWDmIpGNGciABRnu",
489
+ fieldMask: "campaign.resourceName,campaign.id,campaign.name,campaign.status",
490
+ queryResourceConsumption: "74"
491
+ } };
492
+ const accountReportsExamplePayload = { data: {
493
+ accountReports: [{
494
+ accountId: "1234567890",
495
+ aggregatorInfo: { aggregatorProviderId: "1234567890" },
496
+ averageFiveStarRating: 4.8,
497
+ averageWeeklyBudget: 500,
498
+ businessName: "Example Plumbing Services",
499
+ currentPeriodChargedLeads: "15",
500
+ currentPeriodConnectedPhoneCalls: "25",
501
+ currentPeriodPhoneCalls: "30",
502
+ currentPeriodTotalCost: 450.5,
503
+ impressionsLastTwoDays: "1250",
504
+ phoneLeadResponsiveness: .95,
505
+ previousPeriodChargedLeads: "12",
506
+ previousPeriodConnectedPhoneCalls: "20",
507
+ previousPeriodPhoneCalls: "28",
508
+ previousPeriodTotalCost: 380.25,
509
+ totalReview: 125
510
+ }],
511
+ nextPageToken: "CJL5XLT2PWDmIpGNGciABRnu"
512
+ } };
513
+ const detailedLeadReportsExamplePayload = { data: {
514
+ detailedLeadReports: [{
515
+ accountId: "1234567890",
516
+ aggregatorInfo: { aggregatorProviderId: "1234567890" },
517
+ businessName: "Example Plumbing Services",
518
+ leadType: "MESSAGE",
519
+ chargeStatus: "CHARGED",
520
+ currencyCode: "USD",
521
+ disputeStatus: "DISPUTE_INELIGIBLE",
522
+ geo: "San Francisco, CA",
523
+ googleAdsLeadId: "1122334455",
524
+ leadCategory: "PLUMBER",
525
+ leadCreationTimestamp: "2026-01-15T10:30:00Z",
526
+ leadId: "987654321",
527
+ leadPrice: 25.5,
528
+ messageLead: {
529
+ customerName: "Jane Smith",
530
+ jobType: "Pipe Repair",
531
+ postalCode: "94105"
532
+ },
533
+ timezone: {
534
+ id: "America/Los_Angeles",
535
+ version: ""
536
+ }
537
+ }],
538
+ nextPageToken: "CJL5XLT2PWDmIpGNGciABRnu"
539
+ } };
540
+ const inviteUserExamplePayload = { data: { result: {
541
+ resourceName: "customers/1234567890/customerUserAccessInvitations/9876543210",
542
+ multiPartyAuthReview: ""
543
+ } } };
544
+ const listAccessibleCustomersExamplePayload = { data: { resourceNames: ["customers/1234567890", "customers/5555555555"] } };
545
+ const listCustomersExamplePayload = { data: {
546
+ results: [{ customerClient: {
547
+ resourceName: "customers/1234567890/customerClients/1234567890",
548
+ clientCustomer: "customers/1234567890",
549
+ id: "1234567890",
550
+ hidden: false,
551
+ level: "1"
552
+ } }, { customerClient: {
553
+ resourceName: "customers/1234567890/customerClients/5555555555",
554
+ clientCustomer: "customers/5555555555",
555
+ id: "5555555555",
556
+ hidden: false,
557
+ level: "2"
558
+ } }],
559
+ nextPageToken: "CJL5XLT2PWDmIpGNGciABRnu",
560
+ fieldMask: "customerClient.resourceName,customerClient.clientCustomer,customerClient.id,customerClient.level,customerClient.hidden,customerClient.level"
561
+ } };
562
+ const createClientLinkExamplePayload = { data: {
563
+ resourceName: "customers/1111111111/customerClientLinks/2222222222~3333333333",
564
+ managerCustomerId: "1111111111",
565
+ clientCustomerId: "2222222222",
566
+ managerLinkId: "3333333333"
567
+ } };
568
+ const rawRequestExamplePayload = { data: { resourceNames: ["customers/1234567890", "customers/5555555555"] } };
569
+ const listAccessibleCustomersDataSourceExamplePayload = { result: [{
570
+ label: "Example Account - 123-456-7890",
571
+ key: "1234567890"
572
+ }] };
573
+ const listAccessibleSubAccountsExamplePayload = { result: [{
574
+ label: "Example Account - 123-456-7890",
575
+ key: "1234567890"
576
+ }] };
577
+ const campaignChangesTriggerExamplePayload = { payload: {
578
+ headers: {},
579
+ queryParameters: {},
580
+ rawBody: { data: null },
581
+ body: { data: {
582
+ changes: [{
583
+ changeType: "created",
584
+ campaignId: "12345678901",
585
+ campaignName: "Example-Campaign-1",
586
+ field: "campaign",
587
+ oldValue: null,
588
+ newValue: {
589
+ resourceName: "customers/1234567890/campaigns/12345678901",
590
+ status: "ENABLED",
591
+ name: "Example-Campaign-1",
592
+ id: "12345678901"
593
+ },
594
+ changedAt: "2026-01-01 12:00:00"
595
+ }, {
596
+ changeType: "budget",
597
+ campaignId: "12345678901",
598
+ campaignName: "Example-Campaign-1",
599
+ field: "budget_amount_micros",
600
+ oldValue: "50000000",
601
+ newValue: "75000000",
602
+ changedAt: "2026-01-01 12:05:00"
603
+ }],
604
+ changesDetected: 2,
605
+ timeRange: {
606
+ start: "2026-01-01 11:00:00",
607
+ end: "2026-01-01 12:10:00"
608
+ },
609
+ syncedAt: "2026-01-01 12:10:00"
610
+ } },
611
+ pathFragment: "",
612
+ webhookUrls: {},
613
+ webhookApiKeys: {},
614
+ invokeUrl: "",
615
+ executionId: "RXhhbXBsZUV4ZWN1dGlvblJlc3VsdElk",
616
+ customer: {
617
+ id: "testCustomerId",
618
+ name: "Test Customer",
619
+ externalId: "testExternalId"
620
+ },
621
+ instance: {
622
+ id: "testInstanceId",
623
+ name: "Test Instance"
624
+ },
625
+ user: {
626
+ id: "testUserId",
627
+ email: "user@example.com",
628
+ name: "Test User",
629
+ externalId: "testUserExternalId"
630
+ },
631
+ integration: {
632
+ id: "testIntegrationId",
633
+ name: "Test Integration",
634
+ versionSequenceId: "1",
635
+ externalVersion: ""
636
+ },
637
+ flow: {
638
+ id: "testFlowId",
639
+ name: "Test Flow",
640
+ stableId: ""
641
+ },
642
+ startedAt: "2024-01-15T00:00:00.000Z",
643
+ globalDebug: false
644
+ } };
645
+ const budgetAlertTriggerExamplePayload = { payload: {
646
+ headers: {},
647
+ queryParameters: {},
648
+ rawBody: { data: null },
649
+ body: { data: {
650
+ alerts: [{
651
+ campaignId: "23302011123",
652
+ campaignName: "Example-Campaign-1",
653
+ budgetAmount: 100,
654
+ spent: 1e3,
655
+ percentSpent: 1e3,
656
+ remaining: -900,
657
+ period: "DAILY",
658
+ shouldAlert: true,
659
+ severity: BUDGET_SEVERITY.CRITICAL,
660
+ message: "Campaign has exceeded daily"
661
+ }],
662
+ totalCampaignsMonitored: 1,
663
+ alertThreshold: 80
664
+ } },
665
+ pathFragment: "",
666
+ webhookUrls: {},
667
+ webhookApiKeys: {},
668
+ invokeUrl: "",
669
+ executionId: "RXhhbXBsZUV4ZWN1dGlvblJlc3VsdElk",
670
+ customer: {
671
+ id: "testCustomerId",
672
+ name: "Test Customer",
673
+ externalId: "testExternalId"
674
+ },
675
+ instance: {
676
+ id: "testInstanceId",
677
+ name: "Test Instance"
678
+ },
679
+ user: {
680
+ id: "testUserId",
681
+ email: "user@example.com",
682
+ name: "Test User",
683
+ externalId: "testUserExternalId"
684
+ },
685
+ integration: {
686
+ id: "testIntegrationId",
687
+ name: "Test Integration",
688
+ versionSequenceId: "1",
689
+ externalVersion: ""
690
+ },
691
+ flow: {
692
+ id: "testFlowId",
693
+ name: "Test Flow",
694
+ stableId: ""
695
+ },
696
+ startedAt: "2024-01-15T00:00:00.000Z",
697
+ globalDebug: false
698
+ } };
699
+ const changeHistoryTriggerExamplePayload = { payload: {
700
+ headers: {},
701
+ queryParameters: {},
702
+ rawBody: { data: null },
703
+ body: { data: {
704
+ changes: [{ changeEvent: {
705
+ resourceName: "customers/6577008345/changeEvents/1764141874897602~0~1",
706
+ changeDateTime: "2025-11-26 01:24:34.897602",
707
+ changeResourceType: "CAMPAIGN_BUDGET",
708
+ changeResourceName: "customers/6577008345/campaignBudgets/15170398017",
709
+ clientType: "GOOGLE_ADS_WEB_CLIENT",
710
+ userEmail: "exampleUser@email.com",
711
+ oldResource: { campaignBudget: {} },
712
+ newResource: { campaignBudget: {
713
+ resourceName: "customers/6577008345/campaignBudgets/15170398017",
714
+ deliveryMethod: "STANDARD",
715
+ period: "DAILY",
716
+ type: "STANDARD",
717
+ id: "15170398017",
718
+ amountMicros: "481350000",
719
+ explicitlyShared: false
720
+ } },
721
+ resourceChangeOperation: "CREATE"
722
+ } }],
723
+ changeCount: 1,
724
+ timeRange: {
725
+ start: "2025-11-26 01:11:50",
726
+ end: "2025-11-26 01:30:17"
727
+ }
728
+ } },
729
+ pathFragment: "",
730
+ webhookUrls: {},
731
+ webhookApiKeys: {},
732
+ invokeUrl: "",
733
+ executionId: "RXhhbXBsZUV4ZWN1dGlvblJlc3VsdElk",
734
+ customer: {
735
+ id: "testCustomerId",
736
+ name: "Test Customer",
737
+ externalId: "testExternalId"
738
+ },
739
+ instance: {
740
+ id: "testInstanceId",
741
+ name: "Test Instance"
742
+ },
743
+ user: {
744
+ id: "testUserId",
745
+ email: "user@example.com",
746
+ name: "Test User",
747
+ externalId: "testUserExternalId"
748
+ },
749
+ integration: {
750
+ id: "testIntegrationId",
751
+ name: "Test Integration",
752
+ versionSequenceId: "1",
753
+ externalVersion: ""
754
+ },
755
+ flow: {
756
+ id: "testFlowId",
757
+ name: "Test Flow",
758
+ stableId: ""
759
+ },
760
+ startedAt: "2024-01-15T00:00:00.000Z",
761
+ globalDebug: false
762
+ } };
763
+ //#endregion
764
+ //#region src/util/format.ts
765
+ const formatAccountNumber = (accountNumber) => {
766
+ try {
767
+ const match = /^(\d{3})(\d{3})(\d{4})$/.exec(accountNumber);
768
+ if (match) return `${match[1]}-${match[2]}-${match[3]}`;
769
+ } catch {
770
+ return accountNumber;
771
+ }
772
+ return accountNumber;
773
+ };
774
+ //#endregion
775
+ //#region src/util/pagination/searchGoogleAds.ts
776
+ async function searchGoogleAds(client, options) {
777
+ const { customerId, params, fetchAll = false } = options;
778
+ const url = googleAdsSearchPath(customerId);
779
+ if (fetchAll) options.params.pageToken = void 0;
780
+ const firstData = (await client.post(url, params)).data;
781
+ if (!fetchAll || !firstData.nextPageToken) return firstData;
782
+ const accumulated = [...firstData.results ?? []];
783
+ let nextPageToken = firstData.nextPageToken;
784
+ while (nextPageToken) {
785
+ const { data } = await client.post(url, {
786
+ ...options.params,
787
+ pageToken: nextPageToken
788
+ });
789
+ if (data.results) accumulated.push(...data.results);
790
+ nextPageToken = data.nextPageToken || "";
791
+ }
792
+ return {
793
+ ...firstData,
794
+ results: accumulated,
795
+ nextPageToken: void 0
796
+ };
797
+ }
798
+ //#endregion
799
+ //#region src/util/records.ts
800
+ const getCustomerDescriptiveName = async (client, customerId) => {
801
+ try {
802
+ const { data } = await client.post(googleAdsSearchPath(customerId), { query: "SELECT customer.descriptive_name FROM customer" });
803
+ if (data?.results[0]?.customer) return data.results[0].customer?.descriptiveName || "(No descriptive name)";
804
+ return NO_DESCRIPTIVE_NAME;
805
+ } catch (_error) {
806
+ return DESCRIPTIVE_NAME_UNAVAILABLE;
807
+ }
808
+ };
809
+ //#endregion
810
+ //#region src/util/triggers/budgetCalculation.ts
811
+ const calculateBudgetStatus = (campaign, alertThreshold) => {
812
+ const budgetMicros = _prismatic_io_spectral.util.types.toNumber(campaign?.campaignBudget?.amountMicros);
813
+ const costMicros = _prismatic_io_spectral.util.types.toNumber(campaign?.metrics?.costMicros);
814
+ const budgetAmount = budgetMicros / MICROS_TO_DOLLARS_CONVERSION_FACTOR;
815
+ const spent = costMicros / MICROS_TO_DOLLARS_CONVERSION_FACTOR;
816
+ const percentSpent = budgetAmount > 0 ? spent / budgetAmount * 100 : 0;
817
+ const remaining = budgetAmount - spent;
818
+ let severity = BUDGET_SEVERITY.INFO;
819
+ let shouldAlert = false;
820
+ let message = "";
821
+ const period = campaign.campaignBudget?.period?.toLowerCase() || "budget";
822
+ if (percentSpent >= 100) {
823
+ severity = BUDGET_SEVERITY.CRITICAL;
824
+ shouldAlert = true;
825
+ message = `Campaign has exceeded ${period}`;
826
+ } else if (percentSpent >= 95) {
827
+ severity = BUDGET_SEVERITY.CRITICAL;
828
+ shouldAlert = true;
829
+ message = `Campaign has spent ${percentSpent.toFixed(1)}% of ${period}`;
830
+ } else if (percentSpent >= alertThreshold) {
831
+ severity = BUDGET_SEVERITY.WARNING;
832
+ shouldAlert = true;
833
+ message = `Campaign has spent ${percentSpent.toFixed(1)}% of ${period}`;
834
+ }
835
+ return {
836
+ campaignId: campaign.campaign.id,
837
+ campaignName: campaign.campaign.name,
838
+ budgetAmount,
839
+ spent,
840
+ percentSpent: Math.round(percentSpent),
841
+ remaining,
842
+ period: campaign.campaignBudget?.period || "UNKNOWN",
843
+ shouldAlert,
844
+ severity,
845
+ message
846
+ };
847
+ };
848
+ //#endregion
849
+ //#region src/util/triggers/changeDetection.ts
850
+ const isAllChangeTypesSelected = (changeTypes) => changeTypes.length === 0 || changeTypes.includes(CHANGE_TYPE.ALL);
851
+ const isSelected = (changeTypes, changeType) => changeTypes.includes(changeType) || isAllChangeTypesSelected(changeTypes);
852
+ const mapChangeEventsToCampaignChanges = (rows, changeTypes) => {
853
+ const changes = [];
854
+ for (const row of [...rows].reverse()) {
855
+ const event = row.changeEvent;
856
+ if (!event) continue;
857
+ const campaignId = row.campaign?.id ?? "";
858
+ const campaignName = row.campaign?.name ?? "";
859
+ const changedAt = event.changeDateTime;
860
+ const operation = event.resourceChangeOperation;
861
+ const oldCampaign = event.oldResource?.campaign;
862
+ const newCampaign = event.newResource?.campaign;
863
+ if (event.changeResourceType === CAMPAIGN_CHANGE_RESOURCE_TYPE.CAMPAIGN) {
864
+ if (operation === "CREATE") {
865
+ changes.push({
866
+ changeType: "created",
867
+ campaignId,
868
+ campaignName,
869
+ field: "campaign",
870
+ oldValue: null,
871
+ newValue: newCampaign ?? null,
872
+ changedAt
873
+ });
874
+ continue;
875
+ }
876
+ if (operation === "REMOVE") {
877
+ changes.push({
878
+ changeType: "deleted",
879
+ campaignId,
880
+ campaignName,
881
+ field: "campaign",
882
+ oldValue: oldCampaign ?? null,
883
+ newValue: null,
884
+ changedAt
885
+ });
886
+ continue;
887
+ }
888
+ if (isSelected(changeTypes, CHANGE_TYPE.STATUS) && oldCampaign?.status !== newCampaign?.status) changes.push({
889
+ changeType: "status",
890
+ campaignId,
891
+ campaignName,
892
+ field: "status",
893
+ oldValue: oldCampaign?.status ?? null,
894
+ newValue: newCampaign?.status ?? null,
895
+ changedAt
896
+ });
897
+ if (isSelected(changeTypes, CHANGE_TYPE.BIDDING) && oldCampaign?.biddingStrategyType !== newCampaign?.biddingStrategyType) changes.push({
898
+ changeType: "bidding",
899
+ campaignId,
900
+ campaignName,
901
+ field: "bidding_strategy_type",
902
+ oldValue: oldCampaign?.biddingStrategyType ?? null,
903
+ newValue: newCampaign?.biddingStrategyType ?? null,
904
+ changedAt
905
+ });
906
+ continue;
907
+ }
908
+ const oldAmount = event.oldResource?.campaignBudget?.amountMicros;
909
+ const newAmount = event.newResource?.campaignBudget?.amountMicros;
910
+ if (isSelected(changeTypes, CHANGE_TYPE.BUDGET) && oldAmount !== newAmount) changes.push({
911
+ changeType: "budget",
912
+ campaignId,
913
+ campaignName,
914
+ field: "budget_amount_micros",
915
+ oldValue: oldAmount ?? null,
916
+ newValue: newAmount ?? null,
917
+ changedAt
918
+ });
919
+ }
920
+ return changes.reverse();
921
+ };
922
+ //#endregion
923
+ //#region src/util/triggers/dateUtils.ts
924
+ const getCustomerTimezone = async (client, customerId) => {
925
+ const timeZone = (await client.post(googleAdsSearchPath(customerId), { query: "SELECT customer.time_zone FROM customer LIMIT 1" })).data?.results?.[0]?.customer?.timeZone;
926
+ if (!timeZone) throw new Error(`Unable to retrieve timezone for customer ${customerId}`);
927
+ return timeZone;
928
+ };
929
+ const getCurrentDate = (timeZone) => {
930
+ return new Intl.DateTimeFormat("en-CA", {
931
+ timeZone,
932
+ year: "numeric",
933
+ month: "2-digit",
934
+ day: "2-digit"
935
+ }).format(/* @__PURE__ */ new Date());
936
+ };
937
+ const getPreviousDate = (timeZone) => {
938
+ const formatter = new Intl.DateTimeFormat("en-CA", {
939
+ timeZone,
940
+ year: "numeric",
941
+ month: "2-digit",
942
+ day: "2-digit"
943
+ });
944
+ const date = /* @__PURE__ */ new Date();
945
+ date.setDate(date.getDate() - 1);
946
+ return formatter.format(date);
947
+ };
948
+ const getGAQLDateTime = (timeZone, hoursAgo) => {
949
+ const date = /* @__PURE__ */ new Date();
950
+ if (typeof hoursAgo === "number" && hoursAgo > 0) date.setHours(date.getHours() - hoursAgo);
951
+ const dateFormatter = new Intl.DateTimeFormat("en-CA", {
952
+ timeZone,
953
+ year: "numeric",
954
+ month: "2-digit",
955
+ day: "2-digit"
956
+ });
957
+ const timeFormatter = new Intl.DateTimeFormat("en-GB", {
958
+ timeZone,
959
+ hour: "2-digit",
960
+ minute: "2-digit",
961
+ second: "2-digit",
962
+ hour12: false
963
+ });
964
+ return `${dateFormatter.format(date)} ${timeFormatter.format(date)}`;
965
+ };
966
+ const clampToChangeEventWindow = (sinceTime, timeZone) => {
967
+ const floor = getGAQLDateTime(timeZone, 696);
968
+ return sinceTime < floor ? floor : sinceTime;
969
+ };
970
+ //#endregion
971
+ //#region src/util/triggers/changeEventPagination.ts
972
+ const toGAQLSecond = (changeDateTime) => changeDateTime.slice(0, 19);
973
+ const nextGAQLSecond = (dateTime) => new Date(Date.parse(`${dateTime.replace(" ", "T")}Z`) + 1e3).toISOString().slice(0, 19).replace("T", " ");
974
+ const isChangeEventCursor = (value) => {
975
+ const cursor = value;
976
+ return typeof cursor?.sinceTime === "string" && typeof cursor.toTime === "string" && Array.isArray(cursor.boundaryResourceNames);
977
+ };
978
+ const resolveChangeEventPageSize = (batchEnabled) => batchEnabled === true ? BATCHED_CHANGE_EVENT_ROW_LIMIT : CHANGE_EVENT_ROW_LIMIT;
979
+ const resolveChangeEventCursor = (options) => {
980
+ const { incoming, state, nowTime, timeZone } = options;
981
+ const cursor = [incoming, state.inFlightCursor].find(isChangeEventCursor) ?? {
982
+ sinceTime: state.lastChangeTime,
983
+ toTime: nowTime,
984
+ boundaryResourceNames: []
985
+ };
986
+ return {
987
+ ...cursor,
988
+ sinceTime: clampToChangeEventWindow(cursor.sinceTime, timeZone)
989
+ };
990
+ };
991
+ const advanceChangeEventCursor = (rows, cursor, pageSize, logger) => {
992
+ const alreadyEmitted = new Set(cursor.boundaryResourceNames);
993
+ const emit = rows.filter((row) => !alreadyEmitted.has(row.changeEvent?.resourceName ?? ""));
994
+ if (rows.length < pageSize) return {
995
+ emit,
996
+ nextCursor: null
997
+ };
998
+ const seconds = rows.map((row) => toGAQLSecond(row.changeEvent?.changeDateTime ?? cursor.sinceTime));
999
+ const newestSecond = seconds.reduce((max, s) => s > max ? s : max);
1000
+ if (emit.length === 0) {
1001
+ logger?.warn(`More than ${pageSize} change events share ${newestSecond}; skipping to the next second.`);
1002
+ const sinceTime = nextGAQLSecond(newestSecond);
1003
+ return {
1004
+ emit,
1005
+ nextCursor: sinceTime < cursor.toTime ? {
1006
+ ...cursor,
1007
+ sinceTime,
1008
+ boundaryResourceNames: []
1009
+ } : null
1010
+ };
1011
+ }
1012
+ const atNewestSecond = rows.filter((_row, index) => seconds[index] === newestSecond).map((row) => row.changeEvent?.resourceName ?? "").filter((name) => name !== "");
1013
+ const carried = newestSecond === cursor.sinceTime ? cursor.boundaryResourceNames : [];
1014
+ return {
1015
+ emit,
1016
+ nextCursor: {
1017
+ ...cursor,
1018
+ sinceTime: newestSecond,
1019
+ boundaryResourceNames: [.../* @__PURE__ */ new Set([...carried, ...atNewestSecond])]
1020
+ }
1021
+ };
1022
+ };
1023
+ //#endregion
1024
+ //#region src/util/triggers/handlePollingError.ts
1025
+ const handlePollingError = (error, pollState, context, serviceName) => {
1026
+ const consecutiveErrors = (pollState.consecutiveErrors || 0) + 1;
1027
+ const errorCount = (pollState.errorCount || 0) + 1;
1028
+ context.polling.setState({
1029
+ ...pollState,
1030
+ errorCount,
1031
+ consecutiveErrors
1032
+ });
1033
+ if (consecutiveErrors >= 3) context.logger.error(`${serviceName} polling failed ${consecutiveErrors} times consecutively. Error: ${error.message}`);
1034
+ throw error;
1035
+ };
1036
+ //#endregion
1037
+ //#region src/util/triggers/queryBuilders.ts
1038
+ const resolveCampaignChangeResourceTypes = (changeTypes) => {
1039
+ const all = isAllChangeTypesSelected(changeTypes);
1040
+ const types = [CAMPAIGN_CHANGE_RESOURCE_TYPE.CAMPAIGN];
1041
+ if (all || changeTypes.includes(CHANGE_TYPE.BUDGET)) types.push(CAMPAIGN_CHANGE_RESOURCE_TYPE.CAMPAIGN_BUDGET);
1042
+ return types;
1043
+ };
1044
+ const buildCampaignChangeEventQuery = (options) => {
1045
+ const { sinceTime, toTime, changeTypes, limit } = options;
1046
+ return `
1047
+ SELECT
1048
+ change_event.resource_name,
1049
+ change_event.change_date_time,
1050
+ change_event.change_resource_type,
1051
+ change_event.change_resource_name,
1052
+ change_event.resource_change_operation,
1053
+ change_event.changed_fields,
1054
+ change_event.old_resource,
1055
+ change_event.new_resource,
1056
+ campaign.id,
1057
+ campaign.name
1058
+ FROM change_event
1059
+ WHERE change_event.change_date_time >= '${sinceTime}'
1060
+ AND change_event.change_date_time < '${toTime}'
1061
+ AND change_event.change_resource_type IN (${resolveCampaignChangeResourceTypes(changeTypes).map((type) => `'${type}'`).join(",")})
1062
+ ORDER BY change_event.change_date_time ASC
1063
+ LIMIT ${limit}
1064
+ `.trim();
1065
+ };
1066
+ const buildBudgetAlertQuery = (options) => {
1067
+ const { includeSharedBudgets } = options;
1068
+ return `
1069
+ SELECT
1070
+ campaign.id,
1071
+ campaign.name,
1072
+ campaign_budget.amount_micros,
1073
+ campaign_budget.total_amount_micros,
1074
+ campaign_budget.period,
1075
+ metrics.cost_micros
1076
+ FROM campaign
1077
+ WHERE segments.date DURING TODAY
1078
+ AND campaign.status = 'ENABLED'${includeSharedBudgets ? "" : "\n AND campaign_budget.explicitly_shared = FALSE"}
1079
+ `.trim();
1080
+ };
1081
+ const buildChangeHistoryQuery = (options) => {
1082
+ const { sinceTime, toTime, resourceTypes, includeUserInfo, limit } = options;
1083
+ const resourceFilter = resourceTypes.length > 0 ? `AND change_event.change_resource_type IN (${resourceTypes.map((type) => `'${CHANGE_HISTORY_RESOURCE_TYPE_QUERY_VALUE[type] ?? type}'`).join(",")})` : "";
1084
+ return `
1085
+ SELECT
1086
+ change_event.resource_name,
1087
+ change_event.change_date_time,
1088
+ change_event.change_resource_type,
1089
+ change_event.change_resource_name,
1090
+ ${includeUserInfo ? "change_event.user_email,\n change_event.client_type," : ""}
1091
+ change_event.resource_change_operation,
1092
+ change_event.old_resource,
1093
+ change_event.new_resource
1094
+ FROM change_event
1095
+ WHERE change_event.change_date_time >= '${sinceTime}'
1096
+ AND change_event.change_date_time < '${toTime}'
1097
+ ${resourceFilter}
1098
+ ORDER BY change_event.change_date_time ASC
1099
+ LIMIT ${limit}
1100
+ `.trim();
1101
+ };
1102
+ //#endregion
1103
+ //#region src/util/triggers/resolvers.ts
1104
+ const resolveCampaignChanges = (data) => {
1105
+ return ((data ?? {
1106
+ changes: [],
1107
+ changesDetected: 0,
1108
+ timeRange: {
1109
+ start: "",
1110
+ end: ""
1111
+ },
1112
+ syncedAt: ""
1113
+ }).changes ?? []).map((record) => ({
1114
+ changeType: record.changeType,
1115
+ record
1116
+ }));
1117
+ };
1118
+ const resolveChangeHistoryItems = (data) => {
1119
+ return ((data ?? {
1120
+ changes: [],
1121
+ changeCount: 0,
1122
+ timeRange: {
1123
+ start: "",
1124
+ end: ""
1125
+ }
1126
+ }).changes ?? []).map((record) => ({
1127
+ changeType: OPERATION_TO_CHANGE_TYPE[record.changeEvent.resourceChangeOperation] ?? "updated",
1128
+ record
1129
+ }));
1130
+ };
1131
+ const resolveBudgetAlerts = (data) => {
1132
+ return ((data ?? {
1133
+ alerts: [],
1134
+ totalCampaignsMonitored: 0,
1135
+ alertThreshold: 0
1136
+ }).alerts ?? []).map((record) => ({
1137
+ changeType: record.severity,
1138
+ record
1139
+ }));
1140
+ };
1141
+ //#endregion
1142
+ //#region src/util/triggers/triggerHelpers.ts
1143
+ const createTriggerClient = async (context, params) => {
1144
+ const client = createClient({
1145
+ connection: params.connection,
1146
+ debugEnabled: context.debug.enabled,
1147
+ logger: context.logger,
1148
+ loginCustomerId: params.managerCustomerId
1149
+ });
1150
+ return {
1151
+ client,
1152
+ timezone: await getCustomerTimezone(client, params.customerId)
1153
+ };
1154
+ };
1155
+ const getPollingState = (context, initialState) => {
1156
+ const state = context.polling.getState();
1157
+ return state && Object.keys(state).length > 0 ? state : initialState;
1158
+ };
1159
+ const buildTriggerPayload = (payload, data) => ({
1160
+ ...payload,
1161
+ body: { data }
1162
+ });
1163
+ //#endregion
1164
+ //#region src/inputs/common.ts
1165
+ const connectionInput = (0, _prismatic_io_spectral.input)({
1166
+ label: "Connection",
1167
+ type: "connection",
1168
+ required: true,
1169
+ comments: "The Google Ads connection to use."
1170
+ });
1171
+ const customerIdInput = (0, _prismatic_io_spectral.input)({
1172
+ label: "Customer ID",
1173
+ placeholder: "Enter customer ID",
1174
+ type: "string",
1175
+ required: true,
1176
+ comments: "The unique identifier for the Google Ads client account. Accepts hyphenated or number forms. See [Customer ID documentation](https://developers.google.com/google-ads/api/docs/concepts/call-structure#cid).",
1177
+ example: "111-222-4444",
1178
+ dataSource: "listAccessibleCustomers",
1179
+ clean: cleanCustomerId
1180
+ });
1181
+ const managerCustomerIdInput = (0, _prismatic_io_spectral.input)({
1182
+ label: "Manager Customer ID",
1183
+ placeholder: "Enter manager customer ID",
1184
+ type: "string",
1185
+ required: true,
1186
+ comments: "The unique identifier for the Google Ads Manager account. Accepts hyphenated or number forms. When used in conjunction with a sub account as the customer ID, this value is used as the 'login-customer-id' header for the HTTP request. See [Customer ID documentation](https://developers.google.com/google-ads/api/docs/concepts/call-structure#cid).",
1187
+ example: "123-456-7890",
1188
+ dataSource: "listAccessibleCustomers",
1189
+ clean: cleanCustomerId
1190
+ });
1191
+ const pageSizeInput = (0, _prismatic_io_spectral.input)({
1192
+ label: "Page Size",
1193
+ placeholder: "Enter page size",
1194
+ type: "string",
1195
+ required: false,
1196
+ example: "100",
1197
+ clean: toOptionalInt,
1198
+ comments: "The maximum number of results to return per page."
1199
+ });
1200
+ const pageTokenInput = (0, _prismatic_io_spectral.input)({
1201
+ label: "Page Token",
1202
+ placeholder: "Enter page token",
1203
+ type: "string",
1204
+ required: false,
1205
+ example: "CJL5XLT2PWDmIpGNGciABRnu",
1206
+ clean: cleanString,
1207
+ comments: "The pagination cursor from a previous request. Returned in previous page responses."
1208
+ });
1209
+ const validateOnly = (0, _prismatic_io_spectral.input)({
1210
+ label: "Validate Only",
1211
+ type: "boolean",
1212
+ required: false,
1213
+ clean: _prismatic_io_spectral.util.types.toBool,
1214
+ default: "false",
1215
+ comments: "When true, the request is validated but not executed. Only errors are returned, not results."
1216
+ });
1217
+ const fetchAll = (0, _prismatic_io_spectral.input)({
1218
+ label: "Fetch All",
1219
+ type: "boolean",
1220
+ required: false,
1221
+ default: "false",
1222
+ clean: _prismatic_io_spectral.util.types.toBool,
1223
+ comments: "When true, automatically fetches all pages of results. When false, only the first page of results will be returned."
1224
+ });
1225
+ const startDateInput = (0, _prismatic_io_spectral.input)({
1226
+ label: "Start Date",
1227
+ placeholder: "Enter start date (MM-DD-YYYY)",
1228
+ type: "string",
1229
+ required: false,
1230
+ example: "01-01-2025",
1231
+ comments: "The start date of the date range, inclusive. Format: MM-DD-YYYY.",
1232
+ clean: cleanString
1233
+ });
1234
+ const endDateInput = (0, _prismatic_io_spectral.input)({
1235
+ label: "End Date",
1236
+ placeholder: "Enter end date (MM-DD-YYYY)",
1237
+ type: "string",
1238
+ required: false,
1239
+ example: "12-31-2025",
1240
+ comments: "The end date of the date range, inclusive. Format: MM-DD-YYYY.",
1241
+ clean: cleanString
1242
+ });
1243
+ const customerClientLevel = (0, _prismatic_io_spectral.input)({
1244
+ label: "Customer Client Level",
1245
+ type: "string",
1246
+ required: false,
1247
+ comments: "The hierarchy depth level of the customer client to retrieve. Defaults to 1 (direct children only).",
1248
+ example: "1",
1249
+ placeholder: "Enter level",
1250
+ default: "1",
1251
+ clean: cleanString
1252
+ });
1253
+ //#endregion
1254
+ //#region src/inputs/campaign.ts
1255
+ const partialFailure = (0, _prismatic_io_spectral.input)({
1256
+ label: "Partial Failure",
1257
+ type: "boolean",
1258
+ required: true,
1259
+ clean: _prismatic_io_spectral.util.types.toBool,
1260
+ default: "false",
1261
+ comments: "When true, successful operations will be carried out and invalid operations will return errors. When false, all operations will be carried out in one transaction if and only if they are all valid. This should always be set to true. See [Partial failure documentation](https://developers.google.com/google-ads/api/docs/best-practices/partial-failures)."
1262
+ });
1263
+ const operations = (0, _prismatic_io_spectral.input)({
1264
+ label: "Operations",
1265
+ placeholder: "Enter campaign operations as a JSON array",
1266
+ type: "code",
1267
+ language: "json",
1268
+ required: true,
1269
+ default: JSON.stringify(campaignOperationsExampleInput, null, 2),
1270
+ comments: "The list of operations to perform on individual campaigns. See [Campaign operations documentation](https://developers.google.com/google-ads/api/reference/rpc/v25/CampaignOperation).",
1271
+ clean: _prismatic_io_spectral.util.types.toObject
1272
+ });
1273
+ const mutateCampaignInputs = {
1274
+ connection: connectionInput,
1275
+ customerId: customerIdInput,
1276
+ operations,
1277
+ partialFailure,
1278
+ managerCustomerId: {
1279
+ ...managerCustomerIdInput,
1280
+ required: false,
1281
+ clean: toOptionalCustomerId
1282
+ },
1283
+ validateOnly
1284
+ };
1285
+ const mutateCampaignCriteriaInputs = {
1286
+ connection: connectionInput,
1287
+ customerId: customerIdInput,
1288
+ operations: {
1289
+ ...operations,
1290
+ default: JSON.stringify(campaignCriteriaOperationsExampleInput, null, 2),
1291
+ placeholder: "Enter campaign criterion operations as a JSON array",
1292
+ comments: "The list of operations to create, update, or remove individual campaign criteria. See [Campaign criterion operations documentation](https://developers.google.com/google-ads/api/reference/rpc/v25/CampaignCriterionOperation)."
1293
+ },
1294
+ partialFailure,
1295
+ managerCustomerId: {
1296
+ ...managerCustomerIdInput,
1297
+ required: false,
1298
+ clean: toOptionalCustomerId
1299
+ },
1300
+ validateOnly
1301
+ };
1302
+ //#endregion
1303
+ //#region src/inputs/conversions.ts
1304
+ const conversions = (0, _prismatic_io_spectral.input)({
1305
+ label: "Conversions",
1306
+ placeholder: "Enter conversions as a JSON array",
1307
+ type: "code",
1308
+ language: "json",
1309
+ required: true,
1310
+ example: JSON.stringify(clickConversionsExampleInput, null, 2),
1311
+ comments: "The click conversions to upload, each identifying the click (for example, by GCLID) and the conversion action, time, and value. See [Offline conversions documentation](https://developers.google.com/google-ads/api/docs/conversions/upload-offline).",
1312
+ clean: _prismatic_io_spectral.util.types.toObject
1313
+ });
1314
+ const ingestOfflineConversionsInputs = {
1315
+ connection: connectionInput,
1316
+ events: (0, _prismatic_io_spectral.input)({
1317
+ label: "Events",
1318
+ placeholder: "Enter conversion events as a JSON array",
1319
+ type: "code",
1320
+ language: "json",
1321
+ required: true,
1322
+ example: JSON.stringify([{
1323
+ eventTimestamp: "2026-05-15T12:30:00Z",
1324
+ transactionId: "ORDER-2026-00123",
1325
+ adIdentifiers: { gclid: "CjwKCAjw1234567890abcdefGHIJKLmnoPQRStuvwxyz" },
1326
+ conversionValue: 149.99,
1327
+ currency: "USD",
1328
+ consent: {
1329
+ adUserData: "CONSENT_GRANTED",
1330
+ adPersonalization: "CONSENT_GRANTED"
1331
+ }
1332
+ }], null, 2),
1333
+ comments: "The array of conversion events to ingest (max 2000 per request). See [Event resource](https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest).",
1334
+ clean: _prismatic_io_spectral.util.types.toObject
1335
+ }),
1336
+ destinations: (0, _prismatic_io_spectral.input)({
1337
+ label: "Destinations",
1338
+ placeholder: "Enter destinations as a JSON array",
1339
+ type: "code",
1340
+ language: "json",
1341
+ required: true,
1342
+ example: JSON.stringify([{
1343
+ operatingAccount: {
1344
+ accountType: "GOOGLE_ADS",
1345
+ accountId: "1234567890"
1346
+ },
1347
+ productDestinationId: "987654321"
1348
+ }], null, 2),
1349
+ comments: "The array of destinations that describe where each event should be ingested. See [Destination reference](https://developers.google.com/data-manager/api/reference/rest/v1/Destination).",
1350
+ clean: _prismatic_io_spectral.util.types.toObject
1351
+ }),
1352
+ encoding: (0, _prismatic_io_spectral.input)({
1353
+ label: "Hash Encoding",
1354
+ type: "string",
1355
+ required: false,
1356
+ example: "HEX",
1357
+ model: [{
1358
+ label: "HEX",
1359
+ value: "HEX"
1360
+ }, {
1361
+ label: "BASE64",
1362
+ value: "BASE64"
1363
+ }],
1364
+ comments: "The encoding format to select for hashed user data fields (such as email or phone). Required when `userData` fields are included in events.",
1365
+ clean: cleanString
1366
+ }),
1367
+ validateOnly
1368
+ };
1369
+ const uploadCallConversionsInputs = {
1370
+ connection: connectionInput,
1371
+ customerId: customerIdInput,
1372
+ conversions: {
1373
+ ...conversions,
1374
+ example: JSON.stringify([{
1375
+ callerId: "+16505550100",
1376
+ callStartDateTime: "2026-01-15 10:00:00-05:00",
1377
+ conversionAction: "customers/1234567890/conversionActions/987654321",
1378
+ conversionDateTime: "2026-01-15 10:30:00-05:00",
1379
+ conversionValue: 149.99,
1380
+ currencyCode: "USD",
1381
+ consent: {
1382
+ adUserData: "GRANTED",
1383
+ adPersonalization: "GRANTED"
1384
+ },
1385
+ customVariables: [{
1386
+ conversionCustomVariable: "customers/1234567890/conversionCustomVariables/111222333",
1387
+ value: "premium"
1388
+ }]
1389
+ }], null, 2),
1390
+ comments: "The call conversions to upload, each identifying the call by caller ID and call start time. See [Call conversions documentation](https://developers.google.com/google-ads/api/docs/conversions/upload-calls)."
1391
+ },
1392
+ managerCustomerId: {
1393
+ ...managerCustomerIdInput,
1394
+ required: false,
1395
+ clean: toOptionalCustomerId
1396
+ },
1397
+ validateOnly
1398
+ };
1399
+ const uploadClickConversionsInputs = {
1400
+ connection: connectionInput,
1401
+ customerId: customerIdInput,
1402
+ conversions,
1403
+ managerCustomerId: {
1404
+ ...managerCustomerIdInput,
1405
+ required: false,
1406
+ clean: toOptionalCustomerId
1407
+ },
1408
+ validateOnly
1409
+ };
1410
+ const confirmClientLinkInputs = {
1411
+ connection: connectionInput,
1412
+ managerCustomerId: managerCustomerIdInput,
1413
+ customerId: customerIdInput,
1414
+ managerLinkId: (0, _prismatic_io_spectral.input)({
1415
+ label: "Manager Link ID",
1416
+ placeholder: "Enter manager link ID",
1417
+ type: "string",
1418
+ required: true,
1419
+ example: "1234567890",
1420
+ comments: "The unique identifier of the manager link. See [Customer manager link documentation](https://developers.google.com/google-ads/api/reference/rpc/v25/CustomerManagerLink).",
1421
+ clean: _prismatic_io_spectral.util.types.toString
1422
+ })
1423
+ };
1424
+ const createClientLinkInputs = {
1425
+ connection: connectionInput,
1426
+ managerCustomerId: managerCustomerIdInput,
1427
+ customerId: customerIdInput
1428
+ };
1429
+ const customerEntityInputs = {
1430
+ connection: connectionInput,
1431
+ customerId: customerIdInput,
1432
+ pageToken: pageTokenInput,
1433
+ managerCustomerId: {
1434
+ ...managerCustomerIdInput,
1435
+ required: false,
1436
+ clean: toOptionalCustomerId
1437
+ }
1438
+ };
1439
+ const listCustomersInputs = {
1440
+ connection: connectionInput,
1441
+ managerCustomerId: managerCustomerIdInput,
1442
+ fetchAll,
1443
+ pageToken: pageTokenInput
1444
+ };
1445
+ //#endregion
1446
+ //#region src/inputs/dataSources.ts
1447
+ const listAccessibleCustomersInputs = { connection: connectionInput };
1448
+ const listAccessibleSubAccountsInputs = {
1449
+ connection: connectionInput,
1450
+ customerId: {
1451
+ ...customerIdInput,
1452
+ dataSource: void 0
1453
+ },
1454
+ customerClientLevel
1455
+ };
1456
+ //#endregion
1457
+ //#region src/inputs/localServices.ts
1458
+ const customerIds = (0, _prismatic_io_spectral.input)({
1459
+ label: "Customer IDs",
1460
+ placeholder: "Enter customer ID",
1461
+ type: "string",
1462
+ collection: "valuelist",
1463
+ required: false,
1464
+ clean: valueListInputClean,
1465
+ comments: "The Google Ads customer IDs to filter the Local Services report. Leave empty to include all accessible customers."
1466
+ });
1467
+ const pagination = (0, _prismatic_io_spectral.structuredObjectInput)({
1468
+ label: "Pagination",
1469
+ required: false,
1470
+ comments: "Page size and cursor controls for the result set.",
1471
+ inputs: {
1472
+ pageSizeInput: {
1473
+ ...pageSizeInput,
1474
+ required: false,
1475
+ default: "1000"
1476
+ },
1477
+ pageTokenInput
1478
+ }
1479
+ });
1480
+ const accountReportsInputs = {
1481
+ connection: connectionInput,
1482
+ managerCustomerIdInput,
1483
+ pagination,
1484
+ customerIds,
1485
+ startDateInput,
1486
+ endDateInput
1487
+ };
1488
+ const detailedLeadReportsInputs = {
1489
+ connection: connectionInput,
1490
+ managerCustomerIdInput,
1491
+ pagination,
1492
+ customerIds,
1493
+ startDateInput,
1494
+ endDateInput
1495
+ };
1496
+ //#endregion
1497
+ //#region src/inputs/misc.ts
1498
+ const { debugRequest: _, ...baseRawRequestInputs } = _prismatic_io_spectral_http.inputs;
1499
+ const rawRequestInputs = {
1500
+ connection: connectionInput,
1501
+ ...baseRawRequestInputs,
1502
+ url: {
1503
+ ...baseRawRequestInputs.url,
1504
+ example: `/v25/customers:listAccessibleCustomers`,
1505
+ comments: `Input the path only (/v25/customers:listAccessibleCustomers), The base URL is already included (${GOOGLE_ADS_BASE_URL}). For example, to connect to ${GOOGLE_ADS_BASE_URL}/v25/customers:listAccessibleCustomers, only /v25/customers:listAccessibleCustomers is entered in this field. Note: When using the Raw Request action, the API version must be specified in the path (e.g., /v25/) to override the connection default.`,
1506
+ default: `/v25/`
1507
+ }
1508
+ };
1509
+ //#endregion
1510
+ //#region src/inputs/search.ts
1511
+ const query = (0, _prismatic_io_spectral.input)({
1512
+ label: "Query",
1513
+ placeholder: "Enter GAQL query",
1514
+ type: "string",
1515
+ required: true,
1516
+ clean: _prismatic_io_spectral.util.types.toString,
1517
+ comments: "Google Ads Query Language (GAQL) query string. See [GAQL documentation](https://developers.google.com/google-ads/api/docs/query/overview).",
1518
+ example: `SELECT
1519
+ campaign.id,
1520
+ campaign.status,
1521
+ campaign_budget.id,
1522
+ campaign_budget.period,
1523
+ campaign_budget.amount_micros,
1524
+ campaign_budget.type
1525
+ FROM campaign
1526
+ WHERE campaign.advertising_channel_type = 'LOCAL_SERVICES'`
1527
+ });
1528
+ const returnTotalResultsCount = (0, _prismatic_io_spectral.input)({
1529
+ label: "Return Total Results Count",
1530
+ type: "boolean",
1531
+ required: false,
1532
+ default: "false",
1533
+ clean: _prismatic_io_spectral.util.types.toBool,
1534
+ comments: "When true, the total number of results that match the query ignoring the LIMIT clause will be included in the response. Default is false."
1535
+ });
1536
+ const searchAdsLocalServicesInputs = {
1537
+ connection: connectionInput,
1538
+ customerId: customerIdInput,
1539
+ query,
1540
+ fetchAll,
1541
+ pageTokenInput,
1542
+ managerCustomerId: {
1543
+ ...managerCustomerIdInput,
1544
+ required: false,
1545
+ clean: toOptionalCustomerId
1546
+ },
1547
+ returnTotalResultsCount
1548
+ };
1549
+ //#endregion
1550
+ //#region src/inputs/triggers.ts
1551
+ const lookBackDate = (0, _prismatic_io_spectral.input)({
1552
+ label: "Look-back Date",
1553
+ placeholder: "Enter look-back date (YYYY-MM-DD)",
1554
+ type: "string",
1555
+ required: false,
1556
+ comments: "The date the initial sync starts from, in YYYY-MM-DD format. Cannot be a future date. Leave empty to start from the first recurrence with no backfill. When set, the initial sync's change_event query starts from this date (clamped to Google's 30-day change_event window) instead of one hour before the first run.",
1557
+ example: "2026-01-01",
1558
+ clean: lookBackDateClean
1559
+ });
1560
+ const changeTypes = (0, _prismatic_io_spectral.input)({
1561
+ label: "Change Types to Monitor",
1562
+ type: "string",
1563
+ collection: "valuelist",
1564
+ required: false,
1565
+ model: CHANGE_TYPES_MODEL,
1566
+ comments: "Types of campaign changes to detect. Leave empty to detect all change types. The Campaign Settings option is not yet implemented and currently has no effect; no settings fields are evaluated.",
1567
+ placeholder: "Enter change types",
1568
+ clean: toStringList
1569
+ });
1570
+ const campaignChangesTriggerInputs = {
1571
+ connection: connectionInput,
1572
+ customerId: customerIdInput,
1573
+ lookBackDate,
1574
+ managerCustomerId: {
1575
+ ...managerCustomerIdInput,
1576
+ required: false,
1577
+ clean: toOptionalCustomerId
1578
+ },
1579
+ changeTypes
1580
+ };
1581
+ const alertThreshold = (0, _prismatic_io_spectral.input)({
1582
+ label: "Alert Threshold (%)",
1583
+ type: "string",
1584
+ required: false,
1585
+ default: 80 .toString(),
1586
+ comments: "Budget spend percentage at which to trigger an alert.",
1587
+ example: 80 .toString(),
1588
+ placeholder: "Enter alert threshold percentage",
1589
+ clean: toOptionalInt
1590
+ });
1591
+ const includeSharedBudgets = (0, _prismatic_io_spectral.input)({
1592
+ label: "Include Shared Budgets",
1593
+ type: "boolean",
1594
+ required: false,
1595
+ default: "true",
1596
+ comments: "When true, shared budgets across multiple campaigns will be monitored.",
1597
+ clean: _prismatic_io_spectral.util.types.toBool
1598
+ });
1599
+ const budgetAlertTriggerInputs = {
1600
+ connection: connectionInput,
1601
+ customerId: {
1602
+ ...customerIdInput,
1603
+ comments: "The unique identifier for the Google Ads client account. Accepts hyphenated or number forms. Use a client account; manager accounts return no metrics. See [Customer ID documentation](https://developers.google.com/google-ads/api/docs/concepts/call-structure#cid)."
1604
+ },
1605
+ managerCustomerId: {
1606
+ ...managerCustomerIdInput,
1607
+ required: false,
1608
+ clean: toOptionalCustomerId
1609
+ },
1610
+ alertThreshold,
1611
+ includeSharedBudgets
1612
+ };
1613
+ const resourceTypes = (0, _prismatic_io_spectral.input)({
1614
+ label: "Resource Types",
1615
+ type: "string",
1616
+ collection: "valuelist",
1617
+ required: false,
1618
+ model: [
1619
+ {
1620
+ label: "Campaigns",
1621
+ value: "CAMPAIGN"
1622
+ },
1623
+ {
1624
+ label: "Ad Groups",
1625
+ value: "AD_GROUP"
1626
+ },
1627
+ {
1628
+ label: "Ads",
1629
+ value: "AD"
1630
+ },
1631
+ {
1632
+ label: "Keywords",
1633
+ value: "KEYWORD"
1634
+ }
1635
+ ],
1636
+ comments: "Types of resources to track changes for. Leave empty to track all resource types.",
1637
+ placeholder: "Enter resource types",
1638
+ clean: toStringList
1639
+ });
1640
+ const includeUserInfo = (0, _prismatic_io_spectral.input)({
1641
+ label: "Include User Info",
1642
+ type: "boolean",
1643
+ required: false,
1644
+ default: "true",
1645
+ comments: "When true, user email and client type will be included in change events.",
1646
+ clean: _prismatic_io_spectral.util.types.toBool
1647
+ });
1648
+ const changeHistoryTriggerInputs = {
1649
+ connection: connectionInput,
1650
+ customerId: customerIdInput,
1651
+ lookBackDate,
1652
+ managerCustomerId: {
1653
+ ...managerCustomerIdInput,
1654
+ required: false,
1655
+ clean: toOptionalCustomerId
1656
+ },
1657
+ resourceTypes,
1658
+ includeUserInfo
1659
+ };
1660
+ const inviteUserInputs = {
1661
+ connection: connectionInput,
1662
+ customerId: customerIdInput,
1663
+ emailAddress: (0, _prismatic_io_spectral.input)({
1664
+ label: "Email Address",
1665
+ placeholder: "Enter email address",
1666
+ type: "string",
1667
+ required: true,
1668
+ example: "john.doe@example.com",
1669
+ comments: "The email address of the user to invite to the customer account.",
1670
+ clean: _prismatic_io_spectral.util.types.toString
1671
+ }),
1672
+ accessRole: (0, _prismatic_io_spectral.input)({
1673
+ label: "Access Role",
1674
+ placeholder: "Enter access role",
1675
+ type: "string",
1676
+ required: true,
1677
+ comments: "The access role to grant to the user. See [Access roles documentation](https://developers.google.com/google-ads/api/reference/rpc/v25/AccessRoleEnum.AccessRole).",
1678
+ model: [
1679
+ "ADMIN",
1680
+ "STANDARD",
1681
+ "READ_ONLY",
1682
+ "EMAIL_ONLY"
1683
+ ].map((role) => ({
1684
+ label: role,
1685
+ value: role
1686
+ })),
1687
+ clean: _prismatic_io_spectral.util.types.toString
1688
+ })
1689
+ };
1690
+ //#endregion
1691
+ //#region src/outputSchemas/common.ts
1692
+ const googleRpcStatusSchema = {
1693
+ type: "object",
1694
+ properties: {
1695
+ code: { type: "integer" },
1696
+ message: { type: "string" },
1697
+ details: {
1698
+ type: "array",
1699
+ items: { type: "object" }
1700
+ }
1701
+ }
1702
+ };
1703
+ const mutateResourceNameResultSchema = {
1704
+ type: "object",
1705
+ properties: { resourceName: { type: "string" } }
1706
+ };
1707
+ const localServicesAggregatorInfoSchema = {
1708
+ type: "object",
1709
+ properties: { aggregatorProviderId: { type: "string" } }
1710
+ };
1711
+ const userIdentifierSchema = {
1712
+ type: "object",
1713
+ properties: {
1714
+ userIdentifierSource: {
1715
+ type: "string",
1716
+ enum: [
1717
+ "UNSPECIFIED",
1718
+ "UNKNOWN",
1719
+ "FIRST_PARTY",
1720
+ "THIRD_PARTY"
1721
+ ]
1722
+ },
1723
+ hashedEmail: { type: "string" },
1724
+ hashedPhoneNumber: { type: "string" },
1725
+ mobileId: { type: "string" },
1726
+ thirdPartyUserId: { type: "string" },
1727
+ addressInfo: {
1728
+ type: "object",
1729
+ properties: {
1730
+ hashedFirstName: { type: "string" },
1731
+ hashedLastName: { type: "string" },
1732
+ city: { type: "string" },
1733
+ state: { type: "string" },
1734
+ countryCode: { type: "string" },
1735
+ postalCode: { type: "string" },
1736
+ hashedStreetAddress: { type: "string" }
1737
+ }
1738
+ }
1739
+ }
1740
+ };
1741
+ //#endregion
1742
+ //#region src/outputSchemas/campaign.ts
1743
+ const mutateCampaignOutputSchema = {
1744
+ type: "object",
1745
+ properties: {
1746
+ partialFailureError: googleRpcStatusSchema,
1747
+ results: {
1748
+ type: "array",
1749
+ items: mutateResourceNameResultSchema
1750
+ }
1751
+ }
1752
+ };
1753
+ const mutateCampaignCriteriaOutputSchema = {
1754
+ type: "object",
1755
+ properties: {
1756
+ partialFailureError: googleRpcStatusSchema,
1757
+ results: {
1758
+ type: "array",
1759
+ items: mutateResourceNameResultSchema
1760
+ }
1761
+ }
1762
+ };
1763
+ //#endregion
1764
+ //#region src/outputSchemas/conversions.ts
1765
+ const uploadCallConversionsOutputSchema = {
1766
+ type: "object",
1767
+ properties: {
1768
+ partialFailureError: googleRpcStatusSchema,
1769
+ results: {
1770
+ type: "array",
1771
+ items: {
1772
+ type: "object",
1773
+ properties: {
1774
+ callerId: { type: "string" },
1775
+ callStartDateTime: { type: "string" },
1776
+ conversionAction: { type: "string" },
1777
+ conversionDateTime: { type: "string" }
1778
+ }
1779
+ }
1780
+ }
1781
+ }
1782
+ };
1783
+ const uploadClickConversionsOutputSchema = {
1784
+ type: "object",
1785
+ properties: {
1786
+ partialFailureError: googleRpcStatusSchema,
1787
+ jobId: { type: "string" },
1788
+ results: {
1789
+ type: "array",
1790
+ items: {
1791
+ type: "object",
1792
+ properties: {
1793
+ gclid: { type: "string" },
1794
+ gbraid: { type: "string" },
1795
+ wbraid: { type: "string" },
1796
+ conversionAction: { type: "string" },
1797
+ conversionDateTime: { type: "string" },
1798
+ userIdentifiers: {
1799
+ type: "array",
1800
+ items: userIdentifierSchema
1801
+ }
1802
+ }
1803
+ }
1804
+ }
1805
+ }
1806
+ };
1807
+ const ingestOfflineConversionsOutputSchema = {
1808
+ type: "object",
1809
+ properties: {
1810
+ requestId: { type: "string" },
1811
+ fieldWarnings: {
1812
+ type: "array",
1813
+ items: {
1814
+ type: "object",
1815
+ properties: {
1816
+ reason: {
1817
+ type: "string",
1818
+ enum: [
1819
+ "WARNING_REASON_UNSPECIFIED",
1820
+ "WARNING_REASON_CUSTOM_VARIABLE_NOT_ENABLED",
1821
+ "WARNING_REASON_CUSTOM_VARIABLE_NOT_PREDEFINED",
1822
+ "WARNING_REASON_CART_DATA_NOT_SUPPORTED_WITH_GBRAID_OR_WBRAID",
1823
+ "WARNING_REASON_CART_DATA_ITEM_MERCHANT_PRODUCT_ID_MISSING",
1824
+ "WARNING_REASON_CART_DATA_ITEM_UNIT_PRICE_MISSING",
1825
+ "WARNING_REASON_GENERIC",
1826
+ "WARNING_REASON_INVALID_CLIENT_ID",
1827
+ "WARNING_REASON_INVALID_SUBDIVISION_CODE",
1828
+ "WARNING_REASON_INVALID_REGION_CODE",
1829
+ "WARNING_REASON_INVALID_SUBCONTINENT_CODE",
1830
+ "WARNING_REASON_INVALID_CONTINENT_CODE",
1831
+ "WARNING_REASON_INVALID_DEVICE_CATEGORY",
1832
+ "WARNING_REASON_INVALID_DEVICE_SCREEN_RESOLUTION",
1833
+ "WARNING_REASON_INVALID_MERCHANT_ID"
1834
+ ]
1835
+ },
1836
+ description: { type: "string" },
1837
+ field: { type: "string" }
1838
+ }
1839
+ }
1840
+ }
1841
+ }
1842
+ };
1843
+ //#endregion
1844
+ //#region src/outputSchemas/customer.ts
1845
+ const listAccessibleCustomersOutputSchema = {
1846
+ type: "object",
1847
+ properties: { resourceNames: {
1848
+ type: "array",
1849
+ items: { type: "string" }
1850
+ } }
1851
+ };
1852
+ const listCustomersOutputSchema = {
1853
+ type: "object",
1854
+ properties: {
1855
+ results: {
1856
+ type: "array",
1857
+ items: {
1858
+ type: "object",
1859
+ properties: { customerClient: {
1860
+ type: "object",
1861
+ properties: {
1862
+ resourceName: { type: "string" },
1863
+ clientCustomer: { type: "string" },
1864
+ id: { type: "string" },
1865
+ level: { type: "string" },
1866
+ hidden: { type: "boolean" }
1867
+ },
1868
+ required: ["resourceName"]
1869
+ } },
1870
+ required: ["customerClient"]
1871
+ }
1872
+ },
1873
+ nextPageToken: { type: "string" },
1874
+ fieldMask: { type: "string" },
1875
+ queryResourceConsumption: { type: "string" }
1876
+ }
1877
+ };
1878
+ const getCustomerOutputSchema = {
1879
+ type: "object",
1880
+ properties: {
1881
+ results: {
1882
+ type: "array",
1883
+ items: {
1884
+ type: "object",
1885
+ properties: { customer: {
1886
+ type: "object",
1887
+ properties: {
1888
+ resourceName: { type: "string" },
1889
+ id: { type: "string" },
1890
+ descriptiveName: { type: "string" },
1891
+ status: {
1892
+ type: "string",
1893
+ enum: [
1894
+ "UNSPECIFIED",
1895
+ "UNKNOWN",
1896
+ "ENABLED",
1897
+ "CANCELED",
1898
+ "SUSPENDED",
1899
+ "CLOSED"
1900
+ ]
1901
+ },
1902
+ testAccount: { type: "boolean" },
1903
+ manager: { type: "boolean" }
1904
+ },
1905
+ required: ["resourceName"]
1906
+ } },
1907
+ required: ["customer"]
1908
+ }
1909
+ },
1910
+ nextPageToken: { type: "string" },
1911
+ fieldMask: { type: "string" },
1912
+ queryResourceConsumption: { type: "string" }
1913
+ }
1914
+ };
1915
+ const getConversionActionOutputSchema = {
1916
+ type: "object",
1917
+ properties: {
1918
+ results: {
1919
+ type: "array",
1920
+ items: {
1921
+ type: "object",
1922
+ properties: { conversionAction: {
1923
+ type: "object",
1924
+ properties: {
1925
+ resourceName: { type: "string" },
1926
+ id: { type: "string" },
1927
+ name: { type: "string" }
1928
+ },
1929
+ required: ["resourceName"]
1930
+ } },
1931
+ required: ["conversionAction"]
1932
+ }
1933
+ },
1934
+ nextPageToken: { type: "string" },
1935
+ fieldMask: { type: "string" },
1936
+ queryResourceConsumption: { type: "string" }
1937
+ }
1938
+ };
1939
+ const createClientLinkOutputSchema = {
1940
+ type: "object",
1941
+ properties: {
1942
+ resourceName: { type: "string" },
1943
+ managerCustomerId: { type: "string" },
1944
+ clientCustomerId: { type: "string" },
1945
+ managerLinkId: { type: "string" }
1946
+ },
1947
+ required: [
1948
+ "resourceName",
1949
+ "managerCustomerId",
1950
+ "clientCustomerId"
1951
+ ]
1952
+ };
1953
+ const confirmClientLinkOutputSchema = {
1954
+ type: "object",
1955
+ properties: { results: {
1956
+ type: "array",
1957
+ items: mutateResourceNameResultSchema
1958
+ } }
1959
+ };
1960
+ //#endregion
1961
+ //#region src/outputSchemas/localServices.ts
1962
+ const accountReportsOutputSchema = {
1963
+ type: "object",
1964
+ properties: {
1965
+ accountReports: {
1966
+ type: "array",
1967
+ items: {
1968
+ type: "object",
1969
+ properties: {
1970
+ accountId: { type: "string" },
1971
+ businessName: { type: "string" },
1972
+ averageWeeklyBudget: { type: "number" },
1973
+ averageFiveStarRating: { type: "number" },
1974
+ totalReview: { type: "integer" },
1975
+ impressionsLastTwoDays: { type: "string" },
1976
+ phoneLeadResponsiveness: { type: "number" },
1977
+ currentPeriodChargedLeads: { type: "string" },
1978
+ previousPeriodChargedLeads: { type: "string" },
1979
+ currentPeriodTotalCost: { type: "number" },
1980
+ previousPeriodTotalCost: { type: "number" },
1981
+ currencyCode: { type: "string" },
1982
+ currentPeriodPhoneCalls: { type: "string" },
1983
+ previousPeriodPhoneCalls: { type: "string" },
1984
+ currentPeriodConnectedPhoneCalls: { type: "string" },
1985
+ previousPeriodConnectedPhoneCalls: { type: "string" },
1986
+ aggregatorInfo: localServicesAggregatorInfoSchema
1987
+ }
1988
+ }
1989
+ },
1990
+ nextPageToken: { type: "string" }
1991
+ }
1992
+ };
1993
+ const detailedLeadReportsOutputSchema = {
1994
+ type: "object",
1995
+ properties: {
1996
+ detailedLeadReports: {
1997
+ type: "array",
1998
+ items: {
1999
+ type: "object",
2000
+ properties: {
2001
+ leadId: { type: "string" },
2002
+ googleAdsLeadId: { type: "string" },
2003
+ accountId: { type: "string" },
2004
+ businessName: { type: "string" },
2005
+ leadCreationTimestamp: { type: "string" },
2006
+ leadType: {
2007
+ type: "string",
2008
+ enum: [
2009
+ "LEAD_TYPE_UNSPECIFIED",
2010
+ "MESSAGE",
2011
+ "PHONE_CALL",
2012
+ "BOOKING"
2013
+ ]
2014
+ },
2015
+ leadCategory: { type: "string" },
2016
+ geo: { type: "string" },
2017
+ leadPrice: { type: "number" },
2018
+ chargeStatus: {
2019
+ type: "string",
2020
+ enum: [
2021
+ "CHARGE_STATUS_UNSPECIFIED",
2022
+ "CHARGED",
2023
+ "NOT_CHARGED"
2024
+ ]
2025
+ },
2026
+ currencyCode: { type: "string" },
2027
+ disputeStatus: { type: "string" },
2028
+ messageLead: {
2029
+ type: "object",
2030
+ properties: {
2031
+ customerName: { type: "string" },
2032
+ jobType: { type: "string" },
2033
+ postalCode: { type: "string" },
2034
+ consumerPhoneNumber: { type: "string" }
2035
+ }
2036
+ },
2037
+ phoneLead: {
2038
+ type: "object",
2039
+ properties: {
2040
+ chargedCallTimestamp: { type: "string" },
2041
+ chargedConnectedCallDurationSeconds: { type: "string" },
2042
+ consumerPhoneNumber: { type: "string" }
2043
+ }
2044
+ },
2045
+ bookingLead: {
2046
+ type: "object",
2047
+ properties: {
2048
+ customerName: { type: "string" },
2049
+ consumerPhoneNumber: { type: "string" },
2050
+ jobType: { type: "string" },
2051
+ consumerEmail: { type: "string" },
2052
+ bookingAppointmentTimestamp: { type: "string" }
2053
+ }
2054
+ },
2055
+ aggregatorInfo: localServicesAggregatorInfoSchema,
2056
+ timezone: {
2057
+ type: "object",
2058
+ properties: {
2059
+ id: { type: "string" },
2060
+ version: { type: "string" }
2061
+ }
2062
+ }
2063
+ }
2064
+ }
2065
+ },
2066
+ nextPageToken: { type: "string" }
2067
+ }
2068
+ };
2069
+ //#endregion
2070
+ //#region src/outputSchemas/search.ts
2071
+ const searchAdsOutputSchema = {
2072
+ type: "object",
2073
+ properties: {
2074
+ results: {
2075
+ type: "array",
2076
+ items: {
2077
+ type: "object",
2078
+ properties: { campaign: {
2079
+ type: "object",
2080
+ properties: {
2081
+ resourceName: { type: "string" },
2082
+ id: { type: "string" },
2083
+ name: { type: "string" },
2084
+ status: {
2085
+ type: "string",
2086
+ enum: [
2087
+ "UNSPECIFIED",
2088
+ "UNKNOWN",
2089
+ "ENABLED",
2090
+ "PAUSED",
2091
+ "REMOVED"
2092
+ ]
2093
+ }
2094
+ },
2095
+ required: [],
2096
+ additionalProperties: true
2097
+ } },
2098
+ required: [],
2099
+ additionalProperties: true
2100
+ }
2101
+ },
2102
+ nextPageToken: { type: "string" },
2103
+ totalResultsCount: { type: "string" },
2104
+ fieldMask: { type: "string" },
2105
+ queryResourceConsumption: { type: "string" },
2106
+ metricAttributes: {
2107
+ type: "array",
2108
+ items: {
2109
+ type: "object",
2110
+ properties: {
2111
+ name: { type: "string" },
2112
+ attributes: {
2113
+ type: "array",
2114
+ items: {
2115
+ type: "object",
2116
+ properties: {
2117
+ key: { type: "string" },
2118
+ value: { type: "string" }
2119
+ }
2120
+ }
2121
+ }
2122
+ }
2123
+ }
2124
+ }
2125
+ }
2126
+ };
2127
+ //#endregion
2128
+ //#region src/outputSchemas/user.ts
2129
+ const inviteUserOutputSchema = {
2130
+ type: "object",
2131
+ properties: { result: {
2132
+ type: "object",
2133
+ properties: {
2134
+ resourceName: { type: "string" },
2135
+ multiPartyAuthReview: { type: "string" }
2136
+ },
2137
+ required: ["resourceName"]
2138
+ } },
2139
+ required: ["result"]
2140
+ };
2141
+ //#endregion
2142
+ //#region src/actions/campaign/index.ts
2143
+ var campaign_default = {
2144
+ mutateCampaign: (0, _prismatic_io_spectral.action)({
2145
+ display: {
2146
+ label: "Mutate Campaign",
2147
+ description: "Creates, updates, or removes campaigns as well as local services campaigns. Operation statuses are returned. When using API v22+, the containsEuPoliticalAdvertising field is required for campaign creation and location targeting changes."
2148
+ },
2149
+ inputs: mutateCampaignInputs,
2150
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2151
+ type: "actionOutput",
2152
+ schema: mutateCampaignOutputSchema
2153
+ }),
2154
+ performSafety: "notAllowed",
2155
+ perform: async (context, { connection, customerId, managerCustomerId, operations, partialFailure, validateOnly }) => {
2156
+ const { data } = await createClient({
2157
+ connection,
2158
+ debugEnabled: context.debug.enabled,
2159
+ logger: context.logger,
2160
+ loginCustomerId: managerCustomerId
2161
+ }).post(`/customers/${customerId}/campaigns:mutate`, {
2162
+ operations,
2163
+ partialFailure,
2164
+ validateOnly
2165
+ });
2166
+ return { data };
2167
+ },
2168
+ examplePerform: async () => mutateCampaignExamplePayload,
2169
+ examplePayload: mutateCampaignExamplePayload
2170
+ }),
2171
+ mutateCampaignCriteria: (0, _prismatic_io_spectral.action)({
2172
+ display: {
2173
+ label: "Mutate Campaign Criteria",
2174
+ description: "Creates, updates, or removes campaign criteria as well as local services campaign criterion. Operation statuses are returned. When using API v22+, the containsEuPoliticalAdvertising field must be set on the parent campaign before modifying location or proximity targeting criteria."
2175
+ },
2176
+ inputs: mutateCampaignCriteriaInputs,
2177
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2178
+ type: "actionOutput",
2179
+ schema: mutateCampaignCriteriaOutputSchema
2180
+ }),
2181
+ performSafety: "notAllowed",
2182
+ perform: async (context, { connection, customerId, managerCustomerId, operations, partialFailure, validateOnly }) => {
2183
+ const { data } = await createClient({
2184
+ connection,
2185
+ debugEnabled: context.debug.enabled,
2186
+ logger: context.logger,
2187
+ loginCustomerId: managerCustomerId
2188
+ }).post(`/customers/${customerId}/campaignCriteria:mutate`, {
2189
+ operations,
2190
+ partialFailure,
2191
+ validateOnly
2192
+ });
2193
+ return { data };
2194
+ },
2195
+ examplePerform: async () => mutateCampaignCriteriaExamplePayload,
2196
+ examplePayload: mutateCampaignCriteriaExamplePayload
2197
+ })
2198
+ };
2199
+ //#endregion
2200
+ //#region src/actions/conversions/index.ts
2201
+ var conversions_default = {
2202
+ ingestOfflineConversions: (0, _prismatic_io_spectral.action)({
2203
+ display: {
2204
+ label: "Ingest Offline Conversions",
2205
+ description: "Import offline conversion events into Google Ads using the Data Manager API."
2206
+ },
2207
+ inputs: ingestOfflineConversionsInputs,
2208
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2209
+ type: "actionOutput",
2210
+ schema: ingestOfflineConversionsOutputSchema
2211
+ }),
2212
+ performSafety: "notAllowed",
2213
+ perform: async (context, { connection, events, destinations, encoding, validateOnly }) => {
2214
+ const client = createDataManagerClient({
2215
+ connection,
2216
+ debugEnabled: context.debug.enabled,
2217
+ logger: context.logger
2218
+ });
2219
+ const body = {
2220
+ destinations,
2221
+ events,
2222
+ validateOnly,
2223
+ encoding
2224
+ };
2225
+ const { data } = await client.post("/events:ingest", body);
2226
+ return { data };
2227
+ },
2228
+ examplePerform: async () => ingestOfflineConversionsExamplePayload,
2229
+ examplePayload: ingestOfflineConversionsExamplePayload
2230
+ }),
2231
+ uploadCallConversions: (0, _prismatic_io_spectral.action)({
2232
+ display: {
2233
+ label: "Upload Call Conversions",
2234
+ description: "Upload offline call conversions into Google Ads in order to track ads that led to sales."
2235
+ },
2236
+ inputs: uploadCallConversionsInputs,
2237
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2238
+ type: "actionOutput",
2239
+ schema: uploadCallConversionsOutputSchema
2240
+ }),
2241
+ performSafety: "notAllowed",
2242
+ perform: async (context, { connection, customerId, managerCustomerId, conversions, validateOnly }) => {
2243
+ const { data } = await createClient({
2244
+ connection,
2245
+ debugEnabled: context.debug.enabled,
2246
+ logger: context.logger,
2247
+ loginCustomerId: managerCustomerId
2248
+ }).post(`/customers/${customerId}:uploadCallConversions`, {
2249
+ conversions,
2250
+ partialFailure: true,
2251
+ validateOnly
2252
+ });
2253
+ return { data };
2254
+ },
2255
+ examplePerform: async () => uploadCallConversionsExamplePayload,
2256
+ examplePayload: uploadCallConversionsExamplePayload
2257
+ }),
2258
+ uploadClickConversions: (0, _prismatic_io_spectral.action)({
2259
+ display: {
2260
+ label: "Upload Click Conversions",
2261
+ description: "Upload offline click conversions into Google Ads in order to track ads that led to sales. Developer tokens that have not sent an offline conversion upload request between December 17, 2025 and June 15, 2026 will lose access to this endpoint; new integrations should use Ingest Offline Conversions (Data Manager API) instead."
2262
+ },
2263
+ inputs: uploadClickConversionsInputs,
2264
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2265
+ type: "actionOutput",
2266
+ schema: uploadClickConversionsOutputSchema
2267
+ }),
2268
+ performSafety: "notAllowed",
2269
+ perform: async (context, { connection, customerId, managerCustomerId, conversions, validateOnly }) => {
2270
+ const { data } = await createClient({
2271
+ connection,
2272
+ debugEnabled: context.debug.enabled,
2273
+ logger: context.logger,
2274
+ loginCustomerId: managerCustomerId
2275
+ }).post(`/customers/${customerId}:uploadClickConversions`, {
2276
+ conversions,
2277
+ partialFailure: true,
2278
+ validateOnly
2279
+ });
2280
+ return { data };
2281
+ },
2282
+ examplePerform: async () => uploadClickConversionsExamplePayload,
2283
+ examplePayload: uploadClickConversionsExamplePayload
2284
+ })
2285
+ };
2286
+ //#endregion
2287
+ //#region src/actions/customer/confirmClientLink.ts
2288
+ const confirmClientLink = (0, _prismatic_io_spectral.action)({
2289
+ display: {
2290
+ label: "Confirm Client Link",
2291
+ description: "Confirms a pending customer client link."
2292
+ },
2293
+ inputs: confirmClientLinkInputs,
2294
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2295
+ type: "actionOutput",
2296
+ schema: confirmClientLinkOutputSchema
2297
+ }),
2298
+ performSafety: "notAllowed",
2299
+ perform: async (context, { connection, managerCustomerId, customerId, managerLinkId }) => {
2300
+ const client = createClient({
2301
+ connection,
2302
+ debugEnabled: context.debug.enabled,
2303
+ logger: context.logger
2304
+ });
2305
+ const linkResourceName = `customers/${customerId}/customerManagerLinks/${managerCustomerId}~${managerLinkId}`;
2306
+ const { data } = await client.post(`customers/${customerId}/customerManagerLinks:mutate`, { operations: [{
2307
+ updateMask: "status",
2308
+ update: {
2309
+ status: "ACTIVE",
2310
+ resourceName: linkResourceName
2311
+ }
2312
+ }] });
2313
+ return { data };
2314
+ },
2315
+ examplePerform: async () => confirmClientLinkExamplePayload,
2316
+ examplePayload: confirmClientLinkExamplePayload
2317
+ });
2318
+ //#endregion
2319
+ //#region src/actions/customer/createClientLink.ts
2320
+ const createClientLink = (0, _prismatic_io_spectral.action)({
2321
+ display: {
2322
+ label: "Create Client Link",
2323
+ description: "Create an invitation to link a client account to a manager account."
2324
+ },
2325
+ inputs: createClientLinkInputs,
2326
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2327
+ type: "actionOutput",
2328
+ schema: createClientLinkOutputSchema
2329
+ }),
2330
+ performSafety: "notAllowed",
2331
+ perform: async (context, { connection, managerCustomerId, customerId }) => {
2332
+ const client = createClient({
2333
+ connection,
2334
+ debugEnabled: context.debug.enabled,
2335
+ logger: context.logger
2336
+ });
2337
+ const { data } = await client.post(`customers/${managerCustomerId}/customerClientLinks:mutate`, { operation: { create: {
2338
+ status: "PENDING",
2339
+ clientCustomer: `customers/${customerId}`
2340
+ } } });
2341
+ const resourceName = data.result.resourceName;
2342
+ const query = `SELECT customer_client_link.manager_link_id FROM customer_client_link WHERE customer_client_link.resource_name = "${resourceName}"`;
2343
+ const { managerLinkId } = (await client.post(googleAdsSearchPath(managerCustomerId), { query })).data.results?.[0]?.customerClientLink || { managerLinkId: void 0 };
2344
+ return { data: {
2345
+ resourceName,
2346
+ managerCustomerId,
2347
+ clientCustomerId: customerId,
2348
+ managerLinkId
2349
+ } };
2350
+ },
2351
+ examplePerform: async () => createClientLinkExamplePayload,
2352
+ examplePayload: createClientLinkExamplePayload
2353
+ });
2354
+ //#endregion
2355
+ //#region src/actions/customer/customerEntity.ts
2356
+ const customerEntities = {
2357
+ customer: [
2358
+ "id",
2359
+ "descriptive_name",
2360
+ "status",
2361
+ "test_account",
2362
+ "manager"
2363
+ ],
2364
+ conversion_action: [
2365
+ "id",
2366
+ "name",
2367
+ "resource_name"
2368
+ ]
2369
+ };
2370
+ const entityExamplePayloads = {
2371
+ customer: getCustomerExamplePayload,
2372
+ conversion_action: getConversionActionExamplePayload
2373
+ };
2374
+ const entityOutputSchemas = {
2375
+ customer: getCustomerOutputSchema,
2376
+ conversion_action: getConversionActionOutputSchema
2377
+ };
2378
+ const entityPerformSafety = {
2379
+ customer: "safe",
2380
+ conversion_action: "notAllowed"
2381
+ };
2382
+ const entityExamplePerforms = { conversion_action: async () => getConversionActionExamplePayload };
2383
+ const customerEntityActions = Object.entries(customerEntities).reduce((result, [entityName, fieldsList]) => {
2384
+ const key = (0, lodash.camelCase)(`get ${entityName}`);
2385
+ const name = (0, lodash.startCase)(entityName);
2386
+ const querySelect = fieldsList.map((field) => `${entityName}.${field}`).join(", ");
2387
+ result[key] = (0, _prismatic_io_spectral.action)({
2388
+ display: {
2389
+ label: `Get ${name}`,
2390
+ description: `Retrieve ${name} data for a customer account.`
2391
+ },
2392
+ inputs: customerEntityInputs,
2393
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2394
+ type: "actionOutput",
2395
+ schema: entityOutputSchemas[entityName]
2396
+ }),
2397
+ performSafety: entityPerformSafety[entityName],
2398
+ perform: async (context, params) => {
2399
+ const client = createClient({
2400
+ connection: params.connection,
2401
+ debugEnabled: context.debug.enabled,
2402
+ logger: context.logger,
2403
+ loginCustomerId: params.managerCustomerId
2404
+ });
2405
+ const id = cleanCustomerId(params.customerId);
2406
+ const { data } = await client.post(googleAdsSearchPath(id), {
2407
+ pageToken: params.pageToken || void 0,
2408
+ query: `SELECT ${querySelect} FROM ${entityName}`
2409
+ });
2410
+ return { data };
2411
+ },
2412
+ examplePerform: entityExamplePerforms[entityName],
2413
+ examplePayload: entityExamplePayloads[entityName]
2414
+ });
2415
+ return result;
2416
+ }, {});
2417
+ //#endregion
2418
+ //#region src/actions/customer/index.ts
2419
+ var customer_default = {
2420
+ listAccessibleCustomers: (0, _prismatic_io_spectral.action)({
2421
+ display: {
2422
+ label: "List Accessible Customers",
2423
+ description: "Gets a list of customers accessible to the authenticated user."
2424
+ },
2425
+ inputs: listAccessibleCustomersInputs,
2426
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2427
+ type: "actionOutput",
2428
+ schema: listAccessibleCustomersOutputSchema
2429
+ }),
2430
+ performSafety: "safe",
2431
+ perform: async (context, { connection }) => {
2432
+ const { data } = await createClient({
2433
+ connection,
2434
+ debugEnabled: context.debug.enabled,
2435
+ logger: context.logger
2436
+ }).get("/customers:listAccessibleCustomers");
2437
+ return { data };
2438
+ },
2439
+ examplePayload: listAccessibleCustomersExamplePayload
2440
+ }),
2441
+ listCustomers: (0, _prismatic_io_spectral.action)({
2442
+ display: {
2443
+ label: "List Customers by Manager",
2444
+ description: "Lists all customers under a manager account."
2445
+ },
2446
+ inputs: listCustomersInputs,
2447
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2448
+ type: "actionOutput",
2449
+ schema: listCustomersOutputSchema
2450
+ }),
2451
+ performSafety: "notAllowed",
2452
+ perform: async (context, { connection, managerCustomerId, pageToken, fetchAll }) => {
2453
+ return { data: await searchGoogleAds(createClient({
2454
+ connection,
2455
+ debugEnabled: context.debug.enabled,
2456
+ logger: context.logger
2457
+ }), {
2458
+ customerId: managerCustomerId,
2459
+ params: {
2460
+ pageToken: pageToken || void 0,
2461
+ query: `
2462
+ SELECT customer_client.resource_name,
2463
+ customer_client.client_customer,
2464
+ customer_client.id,
2465
+ customer_client.level,
2466
+ customer_client.hidden,
2467
+ customer_client.level
2468
+ FROM customer_client
2469
+ `
2470
+ },
2471
+ fetchAll
2472
+ }) };
2473
+ },
2474
+ examplePerform: async () => listCustomersExamplePayload,
2475
+ examplePayload: listCustomersExamplePayload
2476
+ }),
2477
+ ...customerEntityActions,
2478
+ createClientLink,
2479
+ confirmClientLink
2480
+ };
2481
+ //#endregion
2482
+ //#region src/actions/localServices/index.ts
2483
+ var localServices_default = {
2484
+ accountReports: (0, _prismatic_io_spectral.action)({
2485
+ display: {
2486
+ label: "Get Account Reports",
2487
+ description: "Retrieves account reports showing performance and metrics for Local Services accounts linked to a Manager account."
2488
+ },
2489
+ inputs: accountReportsInputs,
2490
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2491
+ type: "actionOutput",
2492
+ schema: accountReportsOutputSchema
2493
+ }),
2494
+ performSafety: "notAllowed",
2495
+ perform: async (context, { connection, customerIds, managerCustomerIdInput, pagination, endDateInput, startDateInput }) => {
2496
+ const client = createLocalServicesClient({
2497
+ connection,
2498
+ debugEnabled: context.debug.enabled
2499
+ });
2500
+ const startDate = startDateInput ? new Date(startDateInput) : void 0;
2501
+ const endDate = endDateInput ? new Date(endDateInput) : void 0;
2502
+ const query = `manager_customer_id:${managerCustomerIdInput}${customerIds && customerIds !== "" ? `;${customerIds}` : ""}`;
2503
+ const { data } = await client.get("/accountReports:search", { params: {
2504
+ query,
2505
+ pageSize: pagination.pageSizeInput || void 0,
2506
+ pageToken: pagination.pageTokenInput || void 0,
2507
+ ...startDate && {
2508
+ "startDate.day": startDate.getDate(),
2509
+ "startDate.month": startDate.getMonth() + 1,
2510
+ "startDate.year": startDate.getFullYear()
2511
+ },
2512
+ ...endDate && {
2513
+ "endDate.day": endDate.getDate(),
2514
+ "endDate.month": endDate.getMonth() + 1,
2515
+ "endDate.year": endDate.getFullYear()
2516
+ }
2517
+ } });
2518
+ return { data };
2519
+ },
2520
+ examplePerform: async () => accountReportsExamplePayload,
2521
+ examplePayload: accountReportsExamplePayload
2522
+ }),
2523
+ detailedLeadReports: (0, _prismatic_io_spectral.action)({
2524
+ display: {
2525
+ label: "Get Detailed Lead Reports",
2526
+ description: "Retrieves detailed lead reports providing an in-depth view of leads for Local Services accounts linked to a Manager account."
2527
+ },
2528
+ inputs: detailedLeadReportsInputs,
2529
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2530
+ type: "actionOutput",
2531
+ schema: detailedLeadReportsOutputSchema
2532
+ }),
2533
+ performSafety: "notAllowed",
2534
+ perform: async (context, { connection, customerIds, managerCustomerIdInput, pagination, endDateInput, startDateInput }) => {
2535
+ const client = createLocalServicesClient({
2536
+ connection,
2537
+ debugEnabled: context.debug.enabled
2538
+ });
2539
+ const startDate = startDateInput ? new Date(startDateInput) : void 0;
2540
+ const endDate = endDateInput ? new Date(endDateInput) : void 0;
2541
+ const query = `manager_customer_id:${managerCustomerIdInput}${customerIds && customerIds !== "" ? `;${customerIds}` : ""}`;
2542
+ const { data } = await client.get("/detailedLeadReports:search", { params: {
2543
+ query,
2544
+ pageSize: pagination.pageSizeInput || void 0,
2545
+ pageToken: pagination.pageTokenInput || void 0,
2546
+ ...startDate && {
2547
+ "startDate.day": startDate.getDate(),
2548
+ "startDate.month": startDate.getMonth() + 1,
2549
+ "startDate.year": startDate.getFullYear()
2550
+ },
2551
+ ...endDate && {
2552
+ "endDate.day": endDate.getDate(),
2553
+ "endDate.month": endDate.getMonth() + 1,
2554
+ "endDate.year": endDate.getFullYear()
2555
+ }
2556
+ } });
2557
+ return { data };
2558
+ },
2559
+ examplePerform: async () => detailedLeadReportsExamplePayload,
2560
+ examplePayload: detailedLeadReportsExamplePayload
2561
+ })
2562
+ };
2563
+ //#endregion
2564
+ //#region src/actions/misc/index.ts
2565
+ var misc_default = { rawRequest: (0, _prismatic_io_spectral.action)({
2566
+ display: {
2567
+ label: "Raw Request",
2568
+ description: "Sends a raw HTTP request to the Google Ads API."
2569
+ },
2570
+ inputs: rawRequestInputs,
2571
+ performSafety: "notAllowed",
2572
+ perform: async (context, { connection, ...rawRequestInputs }) => {
2573
+ const { accessToken } = validateConnection(connection);
2574
+ const authorizationHeaders = { Authorization: `Bearer ${accessToken}` };
2575
+ const { data } = await (0, _prismatic_io_spectral_http.sendRawRequest)(GOOGLE_ADS_BASE_URL, {
2576
+ ...rawRequestInputs,
2577
+ debugRequest: context.debug.enabled
2578
+ }, authorizationHeaders);
2579
+ return { data };
2580
+ },
2581
+ examplePayload: rawRequestExamplePayload
2582
+ }) };
2583
+ //#endregion
2584
+ //#region src/actions/search/index.ts
2585
+ var search_default = { searchAdsLocalServices: (0, _prismatic_io_spectral.action)({
2586
+ display: {
2587
+ label: "Search Ads",
2588
+ description: "Returns rows matching a GAQL query against Google Ads resources."
2589
+ },
2590
+ inputs: searchAdsLocalServicesInputs,
2591
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2592
+ type: "actionOutput",
2593
+ schema: searchAdsOutputSchema
2594
+ }),
2595
+ performSafety: "notAllowed",
2596
+ perform: async (context, { connection, customerId, managerCustomerId, pageTokenInput, query, returnTotalResultsCount, fetchAll }) => {
2597
+ return { data: await searchGoogleAds(createClient({
2598
+ connection,
2599
+ debugEnabled: context.debug.enabled,
2600
+ logger: context.logger,
2601
+ loginCustomerId: managerCustomerId
2602
+ }), {
2603
+ customerId,
2604
+ fetchAll,
2605
+ params: {
2606
+ pageToken: pageTokenInput || void 0,
2607
+ query: query || void 0,
2608
+ searchSettings: returnTotalResultsCount ? { returnTotalResultsCount: true } : void 0
2609
+ }
2610
+ }) };
2611
+ },
2612
+ examplePerform: async () => searchAdsLocalServicesExamplePayload,
2613
+ examplePayload: searchAdsLocalServicesExamplePayload
2614
+ }) };
2615
+ //#endregion
2616
+ //#region src/actions/user/index.ts
2617
+ var user_default = { inviteUser: (0, _prismatic_io_spectral.action)({
2618
+ display: {
2619
+ label: "Invite User",
2620
+ description: "Invites a user by email to a customer."
2621
+ },
2622
+ inputs: inviteUserInputs,
2623
+ outputSchema: (0, _prismatic_io_spectral.outputSchema)({
2624
+ type: "actionOutput",
2625
+ schema: inviteUserOutputSchema
2626
+ }),
2627
+ performSafety: "notAllowed",
2628
+ perform: async (context, { connection, customerId, accessRole, emailAddress }) => {
2629
+ const { data } = await createClient({
2630
+ connection,
2631
+ debugEnabled: context.debug.enabled,
2632
+ logger: context.logger
2633
+ }).post(`customers/${customerId}/customerUserAccessInvitations:mutate`, { operation: { create: {
2634
+ accessRole,
2635
+ emailAddress
2636
+ } } });
2637
+ return { data };
2638
+ },
2639
+ examplePerform: async () => inviteUserExamplePayload,
2640
+ examplePayload: inviteUserExamplePayload
2641
+ }) };
2642
+ //#endregion
2643
+ //#region src/actions/index.ts
2644
+ var actions_default = {
2645
+ ...campaign_default,
2646
+ ...search_default,
2647
+ ...conversions_default,
2648
+ ...customer_default,
2649
+ ...localServices_default,
2650
+ ...misc_default,
2651
+ ...user_default
2652
+ };
2653
+ //#endregion
2654
+ //#region src/dataSources/index.ts
2655
+ var dataSources_default = {
2656
+ listAccessibleCustomers: (0, _prismatic_io_spectral.dataSource)({
2657
+ display: {
2658
+ label: "List Accessible Customers",
2659
+ description: "Get a list of accessible customers for the logged in user."
2660
+ },
2661
+ inputs: listAccessibleCustomersInputs,
2662
+ perform: async (context, { connection }) => {
2663
+ const client = createClient({
2664
+ connection,
2665
+ debugEnabled: false,
2666
+ logger: context.logger
2667
+ });
2668
+ const { data } = await client.get("customers:listAccessibleCustomers");
2669
+ return { result: await Promise.all(data.resourceNames.map(async (resourceName) => {
2670
+ const customerId = cleanCustomerId(resourceName);
2671
+ return {
2672
+ label: `${await getCustomerDescriptiveName(client, customerId)} - ${formatAccountNumber(customerId)}`,
2673
+ key: customerId
2674
+ };
2675
+ })) };
2676
+ },
2677
+ dataSourceType: "picklist",
2678
+ examplePayload: listAccessibleCustomersDataSourceExamplePayload
2679
+ }),
2680
+ listAccessibleSubAccounts: (0, _prismatic_io_spectral.dataSource)({
2681
+ display: {
2682
+ label: "List Accessible Sub Accounts",
2683
+ description: "Get a list of accessible sub accounts for the customer ID provided."
2684
+ },
2685
+ inputs: listAccessibleSubAccountsInputs,
2686
+ perform: async (context, { connection, customerId, customerClientLevel }) => {
2687
+ return { result: ((await searchGoogleAds(createClient({
2688
+ connection,
2689
+ debugEnabled: false,
2690
+ logger: context.logger
2691
+ }), {
2692
+ customerId,
2693
+ params: { query: `${GET_ACCOUNT_HIERARCHY_QUERY}${customerClientLevel ?? "1"}` },
2694
+ fetchAll: true
2695
+ })).results ?? []).map(({ customerClient }) => {
2696
+ return {
2697
+ label: `${customerClient.descriptiveName} - ${formatAccountNumber(customerClient.id)}`,
2698
+ key: customerClient.id
2699
+ };
2700
+ }) };
2701
+ },
2702
+ dataSourceType: "picklist",
2703
+ examplePayload: listAccessibleSubAccountsExamplePayload
2704
+ })
2705
+ };
2706
+ //#endregion
2707
+ //#region src/triggers/budgetAlertTrigger.ts
2708
+ const budgetAlertPerform = async (context, payload, params) => {
2709
+ const { client, timezone } = await createTriggerClient(context, params);
2710
+ const pollState = getPollingState(context, {
2711
+ lastSyncDate: getPreviousDate(timezone),
2712
+ errorCount: 0,
2713
+ consecutiveErrors: 0
2714
+ });
2715
+ const newSyncDate = getCurrentDate(timezone);
2716
+ const threshold = params.alertThreshold ?? 80;
2717
+ try {
2718
+ const results = (await searchGoogleAds(client, {
2719
+ customerId: params.customerId,
2720
+ params: { query: buildBudgetAlertQuery({ includeSharedBudgets: params.includeSharedBudgets ?? true }) },
2721
+ fetchAll: true
2722
+ })).results ?? [];
2723
+ const budgetAlerts = results.map((campaign) => calculateBudgetStatus(campaign, threshold)).filter((status) => status.shouldAlert);
2724
+ context.polling.setState({
2725
+ lastSyncDate: newSyncDate,
2726
+ errorCount: 0,
2727
+ consecutiveErrors: 0
2728
+ });
2729
+ return Promise.resolve({
2730
+ payload: buildTriggerPayload(payload, {
2731
+ alerts: budgetAlerts,
2732
+ totalCampaignsMonitored: results.length,
2733
+ alertThreshold: threshold
2734
+ }),
2735
+ polledNoChanges: budgetAlerts.length === 0
2736
+ });
2737
+ } catch (e) {
2738
+ handlePollingError(e, pollState, context, "Google Ads budget");
2739
+ }
2740
+ };
2741
+ const budgetAlertTrigger = (0, _prismatic_io_spectral.pollingTrigger)({
2742
+ display: {
2743
+ label: "Campaign Budget Alerts",
2744
+ description: "Checks for campaigns approaching or exceeding budget thresholds on a configured schedule."
2745
+ },
2746
+ inputs: budgetAlertTriggerInputs,
2747
+ triggerResolverSupport: "valid",
2748
+ batchConfig: { batchSize: 50 },
2749
+ triggerResolver: { resolveItems: (_context, { payload }) => resolveBudgetAlerts(payload.body.data) },
2750
+ perform: budgetAlertPerform,
2751
+ examplePayload: budgetAlertTriggerExamplePayload
2752
+ });
2753
+ //#endregion
2754
+ //#region src/triggers/campaignChangesTrigger.ts
2755
+ const campaignChangesPerform = async (context, payload, params) => {
2756
+ const { client, timezone } = await createTriggerClient(context, params);
2757
+ const nowTime = getGAQLDateTime(timezone);
2758
+ const pollState = getPollingState(context, {
2759
+ lastChangeTime: params.lookBackDate ? `${params.lookBackDate} ${LOOK_BACK_DATE_START_TIME}` : getGAQLDateTime(timezone, 1),
2760
+ errorCount: 0,
2761
+ consecutiveErrors: 0
2762
+ });
2763
+ try {
2764
+ const incoming = payload.paginationState;
2765
+ const isContinuation = Boolean(incoming ?? pollState.inFlightCursor);
2766
+ const cursor = resolveChangeEventCursor({
2767
+ incoming,
2768
+ state: pollState,
2769
+ nowTime,
2770
+ timeZone: timezone
2771
+ });
2772
+ const pageSize = resolveChangeEventPageSize(context.batch?.enabled);
2773
+ let rows = [];
2774
+ if (cursor.sinceTime < cursor.toTime) rows = (await searchGoogleAds(client, {
2775
+ customerId: params.customerId,
2776
+ params: { query: buildCampaignChangeEventQuery({
2777
+ sinceTime: cursor.sinceTime,
2778
+ toTime: cursor.toTime,
2779
+ changeTypes: params.changeTypes,
2780
+ limit: pageSize
2781
+ }) },
2782
+ fetchAll: true
2783
+ })).results ?? [];
2784
+ const { emit, nextCursor } = advanceChangeEventCursor(rows, cursor, pageSize, context.logger);
2785
+ const changes = mapChangeEventsToCampaignChanges(emit, params.changeTypes);
2786
+ context.polling.setState({
2787
+ lastChangeTime: nextCursor ? pollState.lastChangeTime : cursor.toTime,
2788
+ ...nextCursor ? { inFlightCursor: nextCursor } : {},
2789
+ errorCount: pollState.errorCount,
2790
+ consecutiveErrors: 0
2791
+ });
2792
+ return Promise.resolve({
2793
+ payload: {
2794
+ ...buildTriggerPayload(payload, {
2795
+ changes,
2796
+ changesDetected: changes.length,
2797
+ timeRange: {
2798
+ start: cursor.sinceTime,
2799
+ end: cursor.toTime
2800
+ },
2801
+ syncedAt: cursor.toTime
2802
+ }),
2803
+ paginationState: nextCursor ?? void 0
2804
+ },
2805
+ polledNoChanges: changes.length === 0 && nextCursor === null && !isContinuation
2806
+ });
2807
+ } catch (e) {
2808
+ handlePollingError(e, pollState, context, "Google Ads");
2809
+ }
2810
+ };
2811
+ const campaignChangesTrigger = (0, _prismatic_io_spectral.pollingTrigger)({
2812
+ display: {
2813
+ label: "New and Updated Campaigns",
2814
+ description: "Checks for new and updated campaigns in a Google Ads account on a configured schedule."
2815
+ },
2816
+ inputs: campaignChangesTriggerInputs,
2817
+ triggerResolverSupport: "valid",
2818
+ batchConfig: { batchSize: 50 },
2819
+ triggerResolver: {
2820
+ resolveItems: (_context, { payload }) => resolveCampaignChanges(payload.body.data),
2821
+ getNextPaginationState: (_context, { payload }) => payload.paginationState ?? null
2822
+ },
2823
+ perform: campaignChangesPerform,
2824
+ examplePayload: campaignChangesTriggerExamplePayload
2825
+ });
2826
+ //#endregion
2827
+ //#region src/triggers/changeHistoryTrigger.ts
2828
+ const changeHistoryPerform = async (context, payload, params) => {
2829
+ const { client, timezone } = await createTriggerClient(context, params);
2830
+ const nowTime = getGAQLDateTime(timezone);
2831
+ const pollState = getPollingState(context, {
2832
+ lastChangeTime: params.lookBackDate ? `${params.lookBackDate} ${LOOK_BACK_DATE_START_TIME}` : getGAQLDateTime(timezone, 1),
2833
+ errorCount: 0,
2834
+ consecutiveErrors: 0
2835
+ });
2836
+ try {
2837
+ const incoming = payload.paginationState;
2838
+ const isContinuation = Boolean(incoming ?? pollState.inFlightCursor);
2839
+ const cursor = resolveChangeEventCursor({
2840
+ incoming,
2841
+ state: pollState,
2842
+ nowTime,
2843
+ timeZone: timezone
2844
+ });
2845
+ const pageSize = resolveChangeEventPageSize(context.batch?.enabled);
2846
+ let rows = [];
2847
+ if (cursor.sinceTime < cursor.toTime) rows = (await searchGoogleAds(client, {
2848
+ customerId: params.customerId,
2849
+ params: { query: buildChangeHistoryQuery({
2850
+ sinceTime: cursor.sinceTime,
2851
+ toTime: cursor.toTime,
2852
+ resourceTypes: params.resourceTypes,
2853
+ includeUserInfo: params.includeUserInfo,
2854
+ limit: pageSize
2855
+ }) },
2856
+ fetchAll: true
2857
+ })).results ?? [];
2858
+ const { emit, nextCursor } = advanceChangeEventCursor(rows, cursor, pageSize, context.logger);
2859
+ const results = [...emit].reverse();
2860
+ context.polling.setState({
2861
+ lastChangeTime: nextCursor ? pollState.lastChangeTime : cursor.toTime,
2862
+ ...nextCursor ? { inFlightCursor: nextCursor } : {},
2863
+ changeCount: results.length,
2864
+ errorCount: 0,
2865
+ consecutiveErrors: 0
2866
+ });
2867
+ return Promise.resolve({
2868
+ payload: {
2869
+ ...buildTriggerPayload(payload, {
2870
+ changes: results,
2871
+ changeCount: results.length,
2872
+ timeRange: {
2873
+ start: cursor.sinceTime,
2874
+ end: cursor.toTime
2875
+ }
2876
+ }),
2877
+ paginationState: nextCursor ?? void 0
2878
+ },
2879
+ polledNoChanges: results.length === 0 && nextCursor === null && !isContinuation
2880
+ });
2881
+ } catch (e) {
2882
+ handlePollingError(e, pollState, context, "Google Ads change history");
2883
+ }
2884
+ };
2885
+ //#endregion
2886
+ //#region src/triggers/index.ts
2887
+ var triggers_default = {
2888
+ campaignChangesTrigger,
2889
+ changeHistoryTrigger: (0, _prismatic_io_spectral.pollingTrigger)({
2890
+ display: {
2891
+ label: "Account Change History",
2892
+ description: "Checks for Google Ads account modifications with user attribution on a configured schedule."
2893
+ },
2894
+ inputs: changeHistoryTriggerInputs,
2895
+ triggerResolverSupport: "valid",
2896
+ batchConfig: { batchSize: 50 },
2897
+ triggerResolver: {
2898
+ resolveItems: (_context, { payload }) => resolveChangeHistoryItems(payload.body.data),
2899
+ getNextPaginationState: (_context, { payload }) => payload.paginationState ?? null
2900
+ },
2901
+ perform: changeHistoryPerform,
2902
+ examplePayload: changeHistoryTriggerExamplePayload
2903
+ }),
2904
+ budgetAlertTrigger
2905
+ };
2906
+ //#endregion
2907
+ //#region src/index.ts
2908
+ var src_default = (0, _prismatic_io_spectral.component)({
2909
+ key: "google-ads",
2910
+ documentationUrl: "https://prismatic.io/docs/components/google-ads/",
2911
+ public: true,
2912
+ display: {
2913
+ label: "Google Ads",
2914
+ description: "Manage campaigns, conversions, customers, and local services in Google Ads.",
2915
+ iconPath: "icon.png",
2916
+ category: "Application Connectors"
2917
+ },
2918
+ hooks: { error: _prismatic_io_spectral_http.handleErrors },
2919
+ connections: connections_default,
2920
+ dataSources: dataSources_default,
2921
+ actions: actions_default,
2922
+ triggers: triggers_default
2923
+ });
2924
+ //#endregion
2925
+ //#region npm-dist/.entry.ts
2926
+ var _entry_default = (0, _prismatic_io_spectral.callableComponent)(src_default);
2927
+ //#endregion
2928
+ module.exports = _entry_default;