+
+
+
+
From 460a3fcf521a517066284bd7cf91b940783cff13 Mon Sep 17 00:00:00 2001
From: Arun Nagarajan
Date: Wed, 15 May 2013 13:52:41 -0300
Subject: [PATCH 03/12] create readme.md
---
IO2013/YouTubeAnalytics/README.md | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 IO2013/YouTubeAnalytics/README.md
diff --git a/IO2013/YouTubeAnalytics/README.md b/IO2013/YouTubeAnalytics/README.md
new file mode 100644
index 0000000..b58a707
--- /dev/null
+++ b/IO2013/YouTubeAnalytics/README.md
@@ -0,0 +1,9 @@
+Companion code for Google IO 2013 session - Ad Hoc Analysis with Google Apps Script and YouTube Analytics API
+
+https://developers.google.com/events/io/sessions/328316141
+
+For setup -
+
+You'll need to create OAuth 2 client ID/secret at http://developers.google.com/console and store it in the right ScriptProperties.
+
+Similarly for the Twitter sample, you'll need to create a Key/Secret with the Twitter API.
From 09a771fe38131cc8fac161045e0997f25c0d571c Mon Sep 17 00:00:00 2001
From: Arun Nagarajan
Date: Thu, 16 May 2013 13:51:55 -0400
Subject: [PATCH 04/12] added code from drive talk
---
IO2013/Drive/Code.gs | 108 ++++++++++++++++++
IO2013/Drive/SpreadsheetUtils.gs | 189 +++++++++++++++++++++++++++++++
IO2013/Drive/ui.html | 53 +++++++++
3 files changed, 350 insertions(+)
create mode 100644 IO2013/Drive/Code.gs
create mode 100644 IO2013/Drive/SpreadsheetUtils.gs
create mode 100644 IO2013/Drive/ui.html
diff --git a/IO2013/Drive/Code.gs b/IO2013/Drive/Code.gs
new file mode 100644
index 0000000..1e473d9
--- /dev/null
+++ b/IO2013/Drive/Code.gs
@@ -0,0 +1,108 @@
+function doGet(e) {
+ var HTMLToOutput;
+ if(e.parameter.state){
+ var state = JSON.parse(e.parameter.state);
+ if(state.action === 'create'){
+ var newFolder = DriveApp.createFolder('New Assignment');
+
+ var templateQuiz = DriveApp.getFileById(QUIZ_TEMPLATE);
+ var templateRoster = DriveApp.getFileById(ROSTER_TEMPLATE);
+
+ var newQuiz = templateQuiz.makeCopy('Quiz Questions');
+ newFolder.addFile(newQuiz);
+ DriveApp.getRootFolder().removeFile(newQuiz);
+
+ var newRoster = templateRoster.makeCopy('Roster');
+ newFolder.addFile(newRoster);
+ DriveApp.getRootFolder().removeFile(newRoster);
+
+ var objectToSaveForClose = { type: 'close',
+ rosterID : newRoster.getId()}
+
+ var objectToSaveForPublish = { type: 'publish',
+ assignmentFolderID : newFolder.getId(),
+ rosterID : newRoster.getId(),
+ quizID : newQuiz.getId()}
+
+ var closeBlob = Utilities.newBlob(JSON.stringify(objectToSaveForClose), 'application/drive-assignment-creator', 'Close Quiz');
+ var publishBlob = Utilities.newBlob(JSON.stringify(objectToSaveForPublish), 'application/drive-assignment-creator', 'Publish Quiz');
+ newFolder.createFile(closeBlob);
+ newFolder.createFile(publishBlob);
+
+ HTMLToOutput = 'Assignment folder created with necessary template. Please update the Roster, fill in the quiz questions and click on the Publish icon in the folder. ';
+ } else {
+ var fileID = state.ids[0];
+ var content = DriveApp.getFileById(fileID).getBlob().getDataAsString();
+ var contentObject = JSON.parse(content);
+ var ssID = contentObject.rosterID;
+ var ss = SpreadsheetApp.openById(ssID);
+ var sheet = ss.getSheets()[0];
+ var rosterRange = ss.getRangeByName('RosterRange');
+ var rosterObjects = getRowsData(sheet, rosterRange);
+
+
+ if(contentObject.type === 'close'){
+ for(var i in rosterObjects){
+ var rosterItem = rosterObjects[i];
+ var quickCopy = DriveApp.getFileById(rosterItem.fileid);
+ quickCopy.removeEditor(rosterItem.studentEmailAddress);
+ quickCopy.addViewer(rosterItem.studentEmailAddress);
+ }
+ HTMLToOutput = 'Quiz is now closed. Your students will no longer be able to save any changes. They wil be able to view your notes on their quizes. ';
+
+ }else if(contentObject.type === 'publish'){
+ var quizFileID = contentObject.quizID;
+ var quiz = DriveApp.getFileById(quizFileID);
+ var folderID = contentObject.assignmentFolderID;
+ var folder = DriveApp.getFolderById(folderID);
+
+ for(var i in rosterObjects){
+ var rosterItem = rosterObjects[i];
+ var quizCopy = quiz.makeCopy('Quiz - ' + rosterItem.studentName);
+ folder.addFile(quizCopy);
+ DriveApp.getRootFolder().removeFile(quizCopy);
+ quizCopy.addEditor(rosterItem.studentEmailAddress);
+ rosterItem.fileid = quizCopy.getId();
+ }
+ setRowsData(sheet,rosterObjects);
+ HTMLToOutput = 'Completed publishing your quizes. Your students should be able to see the files in their "Shared with me" view.';
+ }
+
+ }
+
+ }
+ else if(e.parameter.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit
+ getAndStoreAccessToken(e.parameter.code);
+ HTMLToOutput = 'App is installed, you can close this window now or open up Google Drive.';
+ }
+ else {//we are starting from scratch or resetting
+ }
+ var t = HtmlService.createTemplateFromFile('ui')
+ t.message = HTMLToOutput;
+ return t.evaluate().setSandboxMode(HtmlService.SandboxMode.NATIVE)
+}
+
+function getURLForAuthorization(){
+ return AUTHORIZE_URL + '?response_type=code&client_id='+CLIENT_ID+'&redirect_uri='+REDIRECT_URL +'&scope=https://www.googleapis.com/auth/drive.install';
+}
+
+function getAndStoreAccessToken(code){
+ var parameters = { method : 'post',
+ payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code};
+ //no need to do anything with the token going forward.
+ var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText();
+}
+
+
+//replace with any template files you want -
+var ROSTER_TEMPLATE = '0AkJNj_IM2wiPdEZzbFUxMjQteGtQS1JadHg5VmFMdGc';
+var QUIZ_TEMPLATE = '1_LIZp1lahFhNouXsZNI1s6UluBskt_LOwQGlJIj2ftY';
+
+var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth';
+var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token';
+
+//put your URL below or use ScriptApp.getService().getUrl();
+var REDIRECT_URL= 'https://script.google.com/macros/s/AKfycbxK6aULLp8CL47aiUM_tHZCX9-2YNOK0yrp-ujnUQV8CrUQkUGk/exec';
+var CLIENT_ID = ScriptProperties.getProperty('CLIENT_ID')
+var CLIENT_SECRET = ScriptProperties.getProperty('CLIENT_SECRET');
+
diff --git a/IO2013/Drive/SpreadsheetUtils.gs b/IO2013/Drive/SpreadsheetUtils.gs
new file mode 100644
index 0000000..aca6a3f
--- /dev/null
+++ b/IO2013/Drive/SpreadsheetUtils.gs
@@ -0,0 +1,189 @@
+//https://developers.google.com/apps-script/storing_data_spreadsheets#reading
+
+
+// getRowsData iterates row by row in the input range and returns an array of objects.
+// Each object contains all the data for a given row, indexed by its normalized column name.
+// Arguments:
+// - sheet: the sheet object that contains the data to be processed
+// - range: the exact range of cells where the data is stored
+// - columnHeadersRowIndex: specifies the row number where the column names are stored.
+// This argument is optional and it defaults to the row immediately above range;
+// Returns an Array of objects.
+function getRowsData(sheet, range, columnHeadersRowIndex) {
+ columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1;
+ var numColumns = range.getLastColumn() - range.getColumn() + 1;
+ var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns);
+ var headers = headersRange.getValues()[0];
+ return getObjects(range.getValues(), normalizeHeaders(headers));
+}
+
+// setRowsData fills in one row of data per object defined in the objects Array.
+// For every Column, it checks if data objects define a value for it.
+// Arguments:
+// - sheet: the Sheet Object where the data will be written
+// - objects: an Array of Objects, each of which contains data for a row
+// - optHeadersRange: a Range of cells where the column headers are defined. This
+// defaults to the entire first row in sheet.
+// - optFirstDataRowIndex: index of the first row where data should be written. This
+// defaults to the row immediately below the headers.
+function setRowsData(sheet, objects, optHeadersRange, optFirstDataRowIndex) {
+ var headersRange = optHeadersRange || sheet.getRange(1, 1, 1, sheet.getMaxColumns());
+ var firstDataRowIndex = optFirstDataRowIndex || headersRange.getRowIndex() + 1;
+ var headers = normalizeHeaders(headersRange.getValues()[0]);
+
+ var data = [];
+ for (var i = 0; i < objects.length; ++i) {
+ var values = []
+ for (j = 0; j < headers.length; ++j) {
+ var header = headers[j];
+ // If the header is non-empty and the object value is 0...
+ if ((header.length > 0) && (objects[i][header] == 0)) {
+ values.push(0);
+ }
+ // If the header is empty or the object value is empty...
+ else if ((!(header.length > 0)) || (objects[i][header]=='')) {
+ values.push('');
+ }
+ else {
+ values.push(objects[i][header]);
+ }
+ }
+ data.push(values);
+ }
+
+ var destinationRange = sheet.getRange(firstDataRowIndex, headersRange.getColumnIndex(),
+ objects.length, headers.length);
+ destinationRange.setValues(data);
+}
+
+// getColumnsData iterates column by column in the input range and returns an array of objects.
+// Each object contains all the data for a given column, indexed by its normalized row name.
+// Arguments:
+// - sheet: the sheet object that contains the data to be processed
+// - range: the exact range of cells where the data is stored
+// - rowHeadersColumnIndex: specifies the column number where the row names are stored.
+// This argument is optional and it defaults to the column immediately left of the range;
+// Returns an Array of objects.
+function getColumnsData(sheet, range, rowHeadersColumnIndex) {
+ rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1;
+ var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues();
+ var headers = normalizeHeaders(arrayTranspose(headersTmp)[0]);
+ return getObjects(arrayTranspose(range.getValues()), headers);
+}
+
+
+// For every row of data in data, generates an object that contains the data. Names of
+// object fields are defined in keys.
+// Arguments:
+// - data: JavaScript 2d array
+// - keys: Array of Strings that define the property names for the objects to create
+function getObjects(data, keys) {
+ var objects = [];
+ for (var i = 0; i < data.length; ++i) {
+ var object = {};
+ var hasData = false;
+ for (var j = 0; j < data[i].length; ++j) {
+ var cellData = data[i][j];
+ if (isCellEmpty(cellData)) {
+ continue;
+ }
+ object[keys[j]] = cellData;
+ hasData = true;
+ }
+ if (hasData) {
+ objects.push(object);
+ }
+ }
+ return objects;
+}
+
+// Returns an Array of normalized Strings.
+// Arguments:
+// - headers: Array of Strings to normalize
+function normalizeHeaders(headers) {
+ var keys = [];
+ for (var i = 0; i < headers.length; ++i) {
+ var key = normalizeHeader(headers[i]);
+ if (key.length > 0) {
+ keys.push(key);
+ }
+ }
+ return keys;
+}
+
+// Normalizes a string, by removing all alphanumeric characters and using mixed case
+// to separate words. The output will always start with a lower case letter.
+// This function is designed to produce JavaScript object property names.
+// Arguments:
+// - header: string to normalize
+// Examples:
+// "First Name" -> "firstName"
+// "Market Cap (millions) -> "marketCapMillions
+// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
+function normalizeHeader(header) {
+ var key = "";
+ var upperCase = false;
+ for (var i = 0; i < header.length; ++i) {
+ var letter = header[i];
+ if (letter == " " && key.length > 0) {
+ upperCase = true;
+ continue;
+ }
+ if (!isAlnum(letter)) {
+ continue;
+ }
+ if (key.length == 0 && isDigit(letter)) {
+ continue; // first character must be a letter
+ }
+ if (upperCase) {
+ upperCase = false;
+ key += letter.toUpperCase();
+ } else {
+ key += letter.toLowerCase();
+ }
+ }
+ return key;
+}
+
+// Returns true if the cell where cellData was read from is empty.
+// Arguments:
+// - cellData: string
+function isCellEmpty(cellData) {
+ return typeof(cellData) == "string" && cellData == "";
+}
+
+// Returns true if the character char is alphabetical, false otherwise.
+function isAlnum(char) {
+ return char >= 'A' && char <= 'Z' ||
+ char >= 'a' && char <= 'z' ||
+ isDigit(char);
+}
+
+// Returns true if the character char is a digit, false otherwise.
+function isDigit(char) {
+ return char >= '0' && char <= '9';
+}
+
+// Given a JavaScript 2d Array, this function returns the transposed table.
+// Arguments:
+// - data: JavaScript 2d Array
+// Returns a JavaScript 2d Array
+// Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]].
+function arrayTranspose(data) {
+ if (data.length == 0 || data[0].length == 0) {
+ return null;
+ }
+
+ var ret = [];
+ for (var i = 0; i < data[0].length; ++i) {
+ ret.push([]);
+ }
+
+ for (var i = 0; i < data.length; ++i) {
+ for (var j = 0; j < data[i].length; ++j) {
+ ret[j][i] = data[i][j];
+ }
+ }
+
+ return ret;
+}
\ No newline at end of file
diff --git a/IO2013/Drive/ui.html b/IO2013/Drive/ui.html
new file mode 100644
index 0000000..c009cf0
--- /dev/null
+++ b/IO2013/Drive/ui.html
@@ -0,0 +1,53 @@
+
+Google Drive Quiz Manager
+
+
+
+
+
+
+
+
+
+
+
+
+
Please click on the link below to install the Quiz Manager app to your Google Drive. You will be redirected to a standard Google authorization page.