How to retrieve list of shareable links? - google-apps-script

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"

Related

Copying multiple files in Apps Script

I have a script of which finds all files by name then copies and renames them, I want it to make a copy of multiple files (all of which have the same name) then rename them to the same original name. Currently, it mostly functions as it finds the files by name but it only copies the first file and ignores the rest. My current script is as follows
function copyDocs() {
var file = DriveApp.getFilesByName('PlaceHolder').next();
file.makeCopy('PlaceHolder');
}
I myself am not the most educated on this type of language or any coding language, to be honest, so I'm sorry if this is an easy solution or rookie mistake.
DriveApp.getFilesByName(...) returns a FileIterator. You need to iterate over all of the found files.
function copyDocs()
{
var files = DriveApp.getFilesByName('PlaceHolder');
while(files.hasNext())
{
files.next().makeCopy("PlaceHolder");
}
}
My issue was solved with help from #Cooper and #IMTheNachoMan. I simply needed to use the while(files.hasNext) iterator before placing makeCopy("PlaceHolder"). The end script is as follows
function copyDocs() {
var files = DriveApp.getFilesByName('PlaceHolder');
while (files.hasNext()) {
files.next().makeCopy('PlaceHolder');
}
}

Copy data from specific cell into different sheet with different formatting in a different folder

From inside one google sheet, I want to run a script (via script-attached button) that copies another,different, sheet into another, destination folder while naming the new file according to a cell value in the first sheet. Can anyone point me in the right direction?
Here's what i have so far:
function onOpen(e) {
var jobTitle = SpreadsheetApp.getActiveSpreadsheet().getName();
SpreadsheetApp.getActiveSpreadsheet().getRange("B3").setValue(jobTitle);
}
function CreateManFile8() {
var copyMan = DriveApp.getFileById("Source ID");
var toFolder = DriveApp.getFolderById("Destination ID");
copyMan.makeCopy(toFolder);
}
The first function is fine as it allows me to copy the first spreadsheet template, name it and then, onOpen, the file name goes into an appropriate cell of the input sheet from which it populates throughout the spreadsheet via standard sheets reference formulas and such.
The second function just copies the "Man." template (from inside the first "Sales" template) and puts it into the appropriate folder without changing the name.
I need a way to name the Man. template according to the value in B3 (or any specified cell) from inside the Sales SpreadSheet template. I have the script attached to a button inside the Sales template but it just copies the second template and names it "Source ID";not the value of B3.
ISSUE BACKGROUND: (FOR THOSE INTERESTED)
Each of the projects I manage at work entail Sales processes contained in a "Sales Spreadsheet" Template composed of several inter-linked sheets and Management processes contained in a "Management Spreadsheet" Template composed of several other inter-linked (with standard formulas) sheets. Eventually, both of these Spreadsheet Templates are copied and used for every new project.
First, each Sales prospect engages sales processes that cause the Sales spreadsheet template to be copied, renamed and iteratively populated with relevant customer and sales data throughout the sales process. Then, at some point, contracts are signed which engage management processes and, consequently, the "Management Spreadsheet" Template is copied, named, and utilized for the project as well.
The idea is to have the "Man. Template" get copied from within the 'Sales Template" after the sales template has been manually named and populated with information collected during the sales process.
Most of the data collected, analyzed, and reported during the management phase of any project is different from the data needed to negotiate the sales processes but, the little bit that is redundant needs to be Programmatically transposed from the Sales Template to the Management Template for each project.
And So, I come to YOU...the stack overflow community for guidance. Any and all serious, service-oriented people offering help will be truly appreciated.
I know most of you are smarter than me. It does not help to be reminded. So, anyone Who actually wants to help a dude out just because they love coding and want to share it, I cant wait to hear from you.
If I understood correctly, you want a function that makes a copy of the spreadsheet in an specified folder and with the value of the B3 cell as name.
You can try this modification of your function:
function CreateManFile8() {
var copyMan = DriveApp.getFileById("spreadsheet Id").getId();
var toFolder = DriveApp.getFolderById("folder Id");
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var sheetName = sheet.getRange('B3').getDisplayValue();
DriveApp.getFileById(copyMan).makeCopy(sheetName, toFolder);
}
You can check the documentation of makeCopy and getDisplayValue for more information.
This just a sample, you must modify it for your purpose, this will copy a file from any folder to other folder with new name:
function CreateFile8() {
//This assumes the folders are unique, so I don't use ID
var folders=DriveApp.getFoldersByName('Sistem Folder'); //myfolder = 'my drive' > 'sistem folder'
if (folders.hasNext())
{
var mySrcFolder = folders.next();
var Destfolders = mySrcFolder.getFoldersByName('Test'); //myDestfolder = 'my drive' > 'sistem folder' > 'Test'
var files=mySrcFolder.getFilesByName('Jojo'); //file jojo will be copied (only if not exist), and Jojo is as template
if (files.hasNext() && Destfolders.hasNext())
{
var DestFolder = Destfolders.next();
var myFile = files.next();
var Destfiles=DestFolder.getFilesByName('Jobe'); //file jojo will be copied (only if not exist in dest), and Jojo is as template
if (Destfiles.hasNext()==false)
myFile.makeCopy('Jobe', DestFolder); //And jobe is extracted from your sheet, I don't adopt here to extract from your sheet
}
}
}

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.

Merge folder with the same name in google drive using google script

Who knows how to merge folder with the same name in google drive using google script?
Step 01: Find if any duplicate folder in the root-Folder;
Step 02: If yes, choose one folder as main-folder, and then move all files in the other folder(with the same name) to this main-folder;
Step 03: Delete the other folder(with the same name).
Many thanks in advance.
Merge Folder with the same name under the same Folder
My approach:
Take parent folder in which you want to check for folders with same name
Loop over all subfolders and get their names
Find all folders within parent folder with each found folder name
Specify first iteration for given folderName as destination folder
If there is more than one folder with given name, move all files and folders from this folder to destination folder and trash that folder
Try this:
I have succesfully tested this function on some junk data.
function mergeDuplicateFolders() {
var parentFolder = DriveApp.getFolderById('{paste your folder ID here}'); //parent folder which will be scanned for duplicates
var childFolderIterator = parentFolder.getFolders(); //all folders within parent folder
//iteration over all folders in parent directory
while (childFolderIterator.hasNext()) {
var childFolder = childFolderIterator.next();
var childFolderName = childFolder.getName();
var childSameNameIterator = parentFolder.getFoldersByName(childFolderName);
//just check if folder with givn name exist (there should be at least one)
if(childSameNameIterator.hasNext()) {
var destFolder = childSameNameIterator.next();
//iteration over 2nd and all other folders with given name
while (childSameNameIterator.hasNext()) {
var toMergeFolder = childSameNameIterator.next();
var filesToMove = toMergeFolder.getFiles();
var foldersToMove = toMergeFolder.getFolders()
//iteration over all files
while (filesToMove.hasNext()) {
var file = filesToMove.next();
moveFile(file, destFolder);
}
//iteration over all subfolders
while (foldersToMove.hasNext()) {
var folder = foldersToMove.next();
moveFolder(folder, destFolder);
}
//trashes empty folder
toMergeFolder.setTrashed(true);
}
}
}
}
//custom function which removes all parents from speciefied file and adds file to new folder
function moveFile(file, destFolder) {
var currentParents = file.getParents();
while (currentParents.hasNext()) { // be careful, this loop will remove all parents from the file... if you want to have this file in multiple folders you should add if statement to remove it only from specified folder
var currentParent = currentParents.next();
currentParent.removeFile(file);
}
destFolder.addFile(file);
Logger.log("File moved to " + destFolder.getName());
}
//custom function which removes all parents from speciefied folder and adds that folder to new one
function moveFolder(folder, destFolder) {
var currentParents = folder.getParents();
while (currentParents.hasNext()) { // be careful, this loop will remove all parents from the folder... if you want to have this folder in multiple folders you should add if statement to remove it only from specified folder
var currentParent = currentParents.next();
currentParent.removeFolder(folder);
}
destFolder.addFolder(folder);
Logger.log("Folder moved to " + destFolder.getName());
}
Your application:
You can of course edit first two lines of this function and add parentFolderId as parameter of that function. Then you can use this function in your own iteration and check multiple folders for duplicates.
function mergeDuplicateFolders(parentFolderId) {
var parentFolder = DriveApp.getFolderById(parentFolderId);
//...continue with the code from above
Warnings
Be careful. In Google Drive you can have one file in multiple folders.
This code will remove files (in folders with same name) from all
parent folders and moves them only in one. If you prefere removing it
only from the folder with same name, you need to add some if statement
in moveFolder() and moveFile() functions.
Google script allows to run custom function only for (approx.) 6 minutes. If you have large amout of folders to check, you should make some workaround. You can add e.g. execution time limit. Example is here: Prevent Google Scripts from Exceeding the Maximum Execution Time Limit
EDIT:
Of course there is much simpler way to remove file only frome merged folder... Remove both custom functions and edit both iterations this way... I didn't realize it first.
//iteration over all files
while (filesToMove.hasNext()) {
var file = filesToMove.next();
toMergeFolder.removeFile(file);
destFolder.addFile(file);
}
//iteration over all subfolders
while (foldersToMove.hasNext()) {
var folder = foldersToMove.next();
toMergeFolder.removeFolder(folder);
destFolder.addFolder(folder);
}
Class Folder, currently, doesn't have existing method to move files from one folder in Google Drive to another.
And as far as I know, there is an existing issue on Seeing duplicate files after sync on desktop Drive version 1.14. However, you may follow the given workaround in the forum as follows:
Step 01:
How to Find duplicates files (by name and/or size) in a specific folder of my google drive
Step 02:
How do I copy & move a file to a folder in Google Apps Script?
Step 03: You can use removeFolder(child) method from Class Folder, which can only be used to remove a given folder from a current folder.
I hope that helps.

How to delete/overwrite CSV file using google apps script?

My google apps script imports a csv file [test.csv] into a new tab, manipulates some values, then saves the manipulated tab as a csv file [test.csv]. When it saves the new version, it simply makes another copy [test(1).csv]. I wish instead to overwrite the previous file (or delete the old one then export/write the new version.) Help please?
I am using reference code from the Interacting With Docs List Tutorial
I know this is an old question, but much of the information in the accepted answer has been deprecated by Google since then. DocsList is gone, as are the clear() and append() methods on a file object.
I use the following function to create or overwrite a file:
// Creates or replaces an existing file
function updateFile (folder, filename, data) {
try {
// filename is unique, so we can get first element of iterator
var file = folder.getFilesByName(filename).next()
file.setContent(data)
} catch(e) {
folder.createFile(filename, data)
}
}
For reference, here's some code for doing the same for a folder. It assumes we're operating in the parent folder of the current sheet and want a folder
object for a new or existing folder there.
// Get folder under current folder. Create it if it does not exist
function getOrCreateFolder(csvFolderName) {
var thisFileId = SpreadsheetApp.getActive().getId();
var thisFile = DriveApp.getFileById(thisFileId);
var parentFolder = thisFile.getParents().next();
try {
// csvFolderName is unique, so we can get first element of iterator
var folder = parentFolder.getFoldersByName(csvFolderName).next();
// asking for the folder's name ensures we get an error if the
// folder doesn't exist. I've found I don't necessarily catch
// an error from getFoldersByName if csvFolderName doesn't exist.
fname = folder.getName()
} catch(e) {
var folder = parentFolder.createFolder(csvFolderName);
}
return folder
}
You could do DocsList.find(fileName) which gives you a list of files that have that name. If file names are unique, then you can just do var file = DocsList.find(fileName)[0].
If you are a Google Apps user, you can use file.clear() to remove all the contents of the old file, and then file.append() to insert all of the new contents.
Otherwise, you will have to file.setTrashed(true) and then DocsList.createFile() to make the new one.