How to change Topic names Classroom.Courses.Topics.patch() - google-apps-script

I'm using Google Apps Script to change the names of a few Topics in several Google Classrooms. I'm using Classroom.Courses.Topics.patch() to change only the 'name' value of the Topics, but my script does not change anything when I look at the Classrooms.
Here is an example: I have two Classrooms (course IDs '100000000000' and '100000000001'). In each Classroom I have three Topics (topic names 'topic1', 'topic2', and 'topic3'). I want to change the name of the first two topics to 'newtopic1' and 'newtopic2' respectively, in both classrooms.
I suspect there could be something off with the way I'm doing the update mask, but I've tried re-ordering things, and I still can't get it to go... Could also be my nested for loops?
function updateTopicNames() {
var courseIds = ['100000000000','100000000001'];
var topicNamesOld = ['topic1','topic2'];
var topicNamesNew = ['newtopic1', 'newtopic2'];
for (var i = 0; i < courseIds.length; i++) {
var topics = Classroom.Courses.Topics.list(courseIds[i]).topic;
var topicObj = topics.reduce((o, e) => Object.assign(o, {[e.name]: e.topicId}), {});
for (var j = 0; j < topicObj.length; j++) {
for (var k = 0; k < topicNamesNew.length; k++) {
var topicId = topicObj[topicNamesOld[j]];
var newName = {'name':topicNamesNew[k]};
var extra = {'updateMask':'name'};
var exec = Classroom.Courses.Topics.patch(newName, topicId, courseIds[i], extra);
}
}
}
}
I checked out the courses.topics.patch API, but there is no example of the update mask implementation for me to extrapolate from.
I tried to bootstrap from code for other .patch() things: StudentSubmissions.Patch UpdateMask Error and How to change course owner using Classroom.Courses.patch() but something is not working when I try to convert these for Topics.patch().

I believe your situation as follows.
The index of topicNamesOld is corresponding to the index of topicNamesNew.
Modification point:
In your script, topicObj is not an array. So you are not required to use the for loop.
When this is reflected to your script, it becomes as follows.
Modified script:
function updateTopicNames() {
var courseIds = ['100000000000','100000000001'];
var topicNamesOld = ['topic1','topic2'];
var topicNamesNew = ['newtopic1', 'newtopic2'];
for (var i = 0; i < courseIds.length; i++) {
var topics = Classroom.Courses.Topics.list(courseIds[i]).topic;
var topicObj = topics.reduce((o, e) => Object.assign(o, {[e.name]: e.topicId}), {});
for (var k = 0; k < topicNamesNew.length; k++) {
var topicId = topicObj[topicNamesOld[k]];
var newName = {'name': topicNamesNew[k]};
var extra = {'updateMask':'name'};
var exec = Classroom.Courses.Topics.patch(newName, courseIds[i], topicId, extra);
}
}
}
Note:
In that case, it seems that the topics created with the same GAS project can be updated. For example, it seems that the topics created by other client cannot be updated. It seems that this is the current specification. Please be careful this.
Reference:
Method: courses.topics.patch

Related

Use importData to fetch and parse JSON

I had the following function running perfectly:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var habSheet = ss.getSheetByName("Harvests");
var bVals = habSheet.getRange("b2:b").getValues();
var habs = bVals.filter(String).length;
var habitats = habSheet.getRange("B2:B"+habs+1).getDisplayValues();
var data = [];
var traitNames = habSheet.getRange("D1:U1").getValues();
var values = new Array(habs);
for (i = 0; i < habs; i++) {
values[i] = new Array(traitNames[0].length);
for (j=0; j<traitNames[0].length; j++){
values[i][j] = [""];
}
}
var rawData = "";
var names = new Array(habs);
for (i = 0; i < habs; i++) {
names[i] = new Array(1);
}
for (i=0; i<habs; i++){
try{
rawData = UrlFetchApp.fetch("https://api.genopets.me/habitat/"+habitats[i]);
data[i] = JSON.parse(rawData.getContentText());
names[i][0] = data[i].name;
for (j=0; j<data[i].attributes.length; j++){
value = data[i].attributes[j].value;
trait = data[i].attributes[j].trait_type;
for (k=0; k<=21; k++){
if (traitNames[0][k] == trait){
values[i][k] = value;
}
}
}
}
catch(err){
But I'm exceeding max fetch calls daily. I'm in an emergency situation because this needs to run again within an hour.
I'm trying to build a temporary fix, so I'm using importData to call the API with the following formula:
=join(",",IMPORTDATA("https://api.genopets.me/habitat/"&B2,","))
Then, I want to just replace rawData in the code with this imported data. However, now it comes in as text and can't be parsed in the same way. Is there a quick way to force it into JSON format or otherwise convert to a dictionary as before so that I can parse it with the same code?
I'm getting stuck because .name, .length, etc. are failing as the "rawData" is now just a string.
This is the code snippet I'm playing with to try and get this right and build the quick patch for right now:
// for (i=0; i<habs; i++){
var i=0;
importData = habSheet.getRange("AL1").getDisplayValue();
rawData = JSON.stringify(importData);
// Logger.log(rawData);
data[i] = rawData;
// data[i] = JSON.parse(rawData.getContentText());
names[i][0] = data[i].name;
for (j=0; j<data[i].attributes.length; j++){
value = data[i].attributes[j].value;
trait = data[i].attributes[j].trait_type;
for (k=0; k<=21; k++){
if (traitNames[0][k] == trait){
values[i][k] = value;
}
}
}
I've tried as above, and also without stringify, but I can't get this yet.
For reference, this is an example of the API response:
https://api.genopets.me/habitat/7vTz9dniU14Egpt8XHkMxP1x36BLRd15C11eUTaWhB19
Appreciate any help!
I have done a lot of testing to find a simple workaround, but could not find one, the string resulting from the =join(",",IMPORTDATA(url,",")) (and none of the other =IMPORTXXX functions) will work for your code. When using these IMPORT functions the data is interpreted and certain characters are removed or the values formatted, it is NOT recommended to use these functions.
Since you mentioned the message you are getting is related to quota limits you should consider splitting the load of this script in multiple Apps Script projects. As a possible immediate solution you can make a copy of the script (or file bound to the script), authorize the new copy and try again.
To increase performance you could try using the calls in bulk, use this other function fetchAll (https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchallrequests). There is a 100 request limit for this method. This will result in the same quota usage.

App Scripts If Statement always returning Else Condition

I am working on a script to perform an Index/Match task on two seperate workbooks. The code seems to be working but my If statement is always returning its Else condition. I have logged the compared vairables find and searchref and found that they do match at some point durring the loop but the If statement still returns its Else condition.
I suspect this has something to do with how I am comparing these arrays but I have not been able to figure it out.
Here is a snip of the first few columns and rows for the source spreadsheet for searchData I am trying to access the information in column B.
Source data for searchData
Here is the output from Logger.log for findData and searchData
Logger with labels
Logger arrays
Source data for findData
function generateBillOfMaterials() {
// --------------------------------------------------------------------
// Declare variables
var i, j
var find
var searchref
var found = []
// --------------------------------------------------------------------
var search_spreadsheet = SpreadsheetApp.openById("Searched-Spreadsheet-ID");
var find_spreadsheet = SpreadsheetApp.openById("1xg2yVimBwE5rGvSFMtID9O9CB7RauID34wqIH5LLTeE");
var ssheet = search_spreadsheet.getSheetByName("METAL PARTS");
var fsheet = find_spreadsheet.getSheetByName("Bill of Materials");
var FMaxR = fsheet.getMaxRows();
fsheet.getRange(2, 3, FMaxR, 1).clear({contentsOnly: true});
var findData = fsheet.getDataRange().getValues();
var searchData = ssheet.getDataRange().getValues();
for (i = 0; i < findData.length; i++) {
for (j = 0; j < searchData.length; j++) {
find = findData[i][1];
//Logger.log(find)
searchref = searchData[j][0];
//Logger.log(searchref)
if (find == searchref && find != "")
{
found[i] = searchData[j][1]
}
else
{
found[i] = ['n/a']
}
// found = ssheet.getRange(j+1,2,1,1).getDisplayValue();
// fsheet.getRange(i+1,16,1,1).setValue(found);
}
}
Logger.log(found)
fsheet.getRange(2, 3, found.length, 1).setValues(found)
}
The main problem in the sample code is the else statement containing this:
found[i] = ['n/a']
This will overwrite whatever is found earlier in the loop, because even after a match has been found (and assigned to the found array), the loop continues comparing the remaining values in the inner loop.
The following approach shows how to correct this, making as few changes as possible to your existing code:
function generateBillOfMaterials() {
// --------------------------------------------------------------------
// Declare variables
var i, j
var find
var searchref
// --------------------------------------------------------------------
var search_spreadsheet = ... ;
var find_spreadsheet = ... ;
var ssheet = search_spreadsheet.getSheetByName("METAL PARTS");
var fsheet = find_spreadsheet.getSheetByName("Bill of Materials");
var FMaxR = fsheet.getMaxRows();
fsheet.getRange(2, 3, FMaxR, 1).clear({contentsOnly: true});
var findData = fsheet.getDataRange().getValues();
var found = new Array(findData.length).fill('n/a');
var searchData = ssheet.getDataRange().getValues();
for (i = 0; i < findData.length; i++) {
for (j = 0; j < searchData.length; j++) {
find = findData[i][1];
searchref = searchData[j][0];
if (find === searchref && find !== "") {
found[i] = searchData[j][1];
break;
}
}
}
const found2 = found.slice(1).map(x => [x]);
fsheet.getRange(2, 3, found.length-1, 1).setValues(found2);
}
Notes:
We pre-fill the array of "found" values with "n/a":
var found = new Array(findData.length).fill('n/a');
This allows us to overwrite "n/a" when we find a value - otherwise we leave the "n/a" untouched.
When a match is found, we break out of the inner loop using break.
Then we can remove the else condition - as we no longer need it.
The remaining changes are to ensure the final shape of the found data is a two-dimensional array which can be written to the spreadsheet.
The above approach involves repeatedly looping over the data in the inner loop.
In reality, we only need to visit each list once, in order to perform the lookups we need.
Implementing this alternative approach would basically be a rewrite of what you have - and I would imagine that what you have, even if it is somewhat inefficient, is perfectly OK for your needs. But I did want to mention this.
The other note which may be of interest is that my alternative approach is more-or-less the equivalent of using a Google Sheets vlookup formula. Apologies if you are already aware of that. And I have never tried using that formula across 2 separate files, anyway. But again, just wanted to mention it, for completeness.
Update 2
"Is there a lookup command that could be used in place of the for loops?"
It's more a question of avoiding the nested loops, and using a JavaScript data structure that supports lookups (a Map).
Here is a sketch:
// assume we have already populated searchData and findData, as usual.
// first iterate searchData to build a lookup map:
let partsLookup = new Map();
for (i = 1; i < searchData.length; i++) {
partsLookup.set( searchData[i][0], searchData[i][1] );
}
// now iterate the BOM data and use the lookup map:
for (i = 1; i < findData.length; i++) {
var foundValue = partsLookup.get( findData[i][1] );
console.log( foundValue ); // add this to the "found" array
}
This is obviously not "finished" code - it just shows the approach. But no nested iterations are needed.
The number of loops performed is searchData.length + findData.length, instead of up to searchData.length * findData.length

Check if contact exists under google contacts with "ContactsApp.getContact"

I'm noob regarding scripting so keep that in mind. :-)
I want my script to read from google sheet and and check if that contact exist under google contacts and if not to create one.
Contacts are checked by email and have label "Client". I can't get if statement to confirm if contact exist or not. If i remove If for checking contacts it will create contact for every single entry so i think that that part is fine, but i need to fix part how to check if contact already exists so it wouldn't create duplicated entry.
function addClinet() {
var ss = SpreadsheetApp.openById('XXXX');
var sheetNew = ss.getSheetByName('NewClient');
var Avals = sheetNew.getRange('B1:B').getValues();
var lastRow = Avals.filter(String).length;
for (var i = 2 ; i <= lastRow; i++){
var nameID = sheetNew.getRange(i, 2).getValue();
var emailID = sheetNew.getRange(i, 8).getValue();
var mobID = sheetNew.getRange(i, 9).getValue();
var firstName = nameID.split(' ').slice(0, -1).join(' ');
var lastName = nameID.split(' ').slice(-1).join(' ');
var regex = new RegExp (/^\w/);
var firstChar = regex.exec(mobID);
var contacts = ContactsApp.getContact(emailID);
if (contacts == null){
if (firstChar == 8){
var mobID = 'xxx' + mobID;
}
var contact = ContactsApp.createContact(firstName,lastName, emailID);
var contacts = ContactsApp.getContact(emailID);
contact.addPhone(ContactsApp.Field.WORK_PHONE, mobID);
var group = ContactsApp.getContactGroup("Clients");
group.addContact(contact);
}
}
}
Thx
I wouldn't use the ContactsApp.getContact([email]) function -- For whatever reason Google Apps Script's contacts search by email is excruciatingly slow. Since it sounds like you have a number of contacts that you are sorting through at any given time, I would recommend you use the same -- instead of searching for the email address through Google Apps Script (this takes about 16-20 seconds PER CONTACT)
Using the following function you will be able to create one large JSON object of all of your contacts, with their email addresses as the key so you can quickly test whether an email address is present in your contacts (this takes about 11 seconds for around 5000 contacts:
function emailsasJSON() {
var emailjson = {}
var myContacts = ContactsApp.getContactGroup('Clients').getContacts();
for (var i = 0; i < myContacts.length; i++) {
var emails = myContacts[i].getEmails();
var phonesobj = myContacts[i].getPhones();
var phones = {}
for (var j = 0; j < phonesobj.length; j++) {
phones[phonesobj[j].getPhoneNumber().replace(/[_)(\s.-]/g,'')] = 1;
}
for (var j = 0; j < emails.length; j++) {
emailjson[emails[j].getAddress().toLowerCase()] = {id: myContacts[i].getId(), phones: phones};
}
}
Logger.log(JSON.stringify(emailjson))
return emailjson;
}
Using the emailjson object you can compare each of your contacts MUCH faster -- It will create this in about 10 seconds -- we will use this later.
Secondly, there are some things in your code that I would clean up -- it looks to me like you have a sheet with the name in column B, email in column H, and mobile number in column I.
Instead of collecting all of those values individually per cell (takes a long time), you should collect the entire data set as an array and then work with it that way:
function addClinet() {
var ss = SpreadsheetApp.openById('XXXX');
var sheetNew = ss.getSheetByName('NewClient');
var clientsgroup = ContactsApp.getContactGroup('Clients')
//this is where we will insert the function from above to get the emailjson obj
var emailjson = emailsasJSON()
var contactarray = sheetNew.getDataRange().getValues();
for (var i = 1 ; i < contactarray.length; i++){
var name = contactarray[i][1]
var email = contactarray[i][7]
var phone = contactarray[i][8]
if(emailjson[email.toLowerCase()].id) { //check if email exists
if(!emailjson[email.toLowerCase()]['phones'][phone.replace(/[_)(\s.-]/g,'')]) { //if email exists but phone doesn't, add phone
ContactsApp.getContactById(emailjson[email.toLowerCase()].id).addPhone(ContactsApp.Field.MOBILE_PHONE, phone)
emailjson[email.toLowerCase()]['phones'][phone.replace(/[_)(\s.-]/g,'')] = 1; //add it to the emailjson object in case there are more iterations of this contact in the sheet
}
} else { //add new contact if it doesn't exist
var newcontact = ContactsApp.createContact(name.split(' ')[0],name.split(' ')[1], email)
newcontact.addPhone(ContactsApp.Field.MOBILE_PHONE, phone)
emailjson[email.toLowerCase()]['id'] = newcontact.getId();
emailjson[email.toLowerCase()]['phones'][phone.toString().replace(/[_)(\s.-]/g,'')] = 1;
clientsgroup.addContact(newcontact)
}
}
}
I don't have your datasheet to error check this but this should speed up your function by a considerable amount. Let me know if it throws any errors or, if you could give me an example sheet, I could test it.
Lastly, I imagine that this isn't a client list that you consistently update, so I would take them off of this sheet and move them elsewhere, although it would take a considerable number of contacts on the list to bog this function down.

How to delete CourseWork (Assignments) by title in Google Classroom

I have several courses that all have the same set of topics with coursework (Assignments) that have the same title in each of those topics. I am trying to delete those assignments from just a few of the topics in each course using Apps Script. I expected to be able to use Classroom.Courses.CourseWork.delete(), but, so far, my code does nothing. No error - just no change in the Assignments. I think my permissions and such are in order, because these are assignments that I created with this same account/permissions.
My issue might be that I am not able to get the coursework ids from the name of the assignment. I'm not sure how to do that, but I think it could be missing from my code.
Here's what I tried:
function deleteAssignments() {
var courseIds = ['100000000000','100000000001','100000000002'];
var topicNames = ['topic3','topic2','topic1'];
for (var i = 0; i < courseIds.length; i++) {
var topics = Classroom.Courses.Topics.list(courseIds[i]).topic;
var topicObj = topics.reduce((o, e) => Object.assign(o, {[e.name]: e.topicId}), {});
for (var j = 0; j < topicNames.length; j++) {
var topicId = topicObj[topicNames[j]];
var exec = Classroom.Courses.CourseWork.delete({
title: "Example Assignment",
topicId: topicId,
workType: "ASSIGNMENT",
}, courseIds[i]);
}
}
}
I checked the Google Classroom courses.courseWork docs, but I think I don't understand well enough how to structure my code as a whole to make use of the info there, because I don't have any background knowledge/training.
I used Problem listing assignments of a student in Google Classroom to come up with my code, but mine is not working and I can't figure out where I'm going wrong.
I also looked at Using Google Apps Script to list assignments through Google Classroom API using course.coursework.list, but when I run the code in the answer, my log says that it's loading, but it never appears to finish.
In this sample script, the courseworks are deleted by searching the coursework names.
Sample script:
Before you use this script, please confirm Classroom API is enabled at Advanced Google services, again.
function deleteCourseworks() {
var courseIds = ['###courseId1###', '###courseId2###',,,];
var courseworkNames = ["###coursework name1###", "###coursework name2###",,,];
for (var i = 0; i < courseIds.length; i++) {
var courseWorks = Classroom.Courses.CourseWork.list(courseIds[i], {courseWorkStates: "PUBLISHED"}).courseWork;
var courseWorkObj = courseWorks.reduce((o, e) => Object.assign(o, {[e.title]: e.id}), {});
for (var j = 0; j < courseworkNames.length; j++) {
var courseWorkId = courseWorkObj[courseworkNames[j]];
console.log(courseIds[i])
console.log(courseWorkId)
var exec = Classroom.Courses.CourseWork.remove(courseIds[i], courseWorkId);
}
}
}
In this case, if you want to delete the courseworks of the state of "DRAFT", please modify courseWorkStates: "PUBLISHED" to courseWorkStates: "DRAFT".
Note:
When an error like "status" : "PERMISSION_DENIED" and The Developer Console project is not permitted to make this request. occurs at Classroom.Courses.CourseWork.remove, the reason of issue can be seen at this thread. Please be careful this.
In that case, for example, it seems that the courseworks created with the GAS project can be deleted. It seems that this is the current specification.
References:
Method: courses.courseWork.list
Method: courses.courseWork.delete
Added:
The following sample script deletes the courseworks by searching the coursework names. But in this case, when the same coursework names are existing, all courseworks of them are deleted. So when you use this, please be careful this.
Sample script:
function deleteCourseworks() {
var courseIds = ['###courseId1###', '###courseId2###',,,];
var courseworkNames = ["###coursework name1###", "###coursework name2###",,,];
for (var i = 0; i < courseIds.length; i++) {
var courseWorks = Classroom.Courses.CourseWork.list(courseIds[i], {courseWorkStates: "PUBLISHED"}).courseWork;
var courseWorkObj = courseWorks.reduce((o, e) => Object.assign(o, {[e.title]: o[e.title] ? o[e.title].concat(e.id) : [e.id]}), {});
for (var j = 0; j < courseworkNames.length; j++) {
var courseWorkIds = courseWorkObj[courseworkNames[j]];
if (courseWorkIds) {
courseWorkIds.forEach(id => {
var exec = Classroom.Courses.CourseWork.remove(courseIds[i], id);
});
}
}
}
}

How to check if particular array number exists in second array in Google Script

I am trying to build a fast, pseudo random number generator, with unique numbers, but with a twist. The numbers picked are from a progressively increasing sample set,i.e. (1-20) then (2-21)...but with my limited coding experience, I am getting some duplicates. Using Google Script, is there a way of checking if a particular array element exists in another array?
function temp3() {
var ss =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet6");
var range = ss.getRange("b14:av14");
var m = 0
var picks = [];
for(var j = 1; j < 48; j++) {
var array1 = [1+m, 2+m, 3+m, 4+m, 5+m, 6+m, 7+m, 8+m, 9+m, 10+m,
11+m, 12+m, 13+m, 14+m, 15+m, 16+m, 17+m, 18+m, 19+m, 20+m];
shuffleArray(array1);
picks.push(array1[0]);
m = m + 1
}
ss.getRange(16, 2, 1, 47).setValues([picks]);
}
There are lots of ways to do this but if you don't want to modify your current approach too much, add a separate function that checks if picks already contains your number. If it does drop the first element and run the function again. If not, add to picks.
function temp3() {
var ss =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet6");
var range = ss.getRange("b14:av14");
var m = 0
var picks = [];
for (var j = 1; j < 48; j++) {
var array1 = [1+m, 2+m, 3+m, 4+m, 5+m, 6+m, 7+m, 8+m,
9+m, 10+m, 11+m, 12+m, 13+m, 14+m, 15+m,
16+m, 17+m, 18+m, 19+m, 20+m];
shuffleArray(array1);
picks = addToPicks(array1,picks)
m = m + 1
}
ss.getRange(16, 2, 1, 47).setValues([picks]);
}
function addToPicks(arr,picks) {
if (picks.indexOf(arr[0]) === -1) {
picks.push(arr[0])
return picks
}
arr.shift();
return addToPicks(arr,picks);
}
You could also do this check in your shuffleArray function.
By the way, instead of calling your shuffleArray function try checking out Math.random's documentation. There's an example there of a function that generates a random integer between two numbers, which might be more helpful to you than shuffling an array.
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}