Create student roster from Classroom - google-apps-script

I'm trying to pull the student information from a google classroom roster. Here is what I have so far:
function studentRoster() {
var optionalArgs = {
pageSize: 2
};
var getStudents = Classroom.Courses.Students.list("757828465",optionalArgs).students;
Logger.log(getStudents);
}
Sandy's answer below helped solve part of my problem and I get this as a log (names, id's, emails and such changed):
[16-01-05 17:44:04:734 PST] [{profile={photoUrl=https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg, emailAddress=jsdoe#fjuhsd.org, name={givenName=John, familyName=Doe, fullName=John Doe}, id=108117124004883828162}, courseId=757828465, userId=108117124004883828162}, {profile={photoUrl=https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg, emailAddress=jhdoe#fjuhsd.org, name={givenName=Jane, familyName=Doe, fullName=Jane Doe}, id=115613162385930536688}, courseId=757828465, userId=115613162385930536688}]
So my question now is: How do I extract only certain pieces of this information (like full name and email)?
The end result will be pushing it to a google sheet.

The Collection sections under the Classroom API Reference can help clarify some of the object chaining required to access the fields you want.
The following code will generate a list of student names, their email, and associated course ID.
function listStudents() {
var optionalArgs = {
pageSize: 0 // Max output
};
var response = Classroom.Courses.Students.list(xxxxxxxxx; // Put CourseID here
var students = response.students;
if(students && students.length > 0){
for( i = 0; i < students.length; i++){
var student = students[i];
// fullName is normally prefixed with s/ use of .substring removes first two characters
Logger.log('%s %s %s', student.profile.name.fullName.substring(2), student.profile.emailAddress, student.courseId );
}
} else {
Logger.log('No students in this course');
}
}
Collection courses.students

The Quickstart example is using the list method, and the list method takes optional query parameters. One of them being pageSize.
Query parameters for List
You are using the get method.
Documentation - Get method
The only options that the get method has is for the Path. And there is only one setting, the id.
So, that object literal named optionalArgs, you don't need for what you are doing. optionalArgs is an object because it has curly braces, and has elements that are "key/value" pairs. It's "literal", because it's typed into the code, as opposed to building the object with code.
If you did a search, (open the search dialog with Ctrl + F), and searched for optionalArgs, you'll see that it's not being used anywhere. So, the optionalArgs object with the pageSize property is not needed when using the get method.

Related

How can I group exact string together?

I'm working with a google sheet that contains a column of unique project numbers followed by a column with the equipment description used in that project. I have set up my code to send an automatic email with this information to a list of recipients. What I want to do that I don't know how to is what code can I use that will read the project number column and when it finds two project numbers exactly the same, it will group the equipment together into a list that will be sent in that automatic email. I know the list can be a variable but I just don't know how to make it look for spa project numbers to join the equipment together without changing anything in the google sheet. Thank y'all!
I'm agree with Cooper. To build an object is a most obvious way to get unique values (ID, etc) from any list:
var data = [
[1,'a'],
[2,'b'],
[3,'c'],
[1,'d']
]
var obj = {};
data.forEach(x => {
try { obj[x[0]].push(x[1]) } // try to add the value to an existed array
catch(e) { obj[x[0]] = [x[1]] } // or make an array if there is no existed one
});
console.log(obj); // out: [ {1:[a,d]}, {2:[b]}, {3:[c]} ]
var id = 1
console.log('All the values from ID '+ id + ': ' + obj[id].join(', ')); // out: a, d

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);
}

Need to translate one value into another value from a list

Full disclosure - not a programmer...I've just messed a good bit with Excel and Google Sheets, and am stuck trying to find a simple way to transform a specific set of data. Essentially, we have a website host who provides us with the ability to run an export of the results of various fillable forms. One of them is a registration for e-learning videos. The results of the form provide a specific url for each of about 20 videos we maintain...but neither the url or anything in the form itself automatically indicate a human readable label (like "Intro to Application Use") that's useful if someone wants to use the export track what customer has viewed what specific video. So, for each export I do of the data, I need to find a way to run a macro or script that will run through one column of the data, check it against a key that includes each video-specific url, and then spit out a user-readable name for each video into a second column.
So, I need a script that says, if A1:A100=a,b,c,d,e,f,g ("a,b,c,d,e,f,g" being any entry from a list or urls), then set B1:B100=a*,b*,c*,d*,e*,f*,g* ("a*,b*,c*,d*,e*,f*,g*" being the user readable name of each video represented by the urls).
Any thoughts on this? I think I have a way to do it within a formula in all cells in column B, but I'd be referencing so many lengthy urls in that single formula that it seems absurd not to handle it with a script. I'm just a deadbeat when it comes to scripting...
The Questioner is essentially looking for a means to match a meaningful Movie Title to an un-meaningful (but consistent) url provided by a web service. The Questioner says that they have 100 titles though we have no indication of the volume of transactions. Under the circumstances, and without knowing further volumes, the most efficient option is a linear search.
I created a spreadsheet with two sheets:
1 - Titles: Contains a list of the Supplier URL and the associated meaningful "Title Name". This is a sheet that would be maintained by the questioner as Titles are added or dropped.
2 - Transdata: Contains some same data; includes the Supplier URL, and a column set-aside for the meaningful Title name.
The script involves a nested loop. The first level goes through each row of transaction data. The second, nested loop, evaluates the url for each transaction row and returns the Title, which is saved to the sheet in the "MovieName" Column.
To make it easier to process the function, I've added an OnOpen function so that the Questioner can access the main menu to determine when they process.
function so_52892546() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Titles = ss.getSheetByName("Titles");
var Titlesrange = Titles.getDataRange();
var Titlesvalues = Titlesrange.getValues();
var TitleslastRow = Titles.getLastRow();
var Trans = ss.getSheetByName("TransData");
var Transrange = Trans.getDataRange();
var Transvalues = Transrange.getValues();
var TranslastRow = Trans.getLastRow();
for (var i = 1; i < TranslastRow; i++) {
for (var z = 1; z < TitleslastRow; z++) {
if (Transvalues[i][0] == Titlesvalues[z][0]) {
var Title = Titlesvalues[z][1];
//Logger.log("match i = "+1);
}
}
//Logger.log("i="+i+". Title: "+MovieTitle+", date = "+Transdata[i][1]+", income"+Moviesdata[i][2]);
var targetrange = Trans.getRange(i + 1, 4);
targetrange.setValue(Title);
}
}
function onOpen() {
var spreadsheet = SpreadsheetApp.getActive();
var menuItems = [{
name: 'Update Movie names',
functionName: 'so_52892546'
}, ];
spreadsheet.addMenu('Update Movies', menuItems);
Screenshot of the Titles sheet
"Before" and "After" screenshots of the Transdata sheet.

Split Gmail thread and label by date in a google script

Hy,
I have a server sending me several log mails by day and I want to automaticly label this mails.
I can't touch the server configuration to adapt the mail subject, so the work must be done by "receiver".
The Subject is still same so gmail merge them in a thread of 100, but I want to split them by date. So One Date, one thread. In addition, I want label them whith a nested label: "Server1" -> "Date"
I've only found a way to add label to the thread in globality and no way to split them.
Is it even possible?
After a new look on my issue, perhaps add the date at the message subject can split threads.
Like:
function AddLogSubjectADate() {
var threads = GmailApp.search('from:sender#server.com has:nouserlabels');
threads.forEach(function(messages){
messages.getMessages().forEach(function(msg){
var date = msg.getDate();
var date_of_mail = Utilities.formatDate(date, "GMT+1", "yyyy/MM/dd")
var subj = msg.getSubject()
var newsubj = subj + date_of_mail
//A way to modify subject
});
});
}
But I didn't find a way to change the subject.
Post Scriptum
I don't think it's relevant, but here is my previous work. but it add label to the thread. Like I said I haven't find a way to split threads.
function AddLogLabelbyDate() {
var today = new Date();
var tomorrow = new Date();
var yesterday = new Date();
tomorrow.setDate(today.getDate()+1);
yesterday.setDate(today.getDate()-1);
var date_today = Utilities.formatDate(today, "GMT+1", "yyyy/MM/dd")
var date_tomorrow = Utilities.formatDate(tomorrow, "GMT+1", "yyyy/MM/dd")
var date_yesterday = Utilities.formatDate(yesterday, "GMT+1", "yyyy/MM/dd")
var threads = GmailApp.search('from:sender#server.com has:nouserlabels before:'+ date_tomorrow +' after:'+ date_yesterday +'');
label.addToThreads(threads);
}
Per the API documentation, Gmail follows some rules about thread grouping:
In order to be part of a thread, a message or draft must meet the following criteria:1. The requested threadId must be specified on the Message or Draft.Message you supply with your request.2. The References and In-Reply-To headers must be set in compliance with the RFC 2822 standard.3. The Subject headers must match.
So, you can prevent the automatic grouping into a given conversation thread by modifying any of those 3 parameters.
Alternately, you can apply per-message conversation labels, though this will not really help you if you use "Conversation View" UI.
Both of these methods require the use of the Gmail REST API, for which Apps Script provides an "advanced service" client library. The native GmailApp does not provide a method for per-message thread alteration, or for manipulating messages in the manner needed.
Thread Separation
If you wanted to disable the conversation grouping, in theory you could do this:
Message#get to obtain a full message representation
Modify one of the properties Gmail uses to perform thread grouping
Message#insert or import to create the new message on the server
Message#delete to remove the original
Message#get to get the inserted message metadata, after Gmail has given it a threadId.
Get the remaining messages that should share that new threadId, modify them appropriately, and insert.
Repeat.
I haven't tested that approach, hence my "in theory" comment.
Per-message labeling
The relevant API methods include Gmail.User.Labels.list, Gmail.User.Messages.list, Gmail.User.Messages.modify, and Gmail.User.Messages.batchModify. You'll probably want to use the list and messages.batchModify methods, since you seem to have a large number of messages for which you'd like to make alterations. Note, there are non-trivial rate limits in place, so working in small batches is liable to be most resource-efficient.
This is likely to be the simplest method to implement, since you don't have to actually create or delete messages - just search for messages that should have a given label, add (or create and add) it to them, and remove any non-desired labels. To start you off, here are some minimal examples that show how to work with the Gmail REST API. I expect you will need to refer to the API documentation when you use this information to construct your actual script.
An example Labels#list:
function getLabelsWithName(labelName) {
const search = Gmail.Users.Labels.list("me");
if (!search.labels || !search.labels.length)
return [];
const matches = search.labels.filter(function (label) {
// Return true to include the label, false to omit it.
return label.name === labelName;
});
return matches;
}
An example Messages#list:
function getPartialMessagesWithLabel(labelResource) {
const options = {
labelIds: [ labelResource.id ],
fields: "nextPageToken,messages(id,threadId,labelIds,internalDate)"
};
const results = [];
// Messages#list is paginated, so we must page through them to obtain all results.
do {
var search = Gmail.Users.Messages.list("me", options);
options.pageToken = search.nextPageToken;
if (search.messages && search.messages.length)
Array.prototype.push.apply(results, search.messages);
} while (options.pageToken);
return results;
}
An example Messages#batchModify:
function batchAddLabels(messageArray, labels) {
if (!messageArray || !messageArray.length || !messageArray[0].id)
throw new Error("Missing array of messages to update");
if (!labels || !labels.length || !labels[0].id)
throw new Error("Missing array of label resources to add to the given messages");
const requestMetaData = {
"addLabelIds": labels.map(function (label) { return label.id; }),
"ids": messageArray.map(function (msg) { return msg.id; }) // max 1000 per request!
};
Gmail.Users.Messages.batchModify(requestMetaData, "me");
}
Additional Resources:
Message Searches
"fields" parameter

Writing a Formula/Script conditional on Multiple Threshold Values & Applying it Over Many Rows

I am trying to write a formula or script that would take two inputs, a student's Attendance and GPA and spit out their On-Track rating as described here.
My Google Sheet
function ONTRACK(Att, GPA){
function getAttendanceRow(number){
if(number>=98){row="A";}
else if(number>=95){row="B";}
else if(number>=90){row="C";}
else if(number>=80){row="D";}
else {row="E";}
return row
}
function getGPACol(number){
if(number<1){col="F";}
else if(number<2){col="G";}
else if(number<3){col="H";}
else {col="I";}
return col
}
var matrix=getAttendanceRow(Att) + getGPACol(GPA)
var matrix_hash={'AI':5, 'BI':5,
'AH':4, 'CI':4,
'AG':3, 'BG':3, 'BH':3, 'CH':3, 'DI':3,
'BF':2, 'CF':2, 'CG':2, 'DG':2, 'DH':2,'EH':2,
'DF':1, 'EF':1, 'EG':1 }
return matrix_hash[matrix]
}
TWO QUESTIONS
1. How do I apply this across large amount of rows without getting the timeout error?
I've tried to use setFormula
function makeN(){
ss.getRange("N2").setFormula("=ONTRACK(G2*100,H2)");
ss.getRange("N2").copyTo(ss.getRange("N2:N"+lastRow));
}
I've also played around with the map method as suggested my the documentation, but get an error about the first element being undefined. I'm not familiar with the map method, so my problem could lie there.
function ONTRACK2(input){
if (input.map) { // Test whether input is an array.
return input.map(ONTRACK2); // Recurse over array if so.
} else {
// do actual function work here
return ONTRACK(input[0][0]*100, input[0][1])
}
};
Something similar works for GPA, where I'm calculating GPA without an error:
function GPA2(input){
if (input.map) { // Test whether input is an array.
return input.map(GPA2); // Recurse over array if so.
} else {
// do actual function work here
return myAverage(getPoints(input[0][0]), getPoints(input[0][1]), getPoints(input[0][2]), getPoints(input[0][4]))
}
};
The other script-based idea would be to build some kind of array object through iteration that stores the "On Track" values and then writes them to the correct column.
2. Can this be done without AppScripts, as a in-Sheets formula instead?
I was playing around with the fuzzy lookups referenced here
I made a Reference Table
These are the formulas I've tried, where Columns G and H are my Attendance and GPA respectively.
=INDEX('Reference Table'!F2:F20,MATCH(2,INDEX(1/(('Reference Table'!D2:D20=G2)*('Reference Table'!E2:E20<=H2)),0)))
=ArrayFormula(INDEX('Reference Table'!$F$2:$F$20,MAX(ROW('Reference Table'!$D$2:$D$20)*(('Reference Table'!$D$2:$D$20)=G2)*(('Reference Table'!$E$2:$E$20)<=H16))))
Im not sure about the sheet formula, but you can do this with apps script.
Go to your sheet, on the menu, click "Find students Track" and "Calculate track". Column "P" will fill with students track data. Voila!
This is my function
function FindMyTrack()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var student = ss.getSheetByName("StudentData");//select sheets
var refer = ss.getSheetByName("Reference Table");//select sheets
var stData = student.getRange(2, 7, student.getLastRow(), 2).getValues();// get sheet data
var refData = refer.getRange('D2:F20').getValues();// get sheet data
var tracks = [];
//checking each student attendance and GPA with reference data
for(var n in stData ){
for(var p in refData){
if( stData[n][0] >= refData[p][0] && stData[n][1] >= refData[p][1]){
tracks.push([refData[p][2]]);//save matched value in an array
break;
}
}
}
// setting saved track data in the "P" (16) column
student.getRange(2, 16, tracks.length, 1).setValues(tracks);
}
You can check your sheets' associated code ("GPA") for alternations.