Folder getParents fails to get Team Drive name in Google Script - google-apps-script

I'm trying to build up the full path to a document in a team drive using a script. The code looks like this:
var path = [ ]
var folder = id.getParents()
while (folder && folder.hasNext()) {
var f = folder.next()
path.unshift(f.getName())
folder = f.getParents()
}
This script is bound to a document for testing.
But when I get to the root, instead of returning the actual name of the Team Drive, such as "Accounting" or "Marketing" it instead returns "Team Drive". I need to know the actual name of the Team Drive, why am I not getting this info? If I run this in a script bound to a document in My Drive, it instead says "My Drive" at the root - this at least makes sense, because that's the actual name I see in the browser. In Team Drive, the root is actually "Team Drives" not "Team Drive".

Because Team Drives are implemented differently than "regular" Google Drive "folders", the built-in DriveApp is not guaranteed to work properly for all actions that deal with them. It is possible that at some point DriveApp will be updated to fully support Team Drives, but there are a lot of sensible things that Google still has yet to do ;)
Instead, use the "advanced service" Drive, which is a client application that implements version 2 of the Drive REST API, and allows properly handling Team Drive information. As an "advanced service", you must enable this service before you can use it.
To build the full path of a Team Drive item using only the advanced service:
function getTeamDrivePath(fileId) {
// Declare we know how to handle Team Drive items, and that they be included in responses.
var params = {
supportsTeamDrives: true,
includeTeamDriveItems: true
};
// Return only the fields we want, instead of the whole `File` resource.
params.fields = "id,title,parents/id"
// In a Team Drive, a file can have only one parent folder (e.g. "normal" filesystems).
// (parent.isRoot is never true for Team Drive folders so it is not used.)
var path = [], file;
do {
file = Drive.Files.get(fileId, params);
path.unshift(file.title);
fileId = file.parents.length ? file.parents[0].id : null;
} while (fileId);
// Since we also added the file, the last element of the path array is the filename.
path.pop();
// A Team Drive is subject to different permissions than files, and thus its name must be
// obtained via the Team Drives resource. Since `file` points to the Team Drive, use it:
// Requesting incorrect fields will result in an API error, so request the proper ones:
params.fields = "name"
var td = Drive.Teamdrives.get(file.id, params);
path[0] = td.name;
return path;
}
More reading about Team Drives and handling associated with them is available on the Drive REST API reference. I link the v2 versions since they are what is available via Apps Script's "Advanced Service", but the v3 version should be used for 3rd party applications using the client libraries.
Important resources:
About Team Drives
Enabling Team Drives support
Team Drives API Reference
Enabling "Advanced Services" in Apps Script
API Best Practices: Partial Resources & "fields"

Related

Google Drive API - Adding new users to Shared Drive

I am using a Google App Script to automate the creation of some Google Accounts inside our of our domain. I have been having a little trouble with some of the API calls and such, however this question is more related to whether or not the Drive API gives me the ability to do something. I have code below that adds editor permissions to the new user for their needed Shared Drives, however I would like to give them 'Content Manager' access to the Shared Drive. From the documentation I have looked at it, it does not seem like this is possible using the Google Drive API however I wanted to ask here to make sure I am not missing something when proceeding with the rest of my automation.
switch(ssValues[i][9]){
case "Accounting":
AdminDirectory.Members.insert(groupMember, "notourcompany");
DriveApp.getFolderById("TH1SDR!v3").addEditor(email);
break;
Does not matter if I need to use a different method or another API to achieve this functionality, I just want to know if it is possible to do this using their API's or SDK's through an App Scripts project.
Documentation:
Folder Class-https://developers.google.com/apps-script/reference/drive/folder
Drive API - Drives - https://developers.google.com/drive/api/v3/reference/drives
Drive API - Permissions - https://developers.google.com/drive/api/v3/reference/permissions
It should be possible to manage users over a Shared Drive utilizing the Drive API. The important thing is that you would need to give access over the API and make sure the parameter or argument for supportsAllDrives is set to true.
There is a sample Java code over the official documentation that shows how you can add permissions to a Shared Drive that is "orphaned" or you can test it over it in the permission.create documentation:
You can test it yourself over here.
As you can see in the image, the function is very similar to the code that was created on an old thread utilizing the Drive API V2, however it is using the argument supportsTeamDrive. Sample code that could be edited:
Edit:
I have updated the code based on the one suggested from the thread to a more simplify version.
// Using Apps Script with Advance Google Services v2 of the Drive API enable
function insertPermission() {
const sharedid= 'sharedDriveID' //ID of the Shared Drive
var resource = {
// enter email address here
value: 'emailtest#domain.com',
type: 'user',
// choose from: "owner" or "fileOrganizer". File Organizer would basically be the contentManager of a SharedDrive.
role: 'fileOrganizer'
};
var optionalArgs = {
sendNotificationEmails: false,
supportsAllDrives: true
};
Drive.Permissions.insert(resource, sharedid, optionalArgs);
}
You can review the thread and code here.
References
https://developers.google.com/drive/api/v3/reference/permissions/create
https://developers.google.com/drive/api/guides/manage-shareddrives
I agree with Ricardo Jose Velasquez Cruz. I'm a super admin in my org and this solution finally allowed it to work for me. I had to tweak the optionalArgs to include useDomainAdminAccess.
// Using Apps Script with Advance Google Services v2 of the Drive API enable
function insertPermission() {
const sharedid= 'sharedDriveID' //ID of the Shared Drive
var resource = {
// enter email address here
value: 'emailtest#domain.com',
type: 'user',
// choose from: "owner" or "fileOrganizer". File Organizer would basically be the contentManager of a SharedDrive.
role: 'fileOrganizer'
};
var optionalArgs = {
useDomainAdminAccess: true,
sendNotificationEmails: false,
supportsAllDrives: true
};
Drive.Permissions.insert(resource, sharedid, optionalArgs);
}

How Google drive third party shortcuts work

I was reading about third party shortcuts in Google Drive and able to create a file in Google Drive with the sample payload mentioned in the link.
var fileMetadata = new File()
{
Name = "Project plan",
MimeType = "application/vnd.google-apps.drive-sdk"
};
var request = driveService.Files.Create(fileMetadata);
request.Fields = "id";
var file = request.Execute();
Console.WriteLine("File ID: " + file.Id);
What I am not able to get is where can I set the external file URL here? Or how does it even work?
Answer
It is up to the third-party to handle the redirect to the relevant file for which a shortcut points to. This is done with the Google Drive File ID.
More Information:
Google Drive shortcuts are used to link a file, the contents of which are stored elsewhere than on Drive, such as a third party web app. This isn't done through links, as Drive shortcuts contain no content and are made using the file metadata endpoint.
When you make a Third-party shortcut using the Files.Create method as you show above, the indicative information of the fact it is a shortcut is by the application/vnd.google-apps.drive-sdk mimeType, using the POST URL for inserting file metadata:
POST https://www.googleapis.com/drive/v3/files
Authorization: <AUTHORIZATION HEADER>
{
"title": "File's title",
"description": "File's description",
"mimeType": "application/vnd.google-apps.drive-sdk"
}
When the shortcut is clicked, you will then get redirected to the third party web site from which the file was created - the Google Drive File ID is contained inside the ?state query parameter, as per the documentation on Open Files.
The important thing to know is that it is completely up to the third-party app/website to use Google Drive File ID in the ?state parameter to match the file and content they have stored.
References:
Create a shortcut to an external file
Handle an Open URL
Files: create | Google Drive API | Google Developers
Configure a Drive UI Integration

Get JSON of container-bound Google Apps-Script through Apps-Script or download

If you create a non-container bound g-apps script (i.e. not as part of a gDoc or a gSheet), you can download it (however not view as a .json directly in the browser from the link) from gDrive as a .json. If you download a gDoc or gSheet, it converts to xlsx or docx and opening these with a zip viewer shows a number of files (many of type xml) however none contain the Google version's attached scripts.
Is there a way to read script files as a .json from within another Google Apps
Script? perhaps using the Drive-API or with g-a-s. DriveApp class?
Is there a way to download or read through DriveApp, the .jsons of
container bound scripts (which are usually invisible from all but within the original gFile)?
Update
Based on Kriggs, added a Logger.log(link) and this works great for stand-alone scripts.
How about for container-bound?
for stand alone script files:
exportLinks={
application/vnd.google-apps.script+json=
script.google.com/feeds/download/export?id=[scriptId]&format=json
}
for container-bound script files, there are links to csv, sheet and pdf, but no script json.
exportLinks= {
text/csv=docs.google.com/spreadsheets/export?id=[sheetId]&exportFormat=csv,
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=
docs.google.com/spreadsheets/export?id=[sheetId]exportFormat=xlsx,
application/pdf=
docs.google.com/spreadsheets/export?id=[sheetId]&exportFormat=pdf
}
Update
In Google sheet, go to Tools->script Editor->
URL in address bar looks like:
https://script.google.com/macros/d/
[ProjectKey]/edit?uiv=2&mid=[aVeryLongAlphaNum]
this is the download json:
https://script.google.com/feeds/download/export?id=[ProjectKey]
Question is, can we use the Drive API to find [ProjectKey]
Have there been any feature requests for DriveApp/Drive-API methods to seek Project Keys in your account?
Would there be a way to test if a file has a container bound script? Then the question is, is the script included in the file size (this can be easily tested, however it is unknown to the asker at this point).
Something like this may work although it looks computationally costly:
var SizeOfFile = yourFile.getSize();//
var charsInFile = yourFile.getAsString();
var unicodeSizeReference = [];//get bytes per character array
charsInFile.sort()
//find frequency of characters then multiply from unicoseSizeReference.
//there could be other gotchas as well, however this is just testing for feasibility
var SizeOfTextInFile = [/*#of chars in file name and sheetname*/]+[/*#of chars in all sheets*/];
SizeOfTextInFile *= unicodeBytesPerCharacter;//ranges from 1 to 4
var someThreshold = 10;//bytes
var hasScript=0;
if ([SizeOfFile - SizeOfTextInFile] > someThreshold) hasScript=1
Yes you have to get it trough the Drive API with OAuth2, I used the DriveApp to get the fileId, but you can modify to use Drive api aswell. To enable the Drive API go to Resources -> Advanced Google Services, find the Drive API and turn on.
When you send a get with Drive you get back an object of the file which contains the property exportLinks, using it you fetch the URL with OAuth2 authentication (the ScriptApp.getOAuthToken()), the fetched string will be a JSON, which has the Array fileswith the colection of scripts.
function getAppsScriptAsJson( fileName ) {
var fileDrive = Drive.Files.get( DriveApp.getFilesByName( fileName ).next().getId() );
var link = JSON.parse(fileDrive)[ 'exportLinks' ][ 'application/vnd.google-apps.script+json' ];
var fetched = UrlFetchApp.fetch(link, {headers:{'Accept':'application/vnd.google-apps.script+json', "Authorization":'Bearer '+ScriptApp.getOAuthToken()}, method:'get'});
return JSON.parse(fetched.getContentText());
}
As for container bound:
DriveApp can't get it by name
It doesn't display an ID anywhere, just the project key
Drive API can't lookup by the project id, nor DriveApp
Drive API can't find by the name
There's no reference of the script from the returned object from Drive API nor the DriveApp
I guess it is pretty much incognito, doubt there's any way ATM.
You can always make a Standalone app and set it as a library for the Spreadsheet...

About Google Drive's "state parameter"?

My App for Google Drive is by Google Script.
When I select files in Drive and then call my App, an official "state parameter" will be sent into my App for further digestion. However, the official document is not clear enough for its setting. I need to collect its info from different area like Google I/O video and examples. Is there a good site to introduce it?
Official Site: https://developers.google.com/drive/web/integrate-open
Especially, for the process, User select files in a Active folder => Run App => App save back files to Active Folder ... but this is the problem. How can I know which is the active folder through the state parameter? Any suitable command?
N.B. It is meaningless to use MyFile.getFolders() command, since one file can belong to several folders, and I cannot distinguish which one is "Active" folder.
The documentation shows some code that gets the id after the file is picked from a Google Drive:
// A simple callback implementation.
function pickerCallback(data) {
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].id;
alert('The user selected: ' + fileId);
}
}

how to find files not owned by me in Google apps script

In Google Drive one can search files 'Not owned by me'.
I need access to this from Google Apps Script.
I already tried DocsList.find("Not 'me' in owner"); which appears to be the way to do it in the drive API, but without success (in fact that gets me files with me as owner.) I also replaced me with my email address, with and without quotes, but again without success.
Does anyone know if this is possible (other than by iterating all files and checking getOwner().getEmail() manually, which would take far too long given the enormous amount of files owned by this specific user.)
I think the updated answer as of now is to use DriveApp.searchFiles(params) (https://developers.google.com/apps-script/reference/drive/drive-app#searchFiles(String) ).
Code is something like:
// Log the name of every file in the user's Drive that shared with me
var files = DriveApp.searchFiles('sharedWithMe');
while (files.hasNext()) {
var file = files.next();
Logger.log(file.getName());
}
This function will return an array of all files shared with you. It uses the Advanced Drive Service, which must be enabled before use.
/**
* Get array of files on user's Google Drive that have been shared with them.
* From https://stackoverflow.com/a/15947340/1677912
*
* #returns {Array} Array of file resources
* (see https://developers.google.com/drive/v2/reference/files#resource)
*/
function getSharedWithMe() {
var optionalArgs = {q:"sharedWithMe=true"};
var sharedFiles = Drive.Files.list(optionalArgs);
debugger; // pause in debugger
return sharedFiles.items;
}
You can do the same thing without code, by using the Google Drive SDK "Explorer".
See this previous answer that used this technique to get a list of trashed files.
You'll find the Files List API Explorer at the bottom of the Google Drive SDK documentation page for Files:list. For information about search terms, see Search for files. For our purposes, we'll just use sharedWithMe.
If you leave "fields" blank, you'll get everything that's known about the shared files. You can expand and collapse the results using hard-to-see minus sign tags. But it's helpful to limit the output. I used items(id,selfLink,owners/displayName). Here's how that looks: