Script only works when uploading a single file - google-apps-script

When an employee submits the google form, this script renames the file they have uploaded based off of information said employee fills out in the form. (At the moment it is pulling their Name, Job ID, and Address of the current job location, the file is always pictures of what they completed on the jobsite.)
However the script only pulls this information if a single file/picture is uploaded the form instead of all files.
What modification would allow this to rename all uploaded files in the form.
while (files.hasNext()) {
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
var itemResponseFname = itemResponses[0];
var itemResponseLname = itemResponses[10];
var itemResponseID = itemResponses[11];
var itemResponsePhoto = itemResponses[13];
var photoID = itemResponsePhoto.getResponse();
var newName = itemResponseFname.getResponse() + " " + itemResponseLname.getResponse() + " - " + itemResponseID.getResponse();
var url = baseString + photoID + endString;
var urlCheck = file.getUrl();
if ( url == urlCheck) {
var modName = newName + ".jpg";
file.setName(modName);

The easiest way of doing such thing is the following:
Install a submit trigger
Get the list of IDs of submitted files
Change its name to whatever you like
In practice this will look something like this:
function submit(e) {
const itemResponses = e.response.getItemResponses()
// Read values
const fname = itemResponses[0].getResponse()
const lname = itemResponses[10].getResponse()
const uid = itemResponses[11].getResponse()
const imageIds = itemResponses[13].getResponse()
// Iterate images
for (let imgId of imageIds){
const image = DriveApp.getFileById(imgId)
// Create new filename (preserves extension)
const filename = `${fname} ${lname} - ${uid}${getExtension(image)}`
// Set file name
image.setName(filename)
}
}
/**
* Returns the extension of a file
*
* For example: A file with name 'example.json' will return '.json'.
*
* #param file {DriveApp.File} File to extract the extension from
* #returns {string} The extension with the dot.
*/
function getExtension(file) {
// Gets the last dot and the characters that follow
const r = /(\.\w+)$/.exec(file.getName())
// If it has no extension return an empty string, otherwise return the captured group
return r === null ? '' : r[1]
}
Obviously you need to add all your other code that you seem to have and install the trigger if you haven't, already.

Related

Google Sheets - Convert comma to "#" before generate .CSV

I have the following script in a Google Sheet:
/**
* Create CSV file of Sheet2
* Modified script written by Tanaike
* https://stackoverflow.com/users/7108653/tanaike
*
* Additional Script by AdamD.PE
* version 13.11.2022.1
* https://support.google.com/docs/thread/188230855
*/
/** Date extraction added by Tyrone */
const date = new Date();
/** Extract today's date */
let day = date.getDate();
let month = date.getMonth() + 1;
let year = date.getFullYear();
if (day < 10) {
day = '0' + day;
}
if (month < 10) {
month = `0${month}`;
}
/** Show today's date */
let currentDate = `${day}-${month}-${year}`;
/** Date extraction added by Tyrone */
function sheetToCsvModelo0101() {
var filename = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetName() + "-01" + " - " + currentDate; // CSV file name
filename = filename + '.csv';
var ssid = SpreadsheetApp.getActiveSpreadsheet().getId();
var folders = DriveApp.getFileById(ssid).getParents();
var folder;
if (folders.hasNext()) {
folder = folders.next();
var user = Session.getEffectiveUser().getEmail();
if (!(folder.getOwner().getEmail() == user || folder.getEditors().some(e => e.getEmail() == user))) {
throw new Error("This user has no write permission for the folder.");
}
} else {
throw new Error("This user has no write permission for the folder.");
}
var SelectedRange = "A2:AB3";
var csv = "";
var v = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).getValues();
v.forEach(function (e) {
csv += e.join(",") + "\n";
});
var newDoc = folder.createFile(filename, csv, MimeType.CSV);
console.log(newDoc.getId()); // You can see the file ID.
}
This script basically creates a .CSV file in the same folder where the worksheet is, using the range defined in var SelectedRange.
This script is applied to a button on the worksheet.
The question is: how do I make every comma typed in this spreadsheet be converted into another sign, like # before generating the .CSV file in the folder?
I would also like to know if instead of generating 1 file in the folder it is possible to generate 2 files, each with a name.
Issue:
The question is: how do I make every comma typed in this spreadsheet be converted into another sign, like # before generating the .CSV file in the folder?
After you get the sheet values via getValues, replace all instances of , in the resulting 2D array with #, using map and replaceAll.
I think this is a better approach than TextFinder since sheet values are not modified.
Code snippet:
From your original sample, just add the following line:
// ...stuff...
var v = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).getValues();
v = v.map(r => r.map(c => c.replaceAll(",", "#"))); // Add this line
v.forEach(function (e) {
csv += e.join(",") + "\n";
});
// ...stuff...
If you are doing this to avoid conflicts between the comma in the cells and the csv delimiter then try doing the csv like this:
function sheetToCsv() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0")
const params = { "method": "GET", "headers": { "Authorization": "Bearer " + ScriptApp.getOAuthToken() } };
const url = "https://docs.google.com/spreadsheets/d/" + ss.getId() + "/export?gid=" + sh.getSheetId() + "&format=csv";
const r = UrlFetchApp.fetch(url, params);
const csv = r.getContentText();
return csv;
}
And then put it back in a spreadsheet like this:
function csvToSheet(csv) {
const vs = Utilities.parseCsv(csv,',');
const osh = ss.getSheetByName("Sheet1");
osh.getRange(1,1,vs.length,vs[0].length).setValues(vs);
}
In the meantime I've found a solution that almost works the way I'd like.
I created 2 functions, one to convert , to # and another to convert # to , again, then after the .csv file creation is complete the script switches back from # to , .
/**
* Create CSV file of Sheet2
* Modified script written by Tanaike
* https://stackoverflow.com/users/7108653/tanaike
*
* Additional Script by AdamD.PE
* version 13.11.2022.1
* https://support.google.com/docs/thread/188230855
*/
var SelectedRange = "A2:AB3";
function searchAndReplace_ToHash() {
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).createTextFinder(',').replaceAllWith('#');
}
function searchAndReplace_ToComma() {
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).createTextFinder('#').replaceAllWith(',');
}
function sheetToCsv_02() {
var filename = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetName() + "-01" + " - " + currentDate; // CSV file name
filename = filename + '.csv';
var ssid = SpreadsheetApp.getActiveSpreadsheet().getId();
searchAndReplace_ToHash()
// I modified below script.
var folders = DriveApp.getFileById(ssid).getParents();
var folder;
if (folders.hasNext()) {
folder = folders.next();
var user = Session.getEffectiveUser().getEmail();
if (!(folder.getOwner().getEmail() == user || folder.getEditors().some(e => e.getEmail() == user))) {
throw new Error("This user has no write permission for the folder.");
}
} else {
throw new Error("This user has no write permission for the folder.");
}
var csv = "";
var v = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).getValues();
v.forEach(function (e) {
csv += e.join(",") + "\n";
});
var newDoc = folder.createFile(filename, csv, MimeType.CSV);
console.log(newDoc.getId()); // You can see the file ID.
searchAndReplace_ToComma()
}
It solves the problem, but it would be perfect if this change was not visible in the spreadsheet.
Is it possible to make this substitution without displaying it in the spreadsheet?
As for your script suggestion, I would like to change as little as possible in this script I'm using, it works exactly the way I need it to work, except for the fact that the commas of words conflict with the column divisions.
Anyway, thank you very much for all your attention and patience!

Google Form Upload to New Folder

I have a form with a number of questions including first name, last name and several questions that require the user to upload a document. My goal is that when the user submits the form that a new folder will be created, a suffix added if there is already a folder with the same name and all of the files uploaded go into the new folder for that user. Thanks to the solution posted by Tanaike “here” and with more modifications from Tanaike "here", the form works in completing the new folder however there are 2 elements I’m trying to figure out;
How do I move all uploads from the submission? Currently only the first upload question moves to the new folder.
Our folder names are usually, “lastname, firstname”. I can change the form for the user to enter their name like this but it seems like figuring out how to combine the name fields would be the better solution ie. Last Name+”, “+First Name
function onFormSubmit1(e) {
const folderId = "1fOttSZooEA-dK8ee7COzRtrjVQhzh2Yv"; // Please set top folder ID of the destination folders.
const form = FormApp.getActiveForm();
const formResponses = form.getResponses();
const itemResponses = formResponses[formResponses.length-1].getItemResponses();
Utilities.sleep(3000); // This line might not be required.
// Prepare the folder.
const destFolder = DriveApp.getFolderById(folderId);
let folderName = itemResponses[0].getResponse();
const subFolder = destFolder.searchFolders("title contains '" + folderName + "'");
const reg = new RegExp(`^${folderName}$|^${folderName}_\\d{2}$`);
if (subFolder.hasNext()) {
const folderNames = {};
while (subFolder.hasNext()) {
const fol = subFolder.next();
const fName = fol.getName();
if (reg.test(fName)) folderNames[fName] = fol;
}
const len = Object.keys(folderNames).length;
if (len == 1 && folderNames[folderName]) {
folderNames[folderName].setName(folderName + "_01");
}
folderName += "_" + ("00" + (len + 1)).slice(-2);
}
const folder = destFolder.createFolder(folderName);
// Move files to the folder.
itemResponses[1].getResponse().forEach(id => DriveApp.getFileById(id).moveTo(folder));
}

Change Gmail label once attachment is uploaded to drive

How can you remove the existing label "Global Alcohol" & add a "Global Processed" label to the email messages which have had the attachments uploaded to Google Drive using the following code? I must give credit to Cooper who answered my first question & helped me get the uploads working.
function saveAttachmentInFolder(){
var folder = DriveApp.getFolderById('xxxxxxxxxxxxx');
var userId = "myemail#gmail.com";
var query = "label:Global Alcohol";
var res = Gmail.Users.Messages.list(userId, {q: query});//I assumed that this works
res.messages.forEach(function(m){
var attA=GmailApp.getMessageById(m.id).getAttachments();
attA.forEach(function(a){
var ts=Utilities.formatDate(new Date(),Session.getScriptTimeZone(), "yyMMddHHmmss");
folder.createFile(a.copyBlob()).setName(a.getName()+ts);
});
});
}
I have read the API documentation & can see that you need to use the following code to modify the Labels. However I am stuck with how to integrate it into the function above.
function modifyMessage(userId, messageId, labelsToAdd, labelsToRemove, callback) {
var request = gapi.client.gmail.users.messages.modify({
'userId': userId,
'id': messageId,
'addLabelIds': labelsToAdd,
'removeLabelIds': labelsToRemove
});
request.execute(callback);
}
You were on the right track with the modification call, but the formatting is slightly off. The trick here is that you need to use the label IDs, so I wrote a new function getLabelsByName() that allows you to perform that lookup.
function saveAttachmentInFolder(){
var folder = DriveApp.getFolderById('xxxxxxxxxxxxx');
var userId = "myemail#gmail.com";
var query = "label:Global Alcohol";
var labels = getLabelsByName(userId, ["Global Alcohol", "Global Processed"]);
var res = Gmail.Users.Messages.list(userId, {q: query});//I assumed that this works
res.messages.forEach(function(m){
var attA=GmailApp.getMessageById(m.id).getAttachments();
attA.forEach(function(a){
var ts=Utilities.formatDate(new Date(),Session.getScriptTimeZone(), "yyMMddHHmmss");
folder.createFile(a.copyBlob()).setName(a.getName()+ts);
});
// Remove the old label & add the new one
Gmail.Users.Messages.modify({
addLabelIds: [labels["Global Processed"].id],
removeLabelIds: [labels["Global Alcohol"].id]
}, userId, m.id);
});
}
/**
* Lookup any number of labels by their name using the advanced Gmail service.
* #param {String} userId - The user's email address or "me" to get your own
* #param {String[]} labelNames - An array of labels names to search for
* #returns {Label{}} - Map of labels identified by label name
* https://developers.google.com/gmail/api/v1/reference/users/labels
*/
function getLabelsByName(userId, labelNames) {
var response = Gmail.Users.Labels.list(userId);
var selectedLabels = {};
for (var i = 0; i < response.labels.length; i++) {
var label = response.labels[i];
if (labelNames.indexOf(label.name) != -1) {
selectedLabels[label.name] = label;
}
}
return selectedLabels;
}

DocumentApp.openById() fails with “Service unavailable”

I am trying to read the contents of a spreadsheet which contains some folderId, fileName and targetFile and then based on the data entered in the spreadsheet.
I am finding the latest fileId in the drive for the same fileName as multiple files with the same name are getting added into the folder daily (this is done by the function mostRecentFiIeInFolder) and then I am trying to copy the contents of the latest file with ID dociIdSource into a different file with ID docIdTarget (which is done by the function docCopy).
But when I tried Implementing this using DocumentApp, I am getting a weird error which says
Service unavailable: Docs
for the code var baseDoc = DocumentApp.openById(docID);.
May I know where I am going wrong?
// Test function to call applyDocCopytoList.
function test(){
applyDocCopytoList();
}
// Read the values from the spreadsheet.
function applyDocCopytoList(){
var originalSpreadsheet = SpreadsheetApp.openById('sheet Id goes here').getSheetByName("sheet name goes here");
var getRange = originalSpreadsheet.getDataRange();
var data = originalSpreadsheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
var folderId = data[i][1];
var fileName = data[i][2];
var targetFile = data[i][3];
Logger.log('****************Record No: ' + i);
Logger.log('folderId: ' + data[i][1]);
Logger.log('fileName: ' + data[i][2]);
Logger.log('targetFile: ' + data[i][3]);
var latestFileId = mostRecentFiIeInFolder(folderId, fileName);
if(latestFileId!= undefined){
docCopy(latestFileId, targetFile);
}
}
}
// Log the id of the latest file with a particular name in the folder.
function mostRecentFiIeInFolder(folderId, fileName) {
var folder = DriveApp.getFolderById(folderId);
Logger.log(folder);
var files = DriveApp.getFilesByName(fileName);
Logger.log(fileName);
var result = [];
// Checks whether the given file is in the folder or not
if(!files.hasNext()){
Logger.log('No such file in the folder with the given name');
}
else{
while (files.hasNext()) {
var file = files.next();
result.push([file.getDateCreated(), file.getId()]);
}
Logger.log('************All the file ids with the same file name and their dates created************');
Logger.log(result);
result.sort(function (x, y){
var xp = x[0];// get first element in inner array
var yp = y[0];
return xp == yp ? 0 : xp > yp ? -1 : 1;// choose the sort order, here its in descending order of created date
});
var id = result[0][1];
Logger.log(id);
return id;
}
}
// Copy the contents of the latest file in the target file.
function docCopy(dociIdSource, docIdTarget){
Logger.log('The file with id: ' + dociIdSource + ' will be copied to the target id: ' + docIdTarget);
var docID = docIdTarget;
var baseDoc = DocumentApp.openById(docID); //Service unavailable: Docs error is thrown for this line of code
var body = baseDoc.getBody();
var otherBody = DocumentApp.openById(dociIdSource).getBody();
var totalElements = otherBody.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = otherBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH )
body.appendParagraph(element);
else if( type == DocumentApp.ElementType.TABLE )
body.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.appendListItem(element);
else if( type == DocumentApp.ElementType.INLINE_IMAGE )
body.appendImage(element);
else if( type == DocumentApp.ElementType.TEXT )
body.setText(element);
else
throw new Error("According to the doc this type couldn't appear in the body: " + type);
}
}
Note that your mostRecentFiIeInFolder function never actually uses the folder, and doesn't ever check that the files are of the correct type - i.e., are actually Google Docs files. Thus if your searched name should have found nothing (i.e. there is no recent file with that name in your target folder), but you had some alternate file elsewhere in your Drive, one that is not a Google Docs file, your script will find it and treat it as something it is not.
The solution is to restrict your search to your desired folder, and again by actual Google Docs mimetype:
function testIt() {
var folderIds = [
"", // Search all of Drive
"123428349djf8234", // Search that specific folder
];
// Log (in Stackdriver) the file id of the most recently created Google Docs file with the name "some name" in the various folders:
folderIds.forEach(function (folderId) {
console.log(getMostRecentFileIdWithName("some name", folderId, MimeType.GOOGLE_DOCS));
});
}
function getMostRecentFileIdWithName(fileName, folderId = "", mimeType = "") {
// If a folder was given, restrict the search to that folder. Otherwise, search with
// DriveApp. (Throws an error if the folderId is invalid.)
var parent = folderId ? DriveApp.getFolderById(folderId) : DriveApp;
// I assume your fileName variable does not contain any unescaped single-quotes.
var params = "name='" + fileName + "'";
// If a mimetype was given, search by that type only, otherwise search any type.
if (mimeType)
params += " and mimeType='" + mimeType + "'";
var matches = parent.searchFiles(params),
results = [];
// Collect and report results.
while (matches.hasNext())
results.push(matches.next());
if (!results.length)
throw new Error("Bad search query \"" + params + "\" for folder id = '" + folderId + "'.");
// Sort descending by the creation date (a native Date object).
// (a - b sorts ascending by first column).
results.sort(function (a, b) { return b.getDateCreated() - a.getDateCreated(); });
return results[0].getId();
}
You can read more about the acceptable search parameters in the Drive REST API documentation, and more about the Apps Script native implementation in the DriveApp documentation.

GAS Loop goes indefinite

Problem:-
I need to get Name and ID of the latest created document from some Google Drive folders.
Solution:-
I wasn’t able to find an easy way to get it, hence, created a loop for files within the folder to get max date - then loop again in folder to match the date and Log the name and date – the below code (when commented as mentioned) works properly for the solution.
Instead of rewriting the whole code with just a different folder name, I tried creating a loop for folder name as well.
However app script goes into infinite loop giving out the Maximum execution time limit message.
Any help appreciated.
function Get_lastestdate() {
//Define Spreadsheet and sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("PEOPLE");
//comment from here to work properly
for (var i =1; i < 4; i++) {
if (i = 1) { var foldername = "Folder1"}
if (i = 2) { var foldername = "Folder2"}
if (i = 3) { var foldername = "Folder3"}
// till here
//Define folder and files
var folder = DocsList.getFolder(foldername); //set folder name in case commenting.
var lastfile = folder.getFiles();
//Make a blank array
var noofdaysarray = []
var yeararray = []
//Loop to get all No of days in a year & the year
for (var i in lastfile) {
noofdaysarray.push(Utilities.formatDate(lastfile[i].getDateCreated(),"GMT","D"));
yeararray.push(Utilities.formatDate(lastfile[i].getDateCreated(),"GMT","y"));
}
//Get the max date from date and year
var largestdate = Math.max.apply(Math, noofdaysarray);
var largestyear = Math.max.apply(Math, yeararray);
//Get maximum available date
var matchcriteria = largestdate + largestyear
//Again loop for matching criteria with the actual date uploaded
for (var i in lastfile) {
var lastdate = Utilities.formatDate(lastfile[i].getDateCreated(),"GMT","D");
var lastyear = Utilities.formatDate(lastfile[i].getDateCreated(),"GMT","y");
var wholedate = parseInt(lastdate) + parseInt(lastyear); //parseInt is for converting text to number
//Get doc name if both dates matches
if (wholedate == matchcriteria) {
Logger.log(lastfile[i].getId());
Logger.log(lastfile[i].getName());
}
}
} //comment this as a part of loop
}
Between:- If there's an easier way to do it, please let me know.
You can use DocsList to retrieve the last modified files. you told that you want the last created but maybe the last modified can be usefull. Check the code bellow:
function findLastModified() {
//Retrieve folder
var folder = DocsList.getFolderById("0B0kQD4hSd4KASUJKb2cya0NET1U");
//Ask for 5 files
var numberOfFiles = 5;
//The first parameter is the search query, in this case we want all files
var filesResult = folder.findForPaging("", 5);
//By Default they will be sorted by last modified date
var files = filesResult.getFiles();
Logger.log("Found "+files.length+" files");
//Iterate
for(var x in files) {
var file = files[x];
Logger.log(file.getLastUpdated());
}
}
Live version here.
Dont use "i" in both inner and outer loops. that will be a problem.
wouldn't it be easier to do something like this so you only do one loop?
var mostRecentDate = new Date(2000,01,01);
var fileName="";
var fileId="";
for(var i = 1; i<5; i++){
var folder = DocsList.getFolder('Folder'+i);
var files = folder.getFiles();
for(var j in files) {
if(files[j].getDateCreated()>mostRecentDate){ //or getLastUpdated
mostRecentDate=files[j].getDateCreated();
fileName=files[j].getName();
fileId=files[j].getId();
}
}
}
Logger.log("File: " + filename + " Id: " + fileId + " Created: " + mostRecentDate);
You may need to do paging if you have a huge number of files & folders to iterate through.