How to get All versions' data in Google sheet? - google-apps-script

I have a Google sheet which has a large number of versions. Now, I want to get each version one by one, extract data of that version into Google sheet. I have tried to follow the below solution from #Tanaike:
function myFunction() {
var spreadsheetId = "###"; // Please set the Spreadsheet ID.
var revisionId = "###"; // Please set the revision ID.
var url = "https://docs.google.com/spreadsheets/export?id=" + spreadsheetId + "&revision=" + revisionId + "&exportFormat=xlsx";
var blob = UrlFetchApp.fetch(url, {headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()}}).getBlob();
var id = Drive.Files.insert({mimeType: MimeType.GOOGLE_SHEETS, title: revisionId}, blob).id;
var spreadsheet = SpreadsheetApp.openById(id);
var sheet = spreadsheet.getSheets()[0];
var sheetdata = sheet.getDataRange().getValues();
}
It would be helpful if you can guide me/suggest it to me about it. I just want to get all the data for each version history and put it into Google sheets. Thank you for your guidance.

Following the advice in the comments. The sample code requires the revision ID. A sample code to gather all the revisions ID:
function listRevisions(fileId) {
var fileId = 'yourfileid';
var revisions = Drive.Revisions.list(fileId);
if (revisions.items && revisions.items.length > 0) {
for (var i = 0; i < revisions.items.length; i++) {
var revision = revisions.items[i];
var date = new Date(revision.modifiedDate);
Logger.log(revision.id);
}
} else {
Logger.log('No revisions found.');
}
}
Get the File ID of the Spreadsheet that you want to get its revision (the one you would be adding in the first sample code)
Revisions: list, you can get here the list of all revision that you do for that file in particular and is presented in the sample code above or you can manually test it over the web
By using Revisions: get, you should be able to get an specific revision details of the file by adding both the file id and revision id.
There is also an excellent thread with information about it where the sample code was taken (besides the ones linked in the revision list official documentation, there are javascript samples that can also be used) feel free to review the thread

Related

Google Drive API files by created Date

My goal is to delete files from a specific folder older than 60 days.
I have a script as follows (based on code from another post I can't find anymore):
function getOldFileIDs() {
var fileIDs = [];
// Old date is 60 days
var oldDate = new Date().getTime() - 3600*1000*24*60;
var cutOffDate = Utilities.formatDate(new Date(oldDate), "GMT", "yyyy-MM-dd");
// Get folderID using the URL on google drive
var folder = DriveApp.getFolderById('XXXXX');
var files = folder.searchFiles('modifiedDate < "' + cutOffDate + '"');
while (files.hasNext()) {
var file = files.next();
fileIDs.push(file.getId());
Logger.log('ID: ' + file.getId() + ', Name: ' + file.getName());
}
return fileIDs;
};
function deleteFiles() {
var fileIDs = getOldFileIDs();
fileIDs.forEach(function(fileID) {
DriveApp.getFileById(fileID).setTrashed(true);
});
};
This code works but it uses "modifiedDate" When I look in the folder, I see there are many very old files with a "modified" date of today for some reason, but the "Created" date seems correct.
Therefore, I tried changing the code to "createdDate" but that doesn't work.
I decided to actually look at the documentation:
https://developers.google.com/apps-script/reference/drive/drive-app#searchFiles(String)
https://developers.google.com/drive/api/guides/ref-search-terms
It seems like "modifiedDate" isn't even listed yet it seems to work.
Delete old files:
function delOldFiles() {
const dt = new Date();
const dtv = new Date(dt.getFullYear(),dt.getMonth(),dt.getDate() - 60).valueOf();
var folder = DriveApp.getFolderById('XXXXX');
var files = folder.getFiles();
while (files.hasNext()) {
var file = files.next();
if(new Date(file.getDateCreated()).valueOf() < dtv) {
Drive.Files.remove(file.getId());
}
}
}
Issue and workaround:
The parameter of "searchFiles" method uses the search query for Drive API v2. When I tested createdDate > '####-##-##' for "searchFiles" and "Files: list" of Drive API v2, I confirmed errors like Invalid argument: q and Invalid query occurred, respectively. This has already been mentioned in Lorena Gomez's answer
Fortunately, when Drive API v3 is used, createdTime can be used. createdTime of Drive API v3 is the same with createdDate of Drive API v2. In this answer, as a workaround, I would like to propose using Drive API v3 instead of Drive API v2 ("searchFiles" of DriveApp). I have already posted this workaround at this post. But, this has not been posted in Stackoverflow. When this is posted here, I thought that it might be useful for other users.
When Drive API v3 is reflected in your script, how about the following modification?
Modified script:
This script uses Drive API. So, please enable Drive API at Advanced Google services.
function getOldFileIDs() {
var folderId = "###"; // Please set your folder ID.
var fileIDs = [];
// Old date is 60 days
var oldDate = new Date().getTime() - 3600 * 1000 * 24 * 60;
var cutOffDate = Utilities.formatDate(new Date(oldDate), "GMT", "yyyy-MM-dd");
var query = `'${folderId}' in parents and createdTime < '${cutOffDate}' and trashed=false`;
var pageToken = "";
do {
var url = encodeURI(`https://www.googleapis.com/drive/v3/files?q=${query}&pageSize=1000&pageToken=${pageToken}&fields=files(id,name,createdTime),nextPageToken&orderBy=createdTime`);
var res = UrlFetchApp.fetch(url, { headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() } });
var obj = JSON.parse(res.getContentText());
if (obj.files.length > 0) {
fileIDs = [...fileIDs, ...obj.files.map(({ id }) => id)];
obj.files.forEach(({ id, name, createdTime }) => Logger.log(JSON.stringify({ id, name, createdTime })));
}
pageToken = obj.nextPageToken;
} while (pageToken);
return fileIDs;
}
When this script is run, the file list including file IDs is retrieved with the search query of '${folderId}' in parents and createdTime < '${cutOffDate}' and trashed=false.
When you are not required to check the process, please remove obj.files.forEach(({ id, name, createdTime }) => Logger.log(JSON.stringify({ id, name, createdTime })));.
Note:
As additional information, in order to remove the files, when the number of files is large when setTrashed is run in a loop, the process cost becomes high. In this case, how about using batch requests? When batch requests are used, the process cost can be reduced. Ref
When batch requests are used with Google Apps Script, a script is a bit complicated. So, I created a Google Apps Script library. Ref When your script of deleteFiles() is modified for removing the files with the batch requests, how about the following modification?
1. Install Google Apps Script library.
Please install the Google Apps Script library. You can see how to install it here.
2. Sample script.
In this sample, the result is the same with setTrashed. The files of fileIDs are moved to the trash box.
function deleteFiles() {
var fileIDs = getOldFileIDs();
var requests = fileIDs.map(id => ({
method: "PATCH",
endpoint: `https://www.googleapis.com/drive/v3/files/${id}`,
requestBody: { trashed: true },
}));
var result = BatchRequest.EDo({ batchPath: "batch/drive/v3", requests });
console.log(result);
}
If you want to completely delete the files of fileIDs, please use the following script. But, this script completely deletes the files. So, please be careful about this. So, I would like to recommend using the above script. After you check the files in the trash box, you can empty the trash box.
function deleteFiles() {
var fileIDs = getOldFileIDs();
var requests = fileIDs.map(id => ({
method: "DELETE",
endpoint: `https://www.googleapis.com/drive/v3/files/${id}`,
}));
var result = BatchRequest.EDo({ batchPath: "batch/drive/v3", requests });
console.log(result);
}
References:
Files: list of Drive API v3
BatchRequest of Google Apps Script library
Regarding your question:
Therefore, I tried changing the code to "createdDate" but that doesn't work.
This seems to be an issue which has already been reported in this issue tracker, you can keep track of this to see any updates in the future, this is another thread related to the issue. #Cooper's answer is a good workaround to achieve what you're looking for.

List Google drive folder contents to google sheets with only new files

Looking to learn how to improve my use of loops. Currently I need to list the names and URLS from a google drive Folder to a sheet and this is the code that I have:
Existing Code
function wthFolderContents() {
var folder_id = 'myFolderID';
var folders = DriveApp.getFolderById(folder_id)
var contents = folders.getFiles();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("SheetName");
sheet.clearContents()
sheet.appendRow( ['name', 'link'] );
var file;
var name;
var link;
var row;
while(contents.hasNext()) {
file = contents.next();
name = file.getName();
link = file.getUrl();
sheet.appendRow ( [name, link] );
with this code everytime the script is run the contents are cleared and then relisted. I am looking at a way of doing this dynamically / only update the new files so the script runs more effeciently.
Ive tried the following
New Code
function wthFolderContents2() {
var folder_id = '1vBzucZsb0SMOoHSWGtkUF-5QLQr5Fh1C';
var folders = DriveApp.getFolderById(folder_id)
var contents = folders.getFiles();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("WHTCert");
var lastRow = sheet.getLastRow()
var existing = sheet.getRange(1,1,lastRow,1).getValues()
for(i=1;i<lastRow;i++) {
var existingFilename = existing [i][0]
Logger.log(existingFilename)
while(contents.hasNext()) {
var file;
var name;
var link;
file = contents.next();
name = file.getName();
link = file.getUrl();
if (!name == existingFilename) {
sheet.appendRow ( [name, link] );
}
}
}
I cant get this to work, not sure what exactly where I have gone wrong. Hope someone can point me int he right direction!
Cheers
I believe your goal is as follows.
You want to reduce the process cost of your script.
Modification points:
In your script, appendRow is used. In this case, the process cost will become high. Ref
The search for files is run in a loop. In this case, the process cost will become high.
In your situation, it seems that you want to retrieve the file list just under the specific folder. In this case, I thought that when Drive API is used, the process cost can be reduced. In this answer, I would like to propose using Drive API in your script. When this is reflected in your script, it becomes as follows.
When Drive API is used, all values can be retrieved. So, I thought that your 1st process might be able to be used.
Modified script:
Before you use this script, please enable Drive API at Advanced Google services.
function wthFolderContents2() {
var folder_id = '1vBzucZsb0SMOoHSWGtkUF-5QLQr5Fh1C';
// Retrieve file list.
var q = `'${folder_id}' in parents and trashed = false and mimeType != '${MimeType.FOLDER}'`;
var fileList = [['name', 'link']];
var pageToken = "";
do {
var obj = Drive.Files.list({ q, maxResults: 1000, pageToken, fields: "nextPageToken,items(id,title)", corpora: "allDrives", supportsAllDrives: true, includeItemsFromAllDrives: true });
if (obj.items.length > 0) {
fileList = [...fileList, ...obj.items.map(({ id, title }) => [title, `https://docs.google.com/presentation/d/${id}/edit?usp=drivesdk`])];
}
pageToken = obj.nextPageToken;
} while (pageToken);
// Put the values to Spreadsheet.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("WHTCert");
sheet.clearContents();
sheet.getRange(1, 1, fileList.length, fileList[0].length).setValues(fileList);
}
When this script is run, the file list (filename and URL) is retrieved from the specific folder. And, the retrieved values to the "WHTCert" sheet.
Reference:
Files: list
Please convert this 2 script for scan folder only one subfolder and file, show folder name and link.
function wthFolderContents()
function wthFolderContents2()

Google App Script timing out while removing Viewers, How to make the script more efficient?

I have a GAS that I run every month or so to remove Viewers and Editors from GoogleDocs and GoogleSheets that were created over 1 year ago. I have not found a way to return ONLY the documents which have the specific users I want to remove.
So the code is setup to loop thru all the documents in a specific folder and if the Viewers/Editors do not match the 2 owners, then it removes their access.
The problem is a few folders have a large number of files and it is timing out just reading thru to find out if any Viewers/Editors need to be removed.
Any ideas on how this code could be streamlined or if there is a way to query for only the documents not owned by a specific user?
var folder = folders.next(); //assume the match is the first one
folder = DriveApp.getFolderById(folder.getId()); //use the folderID of the year folder
processFolder(folder); //this starts in with the newest folder modified date under the Proposals/Year folder and works down thru the list until it times out after 5 minutes of running
function processFolder(folder) {
var asset;
var users;
var email;
var files = folder.getFiles();
var todaysDate = new Date();
while (files.hasNext()) {
var file = files.next();
var daysCreated = parseInt(((todaysDate - file.getDateCreated()) / 86400000)); //how many days since the document was created 24/3600/1000 = 86,400,000
if (daysCreated > RETENTION_DAYS) {
asset = DriveApp.getFileById(file.getId());
for (var i = 0; i < 2; i++) {
if (i == 0) {
users = asset.getEditors();
} else {
users = asset.getViewers();
}
for (var cnt = 0; cnt < users.length; cnt++) {
email = users[cnt].getEmail().toLowerCase();
if (email != "xxx1#gmail.com" && email != "xxx2#gmail.com") {
if (i == 0) { //Editors
asset.removeEditor(email);
} else { //Viewers
asset.removeViewer(email);
}
}
}
}
}
} //processFolder
About how this code could be streamlined or if there is a way to query for only the documents not owned by a specific user?, for example, if you want to retrieve only the files without including xxx1#gmail.com and xxx2#gmail.com as the writer and the viewer, how about using searchFiles instead of getFiles? When your script is modified, it becomes as follows.
Modified script:
function processFolder(folder) {
var emails = ["xxx1#gmail.com", "xxx2#gmail.com"]; // Please set the email addresses.
var query = emails.map(e => `not '${e}' in writers and not '${e}' in readers`).join(" and ") + " and trashed=false";
var files = folder.searchFiles(query);
var todaysDate = new Date();
while (files.hasNext()) {
var file = files.next();
var daysCreated = parseInt(((todaysDate - file.getDateCreated()) / 86400000));
if (daysCreated > RETENTION_DAYS) {
file.getEditors().forEach(e => file.removeEditor(e));
file.getViewers().forEach(e => file.removeViewer(e));
}
}
}
When this script is run, the writers and the viewers of the files without including "xxx1#gmail.com" and "xxx2#gmail.com" as the writer and the viewer are removed.
Note:
When this sample script is run, the writers and the viewers of the files without including "xxx1#gmail.com" and "xxx2#gmail.com" as the writer and the viewer are removed. So, I would like to recommend testing this script using the sample files. Please be careful about this.
Reference:
searchFiles(params)
Added:
From your replying, as another approach, in this case, how about the following sample script? In this sample, the following flow is used.
Retrieve all file IDs just under the specific folder using Drive API.
Retrieve permission IDs from the files using Drive API.
Create the requests for deleting the permissions except for "emails".
Delete permissions using Drive API.
Usage:
1. Install a Google Apps Script library.
In this sample, the batch request is used. In this case, I created a Google Apps Script library for this. So, please install the library. About the method for installing it, you can see it at here.
2. Enable Drive API.
This script uses Drive API. So, please enable Drive API at Advanced Google services.
3. Sample script:
Please copy and paste the following script to the script editor and set emails and folderId. And please run sample(). By this, the script is run.
function sample() {
var emails = ["xxx1#gmail.com", "xxx2#gmail.com"]; // Please set the email addresses.
var folderId = "###"; // Please set the folder ID.
// 1. Retrieve all file IDs just under the specific folder using Drive API.
var list = [];
var pageToken = "";
do {
var obj = Drive.Files.list({q: `'${folderId}' in parents`, maxResults: 1000, pageToken, fields: "items(id),nextPageToken"});
if (obj.items.length > 0) list = [...list, ...obj.items.map(({id}) => id)];
pageToken = obj.nextPageToken;
} while(pageToken);
// 2. Retrieve permission IDs from the files using Drive API.
var req1 = list.map(id => ({method: "GET", endpoint: `https://www.googleapis.com/drive/v3/files/${id}/permissions?pageSize=100&fields=permissions(id%2CemailAddress%2Crole)`}))
var token = ScriptApp.getOAuthToken();
var requests1 = {
batchPath: "batch/drive/v3", // batch path. This will be introduced in the near future.
requests: req1,
accessToken: token
};
var result1 = BatchRequest.EDo(requests1);
// 3. Create the requests for deleting the permissions except for "emails".
var req2 = list.reduce((ar, id, i) => {
var p = result1[i].permissions;
if (p.length > 0) {
p.forEach(e => {
if (e.role != "owner" && e.emailAddress && !emails.includes(e.emailAddress)) {
ar.push({method: "DELETE", endpoint: `https://www.googleapis.com/drive/v3/files/${id}/permissions/${e.id}`});
}
})
}
return ar;
}, []);
// 4. Delete permissions using Drive API.
var requests2 = {
batchPath: "batch/drive/v3",
requests: req2,
accessToken: token
};
var result2 = BatchRequest.EDo(requests2);
}
When this script is run, about all files just under the specific folder, all permissions except for the owner and emails are removed.
Note:
This script removes the permissions. Please be careful about this. So in this case, I would like to propose to test using a sample permitted files.
Reference:
BatchRequest

Google app script Error could not parse text

I am trying to retrieve data by ID. Use the 3rd method in this link: How to speed ​up the search data in sheet
I run the function and err : Could not parse text.
I do not understand why I have used this method so many times and ran well, but this case is faulty.
This is my code:
function loadDataOfThread() {
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("sheet1");
var ID = "12345";
var formatRange = ws.getRange(1, 1, ws.getLastRow() ,ws.getLastColumn()).setNumberFormat("#STRING#");
var query = "select * where A ='" + ID + "'";
var url = "https://docs.google.com/spreadsheets/d/" + ss.getId() + "/gviz/tq?gid=" + ws.getSheetId() + "&tqx=out:csv&tq=" + encodeURIComponent(query);
var options = {
headers: {
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()
}
};
var csv = UrlFetchApp.fetch(url, options);
var f = Utilities.parseCsv(csv); // err this line
var dataArr = [];
if (f.length > 0) {
for (var i = 0; i < f.length; i++) {
dataArr.push(f[i][1]);
}
}
}
I think in a spreadsheet whose data type is the date time column and it make err my function but i have convert to string !!! I do not understand why ?
How about this answer? Your issue might be able to be removed with "PasteDataRequest" because "PasteDataRequest" is better than parseCsv() as the parser of CSV data. In this answer, I would like to propose a method for using "PasteDataRequest" of Sheets API. Please think of this as just one of several answers. The flow of this method is as follows.
Insert a sheet as a temporal sheet.
Put the CSV data to the inserted sheet using "PasteDataRequest" of Sheets API.
Retrieve values from the temporal sheet.
Delete the temporal sheet.
Modified script:
When your script is modified, please modify as follows.
Before you use this script, please enable Sheets API at Advanced Google services.
From:
var f = Utilities.parseCsv(csv);
To:
var temp = ss.insertSheet("temp");
var sheetId = temp.getSheetId();
var resource = {requests: [{pasteData: {data: csv.getContentText(), coordinate: {sheetId: sheetId}, delimiter: ","}}]};
Sheets.Spreadsheets.batchUpdate(resource, ss.getId());
var f = temp.getDataRange().getValues();
ss.deleteSheet(temp);
Note:
Of course, I think that the issue can be also removed by modifying csv of var csv = UrlFetchApp.fetch(url, options);. But from your question, I cannot image the values of your issue. So I proposed above method. If you want to use other method, can you provide a sample Spreadsheet for replicating your issue? Of course, please remove your personal information. By this, I would like to think of the issue.
References:
Method: spreadsheets.batchUpdate
PasteDataRequest
If I misunderstood your question and this was not the direction you want, I apologize.

Export Single sheet and save as sheet name

I have a workbook with multiple sheets in it. I have set it up so that on the main page you can click export next to the sheet name and it exports that sheet to xls. Is there a way to export the sheet and save it as that sheet name rather than the workbook name?
function getSheetUrl() {
var SS = SpreadsheetApp.getActiveSpreadsheet();
var ss = SS.getActiveSheet();
var url = 'https://docs.google.com/a/d/spreadsheets/d/Sheet ID/';
url += 'export?format=xlsx&gid=';
url += ss.getSheetId();
return url;
}
In A49 I have:
https://docs.google.com/a/d.net/spreadsheets/d/SHEET ID/export?format=xlsx&gid=
And then in D:D I have the sheet ID's
And this is what generates the URL
=HYPERLINK(CONCATENATE($A$49,D32),"Export")
I don't thing that can be done by using the URL since you have to capture the new document and change the name.
What I was able to do is to create a script function that will create a new file with the name of the sheet but it will store it in your Drive with an specified Folder ID, then it will take the id for the new file and it will create a url that will be added to the A1:A1 cell for you to download the new document with the sheet name. After 1 minute the file will be sent to trash.
function getSheetUrl() {
var SS = SpreadsheetApp.getActiveSpreadsheet();
var ss = SS.getActiveSheet();
var url = 'https://docs.google.com/spreadsheets/d/SpreadsheetId/';
url += 'export?format=xlsx&gid=';
url += ss.getSheetId();
var params = {
method: "GET",
headers: {
"authorization": "Bearer " + ScriptApp.getOAuthToken()
}
};
var response = UrlFetchApp.fetch(url, params).getBlob().setName(ss.getSheetName()).copyBlob();
var dir = DriveApp.getFolderById("FolderId");
var file = dir.createFile(response);
var id = file.getId();
ss.getRange("A1:A1").setValue("https://docs.google.com/uc?id=" + id + "&export=download");
Utilities.sleep(60000);
DriveApp.getFileById(id).setTrashed(true);
}
I am not a master in Apps Script but is the way I was able to make it work. I hope this helps, if you don't want the file to be sent to trash just delete the 2 lines from utilities and the setTrashed one. You can keep the documents to delete them later also from Trash to avoid using your Drive Space, I was not able to find a method that deletes the file permanently just the removeFile() but this just remove the file from the Drive and apparently it will still use Drive space.
Greetings.