Google Forms Rename "File upload" File to "Question - Submitter" - google-apps-script

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.

Related

How to retrieve list of shareable links?

For the following, all files are in Google Sheet format.
My problem :
My work environment is structured as follows (folders / sub-folders):
RACINE :
--> ID4522
--> ID7852
--> ID5864
--> ....
The tracking file "FOLLOW_UP" is located in the RACINE folder. The ID4522, ID7852 and ID5864 folders are subfolders of the RACINE folder.
We find in the ID4522 sub-folder the following Google sheet file "Entry_Form_ID4522", in the ID7852 sub-folder the following Google sheet file "Entry_Form_ID7852",…
Important clarification: The number of sub-files (of the form "IDxxxx") can vary at any time without warning.
My wish
Most likely via a macro in javascript, retrieve in the tracking file ("FOLLOW_UP") from cell B3 and down, the list of shareable links for each of the files "Entry_Form_IDxxxx". List of subfolders can change at any time (for example when my client adds a folder with the associated "Entry_Form_IDxxxx" file).
Thank you by advance.
Regards.
Jérôme
Recurse Folders for File URLs
I'm assuming that you have only one folder named RACINE and there is only one file in it that is named "FOLLOW UP" and that the sheet where your data is located in is the left most sheet. Assuming as that is true then this function will extract the urls out of =HYPERLINK() cells found in column B from row 3 on down to the end of the sheet.
Originally I jumped to the conclusion that there we're links in column B and that you wanted a list of URL's so this function is probably not want you want. Please let me know and I'll remove it.
function getLinks() {
const folder=DriveApp.getFoldersByName("RACINE").next();//assumes only one folder with that name
const file=folder.getFilesByName("FOLLOW UP").next();//assumes only one file by that name
const ss=SpreadsheetApp.openById(file.getId());
const sh=ss.getSheets()[0];//the most left sheet in the spreadsheet
const vs=sh.getRange(3,2,sh.getLastRow()-2,1).getFormulas();
var links=[];
vs.forEach(function(r,i){
let v=r[0].match(/([^"]+)/g);
links.push(v[1]);
})
var ui=HtmlService.createHtmlOutput(links.join('<br />'));
SpreadsheetApp.getUi().showModelessDialog(ui, "URLS");
return links;//as an array
}
After jumping to conclusions
I think this is what you really want and that is a list of urls to the files in your working subdirectories that meet a certain naming format as explained in the following paragraphs. I tested this on some data I generated with the last two functions in this answer. After fixing a couple of typos it ran very nice and producted url in the correct location.
The first function is basically the starter. It assumes that you have only one folder named "RACINE" on your google drive. It calls a recursive function which essentially crawls through all of the RACINE's subfolders looking for files of the form "Entry_Form_IDxxxx" where xxxx are all numbers between 0-9. When it finds a file name like that it loads that url into the next empty cell at the bottom of columnB in sheet[0] of "FOLLOW UP". It also searches for subdirectories of the form "IDxxxx" where xxxx are all numbers from 0-9. When it finds those subfolders in recurses into them by calling getFnF() from inside of getFnF(). It can get hard to follow this process so if your new to it you may very well want to hire some to help you.
function getFileUrlsIntoB3() {
const folder=DriveApp.getFoldersByName("RACINE").next();//assumes only one folder with that name
getFnF(folder);
}
The following function gets the Id for the follow up file into the recursive function so that data can be written into the most left hand sheet of file "FOLLOW UP". It uses the cache service so that all calls after the first happen considerably faster since the file id is taken directly from cache. The cache will hold this value for upto 3 minutes but you can adjust for more if you wish.
function getFollowUpId() {
const cs=CacheService.getScriptCache();
const cached=cs.get('FOLLOW UP ID');
if(cached) {
return cached;
}else{
let folder=DriveApp.getFoldersByName("RACINE").next();
let file=folder.getFilesByName("FOLLOW UP").next();
cs.put('FOLLOW UP ID',file.getId(),180);//3 minute cache time
return file.getId();
}
}
And this is the recursive function. Which just means that it's a function that calls itself.
function getFnF(folder) {
const ss=SpreadsheetApp.openById(getFollowUpId());
var sh=ss.getSheets()[0];
var files=folder.getFiles();
while(files.hasNext()) {
var file=files.next();
if(file.getName().match(/^Entry_Form_ID\d{4}/)) {
sh.getRange(getColumnHeight(2,sh,ss)+1,2).setValue(file.getUrl());
}
}
var subfolders=folder.getFolders()
while(subfolders.hasNext()) {
var subfolder=subfolders.next();
if(subfolder.getName().match(/^ID\d{4}/)) {
getFnF(subfolder);
}
}
}
And finally this is the function that calculates the current height of columnB in Sheet[0] of spreadsheet "FOLLOW UP"
function getColumnHeight(col,sh,ss){
var ss=ss||SpreadsheetApp.getActive();
var sh=sh||ss.getActiveSheet();
var col=col||sh.getActiveCell().getColumn();
var v=sh.getRange(1,col,sh.getLastRow(),1).getValues().map(function(r){return r[0];});
var s=0;
var h=0;
v.forEach(function(e,i){if(e==''){s++;}else{s=0;}h++;});
return (h-s);
}
TESTING
I used the following two functions to create some folders and files and found that testing the code was a breeze. It had only 3 typo type failures and then ran just fine. It's just creating ascii text files not spreadsheets but they're files none the less.
function generateFoldersAndFiles() {
const folder=DriveApp.getFolderById('RACINE FolderId');
for(let i=0;i<4;i++) {
let subfldr=folder.createFolder(getStrings("ID",4));
for(let j=0;j<4;j++) {
subfldr.createFile(getStrings("Entry_Form_ID",4),"Text Content");
}
}
}
function getStrings(prefix,length) {
const nA=[0,1,2,3,4,5,6,7,8,9];
var s=prefix;
for(let i=0;i<length;i++) {
s+=nA[Math.floor(Math.random()*nA.length)];
}
return s;
}
File Structure:
Yes there is only one folder named RACINE and one file named "FOLLOW_UP". I do not understand by the way that there can be several folders and several files with the same name? I remind you that I am not a developer.
The names of subfolders (of type IDxxxx) are also unique. I specify that the name of the sub-folders can also take the form IDxxxx_x (for example ID4522_1).
The IDxxxx and IDxxxx_x subfolders are all located in the RACINE folder.
There is an Entry_Form_IDxxxx (or Entry_Form_IDxxxx_x) file in each IDxxxx (and IDxxxx_x) sub-folder. There is only one Entry_Form_IDxxxx and Entry_Form_IDxxxx_x file.
For example in the ID4522 sub-folder there is the file "Entry_Form_ID4522" (there is only one file "Entry_Form_ID4522").
For example in the ID4522_1 sub-folder there is the file "Entry_Form_ID4522_1" (there is only one file "Entry_Form_ID4522_1").
In each IDxxxx or IDxxxx_x folder there is only one Entry_Form_... file.
I would like to put screenshots to better explain my problem but I do not know how to do it?
The file in which I would like to retrieve the results (that is, the shareable links of the "Entry_Form_IDxxxx" files) is called "FOLLOW_UP" ("feuille 1" tab).
In the "FOLLOW_UP" file ("feuile 1" tab), here is what I would like to obtain:
For example in cell B3:
https://docs.google.com/spreadsheets/d/1VUf_t1gkv2lddfgsfgds7UhJeunrNzo2-QGcRe24/edit?usp=sharing
This link would correspond to the link of the first file found, for example "Entry_Form_ID4522"
In cell B4:
https://docs.google.com/spreadsheets/d/1VUf_jkghkdxjfghgjgjzo2-QGcRe24/edit?usp=sharing
This link would correspond to the link of the second file found, for example "Entry_Form_ID7852"

Google Apps Script moving file to Team Drive folder

Background:
At the core of the issue is I have an Alteryx job dropping files into my google drive. These files need, in actuality, in a Team Drive folder. Try as I might, nowhere have I found a way for Alteryx to do this. So hence the need for this script.
Actual problem:
So here is the criteria: I have the files being created with the same naming convention with only the date changing. I need these files to go from my drive to a team drive where they are eventually worked on.
Using the resources already here on stack I found wonderful solutions here: 1 and here 2 that I was able to cobble together a working script.
Understand I am a marginally functional python programmer for data analytics. So my JS and Google scripting are rudimentary at best. The first time I tested the script, it worked. Wonderfully, right up until it didn't.
It moved my first file with no problem. I then created a few copies of that same file in the drive to see how it handled multiple. I now get an error:
Exception: No item with the given ID could be found, or you do not
have permission to access it. (line 15, file "CodeA1")
Here is my code:
function SearchFiles() {
//searches based on naming criteria
var searchFor ='title contains "Reference Data Performance"'; //test file
var names =[];
var fileIds=[];
var files = DriveApp.searchFiles(searchFor);
while (files.hasNext()) {
var file = files.next();
var fileId = file.getId();// To get FileId of the file
fileIds.push(fileId);
var name = file.getName();
names.push(name);
}
var file = DriveApp.getFileById(fileIds);
supportsTeamDrives: true;
supportTeamDrives: true;
var targetFolder = DriveApp.getFolderById('TEAMDriveID');
targetFolder.addFile(file);
}
Exception: No item with the given ID could be found, or you do not
have permission to access it.
This error occurs most often if either
The TeamDriveId is not correct
The account from which you run the script is not member of the team drive
Also note:
supportsTeamDrives: true;
supportTeamDrives: true;
are parameters
for the Drive API, not to be confused with DriveApp
Update:
I ended up going with a simplified version of this script. I had Alteryx schedule the file creation (three different files) at hour increments. Then made a trigger in Google Apps to run the script at the times directly after each Alteryx scheduled drop. Simple? Yes. Inelegant? Maybe. Been running all week and files arrive at their destination for people to work on.
function SearchFiles() {
//searches based on naming criteria
var searchFor ='title contains "Reference Data Performance"'; //looks for file that matches requirements
var files = DriveApp.searchFiles(searchFor);
while (files.hasNext()) {
var file = files.next();
var fileId = file.getId();// To get FileId of the file
var file = DriveApp.getFileById(fileId); //grabs file ready to pass to google drive.
supportsTeamDrives: true;
supportTeamDrives: true;
var targetFolder = DriveApp.getFolderById('FOLDERID');
targetFolder.addFile(file);//good for getting a single file needs loop to grab multiple from Array
}
}

Sorting Google Docs into Google Drive folders based upon a common string in file and folder names

I have hundreds of Google Docs, and lots of Google Folders. They share a common string in the filename. The Docs need to go into the corresponding Folder, and I'd like to automate this with some Script.
What I want to do is as follows:
I have hundreds of Google Docs which follow a similar pattern in the filename:
SURNAME, Forename_userID_fileName
So for instance,
DOE, Jane_jd5678_Document3
PUBLIC, Tom_tp9123_reportTPS
SMITH, John_js1234_finaldocument
and so on and so forth.
I also have corresponding Folders in a Google Team Drive, set up as follows:
SURNAME, Forename (userCode) (userID) - userType
So for instance,
DOE, Jane (145374578) (jd5678) - AET Full Time
PUBLIC, Tom (673468714) (tp9123) - NR Full Time
SMITH, John (874512456) (js1234) - AET Part Time
The common string between the files and the folder names is the userID string.
Currently I download all the Docs, and then upload drag-and-drop into each corresponding folder. Takes ages! I was hoping there is a bit of Apps Script that I can do to automate this but I really don't know where to start at all. Anyone have any pointers?
This StackOverflow question seems to want to do what I want to do, but as I'm such a beginner with Apps Script I can't really decipher the solution.
This is a basic example to point you in the right direction using the basic Google Apps Script DriveApp (as opposed to the advanced Script Services version or other Google Drive API methods). The main concern that comes to mind is the script timing out if this example takes too long to complete. If that happens, you'll need a more complex solution. You could also chunk it up by having the idRegex only work on, e.g., surnames A-D then E-G, etc. The example regular expression could match random files you have with underscores; you may want to find a more complicated regular expression if that is a concern.
function organizeFilesById() {
var idRegex = /_[A-z]+[0-9]+_/;
var files = DriveApp.getFiles();
while (files.hasNext()) {
var file = files.next();
var filename = file.getName();
var id = idRegex.exec(filename);
if (! id) {
continue;
} else {
id = id[0];
}
var query = "title contains '" + id + "'";
var folders = DriveApp.searchFolders(query);
if (folders.hasNext()) {
var folder = folders.next();
Logger.log("Will add %s to %s", filename, folder.getName());
//folder.addFile(file); // uncomment line to actually do it
} else {
Logger.log("No folder found for %s", filename);
// could create folder or just report "folder not found"
}
}
}

How to open a spreadsheet only by name of the corresponding file in Google Sheets?

I need a way to open a spreadsheet only by having the name of the corresponding file and the folder where the file is located. What I'm trying to do is creating a temporary copy of an active spreadsheet and reading information from the copy file rather than the active one. But unfortunately this copy file won't have a constant ID and URL as it's going to be erased every time after the script is done, and re-created with the next running of the script.
What I already tried is using the URL of the temporary file in order to open the spreadsheet itself, as you can see below.
But it's not useful for me, as I don't have a constant URL of this file (as it will be erased in the end of the script and have a new URL with the next script). But I have a constant name of the file, as well as this will be the only file in the respective folder.
What I also tried is looking for methods to open file only using its name (which is "temp_copy" in a 'temp_folder'). But I didn't find a working solution.
var report = SpreadsheetApp.getActiveSpreadsheet();
var temp_folder =
DriveApp.getFolderById("1EAsvVcFjIw5iGTeF_SxxQJLVlaLHl_pR");
DriveApp.getFileById(report.getId()).makeCopy("temp_copy", temp_folder);
var temp_copy = SpreadsheetApp.openByUrl(' ');
var hiddenSheet = temp_copy.getSheetByName('DATE');
var lastRow = hiddenSheet.getSheetValues(1,1,1,1);
var sheet = temp_copy.getSheetByName('forCSV');
var lastColumn = sheet.getLastColumn();
var activeRange = sheet.getRange(1,2,lastRow,lastColumn);
What I'm expecting as a result is to make the link between the 3rd line of the code (when making a copy of the basic file) and 5th line of the code onwards (when referencing specific range from the temporary file) only by using name of the file or respective folder. In theory it seems so easy, I should be missing something somewhere...
Many thanks in advance!
You can use DriveApp to get a FileIterator which contains the Sheet.
var file, files = DriveApp.getFilesByName("temp_copy");
if (files.hasNext ()){
file = files.next(); // Get first result for name
} else {
return "";
}
var sheet = SpreadsheetApp.openById(file.getId())

Google script to Save open Sheet to file

I'm in the process of learning about Google Scripts and Javascripts, I love coding solutions but need to know a lot more so apologies for being a little slow.
I know how to use script in google docs but I am not sure yet on how to construct many things, I am learning from examples and trying to understand from what I find.
I have a scenario where I have a google sheet that I would like to use as a template so a user can enter in data in all the fields then click a button to save the whole sheet as a file (the same as going File > Save As) but use an ID number in a field as the name of the document when saving and place these in a folder within the same directory as the master template. After clicking the save button the sheet would need to reset to original ready for another entry.
The user can open up the saved document and make changes if required.
I haven't been able to find examples I can quite understand to make them work so any assistance would be great, even pointers for resources that would make it easier for me to learn
Thanks.
First of all, create a folder in your drive named MyTargetFolder. Put your template spreadsheet in, fill "ID" to first row as a filed, any number as value to second row.
all script you need are below :
// create a custom menu to add Save feature.
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Feature')
.addItem('Save Spreadsheet', 'saveSpreadSheet')
.addToUi();
}
Then implement save feature.
function saveSpreadSheet() {
var thisSpreadSheet = SpreadsheetApp.getActive();
/*
What first row and second row look like :
-----
| ID |
-----
| 123 |
-----
*/
var id = thisSpreadSheet.getActiveSheet().getRange(2, 1, 1, 1).getValue();
var folders = DriveApp.getFolders();
var destFolder = '';
while (folders.hasNext()) {
var folder = folders.next();
if ( folder.getName() == 'MyTargetFolder' ) {
destFolder = DriveApp.getFolderById(folder.getId());
}
}
DriveApp.getFileById(thisSpreadSheet.getId()).makeCopy(id, destFolder);
// recover the template
thisSpreadSheet.getActiveSheet().getRange(2, 1, 1, 1).setValue(' ');
SpreadsheetApp
.getUi()
.alert('Check your Drive to make sure file have been saved');
}
All Google app script API that about spreadsheet are well documented here.
And API about Drive.