How to stop Total grade column showing 100% when not all assignments submitted or graded

Jump to solution
Carmen25
Community Member

Hello,

I'm looking for a single field in the Gradebook that can be used to send an API call to a third party system (Salesforce I think) to trigger the student's course completion certificate. 

My problem is, most of our courses have multiple assignments that need to be completed. I was hoping that the Total column in the Gradebook would be useful but I have found that it displays 100% when only one assignment has been graded. 

Does anyone know how to stop that from happening? I.e ideally the total column only shows 100% when every graded assignment has been marked with a passing grade. 

Alternatively, is there another field somewhere that can be used for the API trigger of a course completion status? 

Thanks!

Labels (3)
0 Likes
2 Solutions
James
Community Champion

@Carmen25 

As you realized, using the total grade in the class is problematic.

The total column won't be 100% unless every graded assignment is also 100% (ignoring the possibility of bonus points). There is no sense of a passing grade (which might be 70%) getting turned into 100% for the total.

The only way to get 100% for the total is every graded assignment is 100% (or you have bonus points). That means that you will never be able to rely on just the total for the course as a way of checking completion. The total will be 100% whether they have one, two, or twenty assignments completed at the 100% level. It will only be less than 100% if some assignment has less than 100%, but that will have to be corrected before the total is 100%. That means that looking at the total is not going to meet your needs.

Since what you are asking for won't solve your problem and would be problematic for everyone else, the idea to only show the total when assignment has been graded is the wrong approach.

Knowing a little more about how the class is set up could help us provide the best answer. I'll give some different solutions, hopefully one of them will work or at least inspire you towards a working solution.

If you have a final assessment or require that the assignments be done in order so there is a sense that one assignment marks completion of the course, then you could get the submissions for the final assignment and see when it is at least 100%. This is what we do in our student orientation class. There is a final quiz that students must pass with at least 80%. I fetch the submissions for that assignment to decide when students are done with the course. If this is happening for multiple courses, then you would need to include logic to know which assignment to check. It might be as simple as naming the assignment or it maybe looking at due dates to see which one is due last. If you have a limited number of courses, you might be able to hard-code the IDs of the assignments to check.

If you don't have one assignment that marks successful completion, then you could fetch all submissions for all assignments for all students. This can be done with a single API call, although pagination will likely need to be used. You will then need to process the assignments to see that all of them have been completed satisfactory. This allows for something less than 100%, but requires that you define what a passing grade is for each assignment. I fetch the entire gradebook (all submissions) for some of my scripts and it's not too bad. However, this may be problematic to extend to multiple courses with varying setups and different requirements for what a passing grade is.

If you use modules and requirements to define what a passing grade is, then you could check the module progression to see when the module requirements have been satisfied. If you have multiple modules that can be completed in any order, then you could check the progress on each module. If there is a particular order to the modules, then you would only need to check the completion on the last module. Unfortunately, this endpoint is per-student, so you would have to make a call for each student. Not terrible if this is one course or enrollment is limited, but it's not really scalable if this is for a lot of courses with a lot of students.

View solution in original post

James
Community Champion

@Carmen25 

To make sure I understand.

I am reading that a student must complete all assignments in a module called "Final Assessment" with 100% in order to complete the course. If a student completes one of those assignments with a 90% but the rest with 100%, then they do not complete the course.

Is that correct? If so, a couple more questions will help refine the best way to approach this.

  • Are there module requirements listed for the "Final Assessment" module that contain every assignment that must be completed and what score must be obtained? I understand that they do not have to be completed in order.
  • Are per-chance all of the assignments in that module in their own assignment group?
  • Are there any other assignments in the course that count towards the final grade? Alternatively, are they all individually checked as "do not count towards final" or in an assessment group worth 0% of the grade? If not excluded, do they require 100% to complete the course?

Here's why I ask each question.

If a module exists and module requirements are in place, then we can use the module progress API endpoint to get the completion status. I'm hoping to avoid this because that API call is per student and per course. If you have 200 students in 10 courses, that's 2000 API calls.

If all of the assignments are in one assignment group, then there is a way to fetch grades by assignment group using GraphQL. I can obtain the finalScore for any assignment group. Unlike the current score for the class, it uses zeros for all uncompleted assignments and will never be 100% until all of the assignments in that category are completed (unless you have rules for dropping grades). The number of API calls could be reduced to 2 per course: one to get the assignment group names, which you would then filter to find the one called "final assessment", and one to fetch the grades within that assignment group. You could also get all of the assignment group totals for a class with a single API call.

The enrollments endpoint that tells you what the student's grade is also includes a finalScore for the entire course. It fills in all the missing grades with zeros too. That could be used if none of the other grades in the course counted towards the final, only those in the "final assessment" module. Another possibility is that if a student must get 100% on all assignments that count -- even those outside the final assessment module.

Let's look at some solutions. You'll have to figure out which works best for you.

Assignment Group Approach (1 API call)

This is only useful if all of your final assessments are in the same assignment group.

Here is graphQL code that will fetch all of the assignment group subscores for a course. You can test it by going adding /graphiql to your Canvas dashboard URL.

 

query subScores($courseId: ID!) {
  course(id: $courseId) {
    assignmentGroupsConnection {
      nodes {
        _id
        name
        state
        gradesConnection {
          nodes {
            finalScore
            enrollment {
              state
              user {
                _id
                sisId
                sortableName
                email
              }
            }
          }
        }
      }
    }
  }
}

 

Then, in the Query Variables section at the bottom, you would define the course ID. This is the Canvas Course ID from the URL for the course. I'm using a courseID of mine, but you would need to change it to yours.

 

{
  "courseId": 4014893
}

 

I have small classes and don't have to worry about pagination with graphQL. I think you get 60 seconds for the request to run and it shouldn't take nearly that long to run the request. It took under two seconds to run this for my class.

It returns the following information.

  • The assignment group ID, name, and state. You will want to filter that the state is "available" and the name matches "Final Assessment". You may not need the group ID at all.
  • Under the assignment group will be a gradesConnection that contains an array for each student enrollment. It will have their final score, their enrollment state, enrollment type, Canvas user ID, sisID, sortable name, and email address. You can add or remove fields you don't want. The enrollment state is so you can tell who has already been processed. I'm looking for "active" states, "StudentEnrollment" types, with a finalScore of at least 100.

Note that the enrollments are potentially duplicated if a student is in more than one section of the course, although they should show the same finalScore for each. You will want to go through and deduplicate the results.

Class Total Score (1 API call per class)

This is only beneficial if the student must get 100% on all assignments anywhere in the course that count towards the final grade. This is the closest to what you were asking for in the original question.

Here is another graphQL query you can make. See the last section for how to set it up.

 

query subScores($courseId: ID!) {
  course(id: $courseId) {
    enrollmentsConnection(filter: {states: active, types: StudentEnrollment}) {
      nodes {
        grades {
          finalScore
        }
        user {
          _id
          sisId
          sortableName
          email
        }
      }
    }
  }
}

 

It takes the same variables section as the other query. 

The benefit is that I can tell it to only give me active students through the filter statement. I don't have to filter the results later.

It returns these properties for each enrollment.

  • The finalScore for the class. We want to look for students with at least 100%
  • The users Canvas ID, SIS ID, sortable name, and email.

Enrollments are per section, so if a student is enrolled in multiple sections, the enrollments record may be duplicated.

Module Progression (1 API call per student per course)

For this, you will need to use module requirements for your Final Assessment module.

This is not a graphQL query, it's a regular API call. Here are are using the List modules endpoint of the Modules API.

Replace :course_id with the Canvas course ID and :user_id with the Canvas user ID for the student.

 

GET /api/v1/courses/:course_id/modules?student_id=:user_id

 

An example request might look like this

 

/api/v1/courses/4014893/modules?student_id=13659140

 

Note that if you have more than 10 module items, this will only return the first 10. You can add per_page=20, per_page=50, up to per_page=100 to the query parameters to get more than 10.  I have about 64 modules in my course and getting the first 10 only took about 0.5 seconds. When I added the per_page=100, it took 1.1 seconds.

Inside the returned data are state and completed_at properties. A "completed" state with a timestamp for completed_at indicates that the module has been completed and when it was completed.

That endpoint supports a search query parameter to search for the module name. Since all of your final modules are named "Final Assessment", you could use that. You could also want to limit the per_page to 1. Not because there is going to be more than 1 (we hope), but that tells the database to stop looking as soon as one is found. With the search parameter for one of my module names and the per_page set to 1, it took just about 0.38 seconds. Without the per_page set, it defaults to 10 items and it took a little over 0.45 seconds. Not a huge difference, but if you're making 2000 calls, then that's an extra 2 minutes.

The name of the module should be URI encoded, which, at a minimum, means replacing spaces with %20. Thus "Final%20Assessment" would be the search term. While it should be URI encoded, the request worked if I used spaces or + signs for spaces.

Note the search term is not case sensitive.

 

GET /api/v1/courses/:course_id/modules?search=Final%20Assessment&student_id=:user_id&per_page=1

 

Bulk User Progress (1 API call per course)

I did not know about this one until writing this response and had to check it out to see how it works. I'm adding the comment here after I have written everything I was on the next-to-last last paragraph in the final comments when I found it. Instead of removing everything else, I'm leaving the other comments because this one might not work for you.

There is a bulk user progress endpoint of the Courses API. You should specify the per_page=100 and use pagination to get all of the students in the course.

GET /api/v1/courses/:course_id/bulk_user_progress

For each student, it contains their Canvas user ID, display name, and progress for the course. The progress contains a requirement_count, which is all of the requirements for all of the modules in the course. For my class, that's sitting at 338. Then it has a requirement_completed_count, which is how many requirements the student has completed. It also has a completed_at, which is when the student completed all of the requirements for the course.

If the requirement_completed_count equals the requirement_count and the completed_at has a timestamp, then the student has finished.

If you go through and add requirements to the final assessment module (at a minimum), then this would let you know when a student has completed the course.

It is an expensive call timewise. Getting just the first 10 students took it over 4 seconds. Getting my 21 students took about 8.4 seconds. You may need to extend the timeout in your API request if you ask for 100 at a time. There's no way to filter it based on when the student completed the requirements.

None of the above (2 or more API calls per course)

This is for if you don't have module requirements for your final assessment module, the assessments that count aren't in a single assignment group, and you have other grades that count towards the final (that can potentially be less than 100%).

In this case, you will want to get the final assessment module but include the items that are in it.

 

GET /api/v1/courses/:course_id/modules?search=Final%20Assessment&include[]=items&per_page=1

 

This will get you all of the items that are in the final assessments module. For each item, you get the type and content_id. For example, I have a type="Assignment" and content_id=45956363, which means I want the assignment with the assignment ID of 45956363. Yet another has a type of "Assignment" and a content_id of 45956365. Another module item might have a type of "Quiz" and a content_id of 118705648, which is a quiz with quiz ID of 118705648.

You may also want to keep track of how many graded items there are for the module for checking the next request. You may not need to, either. Check the results to see.

The submissions API has the list submissions for multiple assignments endpoint that allows you to fetch submissions for specific assignments for all students at once.

GET /api/v1/courses/:course_id/students/submissions

Here are tips on query parameters to use.

  • Use assignment_ids[]= multiple times, once for each assignment ID in the final assessment module.
  • Use student_ids[]=all to get all students
  • Do not use submitted_since= to speed up the process. It can be used to fetch fewer submissions, which sounds like a good thing, but it's possible a student might have completed part of the assignments before the submitted_since date and some after and you wouldn't get the entire picture.
  • Use enrollment_state=active to limit it to just active students. This is helpful if you conclude enrollments once they complete the course.
  • Consider using grouped=true to group the results by student. This is optional, but it puts all of each student's scores together, making it easier to see if they have submitted all the results.
  • Use per_page=100 because you will likely have lots of grades. You will also likely have to use pagination because the results will be too many to fit in one request. This is where GraphQL comes in handy, but you cannot do the filtering like you can with the REST API.
  • Potentially use include[]=assignment because it won't tell you how many points are possible for the assignment if you don't. If you need to use the score to determine whether they have 100%, then knowing the points possible is required. Note that this will significantly increase the time and amount of information downloaded as you now get all of the assignment descriptions as well -- for each student. You may want to fetch the assignment information separately rather than including it here.
  • Potentially use include[]=user to get information about the user. This would be listed once per student (if grouped) or repeated for each submission if not grouped. The Canvas user ID is included with the base object, so you might want to fetch user information separately if you need more information.

The submissions object is going to return a score, which is a number, and grade which is what is displayed to the student. The grade might be a letter if that's what you're using. You may need to compute the percentage grade for the assignment, which comes from the assignment.

You're probably thinking it cannot be that simple.

Well, there is one extra catch. If any of the module items were Classic Quizzes, then you're getting a quiz ID from the module item. The submissions request to get the grade needs the assignment ID, not the quiz ID. You'll need to make at least one extra request if you have quizzes to get the assignment ID for the quiz.

If you only have one Classic Quiz quiz in the module, you can get the information using the get a single quiz endpoint of the Quizzes API.

GET /api/v1/courses/:course_id/quizzes/:quiz_id

It's not documented in the API documentation, but the object returned includes a property for assignment_id. You need to use that assignment_id when you fetch all the submissions, which means that you need to look up any quiz IDs before you make the call to get the submissions.

If you have more than one quiz in the module, you may want to list all of the quizzes in the course and then filter through to find the ones that you need. Again, that would need to happen before you make the call to get the submissions.

Final Remarks

This may be an opportunity to revise how you do things. If it turns out that the "none of the above" is the approach you have to take, then it can certainly be done, but it's not nearly as efficient as some of the other methods. Of course, you have a system that works for you and you shouldn't change it just for this. These suggestions are helpful only if they don't interfere with other things you're doing.

You might want to consider going through and using module requirements for the final assessment module, but then you still have one request per student per enrollment.

The fastest way is going to be if you can get all of the assignments into a single assignment group or make sure that there are no other grades that factor into the total overall grade that can be completed with less than 100%.

Once you find a method that works, then you can look at optimization and which is the fastest. Ideally, you'll not want to keep a student in a course much longer than they need to be. If you have to scan 20000 enrollments, it's going to take some significant time. If you have to scan 200 courses, then it's a lot less work and you can do it more often.

One potential route is to create a post-completion module that has a single assignment in it. You could call the module "Congratulations" and put an exit survey that students must complete to finish the course. Then you could search for that assignment and get the grades. Since it's a single assignment, you could look for submissions that have happened since a previous time. This really speeds up the amount of querying that needs to be done.

The problem there is that some students will choose not complete the quiz. If you allow for that, then you still have to go through one of the approaches listed above. If all you have to do is scan that one quiz, then you could do it every 10 minutes and get them out of the course faster.

Another approach is to Canvas Live Events. Here Canvas sends you a notification when certain events happen. For example, you could set it to notify you whenever a course grade change happens. This happens when the final_score for a course is changed, but also any other grade. On that same page is a grade change, which happens when a submission is graded. You could also look for when a submission is created, which happens when a student submits or re-submits an assignment, or when a submission is updated. These would give you indications of which students you should check to see if they have completed the module.

Even better, there is a course completed event sent when all of the module requirements in a course are met. That sounds exactly like what you want, provided that you have module requirements for that final assessment module. This keeps you from scanning all courses looking for students and just reacting to the ones that have actually completed the course.

However, Canvas Live Events are not guaranteed to reach you. Canvas makes a best effort attempt to send them, but if there is no error checking to make sure the message was received, so network issues can cause some loss. That means you would still have to do the scan as a fallback.

View solution in original post