This repository has been archived by the owner on Jan 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
app_script.js
342 lines (304 loc) · 10.8 KB
/
app_script.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Create a Google Analytics Audience once in the UI & copy it to multiple
* destinations.
*
* For more information about the API calls in this file, see the documentation:
* https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference
*/
const URL_NAMED_RANGE = 'gaAudienceURL';
const DESTINATION_SHEET_NAME = 'Destinations';
const DESTINATION_LOG_RANGE = 'A2:Z';
const CONFIG_SHEET_NAME = 'Config';
const LOG_SHEET_NAME = 'Log';
const LOG_SHEET_RANGE = 'A2:Z';
// Google Analytics limits the number of destinations an audience can have to
// 10. Therefore our audience list needs to be chunked into groups of 10. See
// the docs for more information:
// https://support.google.com/analytics/answer/2611404?hl=en
const DESTINATION_BATCH_SIZE = 10;
/**
* The entry point to the scripts. This provides the orchestration to create the
* audiences.
*/
function runAudienceCreator() {
Logger.log('Running audience creator...');
clearGoogleSheet(LOG_SHEET_NAME, LOG_SHEET_RANGE);
const gaUiUrl = getUrlFromSheet();
const accountId = extractAccountIdFromUrl(gaUiUrl);
const audienceId = extractAudienceIdFromUrl(gaUiUrl);
const internalWebPropertyId = extractInternalWebPropertyIdFromUrl(gaUiUrl);
const audience = getAudienceWithId(
accountId, audienceId, internalWebPropertyId);
const audienceName = audience['name'];
Logger.log('-Using base audience: ' + audienceName);
Logger.log(audience);
const destinations = getDestinations();
const chunks = chunkArray(destinations, DESTINATION_BATCH_SIZE);
for (const [i, batch] of chunks.entries()) {
// i is 0 based so bump by 1 to make more human readable when naming new
//audiences
modifyAudienceNameAndAccounts(audience, i+1, batch, audienceName);
Logger.log('-Creating audience:');
Logger.log(audience);
const response = Analytics.Management.RemarketingAudience.insert(
audience, audience['accountId'], audience['webPropertyId']);
Logger.log('-Response:');
Logger.log(response);
logResponseToSheet(response, batch);
}
setActiveSheetByName(LOG_SHEET_NAME);
Logger.log('Done.');
}
/**
* Split an array into smaller chunks.
* The process uses splice to create the chunks. This means that the arr
* parameter will be completely emptied by this method.
* @param {!Array<?>} arr: the array to chunk.
* @param {number} chunk_size: the size of the chunks.
* @return {!Array<?Array>}: an array containing each of the chunks.
*/
function chunkArray(arr, chunk_size) {
const chunked = [];
while(arr.length) {
chunked.push(arr.splice(0, chunk_size));
}
return chunked;
}
/**
* Set the active sheet by name.
* @param {string} name: the name of the sheet
*/
function setActiveSheetByName(name) {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName(name));
}
/**
* Get the URL from the sheet
* @return {!Object}: the url.
*/
function getUrlFromSheet() {
return SpreadsheetApp.getActiveSpreadsheet().getRangeByName(
URL_NAMED_RANGE).getValue();
}
/**
* Extracts the audience ID from the Google Analytics UI url.
* @param {string} url: the url to read the audience ID from.
* @return {string}: the audience ID if found, else null;
*/
function extractAudienceIdFromUrl(url) {
const pattern = /m-content\.key=([^&]+)/;
return extractPatternFromUrl(url, pattern);
}
/**
* Extracts the account ID from the Google Analytics UI url.
* @param {string} url: the url to read the account ID from.
* @return {string}: the account ID if found, else null;
*/
function extractAccountIdFromUrl(url) {
const pattern = /a(\d*)w\d*p\d*/;
return extractPatternFromUrl(url, pattern);
}
/**
* Extracts the internal Web Property ID from the Google Analytics UI url.
* @param {string} url: the url to read the account ID from.
* @return {string}: the internal web property ID if found, else null;
*/
function extractInternalWebPropertyIdFromUrl(url) {
const pattern = /a\d*w(\d*)p\d*/;
return extractPatternFromUrl(url, pattern);
}
/**
* Given the regex pattern, return the first match in the url.
* @param {string} url: the url to use.
* @param {!RegExp} pattern: the regex patten to use.
* @return {string}: the first regex match if found, else null;
*/
function extractPatternFromUrl(url, pattern) {
const match = pattern.exec(url);
if (match) {
return match[1];
}
return null;
}
/**
* Build the audience URL based on the original url input.
* Replace the m-content-key with the updated ID
* @param {string} url: an example url.
* @param {string} audienceId: the new audience ID.
* @return {string}: the new url.
*/
function buildAudienceUrl(url, audienceId) {
return url.replace(/(m-content\.key=)[^\&]+/, '$1' + audienceId);
}
/**
* Returns the remakreting audience specified by the parameters.
* @param {string} accountId: the account ID.
* @param {string} audienceId: the audience ID.
* @param {string} internalWebPropertyId: the internal web property ID.
* @return {?audience}: the audience if found, else null.
*/
function getAudienceWithId(accountId, audienceId, internalWebPropertyId) {
const webProperties = Analytics.Management.Webproperties.list(accountId);
if (webProperties.items) {
for (const webProperty of webProperties.items) {
if (webProperty.internalWebPropertyId == internalWebPropertyId) {
return Analytics.Management.RemarketingAudience.get(
accountId, webProperty.getId(), audienceId);
}
}
}
return null;
}
/**
* Get the destinations from the Google Sheet.
* @return {!Array<!Array>}: An array containing each row of destinations.
*/
function getDestinations() {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName(CONFIG_SHEET_NAME);
return sheet.getRange('B2:C' + sheet.getLastRow()).getValues();
}
/**
* Modify the name of the existing audience and update the linked accounts.
* Updates the name of the audience & updates the destinations.
* @param {!Object} audience: the audience to modify.
* @param {!int} batch_number: the batch number of the audience.
* @param {!Array<!Array>} destinations: the rows containing the destinations.
* @param {string} name: the original name of the audience.
*/
function modifyAudienceNameAndAccounts(
audience, batch_number, destinations, name) {
audience['name'] = name + ': v' + batch_number;
audience['linkedAdAccounts'] = toAPIDestinationsArray(destinations);
}
/**
* Turns an array of destinations into the correct JSON format for the API.
* @param {!rows} destinations: the rows containing the destinations.
* @return {?Array<?Object>}: the JSON required for the API.
*/
function toAPIDestinationsArray(destinations) {
return destinations.map(d => {
return {
'kind': 'analytics#linkedForeignAccount',
'type': d[1],
'linkedAccountId': d[0]
};
});
}
/**
* Turns an array of destinations into the correct format to log to a sheet.
* @param {!Array<!Array>} destinations: the rows containing the destinations.
* @return {string}: a comma separated list of destinations.
*/
function formatDestinationsToLog(destinations){
return destinations.map(d => d[0]).join(', ');
}
/**
* Clears a range of a Google Sheet.
* @param {string} sheet_name: the name of the sheet.
* @param {string} sheet_range: the range to clear.
*/
function clearGoogleSheet(sheet_name, sheet_range) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName(sheet_name);
sheet.getRange(sheet_range).clear();
}
/**
* Logs the response from the audience creation to the log sheet.
* @param {!Object} response: the audience to log out.
* @param {!Array<!Array>} destinations: the rows containing the destinations.
*/
function logResponseToSheet(response, destinations) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName(LOG_SHEET_NAME);
sheet.appendRow([
response['id'],
response['accountId'],
response['webPropertyId'],
response['name'],
response['audienceDefinition']['includeConditions']['segment'],
formatDestinationsToLog(destinations),
`=HYPERLINK("${buildAudienceUrl(
getUrlFromSheet(), response['id'])}", "Link")`
]);
}
/**
* Deletes each of the audiences in the log.
*/
function deleteAudiencesInLog() {
Logger.log('Deleting audiences...');
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName(LOG_SHEET_NAME);
const rows = sheet.getRange(LOG_SHEET_RANGE + sheet.getLastRow()).getValues();
for (const row of rows) {
const remarketingAudienceId = row[0];
const accountId = row[1];
const webPropertyId = row[2];
Logger.log('-Deleting audience: ' + remarketingAudienceId);
Analytics.Management.RemarketingAudience.remove(
accountId, webPropertyId, remarketingAudienceId);
}
clearGoogleSheet(LOG_SHEET_NAME, LOG_SHEET_RANGE);
setActiveSheetByName(CONFIG_SHEET_NAME);
Logger.log('Done.');
}
/**
* Fetch destinations from the Google Analytics account and log output.
*/
function fetchDestinations() {
clearGoogleSheet(DESTINATION_SHEET_NAME, DESTINATION_LOG_RANGE);
const accounts = Analytics.Management.Accounts.list();
if (accounts.items) {
for (const account of accounts.items) {
const webProperties = Analytics.Management.Webproperties.list(
account.getId());
if (webProperties.items) {
for (const webProperty of webProperties.items) {
logDestinationToSheet(account.getId(), webProperty.getId());
}
}
}
}
}
/**
* Log all Adwords Links Destinations to a sheet.
* @param {string} accountId: the account ID
* @param {string} webPropertyId: the web property ID
*/
function logDestinationToSheet(accountId, webPropertyId) {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName(DESTINATION_SHEET_NAME);
const adwords_links = Analytics.Management.WebPropertyAdWordsLinks.list(
accountId,
webPropertyId
);
Logger.log(adwords_links);
const destinations = adwords_links['items'];
for (const destination of destinations) {
const accounts = destination['adWordsAccounts'];
const webPropertyRef = destination['entity']['webPropertyRef'];
for (const account of accounts) {
sheet.appendRow([
account['customerId'],
account['kind'],
webPropertyRef['name'],
webPropertyRef['id']
]);
}
}
}