Newly created Google Task omits the supplied "TaskLink" property - google-apps-script

I am trying to make a small Google Script that would automatically add Google Tasks to the "My List" TaskList after searching my GMail emails.
Everything goes fine except for adding a link to the email from which the Task is generated from. Trying to follow the API documentation doesn't really help.
This is the code for the actual task generator function:
function addTask(taskListId, myTitle, myEmailLink) {
var task = Tasks.newTask(); // effectively same as "= {}".
task.title = myTitle
task.notes = 'blank';
task.links = [{}]
task.links[0].description = 'Link to corresponding email';
task.links[0].type = 'email';
task.links[0].link = 'myEmailLink';
task = Tasks.Tasks.insert(task, taskListId);
}
Any ideas why the task I receive back has no links?

As others have noted, according to the Google Tasks API Documentation the links collection is unfortunately read-only.
As a potential work around, it appears you can add links to the notes section of a task, and the links are then directly clickable from the tasks pane in GMail.
Picture: Task with clickable link
Your function can be modified to put the link in the notes section as follows:
function addTask(taskListId, myTitle, myEmailLink) {
var task = Tasks.newTask(); // effectively same as "= {}".
task.title = myTitle
task.notes = 'link: ' + myEmailLink;
task = Tasks.Tasks.insert(task, taskListId);
}
Combining this with the getPermalink() function on the GmailApp threads object allows for grabbing a deep link to the email you are looking for.
Picture: Task with permalink to email
I'm working on a set of scripts that do some of the things you're talking about in addition to a few other things: https://github.com/tedsteinmann/gmailAutoUpdate
In my solution I have a function that grabs the GMail threads with a particular label (in my case #Task) and then creates a task setting the subject to thread.getFirstMessageSubject() and the notes to thread.getPermalink()
The entire function looks like this:
function processPending_() {
var label_pending = GmailApp.getUserLabelByName(LABEL_PENDING);
var label_done = GmailApp.getUserLabelByName(LABEL_DONE);
// The threads currently assigned to the 'pending' label
var threads = label_pending.getThreads();
// Process each one in turn, assuming there's only a single
// message in each thread
for (var t in threads) {
var thread = threads[t];
// Grab the task data
var taskTitle = thread.getFirstMessageSubject();
var taskNote = 'Email: ' + thread.getPermalink();
// Insert the task
addTask_(taskTitle, taskNote, getTasklistId_(TASKLIST));
// Set to 'done' by exchanging labels
thread.removeLabel(label_pending);
thread.addLabel(label_done);
}
// Increment the processed tasks count
Logger.log('Processed %s tasks', threads.length);
}

Per the Google Tasks API Documentation:
links[] list
Collection of links. This collection is read-only.
You cannot set these links by modifying a Task resource, i.e your code
task.links = [{}]
task.links[0].description = 'Link to corresponding email';
task.links[0].type = 'email';
task.links[0].link = 'myEmailLink';
is simply ignored by the server.
TaskLinks are, to my knowledge, unusable and non-configurable outside of the Googleplex. They may as well not exist to API users.
The only way I've been able to generate a Task that has one is by using the Gmail UI and selecting "Add to Tasks". The resulting task then includes this snippet in the last line of the Task item:

Related

how to use nextPageToken

I have a script that archives old classrooms, until the end of 2021 it was working fine.
In the lasts months I got an error (the script works ok, but terminate with error) and today I was investigating it, the script runs only once per month.
The error is due to a supposed change in .nextPageToken function.
var parametri = {"courseStates": "ARCHIVED"};
var page = Classroom.Courses.list(parametri);
var listaClassi = page.courses;
var xyz = page.nextPageToken;
if (page.nextPageToken !== '') {
parametri.pageToken = page.nextPageToken;
page = Classroom.Courses.list(parametri);
listaClassi = listaClassi.concat(page.courses);
};
var xyz has been added to better understand what was happening.
So, in this case the list does not have pagination, is only one page. var xyz returns "undefined", and the "if" statement results "true", this makes that variable listaClassi got appended the same content a second time. That generate the error and the abnormal end of the script.
I found an issue reported here https://issuetracker.google.com/issues/225941023?pli=1 that may be related with my problem.
Now I could change .nextPageToken with .getNextPageToken but I found no docs on the second function and many issues reporting that is not working, can anyone help me?
When using the nextPageToken value obtained to the response make sure to enter it as a separate parameter with a slightly different name. You will obtain nextPageToken in the response, the pageToken parameter needs to be entered in the request. It does look like you are doing it right, the way you add the parameter is a bit odd, yet it should be functional.
To discard problems with the Classroom API (that we can certainly take a look at) try with this simple code example in a new Google Apps Script project, remember you will need to add an Advanced service, information about advanced services can be found in this documentation article https://developers.google.com/apps-script/guides/services/advanced. Use listFiles as the main method in your Apps Script project.
function listFiles() {
var totalClasses = 0;
nextPageToken = "";
console.log("Found the following classes:")
do {
var response = loadPage(nextPageToken);
var classes = response.courses;
for (let x in classes){
console.log("Class ID: " + classes[x].id + " named: '" + classes[x].name + "'.");
}
totalClasses += classes.length;
} while (nextPageToken = response.nextPageToken)
console.log("There are " + totalClasses + " classes.")
}
function loadPage(token = ""){
return Classroom.Courses.list({
fields: 'nextPageToken,courses(id,name)',
pageSize: 10,
pageToken: token
});
}
When we first make the API call with Apps Script we don't specify a pageToken, since it is the first run we don't have one. All calls to the List method may return a nextPageToken value if the returned page contains an incomplete response.
while (nextPageToken = response.nextPageToken)
In my code at the line above once response.nextPageToken is empty (not in the response) inside the condition block JavaScript will return false, breaking the loop and allowing the code to finish execution.
To have your incident reviewed by a Google Workspace technician you can also submit a form to open a ticket with the Google Workspace API Support team at https://support.google.com/a/contact/wsdev.

Converting "Create Multiple Choice Form Question from spreadsheet" script to "Create Checkbox etc." script - error?

Question: What is causing the error in this script when replacing "asMultipleChoiceItem()" with "asCheckboxItem"? And is there an obvious way to correct it?
Short version: I am trying to implement a checkbox version of the solution (found here), by replacing "item.asMultipleChoiceItem()" with "item.asCheckboxItem()"; however, I'm encountering an error on debugging "Invalid conversion for item type: MULTIPLE_CHOICE." (debug image here). I'm having trouble troubleshooting to identify/understand, and therefore figure out how to correct, the error.
My code:
function DeptUpdate() {
// User settings
var OPTIONS_SPREADSHEET_ID = "1q21HxRkwXxVtiw7D5fuO-w0JCQtZRd-A35gRtmJUwKk";
var OPTIONS_SHEET_NAME = "Present";
var OPTIONS_RANGE = "A:A"; // We have the options, listed in column A
var itemNumber = 1; // which question on form does this apply to
//
var options2DArr = SpreadsheetApp.openById(OPTIONS_SPREADSHEET_ID).getSheetByName(OPTIONS_SHEET_NAME).getRange(OPTIONS_RANGE).getValues();
var options = options2DArr.reduce(function(a, e) {
if (e[0] != "") {
return a.concat(e[0]);
}
return a;
}, []);
var form = FormApp.openById("1JHZoCdJrsRIltMwKqWGZizRQuy-2Ak2-XET83s04goc");
var item = form.getItems()[itemNumber - 1];
item.asCheckboxItem()
.setTitle("SELECT NAME")
.setChoiceValues(options)
.showOtherOption(true);
}
Long version:
Goal: Google script in Google sheets, on trigger, updates targeted form's checklist options to reflect the items listed in the defined range (excluding blanks).
Purpose/Context: This is one part of a series of forms and spreadsheet that allow me to track arrivals, hall passes out/in, and departures from a study hall in which any 10-20 students from a pool of 120 possible may attend any given day. Spreadsheet is linked to forms to provide a "head's up display" of which students are present, and which are signed out to other locations (this all works fine). Restricting Hall Pass Out and Departure form answer choices (student names) to only those check in as "present" drastically cuts down on time and user errors in the logging system. Currently works with multiple choice, but students frequently arrive/leave in groups. Checkbox (multiple response) would further expedite the tracking process. Spreadsheet is otherwise set up to process multiple response entries; just need the form to appropriately update.
Process/Attempts: I've read of others who adjusted similar (different purpose) scripts to change from dropdown/multiple choice to checkbox without issue ("I just change this and it worked, great!" is the extent of what I've read), but as soon as I change to checkbox, I get the attached error for both the showOtherOption field, and (if that is removed), the setChoiceValues field. I'm thinking it could possibly be an issue with the checkbox item reading the array differently than the multiple choice item does? However, I haven't be able to find anything in the documentation or Q/A posts on a significant difference between the two functions parameters. At this point, I'm just a little flummoxed on what might be causing the issue.
Background: I have no formal (or significant informal) coding training, but have tweaked and adapted a variety of codes for about a decade. I understand the basic processes/concepts of code and programming logic, but lack a substantial/cohesive vocabulary.
I'm including a link to a dummy copy of the spreadsheet and the form, in case that's helpful.
Spreadsheet
Form
Thank you in advance for any insights!
Brandy
The problem is that you have a ListItem but try to convert it to a CheckboxItem
This is not directly possible. There is a feature request on Google's Public Issue Tracker for this feature. I recommend you to give it a "star" to increase visibility.
In the mean time, if you want to convert an item type, you need to do it manually by creating a new item, passing it the title and choice from the old one and deleting the old item:
Sample
function DeptUpdate() {
// User settings
var OPTIONS_SPREADSHEET_ID = "1wHE6b5ZuAKJTM4N7t6nlB5SdU9h24ueuxon4jnH_0sE";
var OPTIONS_SHEET_NAME = "Present";
var OPTIONS_RANGE = "A:A"; // We have the options, listed in column A
var itemNumber = 1; // which question on form does this apply to
//
var options2DArr = SpreadsheetApp.openById(OPTIONS_SPREADSHEET_ID).getSheetByName(OPTIONS_SHEET_NAME).getRange(OPTIONS_RANGE).getValues();
var options = options2DArr.reduce(function(a, e) {
if (e[0] != "") {
return a.concat(e[0]);
}
return a;
}, []);
var form = FormApp.getActiveForm();
var listItem = form.getItems()[itemNumber - 1];
listItem
.asListItem()
.setTitle("SELECT NAME")
.setChoiceValues(options)
var title = listItem.getTitle();
var choices = listItem.asListItem().getChoices();
var checkboxItem = form.addCheckboxItem();
checkboxItem.setTitle(title)
.setChoices(choices)
.showOtherOption(true);
form.deleteItem(listItem);
}

Using Google Apps Script, how do you remove built in Gmail category labels from an email?

I'd like to completely undo any of Gmails built in category labels. This was my attempt.
function removeBuiltInLabels() {
var updatesLabel = GmailApp.getUserLabelByName("updates");
var socialLabel = GmailApp.getUserLabelByName("social");
var forumsLabel = GmailApp.getUserLabelByName("forums");
var promotionsLabel = GmailApp.getUserLabelByName("promotions");
var inboxThreads = GmailApp.search('in:inbox');
for (var i = 0; i < inboxThreads.length; i++) {
updatesLabel.removeFromThreads(inboxThreads[i]);
socialLabel.removeFromThreads(inboxThreads[i]);
forumsLabel.removeFromThreads(inboxThreads[i]);
promotionsLabel.removeFromThreads(inboxThreads[i]);
}
}
However, this throws....
TypeError: Cannot call method "removeFromThreads" of null.
It seems you can't access the built in labels in this way even though you can successfully search for label:updates in the Gmail search box and get the correct results.
The question...
How do you access the built in Gmail Category labels in Google Apps Script and remove them from an email/thread/threads?
Thanks.
'INBOX' and other system labels like 'CATEGORY_SOCIAL' can be removed using Advanced Gmail Service. In the Script Editor, go to Resources -> Advanced Google services and enable the Gmail service.
More details about naming conventions for system labels in Gmail can be found here Gmail API - Managing Labels
Retrieve the threads labeled with 'CATEGORY_SOCIAL' by calling the list() method of the threads collection:
var threads = Gmail.Users.Threads.list("me", {labels: ["CATEGORY_SOCIAL"]});
var threads = threads.threads;
var nextPageToken = threads.nextPageToken;
Note that you are going to need to store the 'nextPageToken' to iterate over the entire collection of threads. See this answer.
When you get all thread ids, you can call the 'modify()' method of the Threads collection on them:
threads.forEach(function(thread){
var resource = {
"addLabelIds": [],
"removeLabelIds":["CATEGORY_SOCIAL"]
};
Gmail.Users.Threads.modify(resource, "me", threadId);
});
If you have lots of threads in your inbox, you may still need to call the 'modify()' method several times and save state between calls.
Anton's answer is great. I marked it as accepted because it lead directly to the version I'm using.
This function lets you define any valid gmail search to isolate messages and enables batch removal labels.
function removeLabelsFromMessages(query, labelsToRemove) {
var foundThreads = Gmail.Users.Threads.list('me', {'q': query}).threads
if (foundThreads) {
foundThreads.forEach(function (thread) {
Gmail.Users.Threads.modify({removeLabelIds: labelsToRemove}, 'me', thread.id);
});
}
}
I call it via the one minute script trigger like this.
function ProcessInbox() {
removeLabelsFromMessages(
'label:updates OR label:social OR label:forums OR label:promotions',
['CATEGORY_UPDATES', 'CATEGORY_SOCIAL', 'CATEGORY_FORUMS', 'CATEGORY_PROMOTIONS']
)
<...other_stuff_to_process...>
}

addEditors to Document from formSubmit

I'm Working on creating script that will perform the actions described below. so far, I've managed to get the first two parts to function, but am now stuck on getting anything more to work. I've reviewed several response forums and tried the suggestions, but no success.
Desired script flow:
form submitted from spreadsheet form
completes fields:
Timestamp
Username (email collected on submission)
Student
Grade
Intervention Plan
Core Reading/Math
Team (email list)
1 script runs onFormSubmit that then creates a copy of a template document, renames that new copy to the e.value "student" submitted in form,
2 then replaces selected text strings within the document with values submitted in the form.
3 Add editors to new document and sends notification with desired instructions
4 creates event (one week from submission date) event details include instructions and link to shared document for team members to complete with their input, sends event invitation to email list.
Here is the working script so far.
function formSubmitValues(e) {
var timeStamp = e.values[0];
var userEmail = e.values[1];
var student = e.values[2];
var grade = e.values[3];
var conern = e.values[4];
var readingCore = e.values[5];
var mathCore = e.values[6];
var interventions = e.values[7];
var team = e.values[8].toString(); // "just to be sure"..Henrique says add .toString this allowed the replaceText part to work
//Makes copy of template document and renames
var tempID = ("1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE") // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(student + " Initial Referral") // names new copy as student's name
.getId();
// trying to add editors to new document using email list generated in form submit value of "team"
DocsList.getFileById(copyId).addEditors([team]);
// replaces text within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%STUDENT%", student);
body.replaceText("%DATE%", timeStamp);
body.replaceText("%TEACHER%", userEmail);
body.replaceText("%TEAM%", team);
return doc;
}
REPORTED ISSUE RESPONSE: Here is what they said: "The function takes an array or strings, like: DocsList.getFileById(copyId).addEditors(['parent#domain.com', 'parent2#domain.com']);
I tried entering emails directly into script like this and things worked.
So my problem is not the 'addEditors method, but lies in getting the form submitted emails to be passed correctly. Any suggestions on how I would do this?
I have tried what I believe to be all combinations of using .toString(), or not, and using .Split(',').
RE-DEFINE PROBLEM : So it is an issue of how the emails are passed from the e.values form submit.
Here is where I'm at:
When I type emails into script directly: .addEditors(['parent#domain.com', 'email2#domain.net', 'email3#gmail.com']) it works, (I did have to move the addEditors method in the script to go right after the .makeCopy instead of at the end.)
This is what the Execution Transcript shows:
File.addEditors([[parent#domain.com, email2#domain.net]]) and it runs the rest of the script. note: One part I don't understand is the single quotes i.e. 'email' They must be typed in the script, but don't show up on Transcript when run. I've tried putting them around emails in the form, but it makes them show in Transcript and still doesn't run anyway.
So this is what script looks like now:
var tempID = ("1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE") // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(student + " - TestingCopy") // names new copy as student's name + text
.addEditors([team.split(',')])
.getId();
But when I use the var team with or without .split(',') it does not work. But in the Transcript it shows:
File.addEditors([[rreynolds#domain.net, parent#domain.com]])
which looks identical as to what shows when it does work, but that is the last thing shown in Transcript and editors are not added to document and the script does not finish.
I'm obviously not understanding something here. Is there a way I could get the emails in the team e.values to be treated in a way that the addEditors method is requiring? In the spreadsheet cell they appear as a CSV. i.e rreynolds#domain.net, parent#domain.com
Do they have to be read one at a time, or something?
I'm learning a lot, and appreciate all your help. I am sorry for the confusion with all the comments, but am not sure of the correct way to address issues in this forum. For example: should I go back and edit my original script to show the current version, or add it someplace else? I'm trying to keep the conversation flowing, so that it is easier for others to follow - Thanks rob
please let me give a last (hopefully) clear answer : (thanks for sharing the spreadsheet, this is far more easy to work on ;-)
here is your code fully working.
I have created some intermediate variables to show how it works.
function formSubmitEditors(e) {
// defines spreadsheet form events on submit of form. This function formSubmitEditors is triggered on formSubmit
var timeStamp = e.values[0];
var fileName = e.values[1];
var team = e.values[2].replace(/, /g,"|"); // remove unwanted spaces and commas replace by | for visibility ;-)
Logger.log(team);// contains | as separators
var teamArray = team.split('|');
Logger.log(teamArray.length+' : '+teamArray);// check that it is an array of x elements
//Makes copy of template document and renames
var tempID = '1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE' // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(fileName + " - TestingCopy") // names new copy as student's name + text
.getId(); //
var file = DocsList.getFileById(copyId).addEditors(teamArray);
// replaces merged-text values within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%FILE%", fileName);// you wrote %FILENAME% in place of %FILE%
body.replaceText("%DATE%", timeStamp);
body.replaceText("%TEAM%", team);// it will be shown with | as separators, if you don't like it replace team by teamArray.toString() to get commas again.
}
EDIT : I removed the toString() for team event, not necessary since e.parameters are already strings.
EDIT2 : to be complete and do what you needed in the initial question you could replace the end of the code with this one that creates the Cal event on next week and sends invites with link to the doc.
var file = DocsList.getFileById(copyId).addEditors(editorsArray);
var fileurl = file.getUrl();
Logger.log(fileurl)
// replaces merged-text values within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%FILE%", fileName);
body.replaceText("%DATE%", timeStamp);
body.replaceText("%MAILS%", editors);
var Cal = CalendarApp.getCalendarsByName('testencodage')[0];// replace with your calendar name you want to use
var newEvent = Cal.createAllDayEvent('Fill the questionnary', new Date(new Date(newtimeStamp).getTime()+7*24*3600*1000), { guests : e.values[2] , sendInvites : true , description :"Don't forget to fill this document "+fileurl})
}
I'd suggest a couple of things
Add a few Logger.log statements in your code to print out debug information.
Add a try ... catch block around the entire section of code and print out the exception. See if you are getting any exception.
Last, use the Execution transcript window to see where your script stopped, if it did.
There are 2 ways to add an editor by email : addEditors([emailAddresses]) and addEditor(emailAddress)
The first has an "s" and needs an array of email adress strings, the second takes a single string as argument. You should use the second or add [brackets] to the email string in your code.
concerning you comment // need to figure out where/how to: .addEditors(email1,email2,etc);
// is this done from DocsList or DocumentApp class?
addEditor() and addEditors() belong to the file class, a member of DocsList class , you can add user(s) using user(s) objects or user(s) email(s) as explained in the doc.
It could be used like this DocFile.addEditors([email1,email2])
EDIT : A lot of comments on this post, sorry about that, it has become quite uneasy to read... I tested these addEditors feature with spreadsheet and it works as expected, using simple array for multiple user emails and string for single email. The document service seems to have a problem with the addEditor() method and it should be reported to the issue tracker.
REPORTED I've reported issue #1512 - Rocketrob

Allowing others to add Google tasks

I am looking for a way for employees to send me an email or add information to a spreadsheet that will then add tasks to my task list. Ideally, the script would capture the task list, task, due date, and any notes.
I have already successfully implemented five scripts (five task lists) that allow my employees to add tasks to specific tasklists, following this script shown below. This works OK but does not have the capacity to add due dates or notes:
Automated email to task list API
I recently came across references to scripts that monitors task lists, and then posts them to a spread sheet, including task, due dates, notes, etc. It strikes me that a spreadsheet might be a better way to do this though it does not have the convenience of email:
Task list to spreadsheet API
I wonder if the REVERSE can be done. I envision a spreadsheet that I could give my employees access to, with two worksheets (NEW and PROCESSED) with columns:
TASKLIST TASK DUE DATE NOTES
and the script would run through this every hour or two. Anything in NEW would be processed and added to my task list, then moved to the end of PROCESSED.
Does anyone know of something like that out there? Alternatively, perhaps there are ways to change the email script so that it moves anything in the body of the email into the NOTES section of the task. I am a raw newbie at this BTW. Thanks.
you should replace
var newTask = Tasks.newTask().setTitle(title);
by
var newTask = Tasks.newTask().setTitle(title).setDue(date).setNotes(notes);
I'm also stuck in the way
I can from a spreadsheet :
- Create a new tasklist
- Create a new task in a dedicated tasklist (with due date and notes)
I can from the Gtasks :
- Check if the task is completed and mark it as completed in the spreadsheet
- Check if the task still exists in the spreadsheet and remove it if necessary
I'm still looking for a way to make a task completed in GTasks when it's closed in spreadsheet
All the functionality exists for you to accomplish this, but I don't know if there is a pre-built script out there that does what you want. You may want to look into use a Google Form that saves data to the spreadsheet, and then create a trigger for form submit that scoops up the data and creates a new task using it.
Is this [part] of what you're looking for?
https://developers.google.com/apps-script/articles/google_apis_reading_list
It syncs a Spreadsheet based task list with a your regular Task List, and if you mark the task done in gmail, it records that back in the spreadsheet.
// Fetch the list of URLs to keep synchronized
var articleUrls = SpreadsheetApp.getActiveSheet().getRange("A2:A");
for (var rowNum = 0; rowNum < articleUrls.getNumRows(); rowNum++) {
// Limit our range to a single cell containing a URL
var oneUrlCell = articleUrls.offset(rowNum, 0, 1, 1);
if (oneUrlCell.getComment() === "") {
// This is a new URL that needs to be shortened/inserted
var urlText = oneUrlCell.getValue();
if (urlText !== "") {
// Shorten the URL
Logger.log("Adding task for url: " + urlText);
var toShorten = UrlShortener.newUrl().setLongUrl(urlText);
var shortened = UrlShortener.Url.insert(toShorten);
// Insert the shortened URL into our reading list
var taskToInsert = Tasks.newTask().setTitle(shortened.getId());
taskToInsert.setNotes(urlText);
var newTask = Tasks.Tasks.insert(taskToInsert, readingListId);
// Save the new ID as our comment.
oneUrlCell.setComment(newTask.getId());
}
} else {
// This URL has already been inserted, update the status
var existingTask = Tasks.Tasks.get(readingListId, oneUrlCell.getComment());
if (existingTask.getStatus() === "completed") {
var absRowNum = oneUrlCell.getRow();
var completedCell = sheet.getRange(absRowNum, 2);
completedCell.setValue("Yes");
}
}
Should be part of the solution, no?
I'm looking to make something a bit bigger myself.