-
Notifications
You must be signed in to change notification settings - Fork 25
/
magic-script.js
894 lines (736 loc) · 26.7 KB
/
magic-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
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
/**
* A script to automate requesting data from Google Analytics.
*
* @author [email protected] (Nick Mihailovski)
*/
/**
* The name of the configration sheet.
* And various parameters.
*/
var GA_CONFIG = 'gaconfig';
var NAME = 'query';
var VALUE = 'value';
var TYPE = 'type';
var SHEET_NAME = 'sheet-name';
var CORE_OPT_PARAM_NAMES = [
'dimensions',
'sort',
'filters',
'segment',
'start-index',
'max-results'];
var MCF_OPT_PARAM_NAMES = [
'dimensions',
'sort',
'filters',
'start-index',
'max-results'];
/**
* The types of reports.
*/
var CORE_TYPE = 'core';
var MCF_TYPE = 'mcf';
/**
* Create a Menu when the script loads. Adds a new gaconfig sheet if
* one doesn't exist.
*/
function onOpen() {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
// Add a menu.
activeSpreadsheet.addMenu(
'Google Analytics', [{
name: 'Find Profile / ids', functionName: 'findIds_'}, {
name: 'Create Core Report', functionName: 'createCoreReport'}, {
name: 'Create MCF Report', functionName: 'createMcfReport'}, {
name: 'Get Data', functionName: 'getData'
}]);
// Add a sheet called gaconfig if it doesn't already exist.
getOrCreateGaSheet_();
}
/**
* Opens a UIService dialogue for users to select their Profile / ids
* query parameter. This will traverse the Management API hierarchy
* to populate various list boxes.
*/
function findIds_() {
var app = UiApp.createApplication()
.setTitle('Find Your Profile ID / ids Parameter')
.setWidth(700).setHeight(175);
var accountList = app.createListBox()
.setId('accountList').setName('accountList')
.setVisibleItemCount(1).setStyleAttribute('width', '100%');
var webpropertyList = app.createListBox()
.setId('webpropertyList').setName('webpropertyList')
.setVisibleItemCount(1).setStyleAttribute('width', '100%');
var profileList = app.createListBox()
.setId('profileList').setName('profileList')
.setVisibleItemCount(1).setStyleAttribute('width', '100%');
// Grid for dropdowns.
var grid = app.createGrid(3, 2).setStyleAttribute(0, 0, 'width', '130px');
grid.setWidget(0, 0, app.createLabel('Select Account'));
grid.setWidget(0, 1, accountList);
grid.setWidget(1, 0, app.createLabel('Select Web Property'));
grid.setWidget(1, 1, webpropertyList);
grid.setWidget(2, 0, app.createLabel('Select Profile'));
grid.setWidget(2, 1, profileList);
grid.setStyleAttribute('border-bottom', '1px solid #999')
.setStyleAttribute('padding-bottom', '12px')
.setStyleAttribute('margin-bottom', '12px');
// Grid for id results.
//var profileLink = app.createLabel('add ids to sheet').addClickHandler();
var grid2 = app.createGrid(2, 3).setStyleAttribute(0, 0, 'width', '130px');
grid2.setWidget(0, 0, app.createLabel('Profile Id'));
grid2.setWidget(0, 1, app.createLabel().setId('profileId'));
grid2.setWidget(1, 0, app.createLabel('ids'));
grid2.setWidget(1, 1, app.createLabel().setId('ids'));
//grid2.setWidget(1, 2, profileLink);
var handler = app.createServerHandler('queryWebProperties_');
accountList.addChangeHandler(handler);
handler = app.createServerHandler('queryProfiles_');
webpropertyList.addChangeHandler(handler);
handler = app.createServerHandler('displayProfile_');
profileList.addChangeHandler(handler);
app.add(grid).add(grid2);
// Traverse the management hiearchy by default.
queryAccounts_();
// Show display.
var doc = SpreadsheetApp.getActiveSpreadsheet();
doc.show(app);
}
/**
* Handler to query all the accounts for the user then update the
* UI dialogue box.
* @return {Object} The instance of the active application.
*/
function queryAccounts_() {
var app = UiApp.getActiveApplication();
var accountList = app.getElementById('accountList');
// Query Accounts API
var accounts = Analytics.Management.Accounts.list();
if (accounts.getItems()) {
for (var i = 0, item; item = accounts.getItems()[i]; ++i) {
var name = item.getName();
var id = item.getId();
accountList.addItem(name, id);
}
var firstAccountId = accounts.getItems()[0].getId();
UserProperties.setProperty('accountId', firstAccountId);
queryWebProperties_();
} else {
accountList.addItem('No accounts for user.');
app.getElementById('webpropertyList').clear();
app.getElementById('profileList').clear();
displayProfile_('none');
}
return app;
}
/**
* Handler to query all the webproperties.
* @param {object} eventInfo Used retrieve the user's accountId.
* @return {object} A reference to the active application.
*/
function queryWebProperties_(eventInfo) {
var app = UiApp.getActiveApplication();
var webpropertyList = app.getElementById('webpropertyList').clear();
// From saved id.
var accountId = UserProperties.getProperty('accountId');
// From server handler.
if (eventInfo && eventInfo.parameter && eventInfo.parameter.accountList) {
accountId = eventInfo.parameter.accountList;
UserProperties.setProperty('accountId', accountId);
}
var webproperties = Analytics.Management.Webproperties.list(accountId);
if (webproperties.getItems()) {
for (var i = 0, webproperty; webproperty = webproperties.getItems()[i];
++i) {
var name = webproperty.getName();
var id = webproperty.getId();
webpropertyList.addItem(name, id);
}
var firstWebpropertyId = webproperties.getItems()[0].getId();
UserProperties.setProperty('webpropertyId', firstWebpropertyId);
queryProfiles_();
} else {
webpropertyList.addItem('No webproperties for user');
app.getElementById('profileList').clear();
displayProfile_('none');
}
return app;
}
/**
* Handler to query all the profiles.
* @param {object} eventInfo Used retrieve the user's webpropertyId.
* @return {object} A reference to the active application.
*/
function queryProfiles_(eventInfo) {
var app = UiApp.getActiveApplication();
var profileList = app.getElementById('profileList').clear();
var accountId = UserProperties.getProperty('accountId');
var webpropertyId = UserProperties.getProperty('webpropertyId');
if (eventInfo && eventInfo.parameter && eventInfo.parameter.webpropertyList) {
webpropertyId = eventInfo.parameter.webpropertyList;
}
var profiles = Analytics.Management.Profiles.list(accountId, webpropertyId);
if (profiles.getItems()) {
for (var i = 0, profile; profile = profiles.getItems()[i]; ++i) {
profileList.addItem(profile.getName(), profile.getId());
}
var firstProfileId = profiles.getItems()[0].getId();
UserProperties.setProperty('profileId', firstProfileId);
displayProfile_();
} else {
profileList.addItem('No profiles found.');
displayProfile_('none');
}
return app;
}
/**
* Displays the user's profile on the spreadsheet.
* @param {Objecty} eventInfo Used to retrieve the profile ID.
* @return {object} A reference to the active application.
*/
function displayProfile_(eventInfo) {
var app = UiApp.getActiveApplication();
var profileId = '';
var tableId = '';
if (eventInfo != 'none') {
var profileId = UserProperties.getProperty('profileId');
if (eventInfo && eventInfo.parameter && eventInfo.parameter.profileList) {
profileId = eventInfo.parameter.profileList;
}
var tableId = 'ga:' + profileId;
}
app.getElementById('profileId').setText(profileId);
app.getElementById('ids').setText(tableId);
return app;
}
/**
* Returns the GA_CONFIG sheet. If it doesn't exist it is created.
* @return {Sheet} The GA_CONFIG sheet.
*/
function getOrCreateGaSheet_() {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var gaConfig = activeSpreadsheet.getSheetByName(GA_CONFIG);
if (!gaConfig) {
gaConfig = activeSpreadsheet.insertSheet(GA_CONFIG, 0);
}
return gaConfig;
}
/**
* Entry point for a user to create a new Core Report configuration.
*/
function createCoreReport() {
createGaReport_(CORE_TYPE);
}
/**
* Entry point for a user to create a new MCF Report Configuration.
*/
function createMcfReport() {
createGaReport_(MCF_TYPE);
}
/**
* Adds a GA Report configuration to the spreadsheet.
* @param {String} reportType The type of report configuration to add.
* Should be either CORE_TYPE or MCF_TYPE.
*/
function createGaReport_(reportType) {
var sheet = getOrCreateGaSheet_();
var headerNumber = getLastNumber_(sheet);
var config = [
[NAME + headerNumber, VALUE + headerNumber],
[TYPE, reportType],
['ids', ''],
['start-date', ''],
['end-date', ''],
['last-n-days', ''],
['metrics', '']];
// Add API specific fields. Default to Core.
var paramNames = CORE_OPT_PARAM_NAMES;
if (reportType == MCF_TYPE) {
paramNames = MCF_OPT_PARAM_NAMES;
}
for (var i = 0, paramName; paramName = paramNames[i]; ++i) {
config.push([paramName, '']);
}
config.push([SHEET_NAME, '']);
sheet.getRange(1, sheet.getLastColumn() + 1, config.length, 2)
.setValues(config);
}
/**
* Returns 1 greater than the largest trailing number in the header row.
* @param {Object} sheet The sheet in which to find the last number.
* @return {Number} The next largest trailing number.
*/
function getLastNumber_(sheet) {
var maxNumber = 0;
var lastColIndex = sheet.getLastColumn();
if (lastColIndex > 0) {
var range = sheet.getRange(1, 1, 1, lastColIndex);
for (var colIndex = 1; colIndex < sheet.getLastColumn(); ++colIndex) {
var value = range.getCell(1, colIndex).getValue();
var headerNumber = getTrailingNumber_(value);
if (headerNumber) {
var number = parseInt(headerNumber, 10);
maxNumber = number > maxNumber ? number : maxNumber;
}
}
}
return maxNumber + 1;
}
/**
* Main function to get data from the Analytics API. This reads the
* configuration file, executes the queries, and displays the results.
* @param {object} e Some undocumented parameter that is passed only
* when the script is run from a trigger. Used to update
* user on the status of the report.
*/
function getData(e) {
setupLog_();
var now = new Date();
log_('Running on: ' + now);
var sheet = getOrCreateGaSheet_();
var configs = getConfigs_(sheet);
if (!configs.length) {
log_('No report configurations found');
} else {
log_('Found ' + configs.length + ' report configurations.');
for (var i = 0, config; config = configs[i]; ++i) {
var configName = config[NAME];
try {
log_('Executing query: ' + configName);
var results = getResults_(config);
log_('Success. Writing results.');
displayResults_(results, config);
} catch (error) {
log_('Error executing ' + configName + ': ' + error.message);
}
}
}
log_('Script done');
// Update the user about the status of the queries.
if (e === undefined) {
displayLog_();
}
}
/**
* Returns an array of config objects. This reads the gaconfig sheet
* and tries to extract adjacent column names that end with the same
* number. For example Names1 : Values1. Then both columns are used
* to define key-value pairs for the coniguration object. The first
* column defines the keys, and the adjacent column values define
* each keys values.
* @param {Sheet} sheet The GA_Config sheet from which to read configurations.
* @return {Array} An array of API query configuration object.
*/
function getConfigs_(sheet) {
var configs = [];
// There must be at least 2 columns.
if (sheet.getLastColumn() < 2) {
return configs;
}
var headerRange = sheet.getRange(1, 1, 1, sheet.getLastColumn());
// Test the name of each column to see if it has an adjacent column that ends
// in the same number. ie xxxx555 : yyyy555.
// Since we check 2 columns at a time, we don't need to check the last column,
// as there is no second column to also check.
for (var colIndex = 1; colIndex <= headerRange.getNumColumns() - 1;
++colIndex) {
var firstColValue = headerRange.getCell(1, colIndex).getValue();
var firstColNum = getTrailingNumber_(firstColValue);
var secondColValue = headerRange.getCell(1, colIndex + 1).getValue();
var secondColNum = getTrailingNumber_(secondColValue);
if (firstColNum && secondColNum && firstColNum == secondColNum) {
configs.push(getConfigsStartingAtCol_(colIndex));
}
}
return configs;
}
/**
* Returns the trailing number on a string. For example the
* input: xxxx555 will return 555. Inputs with no trailing numbers
* return undefined. Trailing whitespace is not ignored.
* @param {string} input The input to parse.
* @return {number} The trailing number on the input as a string.
* undefined if no number was found.
*/
function getTrailingNumber_(input) {
// Match at one or more digits at the end of the string.
var pattern = /(\d+)$/;
var result = pattern.exec(input);
if (result) {
// Return the matched number.
return result[0];
}
return undefined;
}
/**
* Returns the values from 2 columns from the GA_CONFIG sheet starting at
* colIndex, as key-value pairs. Key-values are only returned if they do
* not contain the empty string or have a boolean value of false.
* If the key is start-date or end-date and the value is an instance of
* the date object, the value will be converted to a string in yyyy-MM-dd.
* If the key is start-index or max-results and the type of the value is
* number, the value will be parsed into a string.
* @param {number} colIndex The column index to return values from.
* @return {object} The values starting in colIndex and the following column
as key-value pairs.
*/
function getConfigsStartingAtCol_(colIndex) {
var config = {};
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(GA_CONFIG);
var range = sheet.getRange(1, colIndex, sheet.getLastRow(), 2);
// The first cell of the first column becomes the name of the query.
config[NAME] = range.getCell(1, 1).getValue();
for (var rowIndex = 2; rowIndex <= range.getLastRow(); ++rowIndex) {
var key = range.getCell(rowIndex, 1).getValue();
var value = range.getCell(rowIndex, 2).getValue();
if (value) {
if ((key == 'start-date' || key == 'end-date') && value instanceof Date) {
// Utilities.formatDate is too complicated since it requires a time zone
// which can be configured by account or per sheet.
value = formatGaDate_(value);
} else if ((key == 'start-index' || key == 'max-results') &&
typeof value == 'number') {
value = value.toString();
}
config[key] = value;
}
}
return config;
}
/**
* Returns the dateInput object in yyyy-MM-dd.
* @param {Date} inputDate The object to convert.
* @return {string} The date object as yyyy-MM-dd.
*/
function formatGaDate_(inputDate) {
var output = [];
var year = inputDate.getFullYear();
var month = inputDate.getMonth() + 1;
if (month < 10) {
month = '0' + month;
}
var day = inputDate.getDate();
if (day < 10) {
day = '0' + day;
}
return [year, month, day].join('-');
}
/**
* Executes a Google Analytics API query and returns the results.
* @param {object} config A configuration object with key-value
* parameters representing various API query parameters.
* @return {object} A JSON object with all the results from the API.
*/
function getResults_(config) {
// Translate last n days into the actual start and end dates.
// This value overrides any existing start or end dates.
if (config['last-n-days']) {
var lastNdays = parseInt(config['last-n-days'], 10);
config['start-date'] = getLastNdays_(lastNdays);
config['end-date'] = getLastNdays_(0);
}
var type = config[TYPE] || CORE_TYPE; // If no type, default to core type.
if (type == CORE_TYPE) {
var optParameters = getOptParamObject_(CORE_OPT_PARAM_NAMES, config);
var apiFunction = Analytics.Data.Ga.get;
} else if (type == MCF_TYPE) {
var optParameters = getOptParamObject_(MCF_OPT_PARAM_NAMES, config);
var apiFunction = Analytics.Data.Mcf.get;
}
// Execute query and return the results.
// If any errors occur, they will be thrown and caught in a higher
// level of code.
var results = apiFunction(
config['ids'],
config['start-date'],
config['end-date'],
config['metrics'],
optParameters);
return results;
}
/**
* Returns a date formatted as YYYY-MM-DD from the number of
* days ago starting from today.
* @param {number} nDays The number of days to request dats from today.
* @return {String} The YYY_MM_DD formatted date of the previous days.
*/
function getLastNdays_(nDays) {
var today = new Date();
var before = new Date();
before.setDate(today.getDate() - nDays);
return formatGaDate_(before);
}
/**
* Returns all the valid optional parameters for a Reporting API Query.
* This ensures non-valid parameters are not added to the query.
* Parameters with empty values will not be added.
* @param {array.<String>} optParamNames An array of all the valid param names.
* @param {Object} config The configuration object.
* @return {object} An object with all the keys as param names and values as
* param values.
*/
function getOptParamObject_(optParamNames, config) {
var optParameters = {};
for (var i = 0, paramName; paramName = optParamNames[i]; ++i) {
if (paramName in config && config[paramName]) {
optParameters[paramName] = config[paramName];
}
}
return optParameters;
}
/**
* Displays all the API results of a sucessful query.
* @param {object} results The data returned from the API.
* @param {object} config An object that contains key value configuration
* parameters.
*/
function displayResults_(results, config) {
// Use an object to force passing by reference.
var row = {};
row.count = 1;
var activeSheet = getOutputSheet_(config[SHEET_NAME]);
activeSheet.clear().setColumnWidth(1, 200);
outputResultInfo_(results, activeSheet, row, config[NAME]);
outputTotalsForAllResults_(results, activeSheet, row);
outputHeaders_(results, activeSheet, row);
// Only output rows if they exist.
if (results.getRows()) {
var type = config[TYPE] || CORE_TYPE; // If no type, default to core type.
if (type == CORE_TYPE) {
outputCoreRows_(results, activeSheet, row);
} else if (type == MCF_TYPE) {
outputMcfRows_(results, activeSheet, row);
}
}
}
/**
* Returns the sheet which should be used to output the results.
* If no sheetName is used, a new sheet will be inserted.
* If a sheetName is used, but doesn't yet exist, it will be inserted.
* If a sheetName is used, and already exists, it will be returned.
* @param {string=} opt_sheetName The name of the sheet to return.
* @return {object} A reference to the active spreadsheet.
*/
function getOutputSheet_(opt_sheetName) {
var sheetName = opt_sheetName || false;
var activeSpreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet;
if (sheetName) {
sheetName += ''; // Make sure sheet is a string.
activeSheet = activeSpreadSheet.getSheetByName(sheetName);
if (!activeSheet) {
activeSheet = activeSpreadSheet.insertSheet(sheetName);
}
} else {
activeSheet = activeSpreadSheet.insertSheet();
}
return activeSheet;
}
/**
* Outputs whether the result contains sampled data.
* @param {object} results The object returned from the API.
* @param {object} activeSheet The active sheet to display the results.
* @param {object} row An object that stores on which row to start outputing.
* @param {String} queryName The name of the query.
*/
function outputResultInfo_(results, activeSheet, row, queryName) {
var now = Utilities.formatDate(new Date(), Session.getTimeZone(),
'yyyy-MM-dd HH:mm:ss');
activeSheet.getRange(row.count, 1, 6, 2).setValues([
['Results for query', queryName],
['Date executed', now],
['Profile Name', results.getProfileInfo().getProfileName()],
['Total Results Found', results.getTotalResults()],
['Total Results Returned', results.getRows().length],
['Contains Sampled Data', results.getContainsSampledData()]
]);
// Merge 4 cells to make it look nice.
// For date / time.
activeSheet.getRange(row.count + 1, 2, 1, 2).mergeAcross();
// For profile name.
activeSheet.getRange(row.count + 2, 2, 1, 4).mergeAcross();
row.count += 7;
}
/**
* Outpus the totals for all results. Goes through each header in one pass.
* The number of dimensions are counted so that the range can be aligned with
* the headers for all the rows. The metric names are put into an array in the
* order they appear in the results. The totals for each metrics are also
* looked up and put into an array in the same order as each name. Finally,
* the name and totals are outputted into the sheet.
* @param {object} results The object returned from the API.
* @param {object} activeSheet The active sheet to display the results.
* @param {object} row An object that stores on which row to start outputing.
*/
function outputTotalsForAllResults_(results, activeSheet, row) {
activeSheet.getRange(row.count, 1).setValue('Totals For All Results');
++row.count;
var numDimensions = 0;
var metricNames = [];
var metricTotals = [];
for (var i = 0, header; header = results.getColumnHeaders()[i]; ++i) {
if (header.getColumnType() == 'DIMENSION') {
++numDimensions;
} else {
var metricName = header.getName();
metricNames.push(metricName);
var totalForMetric = results.getTotalsForAllResults()[metricName];
metricTotals.push(totalForMetric);
}
}
// Get a range that skips over the dimensions.
var range = activeSheet.getRange(row.count, numDimensions + 1, 2,
results.getColumnHeaders().length -
numDimensions);
range.setValues([metricNames, metricTotals]);
row.count += 3;
}
/**
* Outputs the header values in the activeSheet.
* @param {object} results The object returned from the API.
* @param {object} activeSheet The active sheet to display the results.
* @param {object} row An object that stores on which row to start outputing.
*/
function outputHeaders_(results, activeSheet, row) {
var headerRange = activeSheet.getRange(row.count, 1, 1,
results.getColumnHeaders().length);
var headerNames = [];
for (var i = 0; i < results.getColumnHeaders().length; ++i) {
headerNames.push(results.getColumnHeaders()[i].getName());
}
headerRange.setValues([headerNames]);
row.count += 1;
}
/**
* Outputs the rows of data in the activeSheet. This also updates the first
* occurance of the column that has date data to be in yyyy=MM-dd format.
* @param {object} results The object returned from the API.
* @param {object} activeSheet The active sheet to display the results.
* @param {object} row An object that stores on which row to start outputing.
*/
function outputCoreRows_(results, activeSheet, row) {
var rows = results.getRows();
// Update the ga:date columns to be in yyyy-MM-dd format.
var dateCols = getDateColumns_(results.getColumnHeaders());
if (dateCols.length) {
for (var rowIndex = 0; rowIndex < rows.length; ++rowIndex) {
for (var i = 0; i < dateCols.length; ++i) {
var dateColIndex = dateCols[i];
rows[rowIndex][dateColIndex] = convertToIsoDate_(
rows[rowIndex][dateColIndex]);
}
}
}
activeSheet.getRange(row.count, 1,
results.getRows().length,
results.getColumnHeaders().length).setValues(rows);
}
/**
* Outputs the rows of data in the active sheet. Each primitive dimension gets
* it's own cell. Each primitiveValue gets it's own cell. The nodes of each
* conversionPathValue get individual cells. Because the size of the resulting
* table may vary, a counter keeps track of the final table.
* @param {object} results The object returned from the API.
* @param {object} activeSheet The active sheet to display the results.
* @param {object} row An object that stores on which row to start outputing.
*/
function outputMcfRows_(results, activeSheet, row) {
var maxCols = 1;
var outputTable = [];
for (var rowIndex = 0, resultRow; resultRow = results.getRows()[rowIndex];
++rowIndex) {
var outputRow = [];
for (var colIndex = 0, cell; cell = resultRow[colIndex]; ++colIndex) {
var jsonCell = Utilities.jsonParse(cell);
if (jsonCell['primitiveValue']) {
outputRow.push(jsonCell['primitiveValue']);
} else if (jsonCell['conversionPathValue']) {
// All nodes become cells.
var nodeCell = [];
var nodes = jsonCell['conversionPathValue'];
for (var nodeIndex = 0, node; node = nodes[nodeIndex]; ++nodeIndex) {
nodeCell.push(node['nodeValue']);
}
outputRow.push(nodeCell.join(' > ')); //TODO: should we auto-expand?
}
}
outputTable.push(outputRow);
if (outputRow.length > maxCols) {
maxCols = outputRow.length;
}
}
var range = activeSheet.getRange(row.count, 1,
outputTable.length,
maxCols);
range.setValues(outputTable);
}
/**
* Returns an array with all the indicies of columns that contain the ga:date
* dimension. The indicies start from 0.
* @param {object} headers The header object returned from the API.
* @return {Array.<number>} The 0-based indicies of the columns that contain.
*/
function getDateColumns_(headers) {
var indicies = [];
for (var colIndex = 0; colIndex < headers.length; ++colIndex) {
var name = headers[colIndex].getName();
if (name == 'ga:date') {
indicies.push(colIndex);
}
}
return indicies;
}
/**
* Converts a string in the format yyyyMMdd to the format yyyy-MM-dd.
* @param {String} gaDate The string to convert.
* @return {String} The formatted string.
*/
function convertToIsoDate_(gaDate) {
var year = gaDate.substring(0, 4);
var month = gaDate.substring(4, 6);
var day = gaDate.substring(6, 8);
return [year, month, day].join('-');
}
/**
* The output text that should be displayed in the log.
* @private.
*/
var logArray_;
/**
* Clears the in app log.
* @private.
*/
function setupLog_() {
logArray_ = [];
}
/**
* Appends a string as a new line to the log.
* @param {String} value The value to add to the log.
*/
function log_(value) {
logArray_.push(value);
var app = UiApp.getActiveApplication();
var foo = app.getElementById('log');
foo.setText(getLog_());
}
/**
* Returns the log as a string.
* @return {string} The log.
*/
function getLog_() {
return logArray_.join('\n');
}
/**
* Displays the log in memory to the user.
*/
function displayLog_() {
var uiLog = UiApp.createApplication().setTitle('Report Status')
.setWidth(400).setHeight(500);
var panel = uiLog.createVerticalPanel();
uiLog.add(panel);
var txtOutput = uiLog.createTextArea().setId('log').setWidth('400')
.setHeight('500').setValue(getLog_());
panel.add(txtOutput);
SpreadsheetApp.getActiveSpreadsheet().show(uiLog);
}