├── Code.gs ├── ProcessDimensions.html ├── ProcessJavaScript.html ├── PropertySelector.html ├── README.md ├── SelectorJavaScript.html └── Styles.html /Code.gs: -------------------------------------------------------------------------------- 1 | function buildSourceSheet() { 2 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Source Data'); 3 | var i, defaultDim, scopeRange, scopeRule, activeRange, activeRule; 4 | if (!sheet) { 5 | sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('Source Data'); 6 | } 7 | defaultDim = sheet.getRange(2, 2, 1, 3).getValues(); 8 | sheet.getRange(1, 2, 1, 3).setValues([['Name', 'Scope', 'Active']]); 9 | sheet.getRange(2, 1, 1, 1).setValue('DEFAULT/EMPTY'); 10 | sheet.getRange(1, 1, 203, 4).setNumberFormat('@'); 11 | if (isEmpty(defaultDim[0])) { 12 | sheet.getRange(2, 2, 1, 3).setNumberFormat('@').setValues([['(n/a)', 'HIT', 'false']]); 13 | } 14 | for (i = 1; i <= 200; i++) { 15 | sheet.getRange(2 + i, 1, 1, 1).setValue('ga:dimension' + i); 16 | } 17 | 18 | // Set validation for SCOPE 19 | scopeRange = sheet.getRange(2, 3, 201, 1); 20 | scopeRule = SpreadsheetApp.newDataValidation() 21 | .requireValueInList(['HIT','PRODUCT','SESSION','USER']) 22 | .setAllowInvalid(false) 23 | .setHelpText('Scope must be one of HIT, PRODUCT, SESSION or USER') 24 | .build(); 25 | scopeRange.setDataValidation(scopeRule); 26 | 27 | // Set validation for ACTIVE 28 | activeRange = sheet.getRange(2, 4, 201, 1); 29 | activeRule = SpreadsheetApp.newDataValidation() 30 | .requireValueInList(['true', 'false']) 31 | .setAllowInvalid(false) 32 | .setHelpText('Active must be one of true or false') 33 | .build(); 34 | activeRange.setDataValidation(activeRule); 35 | } 36 | 37 | function fetchAccounts() { 38 | return Analytics.Management.AccountSummaries.list({ 39 | fields: 'items(id,name,webProperties(id,name))' 40 | }); 41 | } 42 | 43 | function include(filename) { 44 | return HtmlService.createHtmlOutputFromFile(filename) 45 | .getContent(); 46 | } 47 | 48 | function isEmpty(row) { 49 | return /^$/.test(row[0]) && /^$/.test(row[1]) && /^$/.test(row[2]); 50 | } 51 | 52 | function isValid(row) { 53 | var name = row[0]; 54 | var scope = row[1]; 55 | var active = row[2]; 56 | return !/^$/.test(name) && /HIT|SESSION|USER|PRODUCT/.test(scope) && /true|false/.test(active); 57 | } 58 | 59 | function buildSourceData(sheet) { 60 | var range = sheet.getRange(3, 2, 200, 3).getValues(); 61 | var defaultDim = sheet.getRange(2, 2, 1, 3).getValues(); 62 | if (!isValid(defaultDim[0])) { 63 | throw new Error('Invalid source value found in DEFAULT/EMPTY row'); 64 | } 65 | var sourceDimensions = []; 66 | var i; 67 | for (i = 0; i < range.length; i++) { 68 | if (!isEmpty(range[i]) && !isValid(range[i])) { 69 | throw new Error('Invalid source value found in dimension ga:dimension' + (i + 1)); 70 | } 71 | if (!isEmpty(range[i])) { 72 | sourceDimensions.push({ 73 | id: 'ga:dimension' + (i + 1), 74 | name: range[i][0], 75 | scope: range[i][1], 76 | active: range[i][2] 77 | }); 78 | } else { 79 | sourceDimensions.push({ 80 | name: defaultDim[0][0] || '(n/a)', 81 | scope: defaultDim[0][1] || 'HIT', 82 | active: defaultDim[0][2] || 'false' 83 | }); 84 | } 85 | } 86 | return sourceDimensions; 87 | } 88 | 89 | function updateDimension(action, aid, pid, index, newDimension) { 90 | if (action === 'update') { 91 | return Analytics.Management.CustomDimensions.update(newDimension, aid, pid, 'ga:dimension' + index, {ignoreCustomDataSourceLinks: true}); 92 | } 93 | if (action === 'create') { 94 | return Analytics.Management.CustomDimensions.insert(newDimension, aid, pid); 95 | } 96 | } 97 | 98 | function startProcess(aid, pid, limit) { 99 | var dimensions = Analytics.Management.CustomDimensions.list(aid, pid, {fields: 'items(id, name, scope, active)'}); 100 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Source Data'); 101 | var sourceData = buildSourceData(sheet); 102 | var template = HtmlService.createTemplateFromFile('ProcessDimensions'); 103 | template.data = { 104 | limit: limit, 105 | dimensions: dimensions, 106 | sourceData: sourceData, 107 | accountId: aid, 108 | propertyId: pid 109 | }; 110 | SpreadsheetApp.getUi().showModalDialog(template.evaluate().setWidth(400).setHeight(400), 'Manage Custom Dimensions for ' + pid); 111 | } 112 | 113 | function isValidSheet(sheet) { 114 | var defaultDim = sheet.getRange(2, 2, 1, 3).getValues(); 115 | var dims = sheet.getRange(3, 2, 200, 3).getValues(); 116 | var i; 117 | if (!isValid(defaultDim[0])) { 118 | throw new Error('You must populate the DEFAULT/EMPTY row with proper values'); 119 | } 120 | for (i = 0; i < dims.length; i++) { 121 | if (!isEmpty(dims[i]) && !isValid(dims[i])) { 122 | throw new Error('Invalid values for dimension ga:dimension' + (i + 1)); 123 | } 124 | } 125 | return true; 126 | } 127 | 128 | function openDimensionModal() { 129 | var ui = SpreadsheetApp.getUi(); 130 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Source Data'); 131 | var html = HtmlService.createTemplateFromFile('PropertySelector').evaluate().setWidth(400).setHeight(280); 132 | if (!sheet) { 133 | throw new Error('You need to create the Source Data sheet first'); 134 | } 135 | if (!isValidSheet(sheet)) { 136 | throw new Error('You must populate the Source Data fields correctly'); 137 | } 138 | SpreadsheetApp.getUi().showModalDialog(html, 'Select account and property for management'); 139 | } 140 | 141 | function onOpen() { 142 | SpreadsheetApp.getUi() 143 | .createMenu('Google Analytics Custom Dimension Manager') 144 | .addItem('1. Build/reformat Source Data sheet', 'buildSourceSheet') 145 | .addItem('2. Manage Custom Dimensions', 'openDimensionModal') 146 | .addToUi(); 147 | } 148 | -------------------------------------------------------------------------------- /ProcessDimensions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 |
13 | Processing dimension 1 / 14 |
15 |
16 | 17 | 18 | 21 | 23 | 26 | 27 | 28 | 33 | 36 | 41 | 42 |
19 | Old (Google Analytics) 20 | 22 | 24 | New (Source Data) 25 |
29 | Name:
30 | Scope:
31 | Active: 32 |
34 | => 35 | 37 | Name:
38 | Scope:
39 | Active: 40 |
43 |
44 |
45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ProcessJavaScript.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /PropertySelector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | To CREATE new dimensions, you need EDIT access in both GA Account and Property.
10 | To UPDATE an existing dimension, you need EDIT access in the GA Property. 11 |
12 |
13 | Fetching data... 14 |
15 | 19 | 23 |
24 | Process dimensions 25 |
26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ga-custom-dimension-manager 2 | Custom Dimension Manager for Google Sheets 3 | -------------------------------------------------------------------------------- /SelectorJavaScript.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Styles.html: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------