@google-cloud/nodejs-common 2.2.2 → 2.3.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.
@@ -1,1145 +0,0 @@
1
- // Copyright 2019 Google Inc.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- /**
16
- * @fileoverview Google Ads API (unofficial) Wrapper.
17
- */
18
- 'use strict';
19
-
20
- const {protos: {google: {ads: {googleads}}}} = require('google-ads-node');
21
- const googleAdsLib = googleads[Object.keys(googleads)[0]];
22
- const {
23
- common: {
24
- CustomerMatchUserListMetadata,
25
- StoreSalesMetadata,
26
- TransactionAttribute,
27
- UserAttribute,
28
- UserData,
29
- UserIdentifier,
30
- },
31
- resources: {
32
- GoogleAdsField,
33
- OfflineUserDataJob,
34
- UserList,
35
- },
36
- services: {
37
- AddOfflineUserDataJobOperationsRequest,
38
- CreateOfflineUserDataJobRequest,
39
- RunOfflineUserDataJobRequest,
40
- SearchGoogleAdsFieldsRequest,
41
- UploadCallConversionsRequest,
42
- UploadCallConversionsResponse,
43
- UploadClickConversionsRequest,
44
- UploadClickConversionsResponse,
45
- UploadConversionAdjustmentsRequest,
46
- UploadConversionAdjustmentsResponse,
47
- UploadUserDataRequest,
48
- UploadUserDataResponse,
49
- UserDataOperation,
50
- },
51
- errors: {
52
- GoogleAdsFailure,
53
- },
54
- enums: {
55
- OfflineUserDataJobFailureReasonEnum: { OfflineUserDataJobFailureReason },
56
- OfflineUserDataJobTypeEnum: { OfflineUserDataJobType },
57
- OfflineUserDataJobStatusEnum: { OfflineUserDataJobStatus },
58
- UserListMembershipStatusEnum: { UserListMembershipStatus },
59
- UserListTypeEnum: { UserListType },
60
- CustomerMatchUploadKeyTypeEnum: { CustomerMatchUploadKeyType },
61
- },
62
- } = googleAdsLib;
63
- const {GoogleAdsApi} = require('google-ads-api');
64
- const lodash = require('lodash');
65
-
66
- const AuthClient = require('./auth_client.js');
67
- const {getLogger, BatchResult,} = require('../components/utils.js');
68
-
69
- /** @type {!ReadonlyArray<string>} */
70
- const API_SCOPES = Object.freeze(['https://www.googleapis.com/auth/adwords',]);
71
-
72
- /**
73
- * List of properties that will be taken from the data file as elements of a
74
- * conversion or a conversion adjustment.
75
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/ClickConversion
76
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/CallConversion
77
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/ConversionAdjustment
78
- * @type {Array<string>}
79
- */
80
- const PICKED_PROPERTIES = [
81
- 'external_attribution_data',
82
- 'cart_data',
83
- 'user_identifiers',
84
- 'gbraid',
85
- 'wbraid',
86
- 'gclid',
87
- 'caller_id',
88
- 'call_start_date_time',
89
- 'conversion_action',
90
- 'conversion_date_time',
91
- 'conversion_value',
92
- 'currency_code',
93
- 'order_id',
94
- 'adjustment_type',
95
- 'restatement_value',
96
- 'adjustment_date_time',
97
- 'user_agent',
98
- 'gclid_date_time_pair',
99
- 'consent',
100
- ];
101
-
102
- /**
103
- * Kinds of UserIdentifier.
104
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
105
- * @type {Array<string>}
106
- */
107
- const IDENTIFIERS = [
108
- 'hashed_email',
109
- 'hashed_phone_number',
110
- 'mobile_id',
111
- 'third_party_user_id',
112
- 'address_info',
113
- ];
114
-
115
- /**
116
- * Additional attributes in user data for store sales data or customer match.
117
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserData
118
- * @type {Array<string>}
119
- */
120
- const USERDATA_ADDITIONAL_ATTRIBUTES = [
121
- 'transaction_attribute',
122
- 'user_attribute',
123
- ];
124
-
125
- /**
126
- * Maximum number of user identifiers in single UserData.
127
- * @see https://ads-developers.googleblog.com/2021/10/userdata-enforcement-in-google-ads-api.html
128
- * @type {number}
129
- */
130
- const MAX_IDENTIFIERS_PER_USER = 20;
131
-
132
- /**
133
- * Configuration for uploading click conversions, call converions or conversion
134
- * adjustments for Google Ads, includes:
135
- * gclid, conversion_action, conversion_date_time, conversion_value,
136
- * currency_code, order_id, external_attribution_data,
137
- * caller_id, call_start_date_time,
138
- * adjustment_type, adjustment_date_time, user_agent, gclid_date_time_pair, etc.
139
- * @see PICKED_PROPERTIES
140
- *
141
- * Other properties that will be used to build the conversions but not picked by
142
- * the value directly including:
143
- * 1. 'user_identifier_source', source of the user identifier. If there is user
144
- * identifiers information in the conversion, this property should be set as
145
- * 'FIRST_PARTY'.
146
- * @see IDENTIFIERS
147
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier?hl=en
148
- * 2. 'custom_variable_tags', the tags of conversion custom variables. To upload
149
- * custom variables, 'conversion_custom_variable_id' is required rather than the
150
- * 'tag'. So the invoker is expected to use the function
151
- * 'getConversionCustomVariableId' to get the ids and pass in as a
152
- * map(customVariables) of <tag, id> pairs before uploading conversions.
153
- *
154
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/ClickConversion
155
- * @typedef {{
156
- * external_attribution_data: (GoogleAdsApi.ExternalAttributionData|undefined),
157
- * cart_data: (object|undefined),
158
- * gclid: (string|undefined),
159
- * caller_id: (string|undefined),
160
- * call_start_date_time: (string|undefined),
161
- * conversion_action: string,
162
- * conversion_date_time: string,
163
- * conversion_value: number,
164
- * currency_code:(string|undefined),
165
- * order_id: (string|undefined),
166
- * adjustment_type: (string|undefined),
167
- * adjustment_date_time: (!ConversionAdjustmentType|undefined),
168
- * user_agent: (string|undefined),
169
- * user_identifier_source:(!UserIdentifierSource|undefined),
170
- * custom_variable_tags:(!Array<string>|undefined),
171
- * customVariables:(!Object<string,string>|undefined),
172
- * }}
173
- */
174
- let ConversionConfig;
175
-
176
- /**
177
- * Configuration for uploading customer match to Google Ads, includes:
178
- * customer_id, login_customer_id, list_id and operation.
179
- * If audience list_id is not present, 'list_name' and 'upload_key_type' need to
180
- * be there so they can be used to create a customer match user list.
181
- * operation must be one of the two: 'create' or 'remove'.
182
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
183
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/CustomerMatchUploadKeyTypeEnum.CustomerMatchUploadKeyType
184
- * @typedef {{
185
- * customer_id: (string|number),
186
- * login_customer_id: (string|number),
187
- * list_id: (string|undefined),
188
- * list_name: (string|undefined),
189
- * upload_key_type: ('CONTACT_INFO'|'CRM_ID'|'MOBILE_ADVERTISING_ID'|undefined),
190
- * operation: ('create'|'remove'),
191
- * }}
192
- */
193
- let CustomerMatchConfig;
194
-
195
- /**
196
- * Configuration for offline user data job, includes:
197
- * customer_id, login_customer_id, list_id, operation and type.
198
- * 'operation' should be one of the two: 'create' or 'remove',
199
- * 'type' is OfflineUserDataJobType, it can be 'CUSTOMER_MATCH_USER_LIST',
200
- * 'CUSTOMER_MATCH_WITH_ATTRIBUTES' or 'STORE_SALES_UPLOAD_FIRST_PARTY'.
201
- * For job type 'CUSTOMER_MATCH_USER_LIST', if `list_id` is not present,
202
- * 'list_name' and 'upload_key_type' need to be there so they can be used to
203
- * create a customer match user list.
204
- * For job type 'CUSTOMER_MATCH_WITH_ATTRIBUTES', 'user_attribute' can be used
205
- * to store shared additional user attributes.
206
- * For job type 'STORE_SALES_UPLOAD_FIRST_PARTY', `store_sales_metadata` is
207
- * required to offer StoreSalesMetadata. Besides that, for the store sales data,
208
- * common data (e.g. `currency_code`, `conversion_action`) in
209
- * `transaction_attribute` can be put here as well.
210
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/OfflineUserDataJob
211
- * @typedef {{
212
- * customer_id: (string|number),
213
- * login_customer_id: (string|number),
214
- * list_id: (string|undefined),
215
- * list_name: (string|undefined),
216
- * upload_key_type: ('CONTACT_INFO'|'CRM_ID'|'MOBILE_ADVERTISING_ID'|undefined),
217
- * operation: ('create'|'remove'),
218
- * type: !OfflineUserDataJobType,
219
- * store_sales_metadata: (undefined|StoreSalesMetadata),
220
- * transaction_attribute: (undefined|TransactionAttribute),
221
- * user_attribute: (undefined|UserAttribute),
222
- * }}
223
- */
224
- let OfflineUserDataJobConfig;
225
-
226
- /**
227
- * Configuration for uploading customer match data for Google Ads.
228
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
229
- * @typedef {{
230
- * hashed_email: (string|Array<string>|undefined),
231
- * hashed_phone_number: (string|Array<string>|undefined),
232
- * mobile_id: (string|Array<string>|undefined),
233
- * third_party_user_id: (string|Array<string>|undefined),
234
- * address_info: (GoogleAdsApi.OfflineUserAddressInfo|undefined),
235
- * }}
236
- */
237
- let CustomerMatchRecord;
238
-
239
- /**
240
- * Configuration for querying report from Google Ads, includes:
241
- * entity, attributes, metrics, and constraints etc.
242
- * For other properties, see
243
- * https://opteo.com/dev/google-ads-api/#report
244
- * https://developers.google.com/google-ads/api/docs/query/grammar
245
- * @typedef {{
246
- * entity:string,
247
- * attributes:(!Array<string>|undefined),
248
- * metrics:(!Array<string>|undefined),
249
- * segments:(!Array<string>|undefined),
250
- * constraints:(!Array<{
251
- * key:string,
252
- * op:string,
253
- * val:string,
254
- * }|{string,string}>|undefined),
255
- * date_constant:(string|undefined),
256
- * from_date:(string|undefined),
257
- * to_date:(string|undefined),
258
- * limit:(number|undefined),
259
- * order_by:(string|undefined),
260
- * sort_order:('DESC'|'ASC'|undefined),
261
- * page_size:(number|undefined),
262
- * }}
263
- */
264
- let ReportQueryConfig;
265
-
266
- /**
267
- * Google Ads API class based on Opteo's Nodejs library.
268
- * see https://opteo.com/dev/google-ads-api/#features
269
- */
270
- class GoogleAds {
271
- /**
272
- * Note: Rate limits is set by the access level of Developer token.
273
- * @param {string} developerToken Developer token to access the API.
274
- * @param {boolean=} debugMode This is used to set ONLY validate conversions
275
- * but not real uploading.
276
- * @param {!Object<string,string>=} env The environment object to hold env
277
- * variables.
278
- */
279
- constructor(developerToken, debugMode = false, env = process.env) {
280
- this.developerToken = developerToken;
281
- this.debugMode = debugMode;
282
- this.authClient = new AuthClient(API_SCOPES, env);
283
- this.logger = getLogger('API.ADS');
284
- this.logger.debug(`Init ${this.constructor.name} with Debug Mode.`);
285
- }
286
-
287
- /**
288
- * Gets a report synchronously from a given Customer account.
289
- * The enum fields are present as index number.
290
- * @param {string} customerId
291
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
292
- * @param {!ReportQueryConfig} reportQueryConfig
293
- * @return {!ReadableStream}
294
- */
295
- async getReport(customerId, loginCustomerId, reportQueryConfig) {
296
- const customer = await this.getGoogleAdsApiCustomer_(
297
- loginCustomerId, customerId);
298
- return customer.report(reportQueryConfig);
299
- }
300
-
301
- /**
302
- * Gets report as generator of a given Customer account.
303
- * @param {string} customerId
304
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
305
- * @param {!ReportQueryConfig} reportQueryConfig
306
- * @return {!ReadableStream}
307
- */
308
- async generatorReport(customerId, loginCustomerId, reportQueryConfig) {
309
- const customer = await this.getGoogleAdsApiCustomer_(
310
- loginCustomerId, customerId);
311
- return customer.reportStream(reportQueryConfig);
312
- }
313
-
314
- /**
315
- * Gets stream report of a given Customer account.
316
- * @param {string} customerId
317
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
318
- * @param {!ReportQueryConfig} reportQueryConfig
319
- * @return {!ReadableStream}
320
- */
321
- async streamReport(customerId, loginCustomerId, reportQueryConfig) {
322
- const customer = await this.getGoogleAdsApiCustomer_(
323
- loginCustomerId, customerId);
324
- return customer.reportStreamRaw(reportQueryConfig);
325
- }
326
-
327
- /**
328
- * Returns resources information from Google Ads API. see:
329
- * https://developers.google.com/google-ads/api/docs/concepts/field-service
330
- * Note, it looks like this function doesn't check the CID, just using
331
- * developer token and OAuth.
332
- * @param {string|number} loginCustomerId Login customer account ID.
333
- * @param {Array<string>} adFields Array of Ad fields.
334
- * @param {Array<string>} metadata Select fields, default values are:
335
- * name, data_type, is_repeated, type_url.
336
- * @return {!Promise<!Array<GoogleAdsField>>}
337
- */
338
- async searchMetaData(loginCustomerId, adFields, metadata = [
339
- 'name', 'data_type', 'is_repeated', 'type_url',]) {
340
- const customer = await this.getGoogleAdsApiCustomer_(loginCustomerId);
341
- const selectClause = metadata.join(',');
342
- const fields = adFields.join('","');
343
- const query = `SELECT ${selectClause} WHERE name IN ("${fields}")`;
344
- const request = new SearchGoogleAdsFieldsRequest({query});
345
- const [results] = await customer.googleAdsFields
346
- .searchGoogleAdsFields(request);
347
- return results;
348
- }
349
- /**
350
- * Returns the function to send out a request to Google Ads API with a batch
351
- * of call conversions.
352
- * @param {string} customerId
353
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
354
- * @param {!ConversionConfig} adsConfig Default call conversion params
355
- * @return {!SendSingleBatch} Function which can send a batch of hits to
356
- * Google Ads API.
357
- */
358
- getUploadCallConversionFn(customerId, loginCustomerId, adsConfig) {
359
- return this.getUploadConversionFnBase_(customerId, loginCustomerId,
360
- adsConfig, 'uploadCallConversions', 'caller_id');
361
- }
362
-
363
- /**
364
- * Returns the function to send out a request to Google Ads API with a batch
365
- * of click conversions.
366
- * @param {string} customerId
367
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
368
- * @param {!ConversionConfig} adsConfig Default click conversion params
369
- * @return {!SendSingleBatch} Function which can send a batch of hits to
370
- * Google Ads API.
371
- */
372
- getUploadClickConversionFn(customerId, loginCustomerId, adsConfig) {
373
- return this.getUploadConversionFnBase_(customerId, loginCustomerId,
374
- adsConfig, 'uploadClickConversions', 'gclid');
375
- }
376
-
377
- /**
378
- * Returns the function to send out a request to Google Ads API with a batch
379
- * of conversion adjustments.
380
- * @param {string} customerId
381
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
382
- * @param {!ConversionConfig} adsConfig Default conversion adjustments
383
- * params.
384
- * @return {!SendSingleBatch} Function which can send a batch of hits to
385
- * Google Ads API.
386
- */
387
- getUploadConversionAdjustmentFn(customerId, loginCustomerId, adsConfig) {
388
- return this.getUploadConversionFnBase_(customerId, loginCustomerId,
389
- adsConfig, 'uploadConversionAdjustments', 'order_id');
390
- }
391
-
392
- /**
393
- * Returns the function to send call conversions, click conversions or
394
- * conversion adjustment (enhanced conversions).
395
- * @param {string} customerId
396
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
397
- * @param {!ConversionConfig} adsConfig Default click conversion params
398
- * @param {string} functionName The name of sending converions function, could
399
- * be `uploadClickConversions`, `uploadCallConversions` or
400
- * `uploadConversionAdjustments`.
401
- * @param {string} propertyForDebug The name of property for debug info.
402
- * @return {!SendSingleBatch} Function which can send a batch of hits to
403
- * Google Ads API.
404
- * @private
405
- */
406
- getUploadConversionFnBase_(customerId, loginCustomerId, adsConfig,
407
- functionName, propertyForDebug) {
408
- /**
409
- * Sends a batch of hits to Google Ads API.
410
- * @param {!Array<string>} lines Data for single request. It should be
411
- * guaranteed that it doesn't exceed quota limitation.
412
- * @param {string} batchId The tag for log.
413
- * @return {!BatchResult}
414
- */
415
- return async (lines, batchId) => {
416
- /** @type {!Array<ConversionConfig>} */
417
- const conversions = lines.map(
418
- (line) => buildClickConversionFromLine(line, adsConfig, customerId));
419
- /** @const {BatchResult} */
420
- const batchResult = {
421
- result: true,
422
- numberOfLines: lines.length,
423
- };
424
- try {
425
- const response = await this[functionName](conversions, customerId,
426
- loginCustomerId);
427
- const { results, partial_failure_error: failed } = response;
428
- if (this.logger.isDebugEnabled()) {
429
- const id = results.map((conversion) => conversion[propertyForDebug]);
430
- this.logger.debug(`Uploaded ${propertyForDebug}:`, id);
431
- }
432
- if (failed) {
433
- this.logger.info('partial_failure_error:', failed.message);
434
- const failures = failed.details.map(
435
- ({ value }) => GoogleAdsFailure.decode(value));
436
- this.extraFailedLines_(batchResult, failures, lines, 0);
437
- }
438
- return batchResult;
439
- } catch (error) {
440
- this.logger.error(
441
- `Error in ${functionName} batch: ${batchId}`, error);
442
- this.updateBatchResultWithError(batchResult, error, lines, 0);
443
- return batchResult;
444
- }
445
- }
446
- }
447
-
448
- /**
449
- * Updates the BatchResult based on errors.
450
- *
451
- * There are 2 types of errors here:
452
- * 1. Normal JavaScript Error object. It happens when the whole process fails
453
- * (not partial failure), so there is no detailed failed lines.
454
- * 2. GoogleAdsFailure. It is a Google Ads' own error object which has an
455
- * array of GoogleAdsError (property name 'errors'). GoogleAdsError contains
456
- * the detailed failed data if it is a line-error. For example, a wrong
457
- * encoded user identifier is a line-error, while a wrong user list id is not.
458
- * GoogleAdsFailure: https://developers.google.com/google-ads/api/reference/rpc/latest/GoogleAdsFailure
459
- * GoogleAdsError: https://developers.google.com/google-ads/api/reference/rpc/latest/GoogleAdsError
460
- *
461
- * For Customer Match data uploading, there is not partial failure, so the
462
- * result can be either succeeded or a thrown error. The thrown error will be
463
- * used to build the returned result here.
464
- * For Conversions uploading (partial failure enabled), if there is an error
465
- * fails the whole process, the error will also be thrown and handled here.
466
- * Otherwise, the errors will be wrapped in the response as the property named
467
- * 'partial_failure_error' which contains an array of GoogleAdsFailure. This
468
- * kind of failure doesn't fail the process, while line-errors can be
469
- * extracted from it.
470
- * For more information, see the function `extraFailedLines_`.
471
- *
472
- * An example of 'GoogleAdsFailure' is:
473
- * GoogleAdsFailure {
474
- * errors: [
475
- * GoogleAdsError {
476
- * error_code: ErrorCode { offline_user_data_job_error: 25 },
477
- * message: 'The SHA256 encoded value is malformed.',
478
- * location: ErrorLocation {
479
- * field_path_elements: [
480
- * FieldPathElement { field_name: 'operations', index: 0 },
481
- * FieldPathElement { field_name: 'create' },
482
- * FieldPathElement { field_name: 'user_identifiers', index: 0 },
483
- * FieldPathElement { field_name: 'hashed_email' }
484
- * ]
485
- * }
486
- * }
487
- * ],
488
- * request_id: 'xxxxxxxxxxxxxxx'
489
- * }
490
- *
491
- * @param {!BatchResult} batchResult
492
- * @param {(!GoogleAdsFailure|!Error)} error
493
- * @param {!Array<string>} lines The original input data.
494
- * @param {number} fieldPathIndex The index of 'FieldPathElement' in the array
495
- * 'field_path_elements'. This is used to get the original line related to
496
- * this GoogleAdsError.
497
- */
498
- updateBatchResultWithError(batchResult, error, lines, fieldPathIndex) {
499
- batchResult.result = false;
500
- if (error.errors) { //GoogleAdsFailure
501
- this.extraFailedLines_(batchResult, [error], lines, fieldPathIndex);
502
- } else {
503
- batchResult.errors = [error.message || error.toString()];
504
- }
505
- }
506
-
507
- /**
508
- * Extras failed lines based on the GoogleAdsFailures.
509
- *
510
- * Different errors have different 'fieldPathIndex' which is the index of
511
- * failed lines in original input data (an array of a string).
512
- *
513
- * For conversions, the ErrorLocation is like:
514
- * ErrorLocation {
515
- * field_path_elements: [
516
- * FieldPathElement { field_name: 'operations', index: 0 },
517
- * FieldPathElement { field_name: 'create' }
518
- * ]
519
- * }
520
- * So the index is 0, index of 'operations'.
521
- *
522
- * For customer match upload, the ErrorLocation is like:
523
- * ErrorLocation {
524
- * field_path_elements: [
525
- * FieldPathElement { field_name: 'operations', index: 0 },
526
- * FieldPathElement { field_name: 'create' },
527
- * FieldPathElement { field_name: 'user_identifiers', index: 0 },
528
- * FieldPathElement { field_name: 'hashed_email' }
529
- * ]
530
- * }
531
- * The index should be 2, index of 'user_identifiers'.
532
- *
533
- * With this we can get errors and failed lines. The function will set
534
- * following for the given BatchResult object:
535
- * result - false
536
- * errors - de-duplicated error reasons
537
- * failedLines - failed lines, an array of string. Without the reason of
538
- * failure.
539
- * groupedFailed - a hashmap of failed the lines. The key is the reason, the
540
- * value is the array of failed lines due to this reason.
541
- * @param {!BatchResult} batchResult
542
- * @param {!Array<!GoogleAdsFailure>} failures
543
- * @param {!Array<string>} lines The original input data.
544
- * @param {number} fieldPathIndex The index of 'FieldPathElement' in the array
545
- * 'field_path_elements'. This is used to get the original line related to
546
- * this GoogleAdsError.
547
- * @private
548
- */
549
- extraFailedLines_(batchResult, failures, lines, fieldPathIndex) {
550
- batchResult.result = false;
551
- batchResult.failedLines = [];
552
- batchResult.groupedFailed = {};
553
- const errors = new Set();
554
- failures.forEach((failure) => {
555
- failure.errors.forEach(({message, location}) => {
556
- errors.add(message);
557
- if (location && location.field_path_elements[fieldPathIndex]) {
558
- const {index} = location.field_path_elements[fieldPathIndex];
559
- if (typeof index === 'undefined') {
560
- this.logger.warn(`Unknown field path index: ${fieldPathIndex}`,
561
- location.field_path_elements);
562
- } else {
563
- const groupedFailed = batchResult.groupedFailed[message] || [];
564
- const failedLine = lines[index];
565
- batchResult.failedLines.push(failedLine);
566
- groupedFailed.push(failedLine);
567
- if (groupedFailed.length === 1) {
568
- batchResult.groupedFailed[message] = groupedFailed;
569
- }
570
- }
571
- }
572
- });
573
- });
574
- batchResult.errors = Array.from(errors);
575
- }
576
-
577
- /**
578
- * Uploads call conversions to google ads account.
579
- * It requires an array of call conversions and customer id.
580
- * In DEBUG mode, this function will only validate the conversions.
581
- * @param {Array<ConversionConfig>} callConversions Call Conversions
582
- * @param {string} customerId
583
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
584
- * @return {!Promise<!UploadCallConversionsResponse>}
585
- */
586
- async uploadCallConversions(callConversions, customerId, loginCustomerId) {
587
- this.logger.debug('Upload call conversions for customerId:', customerId);
588
- const customer = await this.getGoogleAdsApiCustomer_(
589
- loginCustomerId, customerId);
590
- const request = new UploadCallConversionsRequest({
591
- conversions: callConversions,
592
- customer_id: customerId,
593
- validate_only: this.debugMode, // when true makes no changes
594
- partial_failure: true, // Will still create the non-failed entities
595
- });
596
- return customer.conversionUploads.uploadCallConversions(request);
597
- }
598
-
599
- /**
600
- * Uploads click conversions to google ads account.
601
- * It requires an array of click conversions and customer id.
602
- * In DEBUG mode, this function will only validate the conversions.
603
- * @param {Array<ConversionConfig>} clickConversions ClickConversions
604
- * @param {string} customerId
605
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
606
- * @return {!Promise<!UploadClickConversionsResponse>}
607
- */
608
- async uploadClickConversions(clickConversions, customerId, loginCustomerId) {
609
- this.logger.debug('Upload click conversions for customerId:', customerId);
610
- const customer = await this.getGoogleAdsApiCustomer_(
611
- loginCustomerId, customerId);
612
- const request = new UploadClickConversionsRequest({
613
- conversions: clickConversions,
614
- customer_id: customerId,
615
- validate_only: this.debugMode, // when true makes no changes
616
- partial_failure: true, // Will still create the non-failed entities
617
- });
618
- return customer.conversionUploads.uploadClickConversions(request);
619
- }
620
-
621
- /**
622
- * Uploads conversion adjustments to google ads account.
623
- * It requires an array of conversion adjustments and customer id.
624
- * In DEBUG mode, this function will only validate the conversion adjustments.
625
- * @param {Array<ConversionAdjustment>} conversionAdjustments Conversion
626
- * adjustments.
627
- * @param {string} customerId
628
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
629
- * @return {!Promise<!UploadConversionAdjustmentsResponse>}
630
- */
631
- async uploadConversionAdjustments(conversionAdjustments, customerId,
632
- loginCustomerId) {
633
- this.logger.debug('Upload conversion adjustments for customerId:',
634
- customerId);
635
- const customer = await this.getGoogleAdsApiCustomer_(
636
- loginCustomerId, customerId);
637
- const request = new UploadConversionAdjustmentsRequest({
638
- conversion_adjustments: conversionAdjustments,
639
- customer_id: customerId,
640
- validate_only: this.debugMode, // when true makes no changes
641
- partial_failure: true, // Will still create the non-failed entities
642
- });
643
- return customer.conversionAdjustmentUploads.uploadConversionAdjustments(
644
- request);
645
- }
646
-
647
- /**
648
- * Returns the id of Conversion Custom Variable with the given tag.
649
- * @param {string} tag Custom Variable tag.
650
- * @param {string} customerId
651
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
652
- * @return {Promise<number|undefined>} Returns undefined if can't find tag.
653
- */
654
- async getConversionCustomVariableId(tag, customerId, loginCustomerId) {
655
- const customer = await this.getGoogleAdsApiCustomer_(
656
- loginCustomerId, customerId);
657
- const customVariables = await customer.query(`
658
- SELECT conversion_custom_variable.id,
659
- conversion_custom_variable.tag
660
- FROM conversion_custom_variable
661
- WHERE conversion_custom_variable.tag = "${tag}" LIMIT 1
662
- `);
663
- if (customVariables.length > 0) {
664
- return customVariables[0].conversion_custom_variable.id;
665
- }
666
- }
667
-
668
- /**
669
- * Gets the user list_id of a given list name and upload key type. It
670
- * only looks for a CRM_BASED and OPEN list.
671
- * @param {!CustomerMatchConfig} customerMatchConfig
672
- * @return {number|undefined} User list_id if it exists.
673
- */
674
- async getCustomerMatchUserListId(customerMatchConfig) {
675
- const customerId = this.getCleanCid_(customerMatchConfig.customer_id);
676
- const loginCustomerId = this.getCleanCid_(
677
- customerMatchConfig.login_customer_id);
678
- const listName = customerMatchConfig.list_name;
679
- const uploadKeyType = customerMatchConfig.upload_key_type;
680
- const reportConfig = {
681
- entity: 'user_list',
682
- attributes: [
683
- 'user_list.id',
684
- 'user_list.resource_name',
685
- ],
686
- constraints: {
687
- 'user_list.name': listName,
688
- 'customer.id': customerId,
689
- 'user_list.type': UserListType.CRM_BASED,
690
- 'user_list.membership_status': UserListMembershipStatus.OPEN,
691
- 'user_list.crm_based_user_list.upload_key_type': uploadKeyType,
692
- },
693
- };
694
- const userlists =
695
- await this.getReport(customerId, loginCustomerId, reportConfig);
696
- return userlists.length === 0 ? undefined : userlists[0].user_list.id;
697
- }
698
-
699
- /**
700
- * Creates the user list based on a given customerMatchConfig and returns the
701
- * Id. The user list would be a CRM_BASED type.
702
- * Trying to create a list with an used name will fail.
703
- * @param {!CustomerMatchConfig} customerMatchConfig
704
- * @return {number} The created user list id. Note this is not the resource
705
- * name.
706
- */
707
- async createCustomerMatchUserList(customerMatchConfig) {
708
- const customerId = this.getCleanCid_(customerMatchConfig.customer_id);
709
- const loginCustomerId = this.getCleanCid_(
710
- customerMatchConfig.login_customer_id);
711
- const listName = customerMatchConfig.list_name;
712
- const uploadKeyType = customerMatchConfig.upload_key_type;
713
- const userList = UserList.create({
714
- name: listName,
715
- type: UserListType.CRM_BASED,
716
- crm_based_user_list: { upload_key_type: uploadKeyType },
717
- });
718
- const options = {
719
- validate_only: this.debugMode, // when true makes no changes
720
- partial_failure: true, // Will still create the non-failed entities
721
- };
722
- const customer = await this.getGoogleAdsApiCustomer_(
723
- loginCustomerId, customerId);
724
- const response = await customer.userLists.create([userList], options);
725
- const { results, partial_failure_error: failed } = response;
726
- if (this.logger.isDebugEnabled()) {
727
- this.logger.debug(`Created crm userlist from`, customerMatchConfig);
728
- }
729
- if (failed) {
730
- const failures = failed.errors.map(({ message }) => message).join(' ');
731
- this.logger.info('partial_failure_error:', failures);
732
- throw new Error(failures);
733
- }
734
- if (!results[0]) {
735
- if (this.debugMode) {
736
- throw new Error('No UserList was created in DEBUG mode.');
737
- } else {
738
- throw new Error('No UserList was created.');
739
- }
740
- }
741
- const resourceName = results[0].resource_name;
742
- const splitted = resourceName.split('/');
743
- return splitted[splitted.length - 1];
744
- }
745
-
746
- /**
747
- * Returns the function to send out a request to Google Ads API with
748
- * user ids for Customer Match upload
749
- * @param {!CustomerMatchConfig} customerMatchConfig
750
- * @return {!SendSingleBatch} Function which can send a batch of hits to
751
- * Google Ads API.
752
- */
753
- getUploadCustomerMatchFn(customerMatchConfig) {
754
- /**
755
- * Sends a batch of hits to Google Ads API.
756
- * @param {!Array<string>} lines Data for single request. It should be
757
- * guaranteed that it doesn't exceed quota limitation.
758
- * @param {string} batchId The tag for log.
759
- * @return {!Promise<BatchResult>}
760
- */
761
- return async (lines, batchId) => {
762
- /** @type {Array<CustomerMatchRecord>} */
763
- const userIds = lines.map((line) => JSON.parse(line));
764
- /** @const {BatchResult} */ const batchResult = {
765
- result: true,
766
- numberOfLines: lines.length,
767
- };
768
- try {
769
- const response = await this.uploadUserDataToUserList(userIds,
770
- customerMatchConfig);
771
- this.logger.debug(`Customer Match upload batch[${batchId}]`, response);
772
- return batchResult;
773
- } catch (error) {
774
- this.logger.error(
775
- `Error in Customer Match upload batch[${batchId}]`, error);
776
- this.updateBatchResultWithError(batchResult, error, lines, 2);
777
- return batchResult;
778
- }
779
- }
780
- }
781
-
782
- /**
783
- * Uploads a user data to a user list (aka customer match).
784
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataService
785
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
786
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserData
787
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
788
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/CustomerMatchUserListMetadata
789
- * Please note: The UserDataService has a limit of 10 UserDataOperations
790
- * and 100 user IDs per request
791
- * @see https://developers.google.com/google-ads/api/docs/migration/user-data-service#rate_limits
792
- * @param {!Array<CustomerMatchRecord>} customerMatchRecords user Ids
793
- * @param {CustomerMatchConfig} customerMatchConfig Customer Match config containing
794
- * customer_id, login_customer_id, list_id, list_type which can be one of the following
795
- * hashed_email, hashed_phone_number, mobile_id, third_party_user_id or address_info and
796
- * operation which can be either 'create' or 'remove'
797
- * @return {!Promise<UploadUserDataResponse>}
798
- */
799
- async uploadUserDataToUserList(customerMatchRecords, customerMatchConfig) {
800
- const customerId = this.getCleanCid_(customerMatchConfig.customer_id);
801
- const loginCustomerId = this.getCleanCid_(
802
- customerMatchConfig.login_customer_id);
803
- const userListId = customerMatchConfig.list_id;
804
- const operation = customerMatchConfig.operation;
805
-
806
- const customer = await this.getGoogleAdsApiCustomer_(
807
- loginCustomerId, customerId);
808
- const operationsList = this.buildOperationsList_(operation,
809
- customerMatchRecords);
810
- const metadata = this.buildCustomerMatchUserListMetadata_(customerId,
811
- userListId);
812
- const request = UploadUserDataRequest.create({
813
- customer_id: customerId,
814
- operations: operationsList,
815
- customer_match_user_list_metadata: metadata,
816
- });
817
- const response = await customer.userData.uploadUserData(request);
818
- return response;
819
- }
820
-
821
- /**
822
- * Builds a list of UserDataOperations.
823
- * Since v6 you can set a user_attribute
824
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserData
825
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
826
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
827
- * @param {string} operationType either 'create' or 'remove'
828
- * @param {Array<CustomerMatchRecord>} customerMatchRecords userIds
829
- * @param {{
830
- * transaction_attribute: TransactionAttribute|undefined,
831
- * user_attribute: UserAttribute,
832
- * }} additionalAttributes Additional attributes for 'UserData', includes
833
- * 'transaction_attribute' or 'user_attribute'.
834
- * @return {Array<UserDataOperation>}
835
- * @private
836
- */
837
- buildOperationsList_(operationType, customerMatchRecords,
838
- additionalAttributes = {}) {
839
- return customerMatchRecords.map((customerMatchRecord) => {
840
- const userIdentifiers = [];
841
- IDENTIFIERS.forEach((idType) => {
842
- const idValue = customerMatchRecord[idType];
843
- if (idValue) {
844
- if (Array.isArray(idValue)) {
845
- idValue.forEach((user) => {
846
- userIdentifiers.push(UserIdentifier.create({[idType]: user}));
847
- });
848
- } else {
849
- userIdentifiers.push(UserIdentifier.create({[idType]: idValue}));
850
- }
851
- }
852
- });
853
- const userData = {};
854
- if (userIdentifiers.length <= MAX_IDENTIFIERS_PER_USER) {
855
- userData.user_identifiers = userIdentifiers;
856
- } else {
857
- this.logger.warn(
858
- `Too many user identifiers, will only send ${MAX_IDENTIFIERS_PER_USER}:`,
859
- JSON.stringify(customerMatchRecord));
860
- userData.user_identifiers =
861
- userIdentifiers.slice(0, MAX_IDENTIFIERS_PER_USER);
862
- }
863
- USERDATA_ADDITIONAL_ATTRIBUTES.forEach((attribute) => {
864
- if (additionalAttributes[attribute] || customerMatchRecord[attribute]) {
865
- userData[attribute] = lodash.merge(
866
- {}, additionalAttributes[attribute], customerMatchRecord[attribute]);
867
- }
868
- })
869
- return UserDataOperation.create(
870
- { [operationType]: UserData.create(userData) });
871
- });
872
- }
873
-
874
- /**
875
- * Creates CustomerMatchUserListMetadata.
876
- * @see https://developers.google.com/google-ads/api/reference/rpc/latest/CustomerMatchUserListMetadata
877
- * @param {string} customerId part of the ResourceName to be mutated
878
- * @param {string} userListId part of the ResourceName to be mutated
879
- * @return {!CustomerMatchUserListMetadata}
880
- * @private
881
- */
882
- buildCustomerMatchUserListMetadata_(customerId, userListId) {
883
- const resourceName = `customers/${customerId}/userLists/${userListId}`;
884
- return CustomerMatchUserListMetadata.create({
885
- user_list: resourceName,
886
- });
887
- }
888
-
889
- /**
890
- * Returns a integer format CID by removing dashes.
891
- * @param {string} cid
892
- * @return {string}
893
- * @private
894
- */
895
- getCleanCid_(cid) {
896
- return cid.replace(/-/g, '');
897
- }
898
-
899
- /**
900
- * Get OfflineUserDataJob status.
901
- * @param {OfflineUserDataJobConfig} config Offline user data job config.
902
- * @param {string} resourceName
903
- * @return {!OfflineUserDataJobStatus} Job status
904
- */
905
- async getOfflineUserDataJob(config, resourceName) {
906
- const loginCustomerId = this.getCleanCid_(config.login_customer_id);
907
- const customerId = this.getCleanCid_(config.customer_id);
908
- const reportConfig = {
909
- entity: 'offline_user_data_job',
910
- attributes: [
911
- 'offline_user_data_job.id',
912
- 'offline_user_data_job.status',
913
- 'offline_user_data_job.type',
914
- 'offline_user_data_job.customer_match_user_list_metadata.user_list',
915
- 'offline_user_data_job.failure_reason',
916
- ],
917
- constraints: {
918
- 'offline_user_data_job.resource_name': resourceName,
919
- },
920
- };
921
- const jobs = await this.getReport(customerId, loginCustomerId, reportConfig);
922
- if (jobs.length === 0) {
923
- throw new Error(`Can't find the OfflineUserDataJob: ${resourceName}`);
924
- }
925
- const { failure_reason: failure, status } = jobs[0].offline_user_data_job;
926
- if (failure > 0) {
927
- this.logger.warn(`Offline UserData Job [${resourceName}] failed: `,
928
- OfflineUserDataJobFailureReason[failure])
929
- }
930
- return OfflineUserDataJobStatus[status];
931
- }
932
-
933
- /**
934
- * Creates a OfflineUserDataJob and returns resource name.
935
- * @param {OfflineUserDataJobConfig} config Offline user data job config.
936
- * @return {string} The resouce name of the creaed job.
937
- */
938
- async createOfflineUserDataJob(config) {
939
- const loginCustomerId = this.getCleanCid_(config.login_customer_id);
940
- const customerId = this.getCleanCid_(config.customer_id);
941
- const { list_id: userListId, type } = config;
942
- this.logger.debug('Creating OfflineUserDataJob for CID:', customerId);
943
- const customer = await this.getGoogleAdsApiCustomer_(
944
- loginCustomerId, customerId);
945
- const job = OfflineUserDataJob.create({
946
- type,
947
- });
948
- // https://developers.google.com/google-ads/api/rest/reference/rest/latest/customers.offlineUserDataJobs?hl=en#CustomerMatchUserListMetadata
949
- if (type.startsWith('CUSTOMER_MATCH')) {
950
- const metadata = this.buildCustomerMatchUserListMetadata_(customerId,
951
- userListId);
952
- job.customer_match_user_list_metadata = metadata;
953
- // https://developers.google.com/google-ads/api/rest/reference/rest/latest/customers.offlineUserDataJobs?hl=en#StoreSalesMetadata
954
- } else if (type.startsWith('STORE_SALES')) {
955
- // Support previous property 'StoreSalesMetadata' for compatibility.
956
- if (config.store_sales_metadata || config.StoreSalesMetadata) {
957
- job.store_sales_metadata = config.store_sales_metadata
958
- || config.StoreSalesMetadata;
959
- }
960
- } else {
961
- throw new Error(`UNSUPPORTED OfflineUserDataJobType: ${type}.`);
962
- }
963
- const request = CreateOfflineUserDataJobRequest.create({
964
- customer_id: customerId,
965
- job,
966
- validate_only: this.debugMode, // when true makes no changes
967
- enable_match_rate_range_preview: true,
968
- });
969
- const { resource_name: resourceName } =
970
- await customer.offlineUserDataJobs.createOfflineUserDataJob(request);
971
- this.logger.info('Created OfflineUserDataJob:', resourceName);
972
- return resourceName;
973
- }
974
-
975
- /**
976
- * Adds user data in to the OfflineUserDataJob.
977
- * @param {OfflineUserDataJobConfig} config Offline user data job config.
978
- * @param {string} jobResourceName
979
- * @param {!Array<CustomerMatchRecord>} customerMatchRecords user Ids
980
- * @return {!Promise<AddOfflineUserDataJobOperationsResponse>}
981
- */
982
- async addOperationsToOfflineUserDataJob(config, jobResourceName, records) {
983
- const start = new Date().getTime();
984
- const loginCustomerId = this.getCleanCid_(config.login_customer_id);
985
- const customerId = this.getCleanCid_(config.customer_id);
986
- const operation = config.operation;
987
- const customer = await this.getGoogleAdsApiCustomer_(
988
- loginCustomerId, customerId);
989
- const operationsList = this.buildOperationsList_(operation, records, config);
990
- const request = AddOfflineUserDataJobOperationsRequest.create({
991
- resource_name: jobResourceName,
992
- operations: operationsList,
993
- validate_only: false,//this.debugMode,
994
- enable_partial_failure: true,
995
- enable_warnings: true,
996
- });
997
- const response = await customer.
998
- offlineUserDataJobs.addOfflineUserDataJobOperations(request);
999
- this.logger.debug(`Added ${records.length} records in (ms):`,
1000
- new Date().getTime() - start);
1001
- return response;
1002
- }
1003
-
1004
- /**
1005
- * Starts the OfflineUserDataJob.
1006
- * @param {OfflineUserDataJobConfig} config Offline user data job config.
1007
- * @param {string} jobResourceName
1008
- * @returns
1009
- */
1010
- async runOfflineUserDataJob(config, jobResourceName) {
1011
- const loginCustomerId = this.getCleanCid_(config.login_customer_id);
1012
- const customerId = this.getCleanCid_(config.customer_id);
1013
- const customer = await this.getGoogleAdsApiCustomer_(
1014
- loginCustomerId, customerId);
1015
- const request = RunOfflineUserDataJobRequest.create({
1016
- resource_name: jobResourceName,
1017
- validate_only: false,//this.debugMode,
1018
- });
1019
- const rawResponse = await customer.
1020
- offlineUserDataJobs.runOfflineUserDataJob(request);
1021
- const response = lodash.pick(rawResponse, ['name', 'done', 'error']);
1022
- this.logger.debug('runOfflineUserDataJob response: ', response);
1023
- return response;
1024
- }
1025
-
1026
- /**
1027
- * Returns the function to send out a request to Google Ads API with
1028
- * user data as operations in OfflineUserDataJob.
1029
- * @param {!OfflineUserDataJobConfig} config
1030
- * @param {string} jobResourceName
1031
- * @return {!SendSingleBatch} Function which can send a batch of hits to
1032
- * Google Ads API.
1033
- */
1034
- getAddOperationsToOfflineUserDataJobFn(config, jobResourceName) {
1035
- /**
1036
- * Sends a batch of hits to Google Ads API.
1037
- * @param {!Array<string>} lines Data for single request. It should be
1038
- * guaranteed that it doesn't exceed quota limitation.
1039
- * @param {string} batchId The tag for log.
1040
- * @return {!Promise<BatchResult>}
1041
- */
1042
- return async (lines, batchId) => {
1043
- /** @type {Array<CustomerMatchRecord>} */
1044
- const records = lines.map((line) => JSON.parse(line));
1045
- /** @const {BatchResult} */ const batchResult = {
1046
- result: true,
1047
- numberOfLines: lines.length,
1048
- };
1049
- try {
1050
- const response = await this.addOperationsToOfflineUserDataJob(config,
1051
- jobResourceName, records);
1052
- this.logger.debug(`Add operation to job batch[${batchId}]`, response);
1053
- const { results, partial_failure_error: failed } = response;
1054
- if (failed) {
1055
- this.logger.info('partial_failure_error:', failed.message);
1056
- const failures = failed.details.map(
1057
- ({ value }) => GoogleAdsFailure.decode(value));
1058
- this.extraFailedLines_(batchResult, failures, lines, 0);
1059
- }
1060
- return batchResult;
1061
- } catch (error) {
1062
- this.logger.error(
1063
- `Error in OfflineUserDataJob add operations batch[${batchId}]`, error);
1064
- this.updateBatchResultWithError(batchResult, error, lines, 2);
1065
- return batchResult;
1066
- }
1067
- }
1068
- }
1069
-
1070
- /**
1071
- * Returns an instance of GoogleAdsApi.Customer on google-ads-api.
1072
- * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
1073
- * @param {string=} customerId Customer account ID, default is the same as
1074
- * the login customer account ID.
1075
- * @return {GoogleAdsApi.Customer}
1076
- * @private
1077
- */
1078
- async getGoogleAdsApiCustomer_(loginCustomerId, customerId = loginCustomerId) {
1079
- if (this.googleAds) return this.googleAds;
1080
- await this.authClient.prepareCredentials();
1081
- const oauthClient = await this.authClient.getOAuth2Token();
1082
- /** @const {GoogleAdsApi} */
1083
- const googleAdsApiClient = new GoogleAdsApi({
1084
- client_id: oauthClient.clientId,
1085
- client_secret: oauthClient.clientSecret,
1086
- developer_token: this.developerToken,
1087
- });
1088
- this.googleAds = googleAdsApiClient.Customer({
1089
- customer_id: customerId,
1090
- login_customer_id: loginCustomerId,
1091
- refresh_token: oauthClient.refreshToken,
1092
- });
1093
- return this.googleAds;
1094
- }
1095
-
1096
- }
1097
-
1098
- /**
1099
- * Returns a conversion object based the given config and line data.
1100
- * @param {string} line A JSON string of a conversion data.
1101
- * @param {ConversionConfig} config Default click conversion params
1102
- * @param {string} customerId
1103
- * @return {object} A conversion
1104
- */
1105
- const buildClickConversionFromLine = (line, config, customerId) => {
1106
- const {customVariables, user_identifier_source} = config;
1107
- const record = JSON.parse(line);
1108
- const conversion = lodash.merge(lodash.pick(config, PICKED_PROPERTIES),
1109
- lodash.pick(record, PICKED_PROPERTIES));
1110
- if (customVariables) {
1111
- const tags = Object.keys(customVariables);
1112
- conversion.custom_variables = tags.map((tag) => {
1113
- return {
1114
- conversion_custom_variable:
1115
- `customers/${customerId}/conversionCustomVariables/${customVariables[tag]}`,
1116
- value: record[tag],
1117
- };
1118
- });
1119
- }
1120
- const user_identifiers = [];
1121
- IDENTIFIERS.forEach((identifier) => {
1122
- if (record[identifier]) {
1123
- user_identifiers.push({
1124
- user_identifier_source,
1125
- [identifier]: record[identifier],
1126
- });
1127
- }
1128
- });
1129
- if (user_identifiers.length > 0) {
1130
- conversion.user_identifiers = user_identifiers;
1131
- }
1132
- return conversion;
1133
- }
1134
-
1135
- module.exports = {
1136
- ConversionConfig,
1137
- CustomerMatchRecord,
1138
- CustomerMatchConfig,
1139
- OfflineUserDataJobType,
1140
- OfflineUserDataJobConfig,
1141
- GoogleAds,
1142
- ReportQueryConfig,
1143
- GoogleAdsField,
1144
- buildClickConversionFromLine,
1145
- };