@google-cloud/nodejs-common 0.9.9-alpha → 1.0.3-alpha

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.
@@ -443,7 +443,7 @@ select_functions_location() {
443
443
  "name~${PROJECT_NAMESPACE}" --format="csv[no-heading](name,REGION)"))
444
444
  if [[ ${#exist_functions[@]} -gt 0 ]]; then
445
445
  local exist_region
446
- exist_region=$(printf "${exist_functions[0]}" | cut -d, -f2 | uniq)
446
+ exist_region=$(printf "${exist_functions[0]}" | cut -d\, -f2 | uniq)
447
447
  printf '%s\n' "Current application has already been installed in region: \
448
448
  ${exist_region}."
449
449
  local i
@@ -466,9 +466,9 @@ Functions for that region. Do you want to continue? [N/y]: "
466
466
  if [[ ${confirm_delete} == "Y" || ${confirm_delete} == "y" ]]; then
467
467
  for i in "${!exist_functions[@]}"; do
468
468
  local exist_function
469
- exist_function=$(printf "${exist_functions[$i]}" | cut -d, -f1)
469
+ exist_function=$(printf "${exist_functions[$i]}" | cut -d\, -f1)
470
470
  local function_region
471
- function_region=$(printf "${exist_functions[$i]}" | cut -d, -f2)
471
+ function_region=$(printf "${exist_functions[$i]}" | cut -d\, -f2)
472
472
  gcloud functions delete --region="${function_region}" \
473
473
  "${exist_function}"
474
474
  done
@@ -1818,31 +1818,66 @@ get_cloud_functions_service_account() {
1818
1818
  }
1819
1819
 
1820
1820
  #######################################
1821
- # Make sure Firestore or Datastore is in the current project.
1822
- # To create the Firestore, the operator need to have following permissions:
1823
- # appengine.applications.create - role: Owner
1824
- # datastore.locations.list - sample role: Cloud Datastore Owner
1825
- # servicemanagement.services.bind - sample role: Editor
1821
+ # Make sure the Firestore database is in the current project. If there is no
1822
+ # Firestore datastore, it will help to create one.
1823
+ # To create the Firestore, the operator need to be the Owner.
1826
1824
  # Globals:
1827
1825
  # GCP_PROJECT
1828
1826
  # Arguments:
1829
- # None.
1827
+ # Firestore mode, 'native' or 'datastore'.
1828
+ # Firestore region, it's not the same list as Cloud Functions regions and it
1829
+ # will be bonded to this Cloud project after created.
1830
1830
  #######################################
1831
1831
  check_firestore_existence() {
1832
- gcloud firestore indexes fields list >/dev/null 2>&1
1833
- while [[ $? -gt 0 ]]; do
1834
- cat <<EOF
1835
- Cannot find Firestore or Datastore in current project. Please visit \
1836
- https://console.cloud.google.com/firestore?project=${GCP_PROJECT} to create a \
1837
- database before continue.
1838
-
1839
- Press any key to continue after you create the database...
1840
- EOF
1832
+ local firestore mode appRegion
1833
+ mode="${1}"
1834
+ appRegion="${2}"
1835
+ firestore=$(gcloud app describe --format="csv[no-heading](databaseType)")
1836
+ if [[ -z "${firestore}" ]]; then
1837
+ printf '%s\n' "Firestore is not ready. Creating a new Firestore database\
1838
+ is an irreversible operation, so read carefully before continue:"
1839
+ printf '%s\n' " 1. You need to be the owner of ${GCP_PROJECT} to continue."
1840
+ printf '%s\n' " 2. Once you select the region and mode, you cannot change it."
1841
+ printf '%s\n' "Press any key to continue..."
1841
1842
  local any
1842
1843
  read -n1 -s any
1843
- printf '\n'
1844
- gcloud firestore indexes fields list >/dev/null 2>&1
1845
- done
1844
+ if [[ -z "${mode}" ]]; then
1845
+ printf '%s\n' " For more information about mode, see \
1846
+ https://cloud.google.com/firestore/docs/firestore-or-datastore#choosing_a_database_mode"
1847
+ fi
1848
+ while [[ -z "${mode}" ]]; do
1849
+ printf '%s' " Enter the mode of your dataset [Native]: "
1850
+ local selectMode
1851
+ read -r selectMode
1852
+ selectMode=$(printf '%s' "${selectMode:-"Native"}" | \
1853
+ tr '[:upper:]' '[:lower:]')
1854
+ if [[ ${selectMode} == 'native' || ${selectMode} == 'datastore' ]]; then
1855
+ mode=${selectMode}
1856
+ fi
1857
+ done
1858
+ printf '%s\n' "Creating Firestore database in ${mode} mode..."
1859
+ gcloud app create --region=${appRegion}
1860
+ if [[ $? -eq 0 ]]; then
1861
+ if [[ "${mode}" == "native" ]]; then
1862
+ appRegion=$(gcloud app describe --format="csv[no-heading](locationId)")
1863
+ gcloud firestore databases create --region="${appRegion}"
1864
+ fi
1865
+ else
1866
+ return 1
1867
+ fi
1868
+ else
1869
+ printf '%s\n' "OK. Firestore is ready in mode ${firestore}."
1870
+ fi
1871
+ }
1872
+
1873
+ #######################################
1874
+ # Installation step for confirming Firestore is ready.
1875
+ # See function check_firestore_existence.
1876
+ #######################################
1877
+ confirm_firestore() {
1878
+ (( STEP += 1 ))
1879
+ printf '%s\n' "Step ${STEP}: Checking the status of Firestore..."
1880
+ check_firestore_existence "$@"
1846
1881
  }
1847
1882
 
1848
1883
  #######################################
@@ -1875,3 +1910,27 @@ join_string_array() {
1875
1910
  shift
1876
1911
  printf %s "$first" "${@/#/$separator}"
1877
1912
  }
1913
+
1914
+ #######################################
1915
+ # Creates or updates the BigQuery view.
1916
+ # Globals:
1917
+ # GCP_PROJECT
1918
+ # DATASET
1919
+ # Arguments:
1920
+ # The name of view.
1921
+ # The query of view.
1922
+ #######################################
1923
+ create_or_update_view() {
1924
+ local viewName viewQuery
1925
+ viewName="${1}"
1926
+ viewQuery="${2}"
1927
+ local action="mk"
1928
+ if [[ $(check_existence_in_bigquery "${DATASET}.${viewName}") -eq 0 ]]; then
1929
+ action="update"
1930
+ fi
1931
+ bq "${action}" \
1932
+ --use_legacy_sql=false \
1933
+ --view "${viewQuery}" \
1934
+ --project_id ${GCP_PROJECT} \
1935
+ "${DATASET}.${viewName}"
1936
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@google-cloud/nodejs-common",
3
- "version": "0.9.9-alpha",
3
+ "version": "1.0.3-alpha",
4
4
  "description": "A NodeJs common library for solutions based on Cloud Functions",
5
5
  "author": "Google Inc.",
6
6
  "license": "Apache-2.0",
@@ -80,6 +80,13 @@ const IDENTIFIERS = [
80
80
  'address_info',
81
81
  ];
82
82
 
83
+ /**
84
+ * Maximum number of user identifiers in single UserData.
85
+ * @see https://ads-developers.googleblog.com/2021/10/userdata-enforcement-in-google-ads-api.html
86
+ * @type {number}
87
+ */
88
+ const MAX_IDENTIFIERS_PER_USER = 20;
89
+
83
90
  /**
84
91
  * Configuration for uploading click conversions for Google Ads, includes:
85
92
  * gclid, conversion_action, conversion_date_time, conversion_value,
@@ -139,15 +146,11 @@ let CustomerMatchConfig;
139
146
  * hashed_email, hashed_phone_number, mobile_id, third_party_user_id or address_info
140
147
  * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
141
148
  * @typedef {{
142
- * hashed_email: string,
143
- * }|{
144
- * hashed_phone_number: string,
145
- * }|{
146
- * mobile_id: string,
147
- * }|{
148
- * third_party_user_id: string,
149
- * }|{
150
- * address_info: GoogleAdsApi.OfflineUserAddressInfo,
149
+ * hashed_email: (string|Array<string>|undefined),
150
+ * hashed_phone_number: (string|Array<string>|undefined),
151
+ * mobile_id: (string|Array<string>|undefined),
152
+ * third_party_user_id: (string|Array<string>|undefined),
153
+ * address_info: (GoogleAdsApi.OfflineUserAddressInfo|undefined),
151
154
  * }}
152
155
  */
153
156
  let CustomerMatchRecord;
@@ -530,18 +533,17 @@ class GoogleAds {
530
533
  const customerId = customerMatchConfig.customer_id.replace(/-/g, '');
531
534
  const loginCustomerId = customerMatchConfig.login_customer_id.replace(/-/g,
532
535
  '');
533
- const userListType = customerMatchConfig.list_type;
534
536
  const userListId = customerMatchConfig.list_id;
535
537
  const operation = customerMatchConfig.operation;
536
538
 
537
539
  const customer = this.getGoogleAdsApiCustomer_(loginCustomerId, customerId);
538
540
  const operationsList = this.buildOperationsList_(operation,
539
- customerMatchRecords, userListType);
541
+ customerMatchRecords);
540
542
  const metadata = this.buildCustomerMatchUserListMetadata_(customerId,
541
543
  userListId);
542
544
  const request = UploadUserDataRequest.create({
543
545
  customer_id: customerId,
544
- operations: [operationsList],
546
+ operations: operationsList,
545
547
  customer_match_user_list_metadata: metadata,
546
548
  });
547
549
  const response = await customer.userData.uploadUserData(request);
@@ -556,18 +558,36 @@ class GoogleAds {
556
558
  * @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
557
559
  * @param {string} operationType either 'create' or 'remove'
558
560
  * @param {Array<CustomerMatchRecord>} customerMatchRecords userIds
559
- * @param {string} userListType One of the following hashed_email, hashed_phone_number,
560
- * mobile_id, third_party_user_id or address_info
561
561
  * @return {Array<UserDataOperation>}
562
562
  * @private
563
563
  */
564
- buildOperationsList_(operationType, customerMatchRecords, userListType) {
565
- const userIdentifiers = customerMatchRecords.map((customerMatchRecord) => {
566
- return UserIdentifier.create(
567
- {[userListType]: customerMatchRecord[userListType]});
564
+ buildOperationsList_(operationType, customerMatchRecords) {
565
+ return customerMatchRecords.map((customerMatchRecord) => {
566
+ const userIdentifiers = [];
567
+ IDENTIFIERS.forEach((idType) => {
568
+ const idValue = customerMatchRecord[idType];
569
+ if (idValue) {
570
+ if (Array.isArray(idValue)) {
571
+ idValue.forEach((user) => {
572
+ userIdentifiers.push(UserIdentifier.create({[idType]: user}));
573
+ });
574
+ } else {
575
+ userIdentifiers.push(UserIdentifier.create({[idType]: idValue}));
576
+ }
577
+ }
578
+ });
579
+ let userData;
580
+ if (userIdentifiers.length <= MAX_IDENTIFIERS_PER_USER) {
581
+ userData = UserData.create({user_identifiers: userIdentifiers});
582
+ } else {
583
+ this.logger.warn(
584
+ `Too many user identifiers, will only send ${MAX_IDENTIFIERS_PER_USER}:`,
585
+ JSON.stringify(customerMatchRecord));
586
+ userData = UserData.create({user_identifiers: userIdentifiers}.slice(0,
587
+ MAX_IDENTIFIERS_PER_USER));
588
+ }
589
+ return UserDataOperation.create({[operationType]: userData});
568
590
  });
569
- const userData = UserData.create({user_identifiers: userIdentifiers});
570
- return UserDataOperation.create({[operationType]: userData});
571
591
  }
572
592
 
573
593
  /**
package/src/apis/index.js CHANGED
@@ -88,7 +88,7 @@ exports.bigquery = require('./bigquery.js');
88
88
  * APIs integration class for Google Ads.
89
89
  * @const {{
90
90
  * GoogleAds:!GoogleAds,
91
- * ClickConversionConfig:!ClickConversionConfig,
91
+ * ConversionConfig:!ConversionConfig,
92
92
  * CustomerMatchConfig: !CustomerMatchConfig,
93
93
  * CustomerMatchRecord: !CustomerMatchRecord,
94
94
  * ReportQueryConfig:!ReportQueryConfig,