Это сложная и странная проблема, с которой я столкнулся, и я надеюсь, что кто-нибудь сможет предложить мне, если не решение проблемы, что-нибудь, чтобы ее обойти. С самого начала я являюсь администратором школы, которая использует Google Suite for Education и Classroom для управления учебной работой со студентами, особенно сейчас, когда мы вынуждены работать из дома.
Мы думаем, что это должно быть Хорошая идея использовать Classroom API, чтобы получить хорошее представление о работе учащихся и их оценках в целом. Поэтому мы создали электронную таблицу и сценарий, чтобы получить отчет, в котором показаны все курсовые работы студента с его оценками по всем предметам (курсам в классе), которые он посещает. Пока что, после устранения некоторых недоразумений API, нам удалось закодировать скрипт.
После этого мы попытались увеличить размер скрипта, чтобы мы могли составить отчет для одной группы студентов за один раз, что является большой прогресс, так как составление 30 индивидуальных отчетов требует времени. Это было не очень сложно, так как у нас есть весь список студентов и групп в таблице, и более или менее это делается с помощью al oop. И это сработало, и мы можем получить полный отчет обо всех студентах в группе по всем их курсовым работам по всем предметам.
Но проблема возникла, когда мы начали запускать этот скрипт, и мы получаем ошибки в некоторых группы, у некоторых студентов, и мы не знаем почему. После некоторого расследования мы обнаружили, что вызов Classroom.Courses.list (optionalArgs) дает 430 курсов, хотя мы используем фильтр для студента в отчете. 430 курсов - это не большое число, но l oop для всех из них, даже с учетом того, что студент не зачислен на них, занимает больше 1800 секунд, и сценарий выдает «несвоевременную ошибку».
Удивительно, но если я запускаю ту же функцию, которая называется внутри l oop для группы, но при прямом вызове, то она работает безупречно и не вызывает ошибок. В этом случае студент, который выдает предыдущую ошибку, не зачислен ни на один курс, поэтому вызов Classroom.Courses.list (optionalArgs) дает мне нулевой набор курсов, и функция возвращается почти мгновенно без выполнения любой отчет.
Я показываю полный код скрипта, но интересная его часть не так уж и длинна. Он состоит из множества частей, которые выполняют вспомогательные задачи, такие как получение данных из других листов или отображение Html диалогов на экране.
С уважением, Рафаэль
PS Простите меня за этот длинный текст, но код намного длиннее, и я думаю, что объяснение ситуации может улучшить понимание нашей проблемы.
// Menu
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Functions')
.addItem('Group report','fReportGroupHtml')
.addItem('Student report','fStudentReportHtml')
.addToUi();
}
//------------------------------------------------------------------------
// Report for 1 student
// Opens HTML dialog to select group and student
//------------------------------------------------------------------------
function fStudentReportHtml()
{
// Html dialog
try {
var output = HtmlService.createHtmlOutputFromFile('SelecStudent');
SpreadsheetApp.getUi().showModalDialog(output,'Select student');
}
catch(err) {
Logger.log(err);
return;
}
}
//---------------------------------------------------------------------------
// Report for 1 student
// Receives information from dialog and makes report
//------------------------------------------------------------------------
function fStudentReport(form_data)
{
// Form information
var nameStudent=form_data.student;
// Split students name and surname
var posco=nameStudent.indexOf(',');
var surname=nameStudent.substring(0,posco);
var name=nameStudent.substring(posco+2);
// Gets student email
var idStudent=getUserEmail(name,surname);
// List student coursework
var ssCourses=SpreadsheetApp.getActive();
var hWorks=ssCourses.getSheetByName("StWork");
fListStudentWork(idStudent,hWorks,nameStudent);
}
//---------------------------------------------------------------------------
// Report for all students in a group
// Opens HTML dialog to select group
function fReportGroupHtml()
{
// Html dialog
try {
var output = HtmlService.createHtmlOutputFromFile('SelecGroup');
SpreadsheetApp.getUi().showModalDialog(output,'Select group');
}
catch(err) {
Logger.log(err);
return;
}
}
//---------------------------------------------------------------------------
// Report for all students in a group
// Receives information from dialog and makes report
function fReportGroup(form_data)
{
var idStudents="1wvjqUFZcRwjTKSspjyQOE1JHio33f7seyx8xCO8qHBQ";
var idTemplateInf="1K6YgZvh195eo4b-DLtRHuvjFdG3SOBJILodzS4CUnBs";
var idCarpInf="16nH3BhAwHPP3BcAUK9eW_FQdWRM6B87T";
var valAlu=[[]];
// Opens students sheet
var ss = SpreadsheetApp.openById(idStudents);
var coursesSS = ss.getSheetByName("Students");
var lastrow=coursesSS.getLastRow();
var rangeA=coursesSS.getRange(2,2,lastrow-1,3).getValues();
// First student in the group
for(var ii=0; ii<lastrow-1; ii++)
{
if (rangeA[ii][2]==form_data.groupAlu)
break;
}
var ini=ii;
// Opens sheet for messaging
var ssClass = SpreadsheetApp.getActive();
// Copies template sheet into a new file
var idNewFileInf = DriveApp.getFileById(idTemplateInf).makeCopy(idCarpInf).getId();
DriveApp.getFileById(idNewFileInf).setName('Report '+form_data.groupAlu);
// Opens new sheet and writes general information
var ssInf = SpreadsheetApp.openById(idNewFileInf);
var hjInfAlu = ssInf.getSheetByName("Students");
hjInfAlu.getRange(2,1).setValue("GRUPO: "+form_data.groupAlu);
var dateToday=new Date();
hjInfAlu.getRange(3,1).setValue("FECHA: "+Utilities.formatDate(dateToday,"GMT+1","dd/MM/yyyy"));
// All students in the group
var iiAlu=1;
while(rangeA[ii][2]==form_data.groupAlu)
{
// Gets student's information
var surname=rangeA[ii][0];
var name=rangeA[ii][1];
var nameStudent=surname+", "+name;
// Gets student email
var idStudent=getUserEmail(name,surname);
// Copies sheet
var hjInfWork = ssInf.getSheetByName("Works").copyTo(ssInf).setName(nameStudent);
// Makes report of student's work and submissions
var mens="Creando informe de student: "+nameStudent;
ssClass.toast(mens, "Informe de tareas",-1);
var states=fListStudentWork(idStudent,hjInfWork,nameStudent);
// Writes student information in first sheet
valAlu[0][0]=iiAlu;
valAlu[0][1]=nameStudent;
valAlu[0][2]=states[0];
valAlu[0][3]=states[1];
valAlu[0][4]=states[2];
hjInfAlu.getRange(iiAlu+5, 1, 1, 5).setValues(valAlu);
ii++;
iiAlu++;
}
// Deletes sheet 'Works'
ssInf.deleteSheet(ssInf.getSheetByName("Works"));
// Final message
ssClass.toast("Fin del informe", "Informe de tareas",5);
}
//---------------------------------------------------------------------------
// List coursework for a student
function fListStudentWork(idStudent, dataSheet, nameStudent)
{
// Variables
var ii=0;
var lWorks=[[]];
var stateWork=[0,0,0];
var pageToken = null;
var pageToken2 = null;
var colorCourse1= "#ddffdd";
var colorCourse2= "#f9e9b0";
var iiColor=0;
var optionalArgs=
{
pageToken: pageToken,
courseStates: 'ACTIVE',
studentId: idStudent,
pageSize: 0
};
var optionalArgs2=
{
userId: idStudent,
pageSize: 0
};
// Date today
var now = new Date();
var year=now.getFullYear();
var month=now.getMonth();
// Date starting term
if (month>=8)
var cadfecha="September 1, "+year.toString();
else
var cadfecha="September 1, "+(year-1).toString();
var fechaini=new Date(cadfecha);
// Empty sheet
var rowWorks=dataSheet.getLastRow();
var colWorks=dataSheet.getLastColumn();
if (rowWorks>3)
{
var rnWorks=dataSheet.getRange(4, 1, rowWorks-3, colWorks);
rnWorks.clearContent().clearFormat();
}
// General information in sheet
if (!nameStudent)
nameStudent=getUserName(idStudent);
Logger.log("INI: "+nameStudent);
dataSheet.getRange(1, 1).setValue("ENTREGAS DEL ALUMNO: "+nameStudent);
var dateToday=new Date();
dataSheet.getRange(2, 1).setValue("FECHA: "+Utilities.formatDate(dateToday,"GMT+1","dd/MM/yyyy"));
// First: courses for the student
var response = Classroom.Courses.list(optionalArgs);
var courses = response.courses;
if (!courses || courses.length === 0)
dataSheet.getRange(4, 2).setValue("No hay clases");
else
{
Logger.log(" courses: "+courses.length);
for (course in courses)
{
var fechaCourse=new Date(courses[course].creationTime);
if (fechaCourse>=fechaini)
{
// Information from the course
var idCourse=courses[course].id;
var nomprof=getUserName(courses[course].ownerId);
var colorCourse=(iiColor==0)? colorCourse1 : colorCourse2;
iiColor=1-iiColor;
// Gets coursework from the course
var responseT = Classroom.Courses.CourseWork.list(idCourse);
var works = responseT.courseWork;
if (!works || works.length === 0)
dataSheet.getRange(4, 2).setValue("No hay información");
else
{
for (work in works)
{
var idWork=works[work].id;
var maxPoints=(works[work].maxPoints==null)? "" : works[work].maxPoints;
// Gets submissions
try
{
var responseE = Classroom.Courses.CourseWork.StudentSubmissions.list(idCourse, idWork, optionalArgs2);
var submis = responseE.studentSubmissions;
}
catch(ee)
{
var submis=null;
}
if (submis && submis.length >0)
{
for (subm in submis)
{
lWorks[0][0]=ii+1;
lWorks[0][1]=courses[course].name;
lWorks[0][2]=nomprof;
lWorks[0][3]=works[work].title;
var dateWork=works[work].dueDate;
if (dateWork==null)
lWorks[0][4]="--/--/----";
else
lWorks[0][4]=dateWork.day+"/"+dateWork.month+"/"+dateWork.year;
var points=(submis[subm].assignedGrade==null)? "" : submis[subm].assignedGrade;
lWorks[0][5]=points+" / "+maxPoints;
var dateSubmis=submis[entrega].creationTime;
if (dateSubmis==null)
lWorks[0][6]="--/--/----";
else lWorks[0][6]=Utilities.formatDate(new Date(dateSubmis), "GMT+1","dd/MM/yyyy");
var state=submis[subm].state;
// Gets state of submission
var cState="";
var lateSubmis=0;
if ((state=="RETURNED")||(state=="TURNED_IN"))
{
cState="ENTREGADO";
stateWork[0]++;
}
else
{
// Checks if late or not handed
if (dateWork!=null)
{
var dateEnd=new Date(dateWork.year,dateWork.month-1,dateWork.day);
if (dateEnd.valueOf()<dateToday.valueOf())
{
cState="NO ENTREGADO";
stateWork[1]++;
lateSubmis=1;
}
else
stateWork[2]++;
}
}
lWorks[0][7]=cState;
dataSheet.getRange(ii+4, 1, 1, 8).setValues(lWorks).setBackground(colorCourse);
if (lateSubmis)
dataSheet.getRange(ii+4, 8).setFontColor("red").setFontWeight("bold");
else
dataSheet.getRange(ii+4, 8).setFontColor("black").setFontWeight("normal");
ii++;
}
}
}
}
}
}
}
return(stateWork);
}
/////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------
// Gets list of groups
function listGroups()
{
var idStudents="1wvjqUFZcRwjTKSspjyQOE1JHio33f7seyx8xCO8qHBQ";
try
{
// Opens students sheet
var ss = SpreadsheetApp.openById(idStudents);
var coursesSS = ss.getSheetByName("Students");
var lastrow=coursesSS.getLastRow();
var rangeA=coursesSS.getRange(2,4,lastrow-1,1).getValues();
var courses = [];
var courseant="";
for(var ii=0; ii<lastrow-1; ii++)
{
if (rangeA[ii][0]!=courseant)
{
courseant=rangeA[ii][0];
courses.push(courseant);
}
}
return courses;
}
catch(err) {
Logger.log(err);
}
}
//------------------------------------------------------------------------
// Gets list of students in group
function listStudents(group)
{
var idStudents="1wvjqUFZcRwjTKSspjyQOE1JHio33f7seyx8xCO8qHBQ";
try
{
// Opens students sheet
var ss = SpreadsheetApp.openById(idStudents);
var coursesSS = ss.getSheetByName("Students");
var lastrow=coursesSS.getLastRow();
var rangeA=coursesSS.getRange(2,1,lastrow-1,4).getValues();
// First student
for(var ii=0; ii<lastrow-1; ii++)
{
if (rangeA[ii][3]==group)
break;
}
var ini=ii;
// Last student
while(rangeA[ii][3]==group)
ii++;
// Gets array of students
var students=coursesSS.getRange(2+ini,2,ii-ini,2).getValues();
return students;
}
catch(err) {
Logger.log(err);
}
}
//------------------------------------------------------------------------
// Gets list of courses
function listCourses()
{
try
{
// Opens courses sheet
var ss = SpreadsheetApp.getActive();
var coursesSS = ss.getSheetByName("Courses");
var lastrow=coursesSS.getLastRow();
var courses=coursesSS.getRange(3,2,lastrow-2,4).getValues();
return courses;
}
catch(err) {
Logger.log(err);
}
}
//---------------------------------------------------------------------------
// Gets user name from ID
function getUserName(usid)
{
var result = AdminDirectory.Users.get(usid, {fields:'name'});
var fullname = result.name.fullName;
return fullname;
}
//----------------------------------------------------------------------------
// Gets user email from name
function getUserEmail(name,surname)
{
var userIds = AdminDirectory.Users.list({domain:"iesciudadjardin.com",
query:"givenName:'"+name+"' familyName:'"+surname+"'"}).users;
if ((userIds==undefined)||(userIds.length!=1))
return null;
else
return userIds[0].primaryEmail;
}
Дополнительная информация
Я понимаю, что вопрос слишком сложен, поэтому я попытаюсь объяснить вещи с минимальным количеством кода. По сути, мой код работает. Иногда это зависает с некоторыми учениками. Основная его часть - это функция fListStudentWork :
// List coursework for a student
function fListStudentWork(idStudent, dataSheet, nameStudent)
{
var optionalArgs=
{
pageToken: pageToken,
courseStates: 'ACTIVE',
studentId: idStudent,
pageSize: 0
};
// First: courses for the student
var response = Classroom.Courses.list(optionalArgs);
var courses = response.courses;
if (!courses || courses.length === 0)
// NO COURSES
else
{
// MAKE REPORT
}
}
И теперь, если я вызываю ее с одним специальным учеником, она работает, возвращая нулевой набор курсов, поскольку Джон Смит не записан ни на один курс (это также случается со студентами, обучающимися на некоторых курсах):
function OneStudentReport()
{
fListStudentWork("john.smith@mydomain.com",mySheet,"John Smith");
}
Но если я oop с набором студентов, в котором участвует Джон Смит, все предыдущие отчеты студентов в порядке, и когда l oop достигает его, он возвращает все курсы в моей школе, и сценарий прерывается с ошибкой тайм-аута.
function fReportGroup(form_data)
{
// All students in the group
while(StudentGroup=="MYGROUP")
{
// Gets student's information
var surname=rangeA[ii][0];
var name=rangeA[ii][1];
var nameStudent=surname+", "+name;
// Gets student email
var idStudent=getUserEmail(name,surname);
// Makes report of student's work and submissions
var states=fListStudentWork(idStudent,MySheet,nameStudent);
}
}
PS Если администратор считает, что я должен удалите первую часть сообщения с полным кодом, потому что он слишком длинный, дайте мне знать. Спасибо