@google-cloud/nodejs-common 2.0.16-beta → 2.1.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.
@@ -19,9 +19,8 @@
19
19
 
20
20
  'use strict';
21
21
 
22
- const {google} = require('googleapis');
23
22
  const {request} = require('gaxios');
24
- const AuthClient = require('./auth_client.js');
23
+ const { GoogleApiClient } = require('./base/google_api_client.js');
25
24
  const {
26
25
  getLogger,
27
26
  SendSingleBatch,
@@ -118,7 +117,7 @@ let ReportRequest;
118
117
  * Quota limits, see:
119
118
  * https://support.google.com/adsihc/answer/6346075?hl=en
120
119
  */
121
- class DoubleClickSearch {
120
+ class DoubleClickSearch extends GoogleApiClient {
122
121
 
123
122
  /**
124
123
  * @constructor
@@ -126,34 +125,19 @@ class DoubleClickSearch {
126
125
  * variables.
127
126
  */
128
127
  constructor(env = process.env) {
129
- this.authClient = new AuthClient(API_SCOPES, env);
128
+ super(env);
129
+ this.googleApi = 'doubleclicksearch';
130
130
  this.logger = getLogger('API.DS');
131
131
  }
132
132
 
133
- /**
134
- * Prepares the Google SA360 instance.
135
- * @return {!google.doubleclicksearch}
136
- * @private
137
- */
138
- async getApiClient_() {
139
- if (this.doubleclicksearch) return this.doubleclicksearch;
140
- this.logger.debug(`Initialized ${this.constructor.name} instance.`);
141
- this.doubleclicksearch = google.doubleclicksearch({
142
- version: API_VERSION,
143
- auth: await this.getAuth_(),
144
- });
145
- return this.doubleclicksearch;
133
+ /** @override */
134
+ getScope() {
135
+ return API_SCOPES;
146
136
  }
147
137
 
148
- /**
149
- * Gets the auth object.
150
- * @return {!Promise<{!OAuth2Client|!JWT|!Compute}>}
151
- */
152
- async getAuth_() {
153
- if (this.auth) return this.auth;
154
- await this.authClient.prepareCredentials();
155
- this.auth = this.authClient.getDefaultAuth();
156
- return this.auth;
138
+ /** @override */
139
+ getVersion() {
140
+ return API_VERSION;
157
141
  }
158
142
 
159
143
  /**
@@ -172,7 +156,7 @@ class DoubleClickSearch {
172
156
  });
173
157
  this.logger.debug('Sending out availabilities', availabilities);
174
158
  try {
175
- const doubleclicksearch = await this.getApiClient_();
159
+ const doubleclicksearch = await this.getApiClient();
176
160
  const response = await doubleclicksearch.conversion.updateAvailability(
177
161
  {requestBody: {availabilities}});
178
162
  this.logger.debug('Get response: ', response);
@@ -221,7 +205,7 @@ class DoubleClickSearch {
221
205
  numberOfLines: lines.length,
222
206
  };
223
207
  try {
224
- const doubleclicksearch = await this.getApiClient_();
208
+ const doubleclicksearch = await this.getApiClient();
225
209
  const response = await doubleclicksearch.conversion.insert(
226
210
  {requestBody: {conversion: conversions}}
227
211
  );
@@ -322,7 +306,7 @@ class DoubleClickSearch {
322
306
  * @return {!Promise<string>}
323
307
  */
324
308
  async requestReports(requestBody) {
325
- const doubleclicksearch = await this.getApiClient_();
309
+ const doubleclicksearch = await this.getApiClient();
326
310
  const { status, data } = await doubleclicksearch.reports.request({ requestBody });
327
311
  if (status >= 200 && status < 300) {
328
312
  return data.id;
@@ -341,7 +325,7 @@ class DoubleClickSearch {
341
325
  * }>>}
342
326
  */
343
327
  async getReportUrls(reportId) {
344
- const doubleclicksearch = await this.getApiClient_();
328
+ const doubleclicksearch = await this.getApiClient();
345
329
  const { status, data } = await doubleclicksearch.reports.get({ reportId });
346
330
  switch (status) {
347
331
  case 200:
@@ -367,11 +351,11 @@ class DoubleClickSearch {
367
351
  * @return {!Promise<string>}
368
352
  */
369
353
  async getReportFile(reportId, reportFragment) {
370
- const doubleclicksearch = await this.getApiClient_();
354
+ const doubleclicksearch = await this.getApiClient();
371
355
  const response = await doubleclicksearch.reports.getFile(
372
356
  {reportId, reportFragment});
373
357
  if (response.status === 200) {
374
- return Buffer.from(await response.data.arrayBuffer()).toString();
358
+ return response.data;
375
359
  }
376
360
  const errorMsg =
377
361
  `Error in get file from reports: ${reportFragment}@${reportId}`;
@@ -387,7 +371,7 @@ class DoubleClickSearch {
387
371
  * @return {!Promise<ReadableStream>}
388
372
  */
389
373
  async getReportFileStream(url) {
390
- const auth = await this.getAuth_();
374
+ const auth = await this.getAuth();
391
375
  const headers = await auth.getRequestHeaders();
392
376
  const response = await request({
393
377
  method: 'GET',
@@ -22,7 +22,7 @@
22
22
 
23
23
  const { Transform } = require('stream');
24
24
  const lodash = require('lodash');
25
-
25
+ const { request: gaxiosRequest } = require('gaxios');
26
26
  const {
27
27
  ConversionAdjustmentUploadServiceClient,
28
28
  ConversionUploadServiceClient,
@@ -33,6 +33,8 @@ const {
33
33
  UserListServiceClient,
34
34
  protos: { google: { ads: { googleads } } },
35
35
  } = require('google-ads-nodejs-client');
36
+
37
+ const API_VERSION = Object.keys(googleads)[0];
36
38
  const {
37
39
  common: {
38
40
  Consent,
@@ -88,7 +90,7 @@ const {
88
90
  UserListTypeEnum: { UserListType },
89
91
  CustomerMatchUploadKeyTypeEnum: { CustomerMatchUploadKeyType },
90
92
  },
91
- } = googleads.v16;
93
+ } = googleads[API_VERSION];
92
94
 
93
95
  const AuthClient = require('./auth_client.js');
94
96
  const {
@@ -99,10 +101,12 @@ const {
99
101
  changeObjectNamingFromSnakeToLowerCamel,
100
102
  changeObjectNamingFromLowerCamelToSnake,
101
103
  } = require('../components/utils.js');
104
+ const { getCleanCid, RestSearchStreamTransform }
105
+ = require('./base/ads_api_common.js');
102
106
 
103
107
  /** @type {!ReadonlyArray<string>} */
104
108
  const API_SCOPES = Object.freeze(['https://www.googleapis.com/auth/adwords']);
105
-
109
+ const API_ENDPOINT = 'https://googleads.googleapis.com';
106
110
  /**
107
111
  * List of properties that will be taken from the data file as elements of a
108
112
  * conversion or a conversion adjustment.
@@ -257,6 +261,9 @@ let ConversionConfig;
257
261
  * If audience listId is not present, 'listName' and 'uploadKeyType' need to
258
262
  * be there so they can be used to create a customer match user list.
259
263
  * operation must be one of 'create' or 'remove'.
264
+ * `customerMatchUserListMetadata` offers a request level metadata, including
265
+ * 'consent'. While the top level 'consent', if presents, it will serve as the
266
+ * fallback value for each UserIdentifier.
260
267
  * Should not include `userIdentifierSource` based on:
261
268
  * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
262
269
  * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
@@ -267,6 +274,7 @@ let ConversionConfig;
267
274
  * listId: (string|undefined),
268
275
  * listName: (string|undefined),
269
276
  * uploadKeyType: ('CONTACT_INFO'|'CRM_ID'|'MOBILE_ADVERTISING_ID'|undefined),
277
+ * customerMatchUserListMetadata: (undefined|CustomerMatchUserListMetadata),
270
278
  * operation: ('create'|'remove'),
271
279
  * consent: (!Consent),
272
280
  * }}
@@ -284,6 +292,9 @@ let CustomerMatchConfig;
284
292
  * create a customer match user list.
285
293
  * For job type 'CUSTOMER_MATCH_WITH_ATTRIBUTES', 'user_attribute' can be used
286
294
  * to store shared additional user attributes.
295
+ * For job type 'CUSTOMER_MATCH_*', `customerMatchUserListMetadata` offers a job
296
+ * level metadata, including 'consent'. While the top level 'consent',
297
+ * if presents, it will serve as the fallback value for each UserIdentifier.
287
298
  * For job type 'STORE_SALES_UPLOAD_FIRST_PARTY', `storeSalesMetadata` is
288
299
  * required to offer StoreSalesMetadata. Besides that, for the store sales data,
289
300
  * common data (e.g. `currencyCode`, `conversionAction`) in
@@ -298,6 +309,7 @@ let CustomerMatchConfig;
298
309
  * operation: ('create'|'remove'),
299
310
  * type: !OfflineUserDataJobType,
300
311
  * storeSalesMetadata: (undefined|StoreSalesMetadata),
312
+ * customerMatchUserListMetadata: (undefined|CustomerMatchUserListMetadata),
301
313
  * transactionAttribute: (undefined|TransactionAttribute),
302
314
  * userAttribute: (undefined|UserAttribute),
303
315
  * userIdentifierSource: (!UserIdentifierSource|undefined),
@@ -356,7 +368,7 @@ class GoogleAdsApi {
356
368
  async getReport(customerId, loginCustomerId, query) {
357
369
  const request = new SearchGoogleAdsRequest({
358
370
  query,
359
- customerId: this.getCleanCid_(customerId),
371
+ customerId: getCleanCid(customerId),
360
372
  });
361
373
  return this.getReport_(request, loginCustomerId, true);
362
374
  }
@@ -382,7 +394,7 @@ class GoogleAdsApi {
382
394
  const request = new SearchGoogleAdsRequest(
383
395
  Object.assign({
384
396
  query,
385
- customerId: this.getCleanCid_(customerId),
397
+ customerId: getCleanCid(customerId),
386
398
  pageSize: 10000,
387
399
  }, options)
388
400
  );
@@ -413,7 +425,6 @@ class GoogleAdsApi {
413
425
  return result;
414
426
  }
415
427
 
416
- //TODO: Test a big report see how the event 'data' is triggered
417
428
  /**
418
429
  * Gets stream report of a given Customer account. The stream will send
419
430
  * `SearchGoogleAdsResponse` objects with the event 'data'.
@@ -426,7 +437,7 @@ class GoogleAdsApi {
426
437
  const client = await this.getGoogleAdsServiceClient_();
427
438
  const request = new SearchGoogleAdsRequest({
428
439
  query,
429
- customerId: this.getCleanCid_(customerId),
440
+ customerId: getCleanCid(customerId),
430
441
  });
431
442
  const callOptions = this.getCallOptions_(loginCustomerId);
432
443
  const response = await client.searchStream(request, callOptions);
@@ -441,11 +452,11 @@ class GoogleAdsApi {
441
452
  * @param {string} customerId
442
453
  * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
443
454
  * @param {string} query A Google Ads Query string.
444
- * @param {boolean} outputSnake Output JSON objects in snake_case.
445
- * @return {!Promise<string>}
455
+ * @param {boolean} snakeCase Output JSON objects in snake_case.
456
+ * @return {!Promise<stream>}
446
457
  */
447
458
  async cleanedStreamReport(customerId, loginCustomerId, query,
448
- outputSnake = false) {
459
+ snakeCase = false) {
449
460
  const cleanReportStream = new Transform({
450
461
  writableObjectMode: true,
451
462
  transform(chunk, encoding, callback) {
@@ -454,7 +465,7 @@ class GoogleAdsApi {
454
465
  return path.split('.').map(changeNamingFromSnakeToLowerCamel).join('.');
455
466
  });
456
467
  const extractor = extractObject(camelPaths);
457
- const results = outputSnake
468
+ const results = snakeCase
458
469
  ? chunk.results.map(extractor).map(changeObjectNamingFromLowerCamelToSnake)
459
470
  : chunk.results.map(extractor);
460
471
  // Add a line break after each chunk to keep files in proper format.
@@ -467,25 +478,69 @@ class GoogleAdsApi {
467
478
  .pipe(cleanReportStream);
468
479
  }
469
480
 
481
+ /**
482
+ * Gets the report stream through REST interface.
483
+ * @param {string} customerId
484
+ * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
485
+ * @param {string} query A Google Ads Query string.
486
+ * @return {!Promise<stream>}
487
+ */
488
+ async restStreamReport(customerId, loginCustomerId, query) {
489
+ await this.authClient.prepareCredentials();
490
+ const headers = Object.assign(
491
+ await this.authClient.getDefaultAuth().getRequestHeaders(),
492
+ this.getGoogleAdsHeaders_(loginCustomerId)
493
+ );
494
+ const options = {
495
+ baseURL: `${API_ENDPOINT}/${API_VERSION}/`,
496
+ url: `customers/${getCleanCid(customerId)}/googleAds:searchStream`,
497
+ headers,
498
+ data: { query },
499
+ method: 'POST',
500
+ responseType: 'stream',
501
+ };
502
+ const response = await gaxiosRequest(options);
503
+ return response.data;
504
+ }
505
+
506
+ /**
507
+ * Gets the report stream through REST interface.
508
+ * Based on the `fieldMask` in the response to filter out
509
+ * selected fields of the report and returns an array of JSON format strings
510
+ * with the delimit of a line breaker.
511
+ * @param {string} customerId
512
+ * @param {string} loginCustomerId Login customer account ID (Mcc Account id).
513
+ * @param {string} query A Google Ads Query string.
514
+ * @param {boolean} snakeCase Output JSON objects in snake_case.
515
+ * @return {!Promise<stream>}
516
+ */
517
+ async cleanedRestStreamReport(customerId, loginCustomerId, query,
518
+ snakeCase = false) {
519
+ const transform = new RestSearchStreamTransform(snakeCase);
520
+ const stream =
521
+ await this.restStreamReport(customerId, loginCustomerId, query);
522
+ return stream.on('error', (error) => transform.emit('error', error))
523
+ .pipe(transform);
524
+ }
525
+
470
526
  /**
471
527
  * Returns resources information from Google Ads API. see:
472
528
  * https://developers.google.com/google-ads/api/docs/concepts/field-service
473
529
  * Note, it looks like this function doesn't check the CID, just using
474
530
  * developer token and OAuth.
475
- * @param {string|number} loginCustomerId Login customer account ID.
476
531
  * @param {Array<string>} adFields Array of Ad fields.
477
532
  * @param {Array<string>} metadata Select fields, default values are:
478
533
  * name, data_type, is_repeated, type_url.
479
534
  * @return {!Promise<!Array<GoogleAdsField>>}
480
535
  */
481
- async searchMetaData(loginCustomerId, adFields, metadata = [
482
- 'name', 'data_type', 'is_repeated', 'type_url',]) {
536
+ async searchReportField(adFields,
537
+ metadata = ['name', 'data_type', 'is_repeated', 'type_url',]) {
483
538
  const client = await this.getGoogleAdsFieldServiceClient_();
484
539
  const selectClause = metadata.join(',');
485
540
  const fields = adFields.join('","');
486
541
  const query = `SELECT ${selectClause} WHERE name IN ("${fields}")`;
487
542
  const request = new SearchGoogleAdsFieldsRequest({ query });
488
- const callOptions = this.getCallOptions_(loginCustomerId);
543
+ const callOptions = this.getCallOptions_();
489
544
  const [results] = await client.searchGoogleAdsFields(request, callOptions);
490
545
  return results;
491
546
  }
@@ -569,8 +624,8 @@ class GoogleAdsApi {
569
624
  functionName, propertyForDebug) {
570
625
  /** @type {!ConversionConfig} */
571
626
  const adsConfig = this.getCamelConfig_(conversionConfig);
572
- adsConfig.customerId = this.getCleanCid_(customerId);
573
- adsConfig.loginCustomerId = this.getCleanCid_(loginCustomerId);
627
+ adsConfig.customerId = getCleanCid(customerId);
628
+ adsConfig.loginCustomerId = getCleanCid(loginCustomerId);
574
629
  /**
575
630
  * Sends a batch of hits to Google Ads API.
576
631
  * @param {!Array<string>} lines Data for single request. It should be
@@ -885,7 +940,7 @@ class GoogleAdsApi {
885
940
  SELECT user_list.id, user_list.resource_name
886
941
  FROM user_list
887
942
  WHERE user_list.name = '${listName}'
888
- AND customer.id = ${this.getCleanCid_(customerId)}
943
+ AND customer.id = ${getCleanCid(customerId)}
889
944
  AND user_list.type = CRM_BASED
890
945
  AND user_list.membership_status = OPEN
891
946
  AND user_list.crm_based_user_list.upload_key_type = ${uploadKeyType}
@@ -915,7 +970,7 @@ class GoogleAdsApi {
915
970
  crmBasedUserList: { uploadKeyType },
916
971
  });
917
972
  const request = new MutateUserListsRequest({
918
- customerId: this.getCleanCid_(customerId),
973
+ customerId: getCleanCid(customerId),
919
974
  operations: [{ create: userList }],
920
975
  validateOnly: this.debugMode, // when true makes no changes
921
976
  partialFailure: false, // Simplify error handling in creating userlist
@@ -1021,9 +1076,9 @@ class GoogleAdsApi {
1021
1076
  const operations = userDataList.map(
1022
1077
  (userData) => new UserDataOperation({ [operation]: new UserData(userData) })
1023
1078
  );
1024
- const metadata = this.buildCustomerMatchUserListMetadata_(customerId, listId);
1079
+ const metadata = this.buildCustomerMatchUserListMetadata_(config);
1025
1080
  const request = new UploadUserDataRequest({
1026
- customerId: this.getCleanCid_(customerId),
1081
+ customerId: getCleanCid(customerId),
1027
1082
  operations,
1028
1083
  customerMatchUserListMetadata: metadata,
1029
1084
  });
@@ -1035,16 +1090,20 @@ class GoogleAdsApi {
1035
1090
  /**
1036
1091
  * Creates CustomerMatchUserListMetadata.
1037
1092
  * @see https://developers.google.com/google-ads/api/reference/rpc/latest/CustomerMatchUserListMetadata
1038
- * @param {string} customerId part of the ResourceName to be mutated
1039
- * @param {string} userListId part of the ResourceName to be mutated
1093
+ * @param {{
1094
+ * customerId: string,
1095
+ * listId: string,
1096
+ * customerMatchUserListMetadata: undefined|!CustomerMatchUserListMetadata
1097
+ * }} config Configuration for CustomerMatchUserListMetadata
1040
1098
  * @return {!CustomerMatchUserListMetadata}
1041
1099
  * @private
1042
1100
  */
1043
- buildCustomerMatchUserListMetadata_(customerId, userListId) {
1044
- const resourceName = `customers/${customerId}/userLists/${userListId}`;
1045
- return new CustomerMatchUserListMetadata({
1101
+ buildCustomerMatchUserListMetadata_(config) {
1102
+ const { customerId, listId, customerMatchUserListMetadata } = config;
1103
+ const resourceName = `customers/${customerId}/userLists/${listId}`;
1104
+ return new CustomerMatchUserListMetadata(Object.assign({
1046
1105
  userList: resourceName,
1047
- });
1106
+ }, customerMatchUserListMetadata));
1048
1107
  }
1049
1108
 
1050
1109
  /**
@@ -1090,8 +1149,7 @@ class GoogleAdsApi {
1090
1149
  const jobData = { type };
1091
1150
  // https://developers.google.com/google-ads/api/rest/reference/rest/latest/OfflineUserDataJobs?hl=en#CustomerMatchUserListMetadata
1092
1151
  if (type.startsWith('CUSTOMER_MATCH')) {
1093
- const metadata = this.buildCustomerMatchUserListMetadata_(customerId,
1094
- listId);
1152
+ const metadata = this.buildCustomerMatchUserListMetadata_(config);
1095
1153
  jobData.customerMatchUserListMetadata = metadata;
1096
1154
  // https://developers.google.com/google-ads/api/rest/reference/rest/latest/OfflineUserDataJob?hl=en#StoreSalesMetadata
1097
1155
  } else if (type.startsWith('STORE_SALES')) {
@@ -1105,7 +1163,7 @@ class GoogleAdsApi {
1105
1163
  }
1106
1164
  const job = new OfflineUserDataJob(jobData);
1107
1165
  const request = new CreateOfflineUserDataJobRequest({
1108
- customerId: this.getCleanCid_(customerId),
1166
+ customerId: getCleanCid(customerId),
1109
1167
  job,
1110
1168
  validateOnly: this.debugMode, // when true makes no changes
1111
1169
  enableMatchRateRangePreview: true,
@@ -1258,16 +1316,6 @@ class GoogleAdsApi {
1258
1316
  }
1259
1317
  }
1260
1318
 
1261
- /**
1262
- * Returns a integer format CID by removing dashes.
1263
- * @param {string} cid
1264
- * @return {string}
1265
- * @private
1266
- */
1267
- getCleanCid_(cid) {
1268
- return cid.toString().replace(/-/g, '');
1269
- }
1270
-
1271
1319
  /**
1272
1320
  * Historically, we used a 3rd party library that adopted snake naming
1273
1321
  * convention as the protobuf files and API documents. However, the
@@ -1289,10 +1337,11 @@ class GoogleAdsApi {
1289
1337
  * @private
1290
1338
  */
1291
1339
  getGoogleAdsHeaders_(loginCustomerId) {
1292
- return {
1293
- "developer-token": this.developerToken,
1294
- "login-customer-id": loginCustomerId,
1295
- };
1340
+ const headers = { 'developer-token': this.developerToken };
1341
+ if (loginCustomerId) {
1342
+ headers['login-customer-id'] = getCleanCid(loginCustomerId);
1343
+ }
1344
+ return headers;
1296
1345
  }
1297
1346
 
1298
1347
  /**
@@ -1305,7 +1354,7 @@ class GoogleAdsApi {
1305
1354
  getCallOptions_(loginCustomerId) {
1306
1355
  return {
1307
1356
  otherArgs: {
1308
- headers: this.getGoogleAdsHeaders_(this.getCleanCid_(loginCustomerId)),
1357
+ headers: this.getGoogleAdsHeaders_(loginCustomerId),
1309
1358
  },
1310
1359
  };
1311
1360
  }
package/src/apis/index.js CHANGED
@@ -88,7 +88,6 @@ exports.searchads = require('./search_ads.js');
88
88
  /**
89
89
  * APIs integration class for DoubleClick BidManager (DV360).
90
90
  * @const {{
91
- * QueryResource:!QueryResource,
92
91
  * DoubleClickBidManager:!DoubleClickBidManager,
93
92
  * }}
94
93
  */