My first Apps Script - can this be more efficient? - google-apps-script

I am a maths teacher who is learning programming and this is my first attempt at using Apps Script. I have used this site to find various bits of code and put them together to form a script that works (thank you to the numerous posters on this site who have unknowingly helped me already!).
My question is: can it be polished and made more efficient? I'm assuming it probably can - I'm grateful for help anyone is kind enough to provide.
Outline:
There are 2 existing Google Sheets - "DATA", containing a list of students and their data; "TEMPLATE", used to create a separate Google Sheet for each student.
The script collects the StudentID numbers from DATA and creates a new Google Sheet for each student based on the TEMPLATE Google Sheet.
Put the studentID in the filename and copy the studentID to cell U1 in the first tab [TEMPLATE contains 2 tabs] then hide the 2nd tab [called 'import'].
Add the student as a viewer.
Restrict copy/download/print for the viewer.
Many thanks,
Dan
function createSheets()
{
// ID of the Google Sheet to be used as a template for the student Sheets
var templateId = '###TEMPLATE###';
// Collect all the studentIDs from DATA and store in a variable called 'students'
var students = Sheets.Spreadsheets.Values.get('###DATA###', '###SheetName###');
for(var i = 0; i < students.values.length; i++)
{
var studentId = students.values[i][0];
// Make a copy of the template file
var documentId = DriveApp.getFileById(templateId).makeCopy().getId();
// Rename the copied file
DriveApp.getFileById(documentId).setName(studentId + ' Learner Map');
// Insert the studentId in cell U1 (of the first tab?)
SpreadsheetApp.openById(documentId).getRange('U1').setValue(studentId);
// Hide the 'import' tab
SpreadsheetApp.openById(documentId).getSheetByName('import').hideSheet();
// Add the student as a viewer
SpreadsheetApp.openById(documentId).addViewer(studentId + '###EMAIL EXTENSION##');
// Restrict copy/download/print for the viewer
// get the file with the Advanced Drive API (REST V2)
var file = Drive.Files.get(documentId);
Logger.log('File "%s", restricted label was: %s', file.title, file.labels.restricted);
// set the restricted label
file.labels.restricted = true;
//update the file
Drive.Files.update(file, documentId);
// check the updated file
var updatedFile = Drive.Files.get(documentId);
Logger.log('File "%s", restricted label is: %s', updatedFile.title, updatedFile.labels.restricted);
}
}

Related

Google Apps Script getting information from google directory

I have been tasked with creating an apps script for google forms that when submitted will send an email with the responses to several people. The form is for bonus recommendation so it will need to go to HR, the executive team, and the employees manager. I have the script working to where it will email the requested HR and the executive team but not the manager. HR and executive was simple because those are static teams, but the manager will change depending on the employee. I have the script where it will get the get the name of the employee and store it in the variable employeeName. I see that apps script has the admin directory service that I should be able to pull from the I am struggling to make it work the way I need. Below is the script so far.
function onFormSubmit(e) {
var responses = e.namedValues;
var htmlBody = '<ol>';
var ss = SpreadsheetApp.openById('redacted');
var gSheet = ss.getSheetByName('Form Responses 1');
var lastRow = gSheet.getLastRow();
var range = gSheet.getRange(lastRow,3);
var employeeName = range.getValue();
for (Key in responses) {
var header = Key;
var data = responses[Key];
htmlBody += '<li>' + header + ': ' + data + '</li>';
};
htmlBody += '</ol>';
GmailApp.sendEmail('redacted', 'Bonus Recommendation', '', {htmlBody:htmlBody})
}
I feel like I am missing something super simple. I should also add that I am NOT a scripter or programmer by any stretch. I'm just the net admin who drew the short straw for this project. (It does seem that I will be learning javascript so I can use apps script better).
Each namedValues is a array so try this:
responses[Key][0];
The reason they do that is because if you reuse the same form repeated the form continues to add new columns for different questions and you can end up with the same name in two different columns.
Personally, I would have used e.values.

Script that adds new row to sheets in selected AND another Google Spreadsheet

I'm working on a Spreadsheet to keep track of team member's project hours. I've created a Spreadsheet per team member for them to fill out weekly, and a project overview Spreadsheet that takes in all data through IMPORTRANGE.
To be able to quickly add a new project I want a macro to insert a new row in the Project overview + the separate Spreadsheets per team member. However I can't figure out how to write the correct code for the separate team member Spreadsheets. What's going wrong here?
If possible I'd also like to make a macro to DELETE a row in the project overview + team member spreadsheets, and one to HIDE a row...
Project overview
Team member Kate
Team member David
My current code:
function InsertRow() {
var ss = SpreadsheetApp.getActive();
var allsheets = ss.getSheets();
var row = SpreadsheetApp.getActiveRange().getRow();
// Array holding the names of the sheets to exclude from the execution
// I only managed to make it work when I exclude the sheet that I actually want to affect instead of the other way around?
var exclude = (["PROJECTS"] ||
SpreadsheetApp.openById("1xjR3lx5_KAA9nqiD3YsjZnulQaMyWGPQqgYsjtzQ0xI").getSheets() ||
SpreadsheetApp.openById("1Q5gtZlqf41of1Zwi8pvZbDx4NN5LcDh5SxfwasLUDMU").getSheets())
for(var s in allsheets){
var sheet = allsheets[s];
// Stop iteration execution if the condition is meet.
if(exclude.indexOf(sheet.getName())==-1) continue;
sheet.insertRowBefore(row);
}
}
As I see it you have a couple of options, which I'll be listing here as A, B, and C. Please note that you might need two different .GS files as you are linking to two sheets
A
Try code found on google app script documentation
I found the google apps script documentation for this command found here, so you might want to check that for this questions and others , but here is the exact code included
// The code below opens a spreadsheet using its ID and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openById("abc1234567");
Logger.log(ss.getName());
B
use open by url instead of open by id
Your issue might be that your current id isn't correct, I have no way of knowing, so here is some alternate code here (link to documentation here)
// The code below opens a spreadsheet using its id and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openByUrl(
'https://docs.google.com/spreadsheets/d/abc1234567/edit');
Logger.log(ss.getName());
C
Tie the google script to one sheet
This last option doesn't require any code, just an explanation. Instead of trying to link your script to two separate sheets, you might be able to automatically link it to a single google sheet and create two pages in the sheets file that you treat as two different sheets but are one thing. This might not be what you want, but I included it anyways. You link the sheet to the code automatically by:
1 opening your sheet
2 going to "tools"
3 clicking script editor
4 copy and paste your code (except for the "open by id" part)
5 success!
Your exclude variable doesn't contain what you think it does. You're using an "or" operator (||), which will take the first "truthy" value and skip the rest.
console.log((["PROJECTS"] || 'something else')); // ["PROJECTS"]
Moreover, you don't have a good way of telling which spreadsheet belongs to which team member. To solve that problem, you can create an object.
const teamSpreadsheetIds = {
'DAVID': 'ABC',
'KATE': '123',
};
console.log(teamSpreadsheetIds['DAVID']); // ABC
With the teamSpreadsheetIds object, you can now go about updating your team member sheets locally as well as their individual spreadsheets. The "PROJECTS" sheet is unique, so there's only one check for it.
function InsertRow() {
const ss = SpreadsheetApp.getActive();
const allSheets = ss.getSheets();
const row = SpreadsheetApp.getActiveRange().getRow();
const teamSpreadsheetIds = {
'DAVID': '1Q5gtZlqf41of1Zwi8pvZbDx4NN5LcDh5SxfwasLUDMU',
'KATE': '1xjR3lx5_KAA9nqiD3YsjZnulQaMyWGPQqgYsjtzQ0xI',
};
for (let sheet of allSheets) {
const sheetName = sheet.getName();
const memberSpreadsheetId = teamSpreadsheetIds[sheetName];
const isSkippable = memberSpreadsheetId === undefined && sheetName !== 'PROJECTS';
if (isSkippable) { continue };
// Insert a row in the local sheet
sheet.insertRowBefore(row);
// Get the member sheet and insert a row
if (memberSpreadsheetId) {
const memberSpreadsheet = SpreadsheetApp.openById(memberSpreadsheetId);
const memberSheet = memberSpreadsheet.getSheets()[0]; // Assumes the first sheet is the one to modify
memberSheet.insertRowBefore(row);
}
}
}

"setOwner" not a function error - App Script

new to Google Scripts and I've looked through other posts on Stack Overflow as well but couldn't find a good answer.
I'm using data collected in Google Sheets to search for a file in Google Drive and transfer ownership of the file. I have google form that my users fill out, once submitted using an add-on I create a file based on the data that was submitted on the form. Now with the script, I'm trying to go gather certain information from sheets such as name, email, and company name -
Sample data image here.
What I have thus far:
function myFunction() {
//Get google sheets
var spreadsheetId = '1WvIIoYdmuIB5BQ3KgSYOOIiEn-K_GTzCkb7rITzRFck';
//get certain values from sheets
var rangeName = 'MDP Form!C25:E';
var values = Sheets.Spreadsheets.Values.get(spreadsheetId, rangeName).values;
if (!values) {
Logger.log('No data found.');
} else {
Logger.log('Name, Email, Customer:');
for (var row = 0; row < values.length; row++) {
// Print columns C and E, which correspond to indices 0 and 4.
Logger.log('Name: %s, Email: %s, Company: %s', values[row][0], values[row][1], values[row][2]);
//Utilities.sleep(90000);
//Searching through google drive
var name = (values[row][0]);
var email = (values[row][1]);
Logger.log(email);
var company = (values[row][2]);
var fileName = ('Mutual Delivery Plan ' + company + ' - ' + name);
Logger.log(fileName);
//add a 1 minute delay
//Utilities.sleep(90000);
//search for target folder
var folder = DriveApp.getFolderById('1whvRupu9hWdyl2CqSF-KvdVj8VE6iiQu');
//search for file by name within folder
var mdpFile = folder.searchFiles(fileName);
//transfer ownership
mdpFile.setOwner(email);
}
}
}
Problem:
The script works for the most part except for the last line "setOwner" is not a function. I've tried creating a separate function for this, used some other suggestions on other posts but still cannot get this to work. If anyone has ideas around what might I be missing here or suggestions that would be super helpful. Thanks!
I believe your goal as follows.
You want to transfer the owner of the file when the file with fileName is found in folder.
For this, how about this answer?
Modification points:
Although you say The script works for the most part except for the last line "setOwner" is not a function., if your script in your question is the current script, how about the following modification?
In your script, fileName is 'Mutual Delivery Plan ' + company + ' - ' + name, and fileName is used with var mdpFile = folder.searchFiles(fileName);. In this case, an error occurs. Because params of searchFiles(params) is required to be the query string.
I think that in your case, it's "title='" + fileName + "'".
Also searchFiles(fileName) returns FileIterator. This has already mentioned by the existing answer. Because at Google Drive, the same filenames can be existing in the same folder and each files are managed by the unique ID. So here, it is required to be modified as follows.
I think that in your case, the following flow is useful.
Confirm whether the file is existing using hasNext().
When the file is existing and the owner is you, the owner of the file is changed to email.
When above points are reflected to your script, please modify as follows.
Modified script:
From:
var mdpFile = folder.searchFiles(fileName);
//transfer ownership
mdpFile.setOwner(email);
To:
var mdpFile = folder.searchFiles("title='" + fileName + "'");
while (mdpFile.hasNext()) {
var file = mdpFile.next();
if (file.getOwner().getEmail() == Session.getActiveUser().getEmail()) {
file.setOwner(email);
}
}
If you don't need to check whether the owner is you, please remove if (file.getOwner().getEmail() == Session.getActiveUser().getEmail()) {.
Note:
In this case, when the file with the filename of fileName is not existing in folder, the script in the if statement is not run. Please be careful this.
Also, when there are several files with the same filename in folder, the owner of those is changed to email.
References:
searchFiles(params)
FileIterator
hasNext()
next()
getActiveUser()
Folder.searchFiles() returns a fileIterator not a file. If it's the only file with that name then you can usually getaway with mdpFile.next();
File Iterator

Google Forms Rename "File upload" File to "Question - Submitter"

I am using Google Forms to collect images from team members and I want to ensure that each file that is uploaded to Google Forms and saved in Google Drive has the same naming convention.
There are five File upload questions that team members are asked to upload their images to. The files are placed into Google Drive folders with random file names followed by - firstName lastName. On an onFormSubmit trigger I would like to change the names of the user-provided files to fileUploadQuestionName - firstName lastName.
I am pretty new to Google Apps Script and I have no idea how to go about doing this. Any help would be greatly appreciated!
You can change the name of the uploaded file on each form submit by the following process
retrieve the last form response onFormSubmit with form.getResponses()[LAST FORM SUBMISSION]
retrieve the ID of the uploaded file with getItemResponses()[QUESTION NUMBER].getResponse()
open the file ID with DriveApp and change its name as desired
function myFunction() {
var form=FormApp.getActiveForm();
// returns the total number of form submissions
var length=form.getResponses().length;
//replace QUESTION NUMBER through the index of the question prompting the file upload - keep in mind that the first question has the index 0
var id=form.getResponses()[length-1].getItemResponses()[QUESTION NUMBER].getResponse();
//getResponses()[length-1] retrieves the last form response, accounting for the fact that the first index is zero and hte last length-1
//gets the name of the question
var fileUploadQuestionName=form.getResponses()[length-1].getItemResponses()[QUESTION NUMBER].getItem().getTitle();
//accesses the uploaded file
var file=DriveApp.getFileById(id);
name = file.getName();
//changes the file name
var name = fileUploadQuestionName+' - '+name.split(' - ')[1]
file.setName(name);
}
PS: If you want to change a posteriori the names of all the files submitted and not just the last files - you need to loop through all form responses:
for(var i=0;i<length;i++){
var id=form.getResponses()[i].getItemResponses()[QUESTION NUMBER].getResponse();
...
...
}
The easiest way to do this would likely be by either iterating through the specified folder in google drive on a time-based trigger and either checking if they meet your specified condition or moving them to a different folder after they're renamed.
function checkFile()
{
var files = DriveApp.getFolderById('ID of Folder').getFiles();
// you can find this by going to form responses and clicking the link to an attached file and copying the id from the URL
// https://drive.google.com/drive/folders/xxxxxxxxxxxxxxxxxxxxxxxxxxx
var fileUploadQuestionName = 'Test';
while(files.hasNext())
{
var file = files.next();
var name = file.getName();
if(name.indexOf(fileUploadQuestionName) == -1)
{
name = fileUploadQuestionName+' - '+name.split('-')[1]
file.setName(name);
}
}
}
From there you'll need to add a time-based trigger to run every hour or day or minute depending on how critical it is to always find files of the correct name.
I can't find any documentation on accessing the file from within the response item on google's formApp documentation.

Google Drive Auto-Expire - Unshare Multiple Files Simultaneously?

Good Evening! I'm Aaron Ayres, an 8th grade mathematics teacher at Noblesville West Middle School in Noblesville, Indiana. Recently for a school-wide incentive my school is using Google Drive to share a "pass" that grants nominated students access to rewards and other treats during the month they are nominated. We typically have about 80 students who are nominated on a monthly basis for their hard work - each educator in the building nominates one student each month based on multiple criteria.
Instead of having to manually go through my folder and un-share the individual passes at the end of the month, I have been researching for a more efficient method of allowing the passes to expire. I came across Amit Argawal's Auto-expire script (http://www.labnol.org/internet/auto-expire-google-drive-links/27509/) and I think it could potentially work but it only expires one file at a time. Is there a way I can modify the script (or create a new script) to reference multiple, sharable links in the script editor so the sharing access to each individual students' pass expires at the end of the month? In other words, is there a way for the script to expire all 80+ passes at the same date and time? I'm a novice to Google Scripts so I'm wondering if I am not formatting my list or URL references correctly so the script recognizes multiple files, or perhaps I could revise the script to recognize an array of links instead of a single variable url link.
Thanks in advance for the assistance!
I hope you have the full script at your hand.Please give the URLs in a list, separated with commas. Are the passwords different for each student? Otherwise you could have just added the URL of the folder without changing the original script at all.
However, Please try changing these portions of the script:
// Enter the full list of URLs(separated with comma) of the public Google Drive file or folder
var URL_LS = ["https://drive.google.com/open?id=0B_NmiOlCM-VTcFp6U1hydTM2MzVCY3AzMUpuTEtVWUhXRXNz", "https://drive.google.com/file/d/0B_NmiOlCM-VTNXFqOU1jTG5JdHJVQ3ZGUGNqZklJNWludllr/view?usp=sharing"] ;
var URL_LN = URL_LS.length;
// Enter the expiry date in YYYY-MM-DD HH:MM format
var EXPIRY_TIME = "2016-02-18 09:46";
And again,replace the autoExpire() as follows:
function autoExpire() {
var id, asset, i, email, users;
try {
for (var j = 0; j < URL_LN; j++) {
var URL = URL_LS[j];
var id = URL.match(/[-\w]{25,}/g);
if (id) {
id = id[id.length-1];
asset = DriveApp.getFileById(id) ? DriveApp.getFileById(id) : DriveApp.getFolderById(id);
if (asset) {
asset.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.NONE);
asset.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.NONE);
users = asset.getEditors();
for (i in users) {
email = users[i].getEmail();
if (email !== "") {
asset.removeEditor(email);
}
}
users = asset.getViewers();
for (i in users) {
email = users[i].getEmail();
if (email !== "") {
asset.removeViewer(email);
}
}
}
}
}
} catch (e) {
Logger.log(e.toString());
}
}