├── README.md
├── Styles.html
├── PropertySelector.html
├── ProcessDimensions.html
├── SelectorJavaScript.html
├── ProcessJavaScript.html
└── Code.gs
/README.md:
--------------------------------------------------------------------------------
1 | # ga-custom-dimension-manager
2 | Custom Dimension Manager for Google Sheets
3 |
--------------------------------------------------------------------------------
/Styles.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 |
16 | Select account
17 |
18 |
19 |
20 | Select property
21 |
22 |
23 |
24 | Process dimensions
25 |
26 | 1-20
27 |
28 | 1-200
29 |
30 |
31 |
32 | Start
33 | Close
34 |
35 | != include('SelectorJavaScript'); ?>
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/ProcessDimensions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | != include('Styles') ?>
7 |
10 |
11 |
12 |
13 | Processing dimension 1 / = data.limit ?>
14 |
15 |
16 |
17 |
18 |
19 | Old (Google Analytics)
20 |
21 |
22 |
23 |
24 | New (Source Data)
25 |
26 |
27 |
28 |
29 | Name:
30 | Scope:
31 | Active:
32 |
33 |
34 | =>
35 |
36 |
37 | Name:
38 | Scope:
39 | Active:
40 |
41 |
42 |
43 |
44 |
45 |
46 | Update
47 | Skip
48 | Cancel
49 |
50 | != include('ProcessJavaScript') ?>
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/SelectorJavaScript.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ProcessJavaScript.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------