-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchange-in-campaign-spending-alert.js
More file actions
308 lines (243 loc) · 9.84 KB
/
change-in-campaign-spending-alert.js
File metadata and controls
308 lines (243 loc) · 9.84 KB
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
// ID: 95864684fe37d20c010dd64ea5607b2b
/**
*
* Change in Campaign Spending Alert
*
* This script uses the current hour to calculate how much has been spent on
* individual campaigns on the day of running. The average spend up to the
* current hour in a specified numbers of days previously is averaged. If the
* spend today is higher by a specified percentage threshold an alert email
* is sent.
*
* There is a 20 minute delay between events occurring and the data being
* available in AdWords. This script should be scheduled to run after 20
* past the hour.
*
* Version: 1.0
* Google AdWords Script maintained on brainlabsdigital.com
*
**/
function main() {
//////////////////////////////////////////////////////////////////////////////
// Options
var campaignNameDoesNotContain = [];
// Use this if you want to exclude some campaigns.
// For example ["Display"] would ignore any campaigns with 'Display' in the name,
// while ["Display","Shopping"] would ignore any campaigns with 'Display' or
// 'Shopping' in the name.
// Leave as [] to not exclude any campaigns.
var campaignNameContains = [];
// Use this if you only want to look at some campaigns.
// For example ["Brand"] would only look at campaigns with 'Brand' in the name,
// while ["Brand","Generic"] would only look at campaigns with 'Brand' or 'Generic'
// in the name.
// Leave as [] to include all campaigns.
var addressesToNotify = [];
// An array of email addresses to send alerts to, example usage:
// ['ron@gryffindor.hogwarts.ac.uk', 'hermione@gryffindor.hogwarts.ac.uk']
// ['luna@ravenclaw.hogwarts.ac.uk']
//////////////////////////////////////////////////////////////////////////////
// Thresholds
var percentageDifferenceSpend = 10;
// The positive or negative percentage change in spend must be greater than
// this number for an alert to be sent. eg 10 means greater than a positive or negative
// 10% change. The variable must be positive.
var averageSpendMinimumThreshold = 100.00;
// This value sets a minimum value that the average historic spend should be
// for it to be compared to today's spend. This number must be greater than 0.
var earliestHour = 7;
// Restricts the script to run only after a certain hour of the day so that a
// significant amount of data can be gathered. This number should be 0 - 23.
//////////////////////////////////////////////////////////////////////////////
// Advanced settings
var timePeriod = 7;
// The default time period averages the previous 7 days of spending. This number
// must be greater than 0.
//////////////////////////////////////////////////////////////////////////////
// The actual code starts here
// Validate input
var validated = validateInput(
addressesToNotify,
percentageDifferenceSpend,
averageSpendMinimumThreshold,
earliestHour,
timePeriod
);
if (validated !== true) {
throw validated;
}
// Create date strings for AWQL query and data comparison
var dates = makeDates(timePeriod);
// Check if it's too early to run the script or not
if (dates.currentHour < earliestHour) {
Logger.log('Too early for code, need coffee.');
return;
}
// Get the IDs of the campaigns to look at
var ignorePausedCampaigns = true;
var activeCampaignIds = getCampaignIds(campaignNameDoesNotContain, campaignNameContains, ignorePausedCampaigns);
// Construct the AWQL query using the campaign IDs and dates
var query = constructQuery(activeCampaignIds, dates);
var queryReport = AdWordsApp.report(query);
// Calculate sum of spend today and historically by campaign ID
var costs = calculateCostByCampaign(queryReport, dates);
Logger.log("Got the costs for all campaigns");
// Generate a dictionary of overspending campaigns
var overSpendingCampaigns = checkPercentageChange(costs, averageSpendMinimumThreshold, timePeriod, percentageDifferenceSpend);
// Do nothing if there are no overspending campaigns
if (Object.keys(overSpendingCampaigns).length === 0) {
Logger.log('No overspending campaigns.');
return;
}
Logger.log('Overspending campaigns: ' + JSON.stringify(overSpendingCampaigns));
// Notify contacts if there are overspending campaigns
notifyContact(addressesToNotify, overSpendingCampaigns, averageSpendMinimumThreshold);
Logger.log("Email sent.");
}
function validateInput(addressesToNotify,
percentageDifferenceSpend,
averageSpendMinimumThreshold,
earliestHour,
timePeriod
) {
if (addressesToNotify.length === 0) {
return 'Please provide at least one email address to notify.';
}
if (percentageDifferenceSpend <= 0) {
return 'Please provide a positive percentage difference spend.';
}
if (averageSpendMinimumThreshold <= 0) {
return 'Please provide a positive average spend minimum threshold.';
}
if (earliestHour > 23 | earliestHour < 0) {
return 'Please provide an earliest hour between 0 and 23 inclusive.'
}
if (timePeriod < 1) {
return 'Please provide a time period of at least one day.'
}
return true;
}
function notifyContact(addresses, overSpendingCampaigns, threshold) {
var accountName = AdWordsApp.currentAccount().getName();
var subject = accountName + ' | Spend Checker Script | Campaigns have exceeded your spend change threshold.';
var body = 'The following campaigns have exceeded the ' + threshold + '% spend threshold:\n\n';
var campaignIds = Object.keys(overSpendingCampaigns);
for (var i = 0; i < campaignIds.length; i++) {
var campaignId = campaignIds[i];
var campaign = overSpendingCampaigns[campaignId];
var campaignName = campaign.campaignName;
var percentageChange = campaign.percentageChange.toFixed(2);
var spendToday = campaign.today.toFixed(2);
body += (i + 1) + '. \nName: ' + campaignName + '\n' +
'ID: ' + campaignId + '\n' +
'Change(%): ' + percentageChange + '\n' +
'Spend today (£): ' + spendToday + '\n\n';
}
MailApp.sendEmail(addresses.join(','), subject, body);
}
function checkPercentageChange(costs, spendThreshold, timePeriod, percentageThreshold) {
var campaignIds = Object.keys(costs);
return campaignIds.reduce(function (overspendingCampaigns, campaignId) {
var campaign = costs[campaignId];
var averageSpend = campaign.sumTimePeriod / timePeriod;
var spendToday = campaign.today;
if (averageSpend < spendThreshold) {
return overspendingCampaigns;
}
var percentageChange = ((spendToday - averageSpend) / averageSpend) * 100;
if (Math.abs(percentageChange) > percentageThreshold) {
campaign['percentageChange'] = percentageChange;
overspendingCampaigns[campaignId] = campaign;
}
return overspendingCampaigns;
}, {});
}
function makeDates(timePeriod) {
var millisPerDay = 1000 * 60 * 60 * 24;
var timeZone = AdWordsApp.currentAccount().getTimeZone();
var now = new Date();
var dateInPast = new Date(now - ((timePeriod + 1) * millisPerDay));
var todayHyphenated = Utilities.formatDate(now, timeZone, 'yyyy-MM-dd');
var todayFormatted = todayHyphenated.replace(/-/g, '');
var currentHour = Utilities.formatDate(now, timeZone, 'H');
var dateInPastFormatted = Utilities.formatDate(dateInPast, timeZone, 'yyyyMMdd');
return {
'todayHyphenated': todayHyphenated,
'todayFormatted': todayFormatted,
'dateInPastFormatted': dateInPastFormatted,
'currentHour': currentHour,
};
}
function constructQuery(activeCampaignIds, dates) {
var currentHour = dates.currentHour;
var todayFormatted = dates.todayFormatted;
var dateInPastFormatted = dates.dateInPastFormatted;
var query =
'SELECT CampaignName, CampaignId, Cost, HourOfDay, Date ' +
'FROM CAMPAIGN_PERFORMANCE_REPORT ' +
'WHERE CampaignId IN [' + activeCampaignIds.join(',') + '] ' +
'AND CampaignStatus = ENABLED ' +
'AND HourOfDay < ' + currentHour + ' ' +
'DURING ' + dateInPastFormatted + ',' + todayFormatted;
Logger.log('AWQL Query: ' + query);
return query;
}
function calculateCostByCampaign(report, dates) {
var reportRows = report.rows();
var costs = {};
while (reportRows.hasNext()) {
var row = reportRows.next();
var cost = parseFloat(row.Cost);
var campaignId = row.CampaignId;
if (costs[campaignId] === undefined) {
costs[campaignId] = {
'today': 0,
'sumTimePeriod': 0,
'campaignName': row.CampaignName,
}
}
if (row.Date === dates.todayHyphenated) {
costs[campaignId].today += cost;
} else {
costs[campaignId].sumTimePeriod += cost;
}
}
return costs;
}
function getCampaignIds(campaignNameDoesNotContain, campaignNameContains, ignorePausedCampaigns) {
var whereStatement = "WHERE ";
var whereStatementsArray = [];
var campaignIds = [];
if (ignorePausedCampaigns) {
whereStatement += "CampaignStatus = ENABLED ";
} else {
whereStatement += "CampaignStatus IN ['ENABLED','PAUSED'] ";
}
for (var i = 0; i < campaignNameDoesNotContain.length; i++) {
whereStatement += "AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE '" + campaignNameDoesNotContain[i].replace(/"/g, '\\\"') + "' ";
}
if (campaignNameContains.length == 0) {
whereStatementsArray = [whereStatement];
} else {
for (var i = 0; i < campaignNameContains.length; i++) {
whereStatementsArray.push(whereStatement + 'AND CampaignName CONTAINS_IGNORE_CASE "' + campaignNameContains[i].replace(/"/g, '\\\"') + '" ');
}
}
for (var i = 0; i < whereStatementsArray.length; i++) {
var campaignReport = AdWordsApp.report(
"SELECT CampaignId " +
"FROM CAMPAIGN_PERFORMANCE_REPORT " +
whereStatementsArray[i] +
"DURING LAST_30_DAYS");
var rows = campaignReport.rows();
while (rows.hasNext()) {
var row = rows.next();
campaignIds.push(row['CampaignId']);
}
}
if (campaignIds.length == 0) {
throw ("No campaigns found with the given settings.");
}
Logger.log(campaignIds.length + " campaigns found");
return campaignIds;
}