Trouble Adding grades using Google Classroom API and Google Apps Script - google-apps-script

I am unable to change grades using the Google Classroom API. When I run the code below, lines 2 and 3 run fine. However, line 4 fails with the following message: #ProjectPermissionDenied The Developer Console project is not permitted to make this request.
1) var studentSubmission = {'assignedGrade':'1'};
2) var studentSubmissions = Classroom.Courses.CourseWork.StudentSubmissions.list(courseId, courseWorkId, {userId:'studentEmail#apps.matsuk12.us'});
3) var studentAssignmentId = studentSubmissions['studentSubmissions'][0].id;
4) Classroom.Courses.CourseWork.StudentSubmissions.patch(studentSubmission, courseId, courseWorkId, studentAssignmentId,{'updateMask':'assignedGrade'});
When I go to Project Properties and look at the scopes, here is what I see:
https://www.googleapis.com/auth/classroom.courses
https://www.googleapis.com/auth/classroom.coursework.students
https://www.googleapis.com/auth/classroom.profile.emails
https://www.googleapis.com/auth/classroom.profile.photos
https://www.googleapis.com/auth/classroom.rosters
https://www.googleapis.com/auth/spreadsheets
I am trying to access my own Google Classroom using a container bound script (spreadsheet). Seems like if I have access to the assignments, I should have access to add a grade. Not sure why I can't add the grade. Is there anything I can do to get this code working? The end goal is to be able to grade assignments using a form (not a google form) and have the score automatically pushed to Google Classroom.
I saw a related post that mentions a solution, but it is not clear how to implement it: Permission denied using Classroom.Courses.CourseWork.StudentSubmissions.list(4140802199, 4801051201);
I also see a related bug report here: https://issuetracker.google.com/issues/67748271 (I'm not sure though if this really is a bug, or if this is just how the Google Classroom API works, or if I'm just doing something wrong)

From the posts you linked someone posted this code:
function createCoursework (id) {
Classroom.Courses.CourseWork
.create(id, {
// doesn't work but triggers permissions correctly "courseId": id, "title": 'foo', "description": 'desc', });
}
Essentially you need to put this code somewhere in your projects code. Then, using the, Run menu, execute that function. It won't do anything, but it will initialize the authorization request from google; by means of a popup. After that you should be good to delete that function and use that scope.

Related

Google Classroom API Create Material

I am aware that there are multiple questions essentially asking the same thing, but part of the problem I've identified is a lack of detail in the questions, so I'm going to try here, since I think I've exhausted all possibilities.
In the newer Google Classroom, there is a Classwork Tab/Page. This is new, and replaced an older "About" page. In this page, as a regular web user, you can create various materials, including one simply called "Material." This is done by clicking the create button and choosing "Material".
These materials contain any combination of title, description, and attached files/links.
However, when using the API, while I can access any OTHER type of Classwork via:
Classroom.Courses.CourseWork, no attempts to find these items (or create them via the API) has been successful. I've successfully created Assignments (a variant of CourseWork using:
Classroom.Courses.CourseWork.create({
"creatorUserId":'me',
"state":'draft',
"courseId":cid,
"title":'test item',
"workType":"ASSIGNMENT",
"materials": [],
}, 'myClassCode');
}
The enum for workType according to the API only contains:
I've tried omitting the workType (receive error saying I need to include it), using various guesses like MATERIAL, NONE, ABOUT, and null (all receive errors for invalid types).
I can query the list of CourseWork and find all of my entries that are questions or assignments but have found no access to the other items, which are current and active in the Classwork page for the class.
It's possible that this simply isn't accessible via the API, but I'd like to confirm. Any advice would be appreciated.
I am running as SuperUser and everything else seems to be working fine.
You are right, at the moment it is not possible to create course materials through Google Classroom API.
Indeed, there is a feature request for it already:
https://issuetracker.google.com/issues/127591179
I suggest you to give it a star to increase visibility and get updates on the implementation status of the feature.
Here is code that I have been using
/------------------------------Create Coursework Material-----------------------------------
function createCourseworkMaterial (id, cTitle, cDescription, topicId) {
var courseArgs = {
"title": cTitle,
"description": cDescription,
"state" : "DRAFT",
"topicId": topicId
};
try{
Classroom.Courses.CourseWorkMaterials.create(courseArgs,id);
}catch(e){
console.error('Classroom.Courses.CourseWorkMaterial.create() yielded an error: ' + e);
}
}

Chromeosdevices deviceid vs serial number

The Chromeosdevices API relies on the deviceID parameter to find devices on the back end. That has caused a bunch of confusion and frustration on my end. Initially, I thought the deviceID was the serial # of the device. We typically do all searches for device on Google's Admin console using the serial #'s, so it just made sense. I realize now that the deviceID is not the serial #.
Is there a way, then, to translate serial # to deviceId? I'm thinking I may need to export out the entire directory in some table, and then do look ups using the serial # as the reference key. But, it would be nice to figure out a programmatic way to do it. I tried searching stack overflow to no avail.
Thanks,
Figured this out. What a headache. First, if you're developing an Apps Script, there is no need to access the Admin Directory API via UrlFetchApp and trying to get it to work with Oauth2 libraries. All you need is the direct API, which I wish was illustrated in Google's API document and API explorer. Instead, use Admin SDK directly. You can create a script file with a bunch of helper functions to call from your main script:
function getDeviceId(serialnum) {
var optionalArgs = {
projection: 'BASIC',
query: serialnum,};
var chromebook = (AdminDirectory.Chromeosdevices.list("my_customer", optionalArgs)) ;
var chromebookDevID = chromebook.chromeosdevices[0].deviceId;
return chromebookDevID ;
}
For example, you call this function, pass in the serial number, and it will return the chromebook's deviceId as such
var deviceId = getDeviceId('5CD81072C4');
From this, you can also have helper functions to enable / disable chromebooks:
function disableChromeBook(deviceId) {
AdminDirectory.Chromeosdevices.action({"action": "disable"}, "my_customer", deviceId) ;
}
function enableChromeBook(deviceId) {
AdminDirectory.Chromeosdevices.action({"action": "reenable"}, "my_customer", deviceId)
}
I found the Google API document to be really obtuse. I hope this helps someone else out. Took me awhile.

Basic UiApp failing with error "ReferenceError: "UIApp" is not defined."

I am creating some google app scripts for my company to use to generate random tests for employee training. I already have the basic scripts written to grab a list of questions from a google sheet, randomize them, grab the first 10 questions, etc. That all works fine. I decided it might be better to re-do the whole thing using a UiApp instead of just separate scripts. That is where the problem comes in. I did a simple bare bones UiApp to test with, published it and tried to hit the URL and that's where I encounter this error. I searched for this error and all I could find was some discussion about this being part of google apps premiere(which should have been folded into regular google apps around 2010). I've been staring at this so long I've frustrated myself. It should be something very simple and yet it's refusing to work. I'm assuming I am doing something wrong at a basic level but I've reached the point where my brain refuses to see it.
Here is the basic script I started with:
function doGet(e) {
var app = UiApp.createApplication();
var mainPanel = app.createVerticalPanel().setId('mainPanel');
mainPanel.add(app.createLabel('test'));
return app;
}
I save it, publish it and go to the URL and that's when I get the above error message. I know it's something simple but I've reached the point of frustration and simply can't see it.
Update: to reflect comments
Another possibility for WebApps not updating is not publishing a new version and only checking the exec URL. For instant changes to the code, always check the dev URL. The exec will only change after saving a version in Manage Versions and re-publishing the app.
First Answer:
I think your question title says it all.
UIApp is not defined, but Class is UiApp. JS is case sensitive. I copied and pasted the code exactly as it is in your question and received no errors. I did have to add one line to make the label show up.
function doGet(e) {
var app = UiApp.createApplication();
var mainPanel = app.createVerticalPanel().setId('mainPanel');
mainPanel.add(app.createLabel('test'));
app.add(mainPanel); // <-- I added this line to see the label
return app;
}

How to Access ScriptDB from Spreadsheet Custom Function?

In this post, I suggested to use the ScriptDB as an intermediate storage for global data of a Container Extension code. I wrote a sample code for my answer but the sample throws the error: You do not have permission to call query (line X) exception in a ScriptDb.getMyDb().query(...); line. I created the following simpler example demonstrating the problem. The code, both getDBSize and getSource functions, is permitted to use the ScriptDB by running it in the editor. The getDBSize function is executed without any problem by pressing the Run button in the Spreadsheet Script Manager Dialog. The getSource function works everywhere.
I published the Spreadsheet for the example - link. It is impossible to share the code for view, but it is possible to output it in a cell, the cell B3 contains exactly bellow code.
How is possible to permit the Spreadsheet Code to have access to the ScriptDB?
function getDBSize() {
var db = ScriptDb.getMyDb();
var result = db.query({});
var count = result.getSize();
return count;
}
function getSource() {
return this.toSource();
}
The problem is that you're trying to run this function as a spreadsheet custom function, and custom functions are way more limited than all examples on the Container Extension page you linked.
But, from a theoretical point of view, custom functions as well as simple event handlers (e.g. onEdit, onOpen), can not access anything that requires the user account or is associated with the user, only "generic" methods. For example, UrlFetchApp, open the current spreadsheet, or read ScriptProperties.
So, in thesis, querying a ScriptDb should be possible, since it's a generic call and has nothing to do with the active user, it's analogous to ScriptProperties. I don't see a workaround that would actually let you query the db, but you could use ScriptProperties instead. You can easily save and retrieve any object you would save on ScriptDb by using JSON.stringify and .parse. Of course, I'm not comparing ScriptDb capabilites with ScriptProperties, it's just a workaround.
Anyway, this seems like a good candidate for an enhancement request on our issue tracker.

Trigger Google Apps Script by email

I'm looking for examples of a pattern where a demon script running within a GoogleAppsForBusiness domain can parse incoming email messages. Some messages will will contain a call to yet a different GAScript that could, for example, change the ACL setting of a specific document.
I'm assuming someone else has already implemented this pattern but not sure how I go about finding examples.
thx
You can find script examples in the Apps Script user guide and tutorials. You may also search for related discussions on the forum. But I don't think there's one that fits you exactly, all code is out there for sure, but not on a single script.
It's possible that someone wrote such script and never published it. Since it's somewhat straightforward to do and everyone's usage is different. For instance, how do you plan on marking your emails (the ones you've already read, executed, etc)? It may be nice to use a gmail filter to help you out, putting the "command" emails in a label right away, and the script just remove the label (and possibly set another one). Point is, see how it can differ a lot.
Also, I think it's easier if you can keep all functions in the same script project. Possibly just on different files. As calling different scripts is way more complicated.
Anyway, he's how I'd start it:
//set a time-driven trigger to run this function on the desired frequency
function monitorEmails() {
var label = GmailApp.getUserLabelByName('command');
var doneLabel = GmailApp.getUserLabelByName('executed');
var cmds = label.getThreads();
var max = Math.min(cmds.length,5);
for( var i = 0; i < max; ++i ) {
var email = cmds[i].getMessages()[0];
var functionName = email.getBody();
//you may need to do extra parsing here, depending on your usage
var ret = undefined;
try {
ret = this[functionName]();
} catch(err) {
ret = err;
}
//replying the function return value to the email
//this may make sense or not
if( ret !== undefined )
email.reply(ret);
cmds[i].removeLabel(label).addLabel(doneLabel);
}
}
ps: I have not tested this code
You can create a google app that will be triggered by an incoming email message sent to a special address for the app. The message is converted to an HTTP POST which your app receives.
More details here:
https://developers.google.com/appengine/docs/python/mail/receivingmail
I havn't tried this myself yet but will be doing so in the next few days.
There are two ways. First you can use Google pub/sub and handle incomming notifications in your AppScrit endpoint. The second is to use the googleapis npm package inside your AppScript code an example here. Hope it helps.
These are the steps:
made a project on https://console.cloud.google.com/cloudpubsub/topicList?project=testmabs thing?
made a pubsub topic
made a subscription to the webhook url
added that url to the sites i own, i guess? I think I had to do DNS things to confirm i own it, and the error was super vague to figure out that was what i had to do, when trying to add the subscription
added permission to the topic for "gmail-api-push#system.gserviceaccount.com" as publisher (I also added ....apps.googleusercontent.com and youtrackapiuser.caps#gmail.com but i dont think I needed them)
created oauth client info and downloaded it in the credentials section of the google console. (oauthtrash.json)