@google-cloud/nodejs-common 1.1.1-beta → 1.3.0
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/apps_scripts.sh +209 -0
- package/bin/install_functions.sh +36 -14
- package/package.json +11 -12
- package/src/apis/google_ads.js +296 -54
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2022 Google Inc.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
# Const the folder name of Apps Script.
|
|
18
|
+
DEFAULT_APPS_SCRIPT_FOLDER="apps_script"
|
|
19
|
+
|
|
20
|
+
#######################################
|
|
21
|
+
# Clasp login.
|
|
22
|
+
# Globals:
|
|
23
|
+
# None
|
|
24
|
+
# Arguments:
|
|
25
|
+
# None
|
|
26
|
+
#######################################
|
|
27
|
+
clasp_login() {
|
|
28
|
+
while :; do
|
|
29
|
+
local claspLogin=$(clasp login --status)
|
|
30
|
+
if [[ "${claspLogin}" != "You are not logged in." ]]; then
|
|
31
|
+
printf '%s' "${claspLogin} Would you like to continue with it? [Y/n]"
|
|
32
|
+
local logout
|
|
33
|
+
read -r logout
|
|
34
|
+
logout=${logout:-"Y"}
|
|
35
|
+
if [[ ${logout} == "Y" || ${logout} == "y" ]]; then
|
|
36
|
+
break
|
|
37
|
+
else
|
|
38
|
+
clasp logout
|
|
39
|
+
fi
|
|
40
|
+
fi
|
|
41
|
+
clasp login --no-localhost
|
|
42
|
+
done
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#######################################
|
|
46
|
+
# Initialize a AppsScript project. Usually it involves following steps:
|
|
47
|
+
# 1. Create a AppsScript project within a new Google Sheet.
|
|
48
|
+
# 2. Prompt to update the Google Cloud Project number of the AppsScript project
|
|
49
|
+
# to enable external APIs for this AppsScript project.
|
|
50
|
+
# 3. Prompt to grant the access of Cloud Functions' default service account to
|
|
51
|
+
# this Google Sheet, so the Cloud Functions can query this Sheet later.
|
|
52
|
+
# 4. Initialize the Sheet based on requests.
|
|
53
|
+
# Globals:
|
|
54
|
+
# None
|
|
55
|
+
# Arguments:
|
|
56
|
+
# The Google Sheet name.
|
|
57
|
+
# The folder for Apps Script code, default value ${DEFAULT_APPS_SCRIPT_FOLDER}
|
|
58
|
+
#######################################
|
|
59
|
+
clasp_initialize() {
|
|
60
|
+
((STEP += 1))
|
|
61
|
+
printf '%s\n' "Step ${STEP}: Starting to create Google Sheets..."
|
|
62
|
+
local sheetName="${1}"
|
|
63
|
+
local apps_script_src="${2-"${DEFAULT_APPS_SCRIPT_FOLDER}"}"
|
|
64
|
+
clasp_login
|
|
65
|
+
while :; do
|
|
66
|
+
local claspStatus=$(
|
|
67
|
+
clasp status -P "${apps_script_src}" >/dev/null 2>&1
|
|
68
|
+
echo $?
|
|
69
|
+
)
|
|
70
|
+
if [[ $claspStatus -gt 0 ]]; then
|
|
71
|
+
clasp create --type sheets --title "${sheetName}" --rootDir "${apps_script_src}"
|
|
72
|
+
local createResult=$?
|
|
73
|
+
if [[ $createResult -gt 0 ]]; then
|
|
74
|
+
printf '%s' "Press any key to continue after you enable the Google \
|
|
75
|
+
Apps Script API: https://script.google.com/home/usersettings..."
|
|
76
|
+
local any
|
|
77
|
+
read -n1 -s any
|
|
78
|
+
printf '\n\n'
|
|
79
|
+
continue
|
|
80
|
+
fi
|
|
81
|
+
break
|
|
82
|
+
else
|
|
83
|
+
printf '%s' "AppsScript project exists. Would you like to continue with \
|
|
84
|
+
it? [Y/n]"
|
|
85
|
+
local useCurrent
|
|
86
|
+
read -r useCurrent
|
|
87
|
+
useCurrent=${useCurrent:-"Y"}
|
|
88
|
+
if [[ ${useCurrent} = "Y" || ${useCurrent} = "y" ]]; then
|
|
89
|
+
break
|
|
90
|
+
else
|
|
91
|
+
printf '%s' "Would you like to delete current AppsScript and create a \
|
|
92
|
+
new one? [N/y]"
|
|
93
|
+
local deleteCurrent
|
|
94
|
+
read -r deleteCurrent
|
|
95
|
+
deleteCurrent=${deleteCurrent:-"N"}
|
|
96
|
+
if [[ ${deleteCurrent} = "Y" || ${deleteCurrent} = "y" ]]; then
|
|
97
|
+
rm "${apps_script_src}/.clasp.json"
|
|
98
|
+
continue
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#######################################
|
|
106
|
+
# Copy GCP project configuration file to AppsScript codes as a constant named
|
|
107
|
+
# `GCP_CONFIG`.
|
|
108
|
+
# Globals:
|
|
109
|
+
# None
|
|
110
|
+
# Arguments:
|
|
111
|
+
# The folder for Apps Script code, default value ${DEFAULT_APPS_SCRIPT_FOLDER}
|
|
112
|
+
#######################################
|
|
113
|
+
generate_config_js_for_apps_script() {
|
|
114
|
+
local apps_script_src="${1-"${DEFAULT_APPS_SCRIPT_FOLDER}"}"
|
|
115
|
+
local generated_file="${apps_script_src}/.generated_config.js"
|
|
116
|
+
if [[ -f "${CONFIG_FILE}" ]]; then
|
|
117
|
+
echo '// Copyright 2022 Google Inc.
|
|
118
|
+
//
|
|
119
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
120
|
+
// you may not use this file except in compliance with the License.
|
|
121
|
+
// You may obtain a copy of the License at
|
|
122
|
+
//
|
|
123
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
124
|
+
//
|
|
125
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
126
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
127
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
128
|
+
// See the License for the specific language governing permissions and
|
|
129
|
+
// limitations under the License.
|
|
130
|
+
|
|
131
|
+
/** @fileoverview Auto-generated configuration file for Apps Script. */
|
|
132
|
+
'>"${generated_file}"
|
|
133
|
+
echo -n "const GCP_CONFIG = " >>"${generated_file}"
|
|
134
|
+
cat "${CONFIG_FILE}" >>"${generated_file}"
|
|
135
|
+
else
|
|
136
|
+
printf '%s\n' "Couldn't find ${CONFIG_FILE}."
|
|
137
|
+
fi
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#######################################
|
|
141
|
+
# Clasp pushes AppsScript codes.
|
|
142
|
+
# Globals:
|
|
143
|
+
# None
|
|
144
|
+
# Arguments:
|
|
145
|
+
# The folder for Apps Script code, default value ${DEFAULT_APPS_SCRIPT_FOLDER}
|
|
146
|
+
#######################################
|
|
147
|
+
clasp_push_codes() {
|
|
148
|
+
((STEP += 1))
|
|
149
|
+
printf '%s\n' "Step ${STEP}: Starting to push codes to the Google Sheets..."
|
|
150
|
+
local apps_script_src="${1-"${DEFAULT_APPS_SCRIPT_FOLDER}"}"
|
|
151
|
+
clasp status -P "${apps_script_src}" >>/dev/null
|
|
152
|
+
local project_status=$?
|
|
153
|
+
if [[ ${project_status} -gt 0 ]]; then
|
|
154
|
+
return ${project_status}
|
|
155
|
+
else
|
|
156
|
+
generate_config_js_for_apps_script "${apps_script_src}"
|
|
157
|
+
clasp push --force -P "${apps_script_src}"
|
|
158
|
+
fi
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#######################################
|
|
162
|
+
# Ask user to update the GCP number of this AppsScript.
|
|
163
|
+
# Globals:
|
|
164
|
+
# GCP_PROJECT
|
|
165
|
+
# Arguments:
|
|
166
|
+
# The folder for Apps Script code, default value ${DEFAULT_APPS_SCRIPT_FOLDER}
|
|
167
|
+
#######################################
|
|
168
|
+
clasp_update_project_number() {
|
|
169
|
+
((STEP += 1))
|
|
170
|
+
local projectNumber=$(get_project_number)
|
|
171
|
+
local apps_script_src="${1-"${DEFAULT_APPS_SCRIPT_FOLDER}"}"
|
|
172
|
+
printf '%s\n' "Step ${STEP}: Update Google Cloud Platform (GCP) Project for \
|
|
173
|
+
Apps Script."
|
|
174
|
+
printf '%s' " "
|
|
175
|
+
clasp open -P "${apps_script_src}"
|
|
176
|
+
printf '%s\n' " On the open tab of Apps Script, use 'Project \
|
|
177
|
+
Settings' to set the Google Cloud Platform (GCP) Project as: ${projectNumber}"
|
|
178
|
+
printf '%s' "Press any key to continue after you update the GCP number..."
|
|
179
|
+
local any
|
|
180
|
+
read -n1 -s any
|
|
181
|
+
printf '\n'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#######################################
|
|
185
|
+
# Ask user to grant the access to CF's default service account.
|
|
186
|
+
# Note: the target GCP needs to have OAuth consent screen.
|
|
187
|
+
# Globals:
|
|
188
|
+
# SHEET_URL
|
|
189
|
+
# Arguments:
|
|
190
|
+
# The folder for Apps Script code, default value ${DEFAULT_APPS_SCRIPT_FOLDER}
|
|
191
|
+
#######################################
|
|
192
|
+
grant_access_to_service_account() {
|
|
193
|
+
((STEP += 1))
|
|
194
|
+
local apps_script_src="${1-"${DEFAULT_APPS_SCRIPT_FOLDER}"}"
|
|
195
|
+
local defaultServiceAccount=$(get_cloud_functions_service_account \
|
|
196
|
+
"${PROJECT_NAMESPACE}_main")
|
|
197
|
+
local parentId=$(get_value_from_json_file "${apps_script_src}"/.clasp.json \
|
|
198
|
+
parentId|cut -d\" -f2)
|
|
199
|
+
printf '%s\n' "Step ${STEP}: Share the Google Sheet with ${SOLUTION_NAME}."
|
|
200
|
+
|
|
201
|
+
printf '%s\n' " Open Google Sheet: \
|
|
202
|
+
https://drive.google.com/open?id=${parentId}"
|
|
203
|
+
printf '%s\n' " Click 'Share' and grant the Viewer access to: \
|
|
204
|
+
${defaultServiceAccount}"
|
|
205
|
+
printf '%s' "Press any key to continue after you grant the access..."
|
|
206
|
+
local any
|
|
207
|
+
read -n1 -s any
|
|
208
|
+
printf '\n'
|
|
209
|
+
}
|
package/bin/install_functions.sh
CHANGED
|
@@ -384,6 +384,15 @@ solution installed [${GCP_PROJECT}]: "
|
|
|
384
384
|
local input result
|
|
385
385
|
read -r input
|
|
386
386
|
input=${input:-"${GCP_PROJECT}"}
|
|
387
|
+
printf '%s' "Checking billing status for [${input}]..."
|
|
388
|
+
result=$(gcloud beta billing projects describe "${input}" \
|
|
389
|
+
--format="csv[no-heading](billingEnabled)")
|
|
390
|
+
if [[ "${result}" != "True" && "${result}" != "true" ]]; then
|
|
391
|
+
printf '%s\n' " there is no billing account."
|
|
392
|
+
return 1
|
|
393
|
+
else
|
|
394
|
+
printf '%s\n' "succeeded."
|
|
395
|
+
fi
|
|
387
396
|
result=$(gcloud config set project "${input}" --user-output-enabled=false \
|
|
388
397
|
2>&1)
|
|
389
398
|
if [[ -z ${result} ]]; then
|
|
@@ -1043,13 +1052,24 @@ refresh the list:"
|
|
|
1043
1052
|
# Globals:
|
|
1044
1053
|
# GCP_PROJECT
|
|
1045
1054
|
# Arguments:
|
|
1046
|
-
# Bucket name var,
|
|
1055
|
+
# Bucket name var with optional usage, e.g. 'GCS_BUCKET_REPORT:reports'.
|
|
1056
|
+
# The default value is 'GCS_BUCKET'.
|
|
1047
1057
|
# Location var name, default value 'REGION'
|
|
1048
1058
|
# If the second location var is unset, use this var as default value
|
|
1049
1059
|
#######################################
|
|
1050
1060
|
confirm_located_bucket() {
|
|
1051
|
-
local gcsName defaultValue defaultBucketName locationName location
|
|
1052
|
-
|
|
1061
|
+
local gcsName usage defaultValue defaultBucketName locationName location
|
|
1062
|
+
if [[ -z "${1}" ]]; then
|
|
1063
|
+
gcsName="GCS_BUCKET"
|
|
1064
|
+
elif [[ "${1}" == *":"* ]]; then
|
|
1065
|
+
gcsName=$(echo "${1}" | cut -d\: -f1)
|
|
1066
|
+
usage=$(echo "${1}" | cut -d\: -f2)
|
|
1067
|
+
if [[ -n "${usage}" ]]; then
|
|
1068
|
+
usage=" for ${usage}"
|
|
1069
|
+
fi
|
|
1070
|
+
else
|
|
1071
|
+
gcsName="${1}"
|
|
1072
|
+
fi
|
|
1053
1073
|
locationName="${2:-"REGION"}"
|
|
1054
1074
|
defaultValue="${!gcsName}"
|
|
1055
1075
|
defaultBucketName=$(get_default_bucket_name "${GCP_PROJECT}")
|
|
@@ -1064,7 +1084,8 @@ confirm_located_bucket() {
|
|
|
1064
1084
|
|
|
1065
1085
|
(( STEP += 1 ))
|
|
1066
1086
|
if [[ -z "${location}" ]]; then
|
|
1067
|
-
printf '%s\n' "Step ${STEP}: Checking or creating a Cloud Storage
|
|
1087
|
+
printf '%s\n' "Step ${STEP}: Checking or creating a Cloud Storage \
|
|
1088
|
+
Bucket${usage}..."
|
|
1068
1089
|
if [[ "${locationName}" == "REGION" ]]; then
|
|
1069
1090
|
select_functions_location ${locationName}
|
|
1070
1091
|
else
|
|
@@ -1072,8 +1093,8 @@ confirm_located_bucket() {
|
|
|
1072
1093
|
fi
|
|
1073
1094
|
location="${!locationName}"
|
|
1074
1095
|
else
|
|
1075
|
-
printf '%s\n' "Step ${STEP}: Checking or creating a Cloud Storage
|
|
1076
|
-
location [${location}] ..."
|
|
1096
|
+
printf '%s\n' "Step ${STEP}: Checking or creating a Cloud Storage \
|
|
1097
|
+
Bucket${usage} in location [${location}] ..."
|
|
1077
1098
|
fi
|
|
1078
1099
|
declare -g "${locationName}=${location}"
|
|
1079
1100
|
while :; do
|
|
@@ -1474,20 +1495,18 @@ EOF
|
|
|
1474
1495
|
"client_id=${client_id}" "scope=${scope}")
|
|
1475
1496
|
local auth_url
|
|
1476
1497
|
auth_url="${OAUTH_BASE_URL}auth?${parameters}"
|
|
1477
|
-
printf '%s\n' "3. Open the link in browser and finish authentication
|
|
1478
|
-
${auth_url}"
|
|
1498
|
+
printf '%s\n' "3. Open the link in your browser and finish authentication. \
|
|
1499
|
+
Do not close the redirected page: ${auth_url}"
|
|
1479
1500
|
cat <<EOF
|
|
1480
1501
|
Note:
|
|
1502
|
+
The succeeded OAuth flow will land the browser on an error page - \
|
|
1503
|
+
"This site can't be reached". This is expected behavior. Copy the whole URL and continue.
|
|
1481
1504
|
If the OAuth client is not for a native application, there will be an \
|
|
1482
1505
|
"Error 400: redirect_uri_mismatch" shown up on the page. In this case, press \
|
|
1483
1506
|
"Enter" to start again with a native application OAuth client ID.
|
|
1484
|
-
If there is no local web server serving at ${REDIRECT_URI}, the \
|
|
1485
|
-
succeeded OAuth flow will land the browser on an error page ("This site can't \
|
|
1486
|
-
be reached"). This is an expected behavior. Copy the whole URL and continue.
|
|
1487
1507
|
|
|
1488
1508
|
EOF
|
|
1489
|
-
printf '%s' "4. Copy the
|
|
1490
|
-
and paste here: "
|
|
1509
|
+
printf '%s' "4. Copy the complete URL from your browser and paste here: "
|
|
1491
1510
|
read -r auth_code
|
|
1492
1511
|
if [[ -z ${auth_code} ]]; then
|
|
1493
1512
|
printf '%s\n\n' "No authorization code. Starting from beginning again..."
|
|
@@ -1679,7 +1698,9 @@ customized_install() {
|
|
|
1679
1698
|
local tasks=("$@")
|
|
1680
1699
|
local task
|
|
1681
1700
|
for task in "${tasks[@]}"; do
|
|
1682
|
-
|
|
1701
|
+
local cmd
|
|
1702
|
+
eval "cmd=(${task})"
|
|
1703
|
+
"${cmd[@]}"
|
|
1683
1704
|
quit_if_failed $?
|
|
1684
1705
|
done
|
|
1685
1706
|
}
|
|
@@ -1992,5 +2013,6 @@ join_string_array() {
|
|
|
1992
2013
|
_SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
1993
2014
|
source "${_SELF}/google_ads.sh"
|
|
1994
2015
|
source "${_SELF}/bigquery.sh"
|
|
2016
|
+
source "${_SELF}/apps_scripts.sh"
|
|
1995
2017
|
|
|
1996
2018
|
printf '%s\n' "Common Bash Library is loaded."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@google-cloud/nodejs-common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "A NodeJs common library for solutions based on Cloud Functions",
|
|
5
5
|
"author": "Google Inc.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -18,24 +18,23 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@google-cloud/aiplatform": "^1.19.0",
|
|
20
20
|
"@google-cloud/automl": "^2.5.2",
|
|
21
|
-
"@google-cloud/bigquery": "^
|
|
21
|
+
"@google-cloud/bigquery": "^6.0.0",
|
|
22
22
|
"@google-cloud/datastore": "^6.6.2",
|
|
23
23
|
"@google-cloud/firestore": "^5.0.2",
|
|
24
|
-
"@google-cloud/logging-winston": "^
|
|
25
|
-
"@google-cloud/pubsub": "^
|
|
26
|
-
"@google-cloud/storage": "^
|
|
27
|
-
"@google-cloud/scheduler": "^
|
|
28
|
-
"gaxios": "^
|
|
29
|
-
"google-ads-api": "^
|
|
30
|
-
"google-ads-node":"^
|
|
31
|
-
"google-auth-library": "^
|
|
24
|
+
"@google-cloud/logging-winston": "^5.1.0",
|
|
25
|
+
"@google-cloud/pubsub": "^3.0.1",
|
|
26
|
+
"@google-cloud/storage": "^6.0.1",
|
|
27
|
+
"@google-cloud/scheduler": "^3.0.0",
|
|
28
|
+
"gaxios": "^5.0.0",
|
|
29
|
+
"google-ads-api": "^11.0.0",
|
|
30
|
+
"google-ads-node": "^9.0.0",
|
|
31
|
+
"google-auth-library": "^8.0.2",
|
|
32
32
|
"googleapis": "^100.0.0",
|
|
33
|
-
"soap": "^0.43.0",
|
|
34
33
|
"winston": "^3.7.2",
|
|
35
34
|
"lodash": "^4.17.21"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
|
-
"jasmine": "^4.0
|
|
37
|
+
"jasmine": "^4.1.0"
|
|
39
38
|
},
|
|
40
39
|
"scripts": {
|
|
41
40
|
"test": "node node_modules/jasmine/bin/jasmine"
|
package/src/apis/google_ads.js
CHANGED
|
@@ -27,9 +27,15 @@ const {
|
|
|
27
27
|
},
|
|
28
28
|
resources: {
|
|
29
29
|
GoogleAdsField,
|
|
30
|
+
OfflineUserDataJob,
|
|
30
31
|
},
|
|
31
32
|
services: {
|
|
33
|
+
CreateOfflineUserDataJobRequest,
|
|
34
|
+
AddOfflineUserDataJobOperationsRequest,
|
|
35
|
+
RunOfflineUserDataJobRequest,
|
|
36
|
+
UploadCallConversionsRequest,
|
|
32
37
|
UploadClickConversionsRequest,
|
|
38
|
+
UploadCallConversionsResponse,
|
|
33
39
|
UploadClickConversionsResponse,
|
|
34
40
|
UploadConversionAdjustmentsRequest,
|
|
35
41
|
UploadConversionAdjustmentsResponse,
|
|
@@ -41,6 +47,10 @@ const {
|
|
|
41
47
|
errors: {
|
|
42
48
|
GoogleAdsFailure,
|
|
43
49
|
},
|
|
50
|
+
enums: {
|
|
51
|
+
OfflineUserDataJobTypeEnum: { OfflineUserDataJobType },
|
|
52
|
+
OfflineUserDataJobStatusEnum: { OfflineUserDataJobStatus },
|
|
53
|
+
},
|
|
44
54
|
} = googleAdsLib;
|
|
45
55
|
const {GoogleAdsApi} = require('google-ads-api');
|
|
46
56
|
const lodash = require('lodash');
|
|
@@ -55,6 +65,7 @@ const API_SCOPES = Object.freeze(['https://www.googleapis.com/auth/adwords',]);
|
|
|
55
65
|
* List of properties that will be taken from the data file as elements of a
|
|
56
66
|
* conversion or a conversion adjustment.
|
|
57
67
|
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/ClickConversion
|
|
68
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/CallConversion
|
|
58
69
|
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/ConversionAdjustment
|
|
59
70
|
* @type {Array<string>}
|
|
60
71
|
*/
|
|
@@ -63,6 +74,8 @@ const PICKED_PROPERTIES = [
|
|
|
63
74
|
'cart_data',
|
|
64
75
|
'user_identifiers',
|
|
65
76
|
'gclid',
|
|
77
|
+
'caller_id',
|
|
78
|
+
'call_start_date_time',
|
|
66
79
|
'conversion_action',
|
|
67
80
|
'conversion_date_time',
|
|
68
81
|
'conversion_value',
|
|
@@ -95,10 +108,11 @@ const IDENTIFIERS = [
|
|
|
95
108
|
const MAX_IDENTIFIERS_PER_USER = 20;
|
|
96
109
|
|
|
97
110
|
/**
|
|
98
|
-
* Configuration for uploading click conversions or conversion
|
|
99
|
-
* Google Ads, includes:
|
|
111
|
+
* Configuration for uploading click conversions, call converions or conversion
|
|
112
|
+
* adjustments for Google Ads, includes:
|
|
100
113
|
* gclid, conversion_action, conversion_date_time, conversion_value,
|
|
101
114
|
* currency_code, order_id, external_attribution_data,
|
|
115
|
+
* caller_id, call_start_date_time,
|
|
102
116
|
* adjustment_type, adjustment_date_time, user_agent, gclid_date_time_pair, etc.
|
|
103
117
|
* @see PICKED_PROPERTIES
|
|
104
118
|
*
|
|
@@ -119,7 +133,9 @@ const MAX_IDENTIFIERS_PER_USER = 20;
|
|
|
119
133
|
* @typedef {{
|
|
120
134
|
* external_attribution_data: (GoogleAdsApi.ExternalAttributionData|undefined),
|
|
121
135
|
* cart_data: (object|undefined),
|
|
122
|
-
* gclid: string,
|
|
136
|
+
* gclid: (string|undefined),
|
|
137
|
+
* caller_id: (string|undefined),
|
|
138
|
+
* call_start_date_time: (string|undefined),
|
|
123
139
|
* conversion_action: string,
|
|
124
140
|
* conversion_date_time: string,
|
|
125
141
|
* conversion_value: number,
|
|
@@ -138,7 +154,7 @@ let ConversionConfig;
|
|
|
138
154
|
/**
|
|
139
155
|
* Configuration for uploading customer match to Google Ads, includes:
|
|
140
156
|
* customer_id, login_customer_id, list_id and operation.
|
|
141
|
-
* operation must be one of the two: 'create' or 'remove'
|
|
157
|
+
* operation must be one of the two: 'create' or 'remove'.
|
|
142
158
|
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserDataOperation
|
|
143
159
|
* @typedef {{
|
|
144
160
|
* customer_id: string,
|
|
@@ -149,6 +165,24 @@ let ConversionConfig;
|
|
|
149
165
|
*/
|
|
150
166
|
let CustomerMatchConfig;
|
|
151
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Configuration for offline user data job, includes:
|
|
170
|
+
* customer_id, login_customer_id, list_id, operation and type.
|
|
171
|
+
* 'operation' should be one of the two: 'create' or 'remove',
|
|
172
|
+
* 'type' is OfflineUserDataJobType, it can be 'CUSTOMER_MATCH_USER_LIST' or
|
|
173
|
+
* 'STORE_SALES_UPLOAD_FIRST_PARTY'.
|
|
174
|
+
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/OfflineUserDataJob
|
|
175
|
+
* @typedef {{
|
|
176
|
+
* customer_id: string,
|
|
177
|
+
* login_customer_id: string,
|
|
178
|
+
* list_id: (undefined|string),
|
|
179
|
+
* operation: 'create'|'remove',
|
|
180
|
+
* type: !OfflineUserDataJobType,
|
|
181
|
+
* storeSalesMetadata: (undefined|object),
|
|
182
|
+
* }}
|
|
183
|
+
*/
|
|
184
|
+
let OfflineUserDataJobConfig;
|
|
185
|
+
|
|
152
186
|
/**
|
|
153
187
|
* Configuration for uploading customer match data for Google Ads.
|
|
154
188
|
* @see https://developers.google.com/google-ads/api/reference/rpc/latest/UserIdentifier
|
|
@@ -215,6 +249,19 @@ class GoogleAds {
|
|
|
215
249
|
this.logger.debug(`Init ${this.constructor.name} with Debug Mode.`);
|
|
216
250
|
}
|
|
217
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Gets a report synchronously from a given Customer account.
|
|
254
|
+
* The enum fields are present as index number.
|
|
255
|
+
* @param {string} customerId
|
|
256
|
+
* @param {string} loginCustomerId Login customer account ID (Mcc Account id).
|
|
257
|
+
* @param {!ReportQueryConfig} reportQueryConfig
|
|
258
|
+
* @return {!ReadableStream}
|
|
259
|
+
*/
|
|
260
|
+
async getReport(customerId, loginCustomerId, reportQueryConfig) {
|
|
261
|
+
const customer = this.getGoogleAdsApiCustomer_(loginCustomerId, customerId);
|
|
262
|
+
return customer.report(reportQueryConfig);
|
|
263
|
+
}
|
|
264
|
+
|
|
218
265
|
/**
|
|
219
266
|
* Gets report as generator of a given Customer account.
|
|
220
267
|
* @param {string} customerId
|
|
@@ -261,6 +308,19 @@ class GoogleAds {
|
|
|
261
308
|
.searchGoogleAdsFields(request);
|
|
262
309
|
return results;
|
|
263
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Returns the function to send out a request to Google Ads API with a batch
|
|
313
|
+
* of call conversions.
|
|
314
|
+
* @param {string} customerId
|
|
315
|
+
* @param {string} loginCustomerId Login customer account ID (Mcc Account id).
|
|
316
|
+
* @param {!ConversionConfig} adsConfig Default call conversion params
|
|
317
|
+
* @return {!SendSingleBatch} Function which can send a batch of hits to
|
|
318
|
+
* Google Ads API.
|
|
319
|
+
*/
|
|
320
|
+
getUploadCallConversionFn(customerId, loginCustomerId, adsConfig) {
|
|
321
|
+
return this.getUploadConversionFnBase_(customerId, loginCustomerId,
|
|
322
|
+
adsConfig, 'uploadCallConversions', 'caller_id');
|
|
323
|
+
}
|
|
264
324
|
|
|
265
325
|
/**
|
|
266
326
|
* Returns the function to send out a request to Google Ads API with a batch
|
|
@@ -271,45 +331,9 @@ class GoogleAds {
|
|
|
271
331
|
* @return {!SendSingleBatch} Function which can send a batch of hits to
|
|
272
332
|
* Google Ads API.
|
|
273
333
|
*/
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
* @param {!Array<string>} lines Data for single request. It should be
|
|
278
|
-
* guaranteed that it doesn't exceed quota limitation.
|
|
279
|
-
* @param {string} batchId The tag for log.
|
|
280
|
-
* @return {!BatchResult}
|
|
281
|
-
*/
|
|
282
|
-
return async (lines, batchId) => {
|
|
283
|
-
/** @type {!Array<ConversionConfig>} */
|
|
284
|
-
const conversions = lines.map(
|
|
285
|
-
(line) => buildClickConversionFromLine(line, adsConfig, customerId));
|
|
286
|
-
/** @const {BatchResult} */
|
|
287
|
-
const batchResult = {
|
|
288
|
-
result: true,
|
|
289
|
-
numberOfLines: lines.length,
|
|
290
|
-
};
|
|
291
|
-
try {
|
|
292
|
-
const response = await this.uploadClickConversions(conversions,
|
|
293
|
-
customerId, loginCustomerId);
|
|
294
|
-
const {results, partial_failure_error: failed} = response;
|
|
295
|
-
if (this.logger.isDebugEnabled()) {
|
|
296
|
-
const gclids = results.map((conversion) => conversion.gclid);
|
|
297
|
-
this.logger.debug('Uploaded gclids:', gclids);
|
|
298
|
-
}
|
|
299
|
-
if (failed) {
|
|
300
|
-
this.logger.info('partial_failure_error:', failed.message);
|
|
301
|
-
const failures = failed.details.map(
|
|
302
|
-
({value}) => GoogleAdsFailure.decode(value));
|
|
303
|
-
this.extraFailedLines_(batchResult, failures, lines, 0);
|
|
304
|
-
}
|
|
305
|
-
return batchResult;
|
|
306
|
-
} catch (error) {
|
|
307
|
-
this.logger.error(
|
|
308
|
-
`Error in upload conversions batch: ${batchId}`, error);
|
|
309
|
-
this.updateBatchResultWithError_(batchResult, error, lines, 0);
|
|
310
|
-
return batchResult;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
334
|
+
getUploadClickConversionFn(customerId, loginCustomerId, adsConfig) {
|
|
335
|
+
return this.getUploadConversionFnBase_(customerId, loginCustomerId,
|
|
336
|
+
adsConfig, 'uploadClickConversions', 'gclid');
|
|
313
337
|
}
|
|
314
338
|
|
|
315
339
|
/**
|
|
@@ -323,6 +347,26 @@ class GoogleAds {
|
|
|
323
347
|
* Google Ads API.
|
|
324
348
|
*/
|
|
325
349
|
getUploadConversionAdjustmentFn(customerId, loginCustomerId, adsConfig) {
|
|
350
|
+
return this.getUploadConversionFnBase_(customerId, loginCustomerId,
|
|
351
|
+
adsConfig, 'uploadConversionAdjustments', 'order_id');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Returns the function to send call conversions, click conversions or
|
|
356
|
+
* conversion adjustment (enhanced conversions).
|
|
357
|
+
* @param {string} customerId
|
|
358
|
+
* @param {string} loginCustomerId Login customer account ID (Mcc Account id).
|
|
359
|
+
* @param {!ConversionConfig} adsConfig Default click conversion params
|
|
360
|
+
* @param {string} functionName The name of sending converions function, could
|
|
361
|
+
* be `uploadClickConversions`, `uploadCallConversions` or
|
|
362
|
+
* `uploadConversionAdjustments`.
|
|
363
|
+
* @param {string} propertyForDebug The name of property for debug info.
|
|
364
|
+
* @return {!SendSingleBatch} Function which can send a batch of hits to
|
|
365
|
+
* Google Ads API.
|
|
366
|
+
* @private
|
|
367
|
+
*/
|
|
368
|
+
getUploadConversionFnBase_(customerId, loginCustomerId, adsConfig,
|
|
369
|
+
functionName, propertyForDebug) {
|
|
326
370
|
/**
|
|
327
371
|
* Sends a batch of hits to Google Ads API.
|
|
328
372
|
* @param {!Array<string>} lines Data for single request. It should be
|
|
@@ -333,30 +377,30 @@ class GoogleAds {
|
|
|
333
377
|
return async (lines, batchId) => {
|
|
334
378
|
/** @type {!Array<ConversionConfig>} */
|
|
335
379
|
const conversions = lines.map(
|
|
336
|
-
|
|
380
|
+
(line) => buildClickConversionFromLine(line, adsConfig, customerId));
|
|
337
381
|
/** @const {BatchResult} */
|
|
338
382
|
const batchResult = {
|
|
339
383
|
result: true,
|
|
340
384
|
numberOfLines: lines.length,
|
|
341
385
|
};
|
|
342
386
|
try {
|
|
343
|
-
const response = await this
|
|
344
|
-
|
|
345
|
-
const {results, partial_failure_error: failed} = response;
|
|
387
|
+
const response = await this[functionName](conversions, customerId,
|
|
388
|
+
loginCustomerId);
|
|
389
|
+
const { results, partial_failure_error: failed } = response;
|
|
346
390
|
if (this.logger.isDebugEnabled()) {
|
|
347
|
-
const
|
|
348
|
-
this.logger.debug(
|
|
391
|
+
const id = results.map((conversion) => conversion[propertyForDebug]);
|
|
392
|
+
this.logger.debug(`Uploaded ${propertyForDebug}:`, id);
|
|
349
393
|
}
|
|
350
394
|
if (failed) {
|
|
351
395
|
this.logger.info('partial_failure_error:', failed.message);
|
|
352
396
|
const failures = failed.details.map(
|
|
353
|
-
|
|
397
|
+
({ value }) => GoogleAdsFailure.decode(value));
|
|
354
398
|
this.extraFailedLines_(batchResult, failures, lines, 0);
|
|
355
399
|
}
|
|
356
400
|
return batchResult;
|
|
357
401
|
} catch (error) {
|
|
358
402
|
this.logger.error(
|
|
359
|
-
|
|
403
|
+
`Error in ${functionName} batch: ${batchId}`, error);
|
|
360
404
|
this.updateBatchResultWithError_(batchResult, error, lines, 0);
|
|
361
405
|
return batchResult;
|
|
362
406
|
}
|
|
@@ -493,6 +537,27 @@ class GoogleAds {
|
|
|
493
537
|
batchResult.errors = Array.from(errors);
|
|
494
538
|
}
|
|
495
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Uploads call conversions to google ads account.
|
|
542
|
+
* It requires an array of call conversions and customer id.
|
|
543
|
+
* In DEBUG mode, this function will only validate the conversions.
|
|
544
|
+
* @param {Array<ConversionConfig>} callConversions Call Conversions
|
|
545
|
+
* @param {string} customerId
|
|
546
|
+
* @param {string} loginCustomerId Login customer account ID (Mcc Account id).
|
|
547
|
+
* @return {!Promise<!UploadCallConversionsResponse>}
|
|
548
|
+
*/
|
|
549
|
+
uploadCallConversions(callConversions, customerId, loginCustomerId) {
|
|
550
|
+
this.logger.debug('Upload call conversions for customerId:', customerId);
|
|
551
|
+
const customer = this.getGoogleAdsApiCustomer_(loginCustomerId, customerId);
|
|
552
|
+
const request = new UploadCallConversionsRequest({
|
|
553
|
+
conversions: callConversions,
|
|
554
|
+
customer_id: customerId,
|
|
555
|
+
validate_only: this.debugMode, // when true makes no changes
|
|
556
|
+
partial_failure: true, // Will still create the non-failed entities
|
|
557
|
+
});
|
|
558
|
+
return customer.conversionUploads.uploadCallConversions(request);
|
|
559
|
+
}
|
|
560
|
+
|
|
496
561
|
/**
|
|
497
562
|
* Uploads click conversions to google ads account.
|
|
498
563
|
* It requires an array of click conversions and customer id.
|
|
@@ -613,9 +678,9 @@ class GoogleAds {
|
|
|
613
678
|
* @return {!Promise<UploadUserDataResponse>}
|
|
614
679
|
*/
|
|
615
680
|
async uploadUserDataToUserList(customerMatchRecords, customerMatchConfig) {
|
|
616
|
-
const customerId = customerMatchConfig.customer_id
|
|
617
|
-
const loginCustomerId =
|
|
618
|
-
|
|
681
|
+
const customerId = this.getCleanCid_(customerMatchConfig.customer_id);
|
|
682
|
+
const loginCustomerId = this.getCleanCid_(
|
|
683
|
+
customerMatchConfig.login_customer_id);
|
|
619
684
|
const userListId = customerMatchConfig.list_id;
|
|
620
685
|
const operation = customerMatchConfig.operation;
|
|
621
686
|
|
|
@@ -688,6 +753,181 @@ class GoogleAds {
|
|
|
688
753
|
});
|
|
689
754
|
}
|
|
690
755
|
|
|
756
|
+
/**
|
|
757
|
+
* Returns a integer format CID by removing dashes.
|
|
758
|
+
* @param {string} cid
|
|
759
|
+
* @return {string}
|
|
760
|
+
* @private
|
|
761
|
+
*/
|
|
762
|
+
getCleanCid_(cid) {
|
|
763
|
+
return cid.replace(/-/g, '');
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Get OfflineUserDataJob status.
|
|
768
|
+
* @param {OfflineUserDataJobConfig} config Offline user data job config.
|
|
769
|
+
* @param {string} resourceName
|
|
770
|
+
* @return {!OfflineUserDataJobStatus} Job status
|
|
771
|
+
*/
|
|
772
|
+
async getOfflineUserDataJob(config, resourceName) {
|
|
773
|
+
const loginCustomerId = this.getCleanCid_(config.login_customer_id);
|
|
774
|
+
const customerId = this.getCleanCid_(config.customer_id);
|
|
775
|
+
const reportConfig = {
|
|
776
|
+
entity: 'offline_user_data_job',
|
|
777
|
+
attributes: [
|
|
778
|
+
'offline_user_data_job.id',
|
|
779
|
+
'offline_user_data_job.status',
|
|
780
|
+
'offline_user_data_job.type',
|
|
781
|
+
'offline_user_data_job.customer_match_user_list_metadata.user_list',
|
|
782
|
+
'offline_user_data_job.failure_reason',
|
|
783
|
+
],
|
|
784
|
+
constraints: {
|
|
785
|
+
'offline_user_data_job.resource_name': resourceName,
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
const jobs = await this.getReport(customerId, loginCustomerId, reportConfig);
|
|
789
|
+
if (jobs.length === 0) {
|
|
790
|
+
throw new Error(`Can't find the OfflineUserDataJob: ${resourceName}`);
|
|
791
|
+
}
|
|
792
|
+
return OfflineUserDataJobStatus[jobs[0].offline_user_data_job.status];
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
//resource_name: 'customers/8368692804/offlineUserDataJobs/23130531867'
|
|
796
|
+
//'customers/8368692804/offlineUserDataJobs/23232922761'
|
|
797
|
+
/**
|
|
798
|
+
* Creates a OfflineUserDataJob and returns resource name.
|
|
799
|
+
* @param {OfflineUserDataJobConfig} config Offline user data job config.
|
|
800
|
+
* @return {string} The resouce name of the creaed job.
|
|
801
|
+
*/
|
|
802
|
+
async createOfflineUserDataJob(config) {
|
|
803
|
+
const loginCustomerId = this.getCleanCid_(config.login_customer_id);
|
|
804
|
+
const customerId = this.getCleanCid_(config.customer_id);
|
|
805
|
+
const { list_id: userListId, type } = config;
|
|
806
|
+
this.logger.debug('Creating OfflineUserDataJob for CID:', customerId);
|
|
807
|
+
const customer = this.getGoogleAdsApiCustomer_(loginCustomerId, customerId);
|
|
808
|
+
// if()CUSTOMER_MATCH_USER_LIST
|
|
809
|
+
const job = OfflineUserDataJob.create({
|
|
810
|
+
type,
|
|
811
|
+
});
|
|
812
|
+
// https://developers.google.com/google-ads/api/rest/reference/rest/latest/customers.offlineUserDataJobs?hl=en#CustomerMatchUserListMetadata
|
|
813
|
+
if (type.startsWith('CUSTOMER_MATCH')) {
|
|
814
|
+
const metadata = this.buildCustomerMatchUserListMetadata_(customerId,
|
|
815
|
+
userListId);
|
|
816
|
+
job.customer_match_user_list_metadata = metadata;
|
|
817
|
+
// https://developers.google.com/google-ads/api/rest/reference/rest/latest/customers.offlineUserDataJobs?hl=en#StoreSalesMetadata
|
|
818
|
+
} else if (type.startsWith('STORE_SALES')) {
|
|
819
|
+
// If there is StoreSalesMetadata in the config
|
|
820
|
+
if (config.storeSalesMetadata) {
|
|
821
|
+
job.store_sales_list_metadata = config.storeSalesMetadata;
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
throw new Error(`UNSUPPORTED OfflineUserDataJobType: ${type}.`);
|
|
825
|
+
}
|
|
826
|
+
const request = CreateOfflineUserDataJobRequest.create({
|
|
827
|
+
customer_id: customerId,
|
|
828
|
+
job,
|
|
829
|
+
validate_only: this.debugMode, // when true makes no changes
|
|
830
|
+
enable_match_rate_range_preview: true,
|
|
831
|
+
});
|
|
832
|
+
const { resource_name: resourceName } =
|
|
833
|
+
await customer.offlineUserDataJobs.createOfflineUserDataJob(request);
|
|
834
|
+
this.logger.info('Created OfflineUserDataJob:', resourceName);
|
|
835
|
+
return resourceName;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Adds user data in to the OfflineUserDataJob.
|
|
840
|
+
* @param {OfflineUserDataJobConfig} config Offline user data job config.
|
|
841
|
+
* @param {string} jobResourceName
|
|
842
|
+
* @param {!Array<CustomerMatchRecord>} customerMatchRecords user Ids
|
|
843
|
+
* @return {!Promise<AddOfflineUserDataJobOperationsResponse>}
|
|
844
|
+
*/
|
|
845
|
+
async addOperationsToOfflineUserDataJob(config, jobResourceName, records) {
|
|
846
|
+
const start = new Date().getTime();
|
|
847
|
+
const loginCustomerId = this.getCleanCid_(config.login_customer_id);
|
|
848
|
+
const customerId = this.getCleanCid_(config.customer_id);
|
|
849
|
+
const operation = config.operation;
|
|
850
|
+
const customer = this.getGoogleAdsApiCustomer_(loginCustomerId, customerId);
|
|
851
|
+
const operationsList = this.buildOperationsList_(operation, records);
|
|
852
|
+
const request = AddOfflineUserDataJobOperationsRequest.create({
|
|
853
|
+
resource_name: jobResourceName,
|
|
854
|
+
operations: operationsList,
|
|
855
|
+
validate_only: false,//this.debugMode,
|
|
856
|
+
enable_partial_failure: true,
|
|
857
|
+
enable_warnings: true,
|
|
858
|
+
});
|
|
859
|
+
const response = await customer.
|
|
860
|
+
offlineUserDataJobs.addOfflineUserDataJobOperations(request);
|
|
861
|
+
this.logger.debug(`Added ${records.length} records in (ms):`,
|
|
862
|
+
new Date().getTime() - start);
|
|
863
|
+
return response;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Starts the OfflineUserDataJob.
|
|
868
|
+
* @param {OfflineUserDataJobConfig} config Offline user data job config.
|
|
869
|
+
* @param {string} jobResourceName
|
|
870
|
+
* @returns
|
|
871
|
+
*/
|
|
872
|
+
async runOfflineUserDataJob(config, jobResourceName) {
|
|
873
|
+
const loginCustomerId = this.getCleanCid_(config.login_customer_id);
|
|
874
|
+
const customerId = this.getCleanCid_(config.customer_id);
|
|
875
|
+
const customer = this.getGoogleAdsApiCustomer_(loginCustomerId, customerId);
|
|
876
|
+
const request = RunOfflineUserDataJobRequest.create({
|
|
877
|
+
resource_name: jobResourceName,
|
|
878
|
+
validate_only: false,//this.debugMode,
|
|
879
|
+
});
|
|
880
|
+
const rawResponse = await customer.
|
|
881
|
+
offlineUserDataJobs.runOfflineUserDataJob(request);
|
|
882
|
+
const response = lodash.pick(rawResponse, ['name', 'done', 'error']);
|
|
883
|
+
this.logger.debug('runOfflineUserDataJob response: ', response);
|
|
884
|
+
return response;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Returns the function to send out a request to Google Ads API with
|
|
889
|
+
* user data as operations in OfflineUserDataJob.
|
|
890
|
+
* @param {!OfflineUserDataJobConfig} config
|
|
891
|
+
* @param {string} jobResourceName
|
|
892
|
+
* @return {!SendSingleBatch} Function which can send a batch of hits to
|
|
893
|
+
* Google Ads API.
|
|
894
|
+
*/
|
|
895
|
+
getAddOperationsToOfflineUserDataJobFn(config, jobResourceName) {
|
|
896
|
+
/**
|
|
897
|
+
* Sends a batch of hits to Google Ads API.
|
|
898
|
+
* @param {!Array<string>} lines Data for single request. It should be
|
|
899
|
+
* guaranteed that it doesn't exceed quota limitation.
|
|
900
|
+
* @param {string} batchId The tag for log.
|
|
901
|
+
* @return {!Promise<BatchResult>}
|
|
902
|
+
*/
|
|
903
|
+
return async (lines, batchId) => {
|
|
904
|
+
/** @type {Array<CustomerMatchRecord>} */
|
|
905
|
+
const records = lines.map((line) => JSON.parse(line));
|
|
906
|
+
/** @const {BatchResult} */ const batchResult = {
|
|
907
|
+
result: true,
|
|
908
|
+
numberOfLines: lines.length,
|
|
909
|
+
};
|
|
910
|
+
try {
|
|
911
|
+
const response = await this.addOperationsToOfflineUserDataJob(config,
|
|
912
|
+
jobResourceName, records);
|
|
913
|
+
this.logger.debug(`Add operation to job batch[${batchId}]`, response);
|
|
914
|
+
const { results, partial_failure_error: failed } = response;
|
|
915
|
+
if (failed) {
|
|
916
|
+
this.logger.info('partial_failure_error:', failed.message);
|
|
917
|
+
const failures = failed.details.map(
|
|
918
|
+
({ value }) => GoogleAdsFailure.decode(value));
|
|
919
|
+
this.extraFailedLines_(batchResult, failures, lines, 0);
|
|
920
|
+
}
|
|
921
|
+
return batchResult;
|
|
922
|
+
} catch (error) {
|
|
923
|
+
this.logger.error(
|
|
924
|
+
`Error in OfflineUserDataJob add operations batch[${batchId}]`, error);
|
|
925
|
+
this.updateBatchResultWithError_(batchResult, error, lines, 2);
|
|
926
|
+
return batchResult;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
691
931
|
/**
|
|
692
932
|
* Returns an instance of GoogleAdsApi.Customer on google-ads-api.
|
|
693
933
|
* @param {string} loginCustomerId Login customer account ID (Mcc Account id).
|
|
@@ -748,6 +988,8 @@ module.exports = {
|
|
|
748
988
|
ConversionConfig,
|
|
749
989
|
CustomerMatchRecord,
|
|
750
990
|
CustomerMatchConfig,
|
|
991
|
+
OfflineUserDataJobType,
|
|
992
|
+
OfflineUserDataJobConfig,
|
|
751
993
|
GoogleAds,
|
|
752
994
|
ReportQueryConfig,
|
|
753
995
|
GoogleAdsField,
|