@google-cloud/nodejs-common 0.9.4-beta2 → 0.9.5-beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/install_functions.sh +4 -0
- package/package.json +3 -2
- package/src/apis/auth_client.js +15 -0
- package/src/apis/dfa_reporting.js +67 -19
- package/src/apis/doubleclick_search.js +161 -89
- package/src/apis/google_ads.js +198 -46
- package/src/apis/index.js +10 -0
- package/src/apis/measurement_protocol.js +45 -25
- package/src/apis/measurement_protocol_ga4.js +7 -1
- package/src/apis/spreadsheets.js +13 -13
- package/src/apis/youtube.js +356 -0
- package/src/components/utils.js +47 -12
package/src/apis/google_ads.js
CHANGED
|
@@ -30,14 +30,19 @@ const {
|
|
|
30
30
|
},
|
|
31
31
|
services: {
|
|
32
32
|
UploadClickConversionsRequest,
|
|
33
|
+
UploadClickConversionsResponse,
|
|
33
34
|
UploadUserDataRequest,
|
|
35
|
+
UploadUserDataResponse,
|
|
34
36
|
UserDataOperation,
|
|
35
37
|
SearchGoogleAdsFieldsRequest,
|
|
36
38
|
},
|
|
39
|
+
errors: {
|
|
40
|
+
GoogleAdsFailure,
|
|
41
|
+
},
|
|
37
42
|
} = googleAdsLib;
|
|
38
43
|
const {GoogleAdsApi} = require('google-ads-api');
|
|
39
44
|
const AuthClient = require('./auth_client.js');
|
|
40
|
-
const {getLogger} = require('../components/utils.js');
|
|
45
|
+
const {getLogger, BatchResult,} = require('../components/utils.js');
|
|
41
46
|
|
|
42
47
|
/** @type {!ReadonlyArray<string>} */
|
|
43
48
|
const API_SCOPES = Object.freeze(['https://www.googleapis.com/auth/adwords',]);
|
|
@@ -46,7 +51,7 @@ const API_SCOPES = Object.freeze(['https://www.googleapis.com/auth/adwords',]);
|
|
|
46
51
|
* Configuration for uploading click conversions for Google Ads, includes:
|
|
47
52
|
* gclid, conversion_action, conversion_date_time, conversion_value,
|
|
48
53
|
* currency_code, order_id, external_attribution_data
|
|
49
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
54
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/ClickConversion
|
|
50
55
|
* @typedef {{
|
|
51
56
|
* gclid: string,
|
|
52
57
|
* conversion_action: string,
|
|
@@ -65,7 +70,7 @@ let ClickConversionConfig;
|
|
|
65
70
|
* list_type must be one of the following: hashed_email,
|
|
66
71
|
* hashed_phone_number, mobile_id, third_party_user_id or address_info;
|
|
67
72
|
* operation must be one of the two: 'create' or 'remove';
|
|
68
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
73
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
|
|
69
74
|
* @typedef {{
|
|
70
75
|
* customer_id: string,
|
|
71
76
|
* login_customer_id: string,
|
|
@@ -80,7 +85,7 @@ let CustomerMatchConfig;
|
|
|
80
85
|
/**
|
|
81
86
|
* Configuration for uploading customer match data for Google Ads, includes one of:
|
|
82
87
|
* hashed_email, hashed_phone_number, mobile_id, third_party_user_id or address_info
|
|
83
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
88
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
|
|
84
89
|
* @typedef {{
|
|
85
90
|
* hashed_email: string,
|
|
86
91
|
* }|{
|
|
@@ -123,17 +128,21 @@ let CustomerMatchRecord;
|
|
|
123
128
|
let ReportQueryConfig;
|
|
124
129
|
|
|
125
130
|
/**
|
|
126
|
-
* Google Ads API
|
|
131
|
+
* Google Ads API class based on Opteo's Nodejs library.
|
|
127
132
|
* see https://opteo.com/dev/google-ads-api/#features
|
|
128
133
|
*/
|
|
129
134
|
class GoogleAds {
|
|
130
135
|
/**
|
|
131
136
|
* Note: Rate limits is set by the access level of Developer token.
|
|
132
137
|
* @param {string} developerToken Developer token to access the API.
|
|
138
|
+
* @param {boolean=} debugMode This is used to set ONLY validate conversions
|
|
139
|
+
* but not real uploading.
|
|
140
|
+
* @param {!Object<string,string>=} env The environment object to hold env
|
|
141
|
+
* variables.
|
|
133
142
|
*/
|
|
134
|
-
constructor(developerToken) {
|
|
135
|
-
this.
|
|
136
|
-
const oauthClient = new AuthClient(API_SCOPES).getOAuth2Token();
|
|
143
|
+
constructor(developerToken, debugMode = false, env = process.env) {
|
|
144
|
+
this.debugMode = debugMode;
|
|
145
|
+
const oauthClient = new AuthClient(API_SCOPES, env).getOAuth2Token();
|
|
137
146
|
/** @const {GoogleAdsApi} */ this.apiClient = new GoogleAdsApi({
|
|
138
147
|
client_id: oauthClient.clientId,
|
|
139
148
|
client_secret: oauthClient.clientSecret,
|
|
@@ -206,7 +215,7 @@ class GoogleAds {
|
|
|
206
215
|
* @param {!Array<string>} lines Data for single request. It should be
|
|
207
216
|
* guaranteed that it doesn't exceed quota limitation.
|
|
208
217
|
* @param {string} batchId The tag for log.
|
|
209
|
-
* @return {!
|
|
218
|
+
* @return {!BatchResult}
|
|
210
219
|
*/
|
|
211
220
|
return async (lines, batchId) => {
|
|
212
221
|
/** @type {!Array<ClickConversionConfig>} */
|
|
@@ -214,17 +223,166 @@ class GoogleAds {
|
|
|
214
223
|
const record = JSON.parse(line);
|
|
215
224
|
return Object.assign({}, adsConfig, record);
|
|
216
225
|
});
|
|
226
|
+
/** @const {BatchResult} */
|
|
227
|
+
const batchResult = {
|
|
228
|
+
result: true,
|
|
229
|
+
numberOfLines: lines.length,
|
|
230
|
+
};
|
|
217
231
|
try {
|
|
218
|
-
|
|
219
|
-
loginCustomerId);
|
|
232
|
+
const response = await this.uploadClickConversions(conversions,
|
|
233
|
+
customerId, loginCustomerId);
|
|
234
|
+
const {results, partial_failure_error: failed} = response;
|
|
235
|
+
if (this.logger.isDebugEnabled()) {
|
|
236
|
+
const gclids = results.map((conversion) => conversion.gclid);
|
|
237
|
+
this.logger.debug('Uploaded gclids:', gclids);
|
|
238
|
+
}
|
|
239
|
+
if (failed) {
|
|
240
|
+
this.logger.info('partial_failure_error:', failed.message);
|
|
241
|
+
const failures = failed.details.map(
|
|
242
|
+
({value}) => GoogleAdsFailure.decode(value));
|
|
243
|
+
this.extraFailedLines_(batchResult, failures, lines, 0);
|
|
244
|
+
}
|
|
245
|
+
return batchResult;
|
|
220
246
|
} catch (error) {
|
|
221
|
-
this.logger.
|
|
222
|
-
`Error in
|
|
223
|
-
|
|
247
|
+
this.logger.error(
|
|
248
|
+
`Error in upload conversions batch: ${batchId}`, error);
|
|
249
|
+
this.updateBatchResultWithError_(batchResult, error, lines, 0);
|
|
250
|
+
return batchResult;
|
|
224
251
|
}
|
|
225
252
|
}
|
|
226
253
|
}
|
|
227
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Updates the BatchResult based on errors.
|
|
257
|
+
*
|
|
258
|
+
* There are 2 types of errors here:
|
|
259
|
+
* 1. Normal JavaScript Error object. It happens when the whole process fails
|
|
260
|
+
* (not partial failure), so there is no detailed failed lines.
|
|
261
|
+
* 2. GoogleAdsFailure. It is a Google Ads' own error object which has an
|
|
262
|
+
* array of GoogleAdsError (property name 'errors'). GoogleAdsError contains
|
|
263
|
+
* the detailed failed data if it is a line-error. For example, a wrong
|
|
264
|
+
* encoded user identifier is a line-error, while a wrong user list id is not.
|
|
265
|
+
* GoogleAdsFailure: https://developers.google.com/google-ads/api/reference/rpc/latest/GoogleAdsFailure
|
|
266
|
+
* GoogleAdsError: https://developers.google.com/google-ads/api/reference/rpc/latest/GoogleAdsError
|
|
267
|
+
*
|
|
268
|
+
* For Customer Match data uploading, there is not partial failure, so the
|
|
269
|
+
* result can be either succeeded or a thrown error. The thrown error will be
|
|
270
|
+
* used to build the returned result here.
|
|
271
|
+
* For Conversions uploading (partial failure enabled), if there is an error
|
|
272
|
+
* fails the whole process, the error will also be thrown and handled here.
|
|
273
|
+
* Otherwise, the errors will be wrapped in the response as the property named
|
|
274
|
+
* 'partial_failure_error' which contains an array of GoogleAdsFailure. This
|
|
275
|
+
* kind of failure doesn't fail the process, while line-errors can be
|
|
276
|
+
* extracted from it.
|
|
277
|
+
* For more information, see the function `extraFailedLines_`.
|
|
278
|
+
*
|
|
279
|
+
* An example of 'GoogleAdsFailure' is:
|
|
280
|
+
* GoogleAdsFailure {
|
|
281
|
+
* errors: [
|
|
282
|
+
* GoogleAdsError {
|
|
283
|
+
* error_code: ErrorCode { offline_user_data_job_error: 25 },
|
|
284
|
+
* message: 'The SHA256 encoded value is malformed.',
|
|
285
|
+
* location: ErrorLocation {
|
|
286
|
+
* field_path_elements: [
|
|
287
|
+
* FieldPathElement { field_name: 'operations', index: 0 },
|
|
288
|
+
* FieldPathElement { field_name: 'create' },
|
|
289
|
+
* FieldPathElement { field_name: 'user_identifiers', index: 0 },
|
|
290
|
+
* FieldPathElement { field_name: 'hashed_email' }
|
|
291
|
+
* ]
|
|
292
|
+
* }
|
|
293
|
+
* }
|
|
294
|
+
* ],
|
|
295
|
+
* request_id: 'xxxxxxxxxxxxxxx'
|
|
296
|
+
* }
|
|
297
|
+
*
|
|
298
|
+
* @param {!BatchResult} batchResult
|
|
299
|
+
* @param {(!GoogleAdsFailure|!Error)} error
|
|
300
|
+
* @param {!Array<string>} lines The original input data.
|
|
301
|
+
* @param {number} fieldPathIndex The index of 'FieldPathElement' in the array
|
|
302
|
+
* 'field_path_elements'. This is used to get the original line related to
|
|
303
|
+
* this GoogleAdsError.
|
|
304
|
+
* @private
|
|
305
|
+
*/
|
|
306
|
+
updateBatchResultWithError_(batchResult, error, lines, fieldPathIndex) {
|
|
307
|
+
batchResult.result = false;
|
|
308
|
+
if (error.errors) { //GoogleAdsFailure
|
|
309
|
+
this.extraFailedLines_(batchResult, [error], lines, fieldPathIndex);
|
|
310
|
+
} else {
|
|
311
|
+
batchResult.errors = [error.message || error.toString()];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Extras failed lines based on the GoogleAdsFailures.
|
|
317
|
+
*
|
|
318
|
+
* Different errors have different 'fieldPathIndex' which is the index of
|
|
319
|
+
* failed lines in original input data (an array of a string).
|
|
320
|
+
*
|
|
321
|
+
* For conversions, the ErrorLocation is like:
|
|
322
|
+
* ErrorLocation {
|
|
323
|
+
* field_path_elements: [
|
|
324
|
+
* FieldPathElement { field_name: 'operations', index: 0 },
|
|
325
|
+
* FieldPathElement { field_name: 'create' }
|
|
326
|
+
* ]
|
|
327
|
+
* }
|
|
328
|
+
* So the index is 0, index of 'operations'.
|
|
329
|
+
*
|
|
330
|
+
* For customer match upload, the ErrorLocation is like:
|
|
331
|
+
* ErrorLocation {
|
|
332
|
+
* field_path_elements: [
|
|
333
|
+
* FieldPathElement { field_name: 'operations', index: 0 },
|
|
334
|
+
* FieldPathElement { field_name: 'create' },
|
|
335
|
+
* FieldPathElement { field_name: 'user_identifiers', index: 0 },
|
|
336
|
+
* FieldPathElement { field_name: 'hashed_email' }
|
|
337
|
+
* ]
|
|
338
|
+
* }
|
|
339
|
+
* The index should be 2, index of 'user_identifiers'.
|
|
340
|
+
*
|
|
341
|
+
* With this we can get errors and failed lines. The function will set
|
|
342
|
+
* following for the given BatchResult object:
|
|
343
|
+
* result - false
|
|
344
|
+
* errors - de-duplicated error reasons
|
|
345
|
+
* failedLines - failed lines, an array of string. Without the reason of
|
|
346
|
+
* failure.
|
|
347
|
+
* groupedFailed - a hashmap of failed the lines. The key is the reason, the
|
|
348
|
+
* value is the array of failed lines due to this reason.
|
|
349
|
+
* TODO: Confirm how to surface and handle groupedFailed.
|
|
350
|
+
* @param {!BatchResult} batchResult
|
|
351
|
+
* @param {!Array<!GoogleAdsFailure>} failures
|
|
352
|
+
* @param {!Array<string>} lines The original input data.
|
|
353
|
+
* @param {number} fieldPathIndex The index of 'FieldPathElement' in the array
|
|
354
|
+
* 'field_path_elements'. This is used to get the original line related to
|
|
355
|
+
* this GoogleAdsError.
|
|
356
|
+
* @private
|
|
357
|
+
*/
|
|
358
|
+
extraFailedLines_(batchResult, failures, lines, fieldPathIndex) {
|
|
359
|
+
batchResult.result = false;
|
|
360
|
+
batchResult.failedLines = [];
|
|
361
|
+
batchResult.groupedFailed = {};
|
|
362
|
+
const errors = new Set();
|
|
363
|
+
failures.forEach((failure) => {
|
|
364
|
+
failure.errors.forEach(({message, location}) => {
|
|
365
|
+
errors.add(message);
|
|
366
|
+
if (location && location.field_path_elements[fieldPathIndex]) {
|
|
367
|
+
const {index} = location.field_path_elements[fieldPathIndex];
|
|
368
|
+
if (typeof index === 'undefined') {
|
|
369
|
+
this.logger.warn(`Unknown field path index: ${fieldPathIndex}`,
|
|
370
|
+
location.field_path_elements);
|
|
371
|
+
} else {
|
|
372
|
+
const groupedFailed = batchResult.groupedFailed[message] || [];
|
|
373
|
+
const failedLine = lines[index];
|
|
374
|
+
batchResult.failedLines.push(failedLine);
|
|
375
|
+
groupedFailed.push(failedLine);
|
|
376
|
+
if (groupedFailed.length === 1) {
|
|
377
|
+
batchResult.groupedFailed[message] = groupedFailed;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
batchResult.errors = Array.from(errors);
|
|
384
|
+
}
|
|
385
|
+
|
|
228
386
|
/**
|
|
229
387
|
* Uploads click conversions to google ads account.
|
|
230
388
|
* It requires an array of click conversions and customer id.
|
|
@@ -232,30 +390,18 @@ class GoogleAds {
|
|
|
232
390
|
* @param {Array<ClickConversionConfig>} clickConversions ClickConversions
|
|
233
391
|
* @param {string} customerId
|
|
234
392
|
* @param {string} loginCustomerId Login customer account ID (Mcc Account id).
|
|
235
|
-
* @return {!Promise
|
|
393
|
+
* @return {!Promise<!UploadClickConversionsResponse>}
|
|
236
394
|
*/
|
|
237
|
-
|
|
395
|
+
uploadClickConversions(clickConversions, customerId, loginCustomerId) {
|
|
238
396
|
this.logger.debug('Upload click conversions for customerId:', customerId);
|
|
239
397
|
const customer = this.getGoogleAdsApiCustomer_(loginCustomerId, customerId);
|
|
240
398
|
const request = new UploadClickConversionsRequest({
|
|
241
399
|
conversions: clickConversions,
|
|
242
400
|
customer_id: customerId,
|
|
243
|
-
validate_only: this.
|
|
401
|
+
validate_only: this.debugMode, // when true makes no changes
|
|
244
402
|
partial_failure: true, // Will still create the non-failed entities
|
|
245
403
|
});
|
|
246
|
-
|
|
247
|
-
request);
|
|
248
|
-
const {results, partial_failure_error: failed} = result;
|
|
249
|
-
const response = results.map((conversion) => conversion.gclid);
|
|
250
|
-
this.logger.debug('Uploaded gclids:', response);
|
|
251
|
-
// const failed = result.partial_failure_error;
|
|
252
|
-
// Note: the response is different from previous version. current 'message'
|
|
253
|
-
// only contains partial failed conversions. The other field 'details'
|
|
254
|
-
// contains more information with the type of Array<Buffer>.
|
|
255
|
-
if (failed) {
|
|
256
|
-
this.logger.info('Errors:', failed.message);
|
|
257
|
-
}
|
|
258
|
-
return !failed;
|
|
404
|
+
return customer.conversionUploads.uploadClickConversions(request);
|
|
259
405
|
}
|
|
260
406
|
|
|
261
407
|
/**
|
|
@@ -271,29 +417,36 @@ class GoogleAds {
|
|
|
271
417
|
* @param {!Array<string>} lines Data for single request. It should be
|
|
272
418
|
* guaranteed that it doesn't exceed quota limitation.
|
|
273
419
|
* @param {string} batchId The tag for log.
|
|
274
|
-
* @return {!Promise<
|
|
420
|
+
* @return {!Promise<BatchResult>}
|
|
275
421
|
*/
|
|
276
422
|
return async (lines, batchId) => {
|
|
277
423
|
/** @type {Array<CustomerMatchRecord>} */
|
|
278
424
|
const userIds = lines.map((line) => JSON.parse(line));
|
|
425
|
+
/** @const {BatchResult} */ const batchResult = {
|
|
426
|
+
result: true,
|
|
427
|
+
numberOfLines: lines.length,
|
|
428
|
+
};
|
|
279
429
|
try {
|
|
280
|
-
|
|
430
|
+
const response = await this.uploadUserDataToUserList(userIds,
|
|
281
431
|
customerMatchConfig);
|
|
432
|
+
this.logger.debug(`Customer Match upload batch[${batchId}]`, response);
|
|
433
|
+
return batchResult;
|
|
282
434
|
} catch (error) {
|
|
283
435
|
this.logger.error(
|
|
284
|
-
`Error in
|
|
285
|
-
|
|
436
|
+
`Error in Customer Match upload batch[${batchId}]`, error);
|
|
437
|
+
this.updateBatchResultWithError_(batchResult, error, lines, 2);
|
|
438
|
+
return batchResult;
|
|
286
439
|
}
|
|
287
440
|
}
|
|
288
441
|
}
|
|
289
442
|
|
|
290
443
|
/**
|
|
291
444
|
* Uploads a user data to a user list (aka customer match).
|
|
292
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
293
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
294
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
295
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
296
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
445
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataService
|
|
446
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
|
|
447
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserData
|
|
448
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
|
|
449
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/CustomerMatchUserListMetadata
|
|
297
450
|
* Please note: The UserDataService has a limit of 10 UserDataOperations
|
|
298
451
|
* and 100 user IDs per request
|
|
299
452
|
* @see https://developers.google.com/google-ads/api/docs/migration/user-data-service#rate_limits
|
|
@@ -302,7 +455,7 @@ class GoogleAds {
|
|
|
302
455
|
* customer_id, login_customer_id, list_id, list_type which can be one of the following
|
|
303
456
|
* hashed_email, hashed_phone_number, mobile_id, third_party_user_id or address_info and
|
|
304
457
|
* operation which can be either 'create' or 'remove'
|
|
305
|
-
* @return {!Promise<
|
|
458
|
+
* @return {!Promise<UploadUserDataResponse>}
|
|
306
459
|
*/
|
|
307
460
|
async uploadUserDataToUserList(customerMatchRecords, customerMatchConfig) {
|
|
308
461
|
const customerId = customerMatchConfig.customer_id.replace(/-/g, '');
|
|
@@ -323,16 +476,15 @@ class GoogleAds {
|
|
|
323
476
|
customer_match_user_list_metadata: metadata,
|
|
324
477
|
});
|
|
325
478
|
const response = await customer.userData.uploadUserData(request);
|
|
326
|
-
|
|
327
|
-
return true;
|
|
479
|
+
return response;
|
|
328
480
|
}
|
|
329
481
|
|
|
330
482
|
/**
|
|
331
483
|
* Builds a list of UserDataOperations.
|
|
332
484
|
* Since v6 you can set a user_attribute
|
|
333
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
334
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
335
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
485
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserData
|
|
486
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
|
|
487
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
|
|
336
488
|
* @param {string} operationType either 'create' or 'remove'
|
|
337
489
|
* @param {Array<CustomerMatchRecord>} customerMatchRecords userIds
|
|
338
490
|
* @param {string} userListType One of the following hashed_email, hashed_phone_number,
|
|
@@ -351,7 +503,7 @@ class GoogleAds {
|
|
|
351
503
|
|
|
352
504
|
/**
|
|
353
505
|
* Creates CustomerMatchUserListMetadata.
|
|
354
|
-
* @see https://developers.google.com/google-ads/api/reference/rpc/
|
|
506
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/CustomerMatchUserListMetadata
|
|
355
507
|
* @param {string} customerId part of the ResourceName to be mutated
|
|
356
508
|
* @param {string} userListId part of the ResourceName to be mutated
|
|
357
509
|
* @return {!CustomerMatchUserListMetadata}
|
package/src/apis/index.js
CHANGED
|
@@ -110,3 +110,13 @@ exports.adsdatahub = require('./ads_data_hub.js');
|
|
|
110
110
|
* }}
|
|
111
111
|
*/
|
|
112
112
|
exports.measurementprotocolga4 = require('./measurement_protocol_ga4.js');
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* APIs integration class for YouTube.
|
|
116
|
+
* @const {{
|
|
117
|
+
* YouTube:!YouTube,
|
|
118
|
+
* ListChannelsConfig: !ListChannelsConfig,
|
|
119
|
+
* ListVideosConfig: !ListVideosConfig,
|
|
120
|
+
* }}
|
|
121
|
+
*/
|
|
122
|
+
exports.youtube = require('./youtube.js');
|
|
@@ -69,18 +69,14 @@ class MeasurementProtocol {
|
|
|
69
69
|
* @return {!Promise<BatchResult>}
|
|
70
70
|
*/
|
|
71
71
|
return async (lines, batchId) => {
|
|
72
|
-
const payload =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
.join('&');
|
|
82
|
-
})
|
|
83
|
-
.join('\n');
|
|
72
|
+
const payload = lines.map((line) => {
|
|
73
|
+
const record = JSON.parse(line);
|
|
74
|
+
const hit = Object.assign({}, config, record);
|
|
75
|
+
return Object.keys(hit).map(
|
|
76
|
+
(key) => `${key}=${encodeURIComponent(hit[key])}`)
|
|
77
|
+
.join('&');
|
|
78
|
+
})
|
|
79
|
+
.join('\n');
|
|
84
80
|
// In debug mode, the path is fixed to '/debug/collect'.
|
|
85
81
|
const path = (this.debugMode) ? '/debug/collect' : '/batch';
|
|
86
82
|
const requestOptions = {
|
|
@@ -112,24 +108,48 @@ class MeasurementProtocol {
|
|
|
112
108
|
if (!this.debugMode) {
|
|
113
109
|
batchResult.result = true;
|
|
114
110
|
} else {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
response.data.hitParsingResult.forEach((result, index) => {
|
|
118
|
-
if (!result.valid) {
|
|
119
|
-
failedHits.push(lines[index]);
|
|
120
|
-
result.parserMessage.forEach(({description}) => {
|
|
121
|
-
failedReasons.add(description);
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
batchResult.result = failedHits.length === 0;
|
|
126
|
-
batchResult.failedLines = failedHits;
|
|
127
|
-
batchResult.errors = Array.from(failedReasons);
|
|
111
|
+
this.extraFailedLines_(batchResult, response.data.hitParsingResult,
|
|
112
|
+
lines);
|
|
128
113
|
}
|
|
129
114
|
return batchResult;
|
|
130
115
|
};
|
|
131
116
|
};
|
|
132
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Extras failed lines based on the hitParsingResult, see:
|
|
120
|
+
* https://developers.google.com/analytics/devguides/collection/protocol/v1/validating-hits
|
|
121
|
+
*
|
|
122
|
+
* Note, only in 'debug' mode, Google Analytics will return this part of data.
|
|
123
|
+
*
|
|
124
|
+
* @param {!BatchResult} batchResult
|
|
125
|
+
* @param {!Array<!Object>} hitParsingResults
|
|
126
|
+
* @param {!Array<string>} lines The original input data.
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
extraFailedLines_(batchResult, hitParsingResults, lines) {
|
|
130
|
+
batchResult.failedLines = [];
|
|
131
|
+
batchResult.groupedFailed = {};
|
|
132
|
+
const errors = new Set();
|
|
133
|
+
hitParsingResults.forEach((result, index) => {
|
|
134
|
+
if (!result.valid) {
|
|
135
|
+
const failedLine = lines[index];
|
|
136
|
+
batchResult.failedLines.push(failedLine);
|
|
137
|
+
result.parserMessage.forEach(({description: error, messageType}) => {
|
|
138
|
+
this.logger.info(`[${messageType}]: ${error} for ${failedLine}`);
|
|
139
|
+
if (messageType === 'ERROR') {
|
|
140
|
+
errors.add(error);
|
|
141
|
+
const groupedFailed = batchResult.groupedFailed[error] || [];
|
|
142
|
+
groupedFailed.push(failedLine);
|
|
143
|
+
if (groupedFailed.length === 1) {
|
|
144
|
+
batchResult.groupedFailed[error] = groupedFailed;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
batchResult.result = batchResult.failedLines.length === 0;
|
|
151
|
+
batchResult.errors = Array.from(errors);
|
|
152
|
+
}
|
|
133
153
|
}
|
|
134
154
|
|
|
135
155
|
module.exports = {MeasurementProtocol};
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
'use strict';
|
|
21
21
|
|
|
22
22
|
const {request} = require('gaxios');
|
|
23
|
+
const lodash = require('lodash');
|
|
23
24
|
const {
|
|
24
25
|
getLogger,
|
|
25
26
|
SendSingleBatch,
|
|
@@ -93,7 +94,11 @@ class MeasurementProtocolGA4 {
|
|
|
93
94
|
*/
|
|
94
95
|
return async (lines, batchId) => {
|
|
95
96
|
const line = lines[0]; // Each request contains one record only.
|
|
96
|
-
|
|
97
|
+
if (lines.length > 1) {
|
|
98
|
+
this.logger.warn(
|
|
99
|
+
"Only one line data expected. Will only send the first line.");
|
|
100
|
+
}
|
|
101
|
+
const hit = lodash.merge({}, config.requestBody, JSON.parse(line));
|
|
97
102
|
|
|
98
103
|
const requestOptions = {
|
|
99
104
|
method: 'POST',
|
|
@@ -135,6 +140,7 @@ class MeasurementProtocolGA4 {
|
|
|
135
140
|
batchResult.failedLines = lines;
|
|
136
141
|
batchResult.errors = response.data.validationMessages.map(
|
|
137
142
|
({description}) => description);
|
|
143
|
+
batchResult.groupedFailed = {[batchResult.errors.join()]: [lines[0]]};
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
return batchResult;
|
package/src/apis/spreadsheets.js
CHANGED
|
@@ -67,11 +67,13 @@ class Spreadsheets {
|
|
|
67
67
|
/**
|
|
68
68
|
* Init Spreadsheets API client.
|
|
69
69
|
* @param {string} spreadsheetId
|
|
70
|
+
* @param {!Object<string,string>=} env The environment object to hold env
|
|
71
|
+
* variables.
|
|
70
72
|
*/
|
|
71
|
-
constructor(spreadsheetId) {
|
|
73
|
+
constructor(spreadsheetId, env = process.env) {
|
|
72
74
|
/** @const {string} */
|
|
73
75
|
this.spreadsheetId = spreadsheetId;
|
|
74
|
-
const authClient = new AuthClient(API_SCOPES);
|
|
76
|
+
const authClient = new AuthClient(API_SCOPES, env);
|
|
75
77
|
const auth = authClient.getDefaultAuth();
|
|
76
78
|
/** @const {!!google.sheets} */
|
|
77
79
|
this.instance = google.sheets({
|
|
@@ -108,7 +110,6 @@ class Spreadsheets {
|
|
|
108
110
|
* see:
|
|
109
111
|
* https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear
|
|
110
112
|
* @param {string} sheetName Name of the Sheet.
|
|
111
|
-
* @return {!Promise<boolean>} Whether the operation succeeded.
|
|
112
113
|
*/
|
|
113
114
|
async clearSheet(sheetName) {
|
|
114
115
|
const request = {
|
|
@@ -119,10 +120,9 @@ class Spreadsheets {
|
|
|
119
120
|
const response = await this.instance.spreadsheets.values.clear(request);
|
|
120
121
|
const data = response.data;
|
|
121
122
|
this.logger.debug(`Clear sheet[${sheetName}}]: `, data);
|
|
122
|
-
return true;
|
|
123
123
|
} catch (error) {
|
|
124
|
-
this.logger.error(error);
|
|
125
|
-
|
|
124
|
+
this.logger.error(error.toString());
|
|
125
|
+
throw error;
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -162,7 +162,6 @@ class Spreadsheets {
|
|
|
162
162
|
* @param {string} sheetName Name of the Sheet.
|
|
163
163
|
* @param {number} targetRows Loaded data rows number.
|
|
164
164
|
* @param {number} targetColumns Loaded data columns number.
|
|
165
|
-
* @return {!Promise<boolean>} Whether the operation succeeded.
|
|
166
165
|
*/
|
|
167
166
|
async reshape(sheetName, targetRows, targetColumns) {
|
|
168
167
|
const request = /** @type{Params$Resource$Spreadsheets$Get} */ {
|
|
@@ -194,13 +193,12 @@ class Spreadsheets {
|
|
|
194
193
|
if (requests.resource.requests.length > 0) {
|
|
195
194
|
const {data} = await this.instance.spreadsheets.batchUpdate(requests);
|
|
196
195
|
this.logger.debug(`Reshape Sheet [${sheetName}]: `, data);
|
|
197
|
-
|
|
196
|
+
} else {
|
|
197
|
+
this.logger.debug('No need to reshape.');
|
|
198
198
|
}
|
|
199
|
-
this.logger.debug('No need to reshape.');
|
|
200
|
-
return true;
|
|
201
199
|
} catch (error) {
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
this.logger.error(error.toString());
|
|
201
|
+
throw error;
|
|
204
202
|
}
|
|
205
203
|
}
|
|
206
204
|
|
|
@@ -218,7 +216,9 @@ class Spreadsheets {
|
|
|
218
216
|
spreadsheetId: this.spreadsheetId,
|
|
219
217
|
resource: {requests: [{pasteData}]},
|
|
220
218
|
};
|
|
221
|
-
/** @type {BatchResult} */ const batchResult = {
|
|
219
|
+
/** @type {BatchResult} */ const batchResult = {
|
|
220
|
+
numberOfLines: data.trim().split('\n').length,
|
|
221
|
+
};
|
|
222
222
|
try {
|
|
223
223
|
const response = await this.instance.spreadsheets.batchUpdate(request);
|
|
224
224
|
const data = response.data;
|