Full Course Listing with Sorting and Filtering

cesbrandt
Community Champion
53
8809

Update 2018-07-25

I know this is late, but I was unable to edit the post to make this update.

On 2018-06-23 this script became OBSOLETE due to Instructure having made their own version a non-optional component of the official UI.

I have recieved a few messages inquiring about using the script for self-hosted instances of Canvas. The script was never tested with self-hosted Canvas, but should still work so long as the new UI is disabled. However, self-hosted Canvas was never supported, thus the status of OBSOLETE will be maintained.

For more information regarding the new UI enforcement, please check out the release notes: https://community.canvaslms.com/docs/DOC-14759#jive_content_id_Account_Settings

Update 2018-03-05

This script is now DEPRECATED due to Instructure having released their own version as part of the official UI. The script will still work so long as the new UI is disabled.

For more information regarding the new UI, please check out the release notes: https://community.canvaslms.com/docs/DOC-14284#jive_content_id_Courses

Original Post 2017-07-09

Disclaimer

This is my first blog post to the Canvas Community, and my first anywhere in nearly 15 years, so my apologies if it is a little off track with what everyone else expects to see. I looked through several frequent posters whose blogs I have used to try and get a feel for what may be considered acceptable formatting. Basically, I concluded that explaining the overall situation, reason for acting, and finally the "solution" developed would be the safest path to take. So, if you feel you have a sufficient understanding on what this post is about from the title (which I have no idea what it will be at the time of my writing this), then feel free to skip to the bottom.

The Situation

As everyone knows, the Courses list at the account-level is lacking in one major area: it only lists 100 courses? Yeah, that is right, it is limited to displaying only 100 courses, the selection of which I have not actually figured out. At first, I thought it was a list of the published courses, alphabetically by name, then unpublished courses until 100 were listed, but I later found that the published status means nothing and the alphabetical sorting is inconsistent (i.e., I get "WORKING ENG120" before "WORKING CJ125").

Furthermore, there is not even an option to view more courses! No pagination, no Show More link, just nothing! Well, that just will not do, and, as it turns out, Instructure agrees! communityteam submitted the idea back in April 2015 to have an Advanced Admin Search & Sort developed, and it was announced in November of that year that the idea was in development. Of course, this idea is for Courses AND Users, but that still includes Courses.

Well, here we are 20 months later with no idea when this might be found (I understand it is a major undertaking, I am not complaining).

Reason for Creating

So, like many others, our development team wanted a more robust Courses list. To be specific, we wanted to be able to see a full list of courses being developed. Of course, we track that outside of Canvas, but it often has its benefits to see what has made it into Canvas at a glance. We also wanted to be able to search using "match all terms", not "match exact string".

My first choice was to see if another developer had already created such a script. Well, in the idea posted by communityteam,  @dschober  posted about the search system developed by PennState, which looks like a great system, but as  @anf107  mentioned, their setup relies on internal systems, making it difficult to share.

Well, the more I searched around, the less I found (I lost some of what I originally found, yeah, that happened). Finally, I just accepted that if we did not want to wait for Instructure to finish development I would have to write it, myself.

The Solution

I call it a solution, but it is really a userscript, and anyone that uses userscripts on a regular basis should be able to say that they are not solutions, just workarounds.

Q: What does this userscript do?
A: Simply put, it replaces the Courses list provided by Canvas with a full list of courses that fall under a subaccount with support for pagination (10, 25, 50, 100, and All per page). It still supports filtering by Term, sorting by Name, sorting by Creation Date (via the course_id), and filtering by Name. However, the filtering by Name has been modified to allow for search terms, not a search string; sorting by Course Code has been added, and Hide enrollmentless courses has been removed.

Q: What do you mean by "filtering by Name has been modified to allow for search terms"?
A: To explain simply, the Canvas-provided filter for Find a Course searched for a fixed string. This meant that if you wanted to find "201701 Miami Sandbox ENG480", you had to supply a fragment of that title that matched exactly (i.e., "201701 Miami", "Miami Sandbox", "Sandbox ENG480". etc.). This modification allows you to search for terms, not a title. So, if you knew the shell you wanted was "201701" and "ENG480", you can submit "201701 ENG480" or "ENG480 201701" and it will come up with the course.

The modified Course Name filter searches for all terms supplied, so only courses whose Name contains all the terms will be provided.

Q: Why is Hide enrollmentless courses removed?
A: Simply put, we do not have need of it. However, that is really only the reason why it was not pursued. The Canvas API options for determining enrollmentless courses is limited to a GET variable defined w... Due to this information not being available as part of the returned data, additional logic would be needed to provide all courses while supporting the identification of enrollmentless courses.

Q: Did you really just make an entire sentence and hyperlink?
A: Yes. Yes I did.

Q: Do you think Hide enrollmentless courses will be added to this userscript at some point?
A: Maybe. It is not like adding the logic for it would be particularly difficult, it just does not serve sufficient purpose to us to be considered worth adding that extra logic.

Q: Is there anything else that was removed? Is so, why were they removed?
A: Yes. Yes this is.

  • Student Count: This information is not provided using the /api/v1/accounts/:account_id/courses API call.
  • Enrolled Teachers: This information is not provided using the /api/v1/accounts/:account_id/courses API call.

Yes, this information could easily be retrieved with additional calls, but that would significantly increase the number of API calls which then slows done the processing time required to generate the course list.

Q: You mentioned the speed at which the course list is generated would be slowed down by making the additional API calls needed to display more information. What are we talking about? 5-seconds? 10-seconds?
A: Minutes to hours; entirely dependent upon the connection, number of courses, and how many additional calls are required to pull all the desired information. I, honestly, have not looked into this beyond seeing that it is not provided right away.

Q: Okay, that makes sense. So how long will this userscript take to run?
A: That depends on your connection to the server, the load on the server, and how many courses fall under the subaccount. A couple hundred can be retrieved in a matter of seconds, but thousands can take minutes.

Q: Why is it so slow?
A: It is slow because the Canvas API forces a pagination limitation of 100 results per call. So, if there are 4783 courses under a subaccount, 48 API calls are needed to retrieve the information for all of them. It may not seem like much, but it adds up, fast.

Dependencies

How It Works

  1. Load the userscript to your Userscript Manger of choice
  2. Enabled the userscript
  3. Access the "Courses List" of any subaccount in the Canvas LMS
  4. Use the filters to narrow your results
    • Course Name: Accepts multiple terms and compares them against the Course Name of all shells for matches of all terms (case-insensitive) for a complete match. (i.e., If you search for "WORKING 201701", it'll return "201701 Miami Working ENG480" because it contains "WORKING" AND "201701", but it won't return "201701 Miami Sandbox ENG480" because it is missing the "WORKING" term.)
    • Teacher(s): Accepts multiple terms and compares them against all Teacher(s) of each shell (case-insensitive) for a complete match. (i.e., If you search for "Smith Jones", it'll return courses with: "Aaron Smith" and "Jones McMillion"; "James Smithemson" and "Eric Jones"; "Smith Jones"; "Jones Smith")
    • Term Filter: This is literally a clone() of the default Canvas term filter, but it now serves this system (which doesn't have to reload the page when changing filters).
    • Sort By: Mostly a clone of the Canvas sorter, but with the ability to sort by the "Course Code", in addition to the original "Course Name" and "Creation Date" options.
    • Display x Shells: How many courses do you want to see per page? The default is 50, but additional options are 10, 25, 100, and All (All is not recommended for large subaccounts!).

Appearance

BEFORE

238052_pastedImage_10.png

AFTER

238054_pastedImage_11.png

Related Ideas/Discussions

https://community.canvaslms.com/ideas/1502

https://community.canvaslms.com/ideas/1126

53 Comments
925024864
Community Champion

Thank you Christopher Esbrandt for this great post, and for the depth of descriptions, explanations and details. I think this can be very helpful and am going to play with it on our test server first.

Thanks again!

cesbrandt
Community Champion

Edit

Fixed. You should be good. Please let me know if you find any bugs. Smiley Happy

Original

Give me a few minutes. One of our developers found a bug with the click detection and the additional links that I'm testing a fix for. I should be able to post it in 5-10 minutes.

James
Community Champion

Thanks for this, cesbrandt! It's good to see other people jumping on the user script bandwagon.

Here's a couple of suggestions based on my experience with them.

  • If you make the extension .user.js instead of just .js, then the user script manager will automatically detect it and offer to install it for you when you click on the link. It makes installation just a little faster.
  • When using the regular expression form of the include line, you don't need to escape forward slashes like you normally would. It takes what you put in and passes it on to the new RegExp() in JavaScript. Also, your regex is a little more picky than what I normally use (not a bad thing), but if you don't mind possibly running on sites with more than one part before the .instructure.com, you can juse use .*\. Although https? is valid, I think everything on instructure.com should be https, so it's not critical. Below I've included what you have and what it could be. I wish you better luck than I've had with the \d+, I've never been able to get it to work for some reason and so now I just go [0-9]+ whenever I write JavaScript.
// @include       /^https?:\/\/[^\.]+\.((beta|test)\.)?instructure\.com\/accounts\/\d+$/
// @include       /^https://.*\.instructure\.com/accounts/\d+$/

A question if you don't mind. What is the server line about?  It only shows up at those 2 lines at the top. You've got instructure.com hard-coded in there a few places. If you're using a custom domain, that makes it harder to implement. What I've found is that none of that is necessary, when you make an AJAX call with jQuery, you just use a relative URL and it automatically goes to the server the page was loaded from.

I haven't ran this yet, just looked at the code, so I may be off, but it looks like you remove the existing filtering and display a warning right away and then start loading the list and don't display it until the entire list is loaded. Did you consider delaying the removal of the existing filtering until after it has started loading stuff (or even finished loading)? That way people could go ahead and use what's there until the new one is ready to use.

cesbrandt
Community Champion

Okay, so I can't address anything now because I'm posting this from my phone at a family reunion, but I will update the script either Sunday night or Monday (if I have the chance with term start and all) to address the first and third "question blocks".

Question Blocks

If you make the extension .user.js instead of just .js, then the user script manager will automatically detect it and offer to install it for you when you click on the link. It makes installation just a little faster.

  • I was trying to figure out what was different between yours and mine, but the filename never occurred to me!

When using the regular expression form of the include line, you don't need to escape forward slashes like you normally would. It takes what you put in and passes it on to the new RegExp() in JavaScript. Also, your regex is a little more picky than what I normally use (not a bad thing), but if you don't mind possibly running on sites with more than one part before the .instructure.com, you can juse use .*\. Although https? is valid, I think everything on instructure.com should be https, so it's not critical. Below I've included what you have and what it could be. I wish you better luck than I've had with the \d+, I've never been able to get it to work for some reason and so now I just go [0-9]+ whenever I write JavaScript.

  • I like to be thorough. As far as I'm aware, the only valid Instructure-provided domain address are <institution>\.((test|beta)\.)?com, so I wrote it for that. Anyone using a custom domain would need to adjust it anyways.
  • As for the escaping, I'm paranoid when it comes to regex. Although I have, admittedly, written some highly complex regex with aide, there's a lot that I still go to places like #regex on Freenode to get help with. Jumping between the different languages, though, I've adapted the policy of always escaping characters that may need to be escaped when working with regex somewhere else.

What is the server line about?  It only shows up at those 2 lines at the top. You've got instructure.com hard-coded in there a few places. If you're using a custom domain, that makes it harder to implement. What I've found is that none of that is necessary, when you make an AJAX call with jQuery, you just use a relative URL and it automatically goes to the server the page was loaded from.

  • Alright, so, in my defense, this is a "refactored" fragment of a much larger userscript I've written for our developers. I tried to remove as much of the excess content as possible, but I wrote the original code for it very quickly and I "rewrote" it much quicker. The full userscript has some functionality that is available only on the test or beta instances, that's why it is declared there, to be used elsewhere in our full script. Obviously I missed a bit when I was trying to clean it up for release here. ^^'

I haven't ran this yet, just looked at the code, so I may be off, but it looks like you remove the existing filtering and display a warning right away and then start loading the list and don't display it until the entire list is loaded. Did you consider delaying the removal of the existing filtering until after it has started loading stuff (or even finished loading)? That way people could go ahead and use what's there until the new one is ready to use.

  • I love when a programmer can look at the code an understand what it does. I may not be the best of making my code do that, but at least you got it. You are absolutely correct about the overall logic of the script.
  • As for leaving the original list and search/filtering while the new one loads, I did consider it and had it that way when I was writing the script. The "Please wait while your party is reached" thing was something I added, literally, at the very end. There is a reason for it, though!

    With the initial feedback I had gotten from developers, they found it annoying to have time to start typing their search, or to setup their filters, just for the new list to appear and interrupt it. Furthermore, this list completely replaces that original functionality, essentially preventing users from accessing /accounts/:account_id/courses entirely (the search results page for filtering). This is fully intentional due to the nature of the change in how that functionality work, specifically the name filtering.

    The problem is, there're too many compromises that need to be made when working with the API instead of the database. For our needs, I chose a single interface experience with a fairly minor loss in information and filtering capability to maintain the fastest load times possible. If you have any ideas on how I might be able to offset this, I'd be happy to give it a try, but the script, as I've released it, is based nearly exclusively on the feedback of a handful of developers from a single institution. I'd be happy to revise it for others.
kona
Community Champion

You wrote all this from your phone? ^

As someone who does a lot of work from my phone you have taken it to a new level and I bow to your mobile abilities!

PS - The script is pretty nifty as well! I've been wanting something like this since we started using Canvas in 2012 and I look forward to giving it a try! Thank you for sharing!

James
Community Champion

It's amazing how well you can communicate with your phone. I can barely even read what's on my wife's, let alone try to type anything on it.

I understand the need to replace the functionality and people can always temporarily disable the user script and reload the page if they need the old functionality. I've used that reasoning myself in the past.

If you wanted to provide both capabilities (I'm not suggesting you do this - just talking out loud), what you could do is add a button the right side that says "Use Canvas search" (or filter or default view or whatever) and then when they click it, it hides your information and shows theirs. Then the button could change to "Use improved search" (or whatever) and it would toggle. I would default to yours, since people are installing the script, but I've found that sometimes people like to take our user scripts and make them part of the custom JavaScript and then it's not easily disabled. You could even store the last state in the custom user data (I wish I had known how to use that when I wrote QuizWiz, but it's probably better to have software with a reasonable set of defaults). Ignore most of this, there's lots of "could haves" and development time is limited.

I'm also glad you've got some developers you can talk to when making something. I'm usually developing in isolation, often about things I'm not going to use, and have to make educated guesses about what is going to work best.

Don't feel compelled to any changes based off things I say. While the current search is a pain, we have a naming system with our SIS IDs that makes it pretty easy for us to find any courses. I've also got a local database that has all of the SIS information and then adds the Canvas IDs to that, so I tend to do a lot of my work in the database rather than in Canvas and don't use the admin course search more than a couple of times a week.

I haven't looked at efficiency, but I did notice on a recent scripts I was working on that I saw better success if I tried to load 50 at a time rather than 100. I know that doesn't make sense, but I had put a timeout of 2 seconds in there just to make sure it was responsive. When I loaded 100, it was taking over the 2 seconds, but with 50, it was taking much less. I didn't do any diagnostics or benchmarking on that. But I think I was also trying to get something on the page quickly and you're wanting to download the entire list before displaying anything.

There may be some ways to improve that process, but I haven't had a chance to dig into the code to see if you're already doing it. I'm teaching this summer and things are moving fast, so I'm trying to keep up / get ahead a little with my own classes and not had much time for development. I think I would start by loading just a list of the current courses and display them, then possibly continue loading the others in the background. if someone clicked a filter that want to show all of the courses or those from other terms, they may have to wait if it hadn't already loaded.

On the other hand, you can do what I do and say "this may not be for everyone", but if you can find it helpful then here it is and if you want to add that other functionality, I've made the source available on GitHub.

cesbrandt
Community Champion

Half-and-half. I used my phone to remote into my home PC, but while that gives me the full capabilities of my desktop, the controls for those capabilities are highly limited. Besides, most of it was copy & paste with some typing. :smileysilly:

But, I do use my phone for a lot. I used to write code on it, but my work has gotten far too big to manage that, now. ^^'

Please do let me know how it works for you. Remember, I only have one customized instance of Canvas to test off of, but I hope I kept it streamlined enough that there shouldn't be any issues. Smiley Happy

cesbrandt
Community Champion

It's great to have such feedback, I just wish I could do more than provide responses. I'll get to more, eventually. I hope for Sunday night or Monday, but it's really hard to say what time I'll be able to dedicate to it.

Anyways, back to the "question blocks"! xD

Question Blocks

It's amazing how well you can communicate with your phone. I can barely even read what's on my wife's, let alone try to type anything on it.

  • What can I say? Technically, I'm a millennial. Does that explain it? xD

If you wanted to provide both capabilities (I'm not suggesting you do this - just talking out loud), what you could do is add a button the right side that says "Use Canvas search" (or filter or default view or whatever) and then when they click it, it hides your information and shows theirs. Then the button could change to "Use improved search" (or whatever) and it would toggle.

  • I actually was considering doing just that via tabs, but since the idea was to provide the revised list, it seemed more important to put it up from the beginning to avoid confusion. Further, there is the slight issue of browser lockups. I hate to say it, but if you access a subaccount with a lot of courses (like the root account) it's going to lockup if your system isn't powerful enough. There are too many API calls to retrieve the data being done and the script will not proceed without all responses because it defeats the purpose to do anything without all results.

    I wanted to avoid such times, though I'm sure they would be rare, where a user is using the Canvas list and the browser just stops responding because it's trying to load the userscript list in the background. Sure, this could be mitigated by setting the userscript list as the alternate, instead of default, view and prevent the loading of the script until it is selected, but that just means the user has to wait longer on those large subaccounts because the script never started to pull the data.

    Basically, I couldn't come up with any good solution on how to handle the potential for browser lockups and/or increased load times, but I'd be grateful for suggestions on how I could do this. For now, I'll plan to adjust the script to turn the course list into a tabbed page. However, I do not have any intention of adding functionality to support /accounts/:account_id/courses. Part of the plan with this script was to eliminate the use of that page entirely. Since the only access point to it via the UI is the Canvas-provided filtering, I see no reason to adapt the userscript to Canvas-filtered content.

You could even store the last state in the custom user data (I wish I had known how to use that when I wrote QuizWiz, but it's probably better to have software with a reasonable set of defaults).

  • I've read about this and saw the API access points for it, but I haven't really looked too much into it. While I think this could be helpful to other institutions, I suspect it may simply cause confusion for our Canvas admins who wouldn't be using it (the scripts I write is specifically for our curriculum development team).

I'm also glad you've got some developers you can talk to when making something. I'm usually developing in isolation, often about things I'm not going to use, and have to make educated guesses about what is going to work best.

  • Curriculum developers, not programming developers. I'm the Curriculum Development System Administrator for our Product Development. They are responsible to the development of nearly all curriculum we provide and I do what I can to enable them through tools to simplify or streamline the development process. Most of my focus is in relation to the job of loading the materials to Canvas, though. This is where I get most of the ideas I submit to the community.

Commercial Break

"Are you tired of having to go back and fourth between loading Outcomes and building Rubrics? Well, make you voice heard and vote for https://community.canvaslms.com/ideas/8791 today!"

Question Blocks (cont'd)

Don't feel compelled to any changes based off things I say. While the current search is a pain, we have a naming system with our SIS IDs that makes it pretty easy for us to find any courses. I've also got a local database that has all of the SIS information and then adds the Canvas IDs to that, so I tend to do a lot of my work in the database rather than in Canvas and don't use the admin course search more than a couple of times a week.

  • This one's easy, there's no question. In all seriousness, we rely on a lot of internal database work, also. However, we don't apply SIS IDs to any course shells other than LiveMaster, and Sandbox w/ LTIs. We rely more on the naming of the shells than anything. From a data-processing perspective, SIS IDs or course IDs would be best, but a user is not able to readily understand what the shell actually is from that information, so we use descriptive names in a uniform format to ensure there's never any doubt about the purpose and content of a shell.

I haven't looked at efficiency, but I did notice on a recent scripts I was working on that I saw better success if I tried to load 50 at a time rather than 100. I know that doesn't make sense, but I had put a timeout of 2 seconds in there just to make sure it was responsive. When I loaded 100, it was taking over the 2 seconds, but with 50, it was taking much less. I didn't do any diagnostics or benchmarking on that. But I think I was also trying to get something on the page quickly and you're wanting to download the entire list before displaying anything.

  • I'll take a look into this. I, honestly, hadn't considered that the system may be quicker with smaller pulls than larger. It makes sense when we're talking massive data dumps because the system stores the data in active memory while it compiles the complete list. However, with only 100-entries per call, I can't fathom how this could be causing any noticeable latency. It is, after all, AWS that hosts Instructure.

There may be some ways to improve that process, but I haven't had a chance to dig into the code to see if you're already doing it. I'm teaching this summer and things are moving fast, so I'm trying to keep up / get ahead a little with my own classes and not had much time for development. I think I would start by loading just a list of the current courses and display them, then possibly continue loading the others in the background. if someone clicked a filter that want to show all of the courses or those from other terms, they may have to wait if it hadn't already loaded.

  • I had considered rendering the list as it's compiled, but that isn't really feasible when you consider the default sorting is in reverse order of the list being queued, not that that has anything to do with what order the results come in. On our root account, I found that if a call hung, the next 5-10 also hung but others after come down without issue, putting the list out-of-order. That's also one of the reasons the list is put through the full sorting function when it is first rendered rather than just printing the reversed list.

    I'd be happy to get any suggestions you can give on how to improve the execution. I spent many years without much opprotunity to program and got back to it still stuck in early- to mid-90s ways. My logic is still playing catch up. ^^'

On the other hand, you can do what I do and say "this may not be for everyone", but if you can find it helpful then here it is and if you want to add that other functionality, I've made the source available on GitHub.

  • Of course, that should go without saying. Not too long ago I remember I made a comment about how what works of some people does not work for others. It's not that it's wrong, it's just different.

    As for making changes, I'm perfectly happy to do so. I released it as we have currently chosen to use it, but I know the rest of the community would want other things. I'm thinking it may be best to rewrite the entire thing to make functionality configurable, not that there currently is much to configure at the moment. Personally, I have a hard time justifying the benefit/risk ratio with allowing both UIs to be usable, but you have voiced that at least one person can find reason for it.
James
Community Champion

I'm glad you mentioned the browser locking up. I almost mentioned that myself in the section on 50 vs 100 for the per_page parameter and possible optimizations. I noticed your firing off all of the requests for the courses based off the last entry in the header. That's more efficient since you're requesting in parallel. In my testing with Firefox, I was able to download 5 streams concurrently instead of just 1 in series.

I wasn't sure, which is why I didn't say anything, but I thought that if you used all the available streams with a download that took a long time to prepare and download (the size of the data isn't the issue as much as the time it takes to prepare it) that it might end up blocking the person from using their browser for something else -- especially if there were a lot [say more than 500 = 5 streams * 100 courses per stream] of courses to download. The 500 wouldn't be too bad -- it took about 4 seconds on my end, but we have 5000 courses, so that might be 40 seconds where nothing else gets done. The untested hypothesis on my end is that if you make the chunks smaller, then other requests have a chance to work their way into the queue faster so the browser doesn't seem like it's lagging.

I keep on hearing people talk about the request limiting threshold, which Canvas says will slow things down if you hit it. I also heard someone (I think it was a Canvas engineer, but I might be misremembering) say that it's not really a slow-down, it's more like a stop. I've never encountered it myself, the closest I've come is probably the access report script I wrote, but it turned out to be browser timeouts (Chrome) rather than my script that hit the limit. I have never monitored that level in anything I've written, but I see you check the status for something not equal to 200. Do you have any sense of how often you've encountered that error with something failing?

Another reason I ask is that requesting 100 per page slows things down so maybe you don't hit the threshold, whereas requesting only 50 might reach that limit. When I looked into the gradebook last year, Canvas was downloading 33 students worth of information at a time. I figured they had done some kind of benchmarking about a good number should be. We (programmers) don't want to mess with pagination, so we download 100, hoping we won't have to mess with it. We also know that there there is overhead with the network calls so we think we need to minimize them. But it might turn out that the logical thinking isn't borne out by practice.

This is a great discussion -- too bad I need to get back to actual course work right now.

Chris_Hofer
Community Coach
Community Coach

This looks really nifty, cesbrandt‌...

Any chance that this might be able to be included in the global JS some time in the future?  I believe  @James ‌ did that with one of his Canvancements, but I'm not sure how technically possible it would be to do it with your script.  Just thought I'd ask out of curiosity.

cesbrandt
Community Champion

Since your entire post relates to the same specific subject, no "question blocks" this time.

You are absolutely right about the check for 200 response codes. I know it results in higher load times, but it's better to ensure you get all the data. What good is this userscript if you have a partial list? If I throw up an error because 1/78 calls failed, how much time wasted is considered acceptable? I'm not going to lie or sugarcoat the situation. If you access a subaccount with more than 100 courses, it's going to take longer for the simple fact that you're making a second or more calls. There will always be the one, but the more the need to be made, the longer it'll take. There's no avoiding that. It's the inherent issue with using API access points instead of direct database querying.

You can craft a database query to give you exactly what you need in a single call and it will respond as fast, if not faster, than the first API call. There's also significantly greater flexibility with database queries. API access points are designed to supply specific information applicable to that logical point. It's understandable, and good for smaller things, but this userscript is really no different than generating an account-wide report. The difference between this userscript and the average account-wide reports I have made is that this userscript has only 1 API access point queried, just one. I could display more data from that one access point, but most of the remaining data is not needed for this "report" or the additional access points would increase the load times at a geometric rate based upon the number of responses from the first access point.

Sorry, I kinda went off on a tangent there.

With regards to tracking failed API calls, so I've never tried since I finished the current callAPI and callAJAX functions I've written (I had some help from a friend that's VERY good with JavaScript to work it out the details, but all of the logic is my own and, if I do say so myself, they are a brilliant set of functions). In fact, the last time I did test it was in PHP when I wanted to test the load on that limit for a MUCH larger script. If you've checked out my GitHub, I have a repository called canvas-php-canvastools. It's intended to be a framework for building more robust reports using the Canvas API. I've made some updates that I haven't released on there (I need to get better about that), but it works well enough as is.

Anyways, I used that framework to build massive reports like Content Searching, which allows the user to search a single course or every course in an account for a specific string, tested against the HTML and strip_tags() equivalent of the main content areas of Content PagesDiscussionsAssignmentsQuizzes (not questions due to API limitations), and the External URLs on the Modules page. This is a massive report easily requiring 100x and more API calls than this userscript does. Nevermind that the returned data is generally much smaller, the overall cost is enormous compared to this little userscript. Insanely, I've made reports in that framework that require even more calls to pull off (our Course Settings checker easily consists for 10x the API calls as the Content Searching report), but I've never seen the any adverse reactions from Canvas.

Still, take all that with a grain of salt. The sheer number of API calls requires these reports to have hours to run. So long as they are not being ran 100x at once, I can't fathom them having problems with the limit.

Long story short (I know, too late), I can't imagine this userscript is going to come anywhere close to causing a bleep on the radar of the threshold limit, let alone breaking it.

Regarding the limit punishment, I remember reading the same thing about the all calls being blocked if the limit is reached, but I couldn't begin to guess where I read it. I read so much on the community, trying to come up with solutions to new productivity concerns. It seems the more I help to speed up the process, the more everyone wants to do. Funny how that works, eh?

James
Community Champion

So I wrote some JavaScript code that you can paste into your browsers window and download all of the courses. It doesn't actually store them, I was doing it for timing purposes. What it does do is make a note of the time when each AJAX call returns success, what the X-Rate-Limit-Remaining, X-Rate-Cost, and X-Runtime values are.

What I discovered is that once you have fetched the list of courses, it is very fast to load the list of courses. In other words, my benchmarking will need to wait until Canvas doesn't have that query cached. I've got 5012 courses that it loads and loading them at 100 per pop (51 fetches) took 6 seconds. I also confirmed that Firefox was making 6 calls at a time, not 5 as I thought. The rate limit remaining starts at 700 and it only got as low as 631. The most a request cost was 0.629 and the longest runtime was 0.696. Again, that's not realistic since Canvas had cached it.  I'll have to wait until it forgets about it to get more realistic values.

I did notice one benefit to starting off with a smaller per_page setting -- that first page load where you determine the last page. When it's not completely cached, it's still taking about 4 seconds for the first page load. Firefox said it took 3.8 seconds and Canvas said the runtime was 3.755345 seconds, so it seems like the long part was waiting for it to generate. The rest of it came fast - not sure if it's cached or the first query setup all the rest so they're fast. It was 9 seconds in all as the browser had to sit there and wait for the first one to download.

I've got to wait for their cache to clear the query before I can get accurate timings, but setting the per_page to 50 still took 8 seconds to make the 101 requests, but the first one should get delivered faster. Things are starting to line up with expectations now -- it does take longer to make the network calls, so there is overhead there, but you can start making them faster with a smaller per_page, so you gain some ground there. There's probably an equilibrium point based off the number of courses that you have, but determining that equilibrium point would require an additional API call -- say fetch 1 course and see what the last page is to get the total count and then determine which is the faster way to do it.

But now that I've done this testing, I'm really anxious for Canvas to forget about the cached data so that I can run it with a fresh copy. Your statement in the original blog about "why is it so slow" is more consistent with what I've seen -- minutes to fetch all of our courses, not seconds. This brings up some questions.

  • Have you noticed that subsequent loads of this page are much faster? Or does no one bother to reload it because it took so long the first time?
  • You may have said this already, but we've written so much that I can't find it right now. How many courses are you pulling in and how long is it taking?

I've definitely got to start using parallel requests in my stuff. Some of it is a Google Sheet and it's synchronous and I can only make one request at a time (unless I'm missing something), but some of the user scripts I've written look for the next link rather than the last link. I've got to work out the logic on that -- because I read somewhere about the last link not always being present.

cesbrandt
Community Champion

If you want to include it in the global JavaScript, it's perfectly doable as-is, though I would NOT recommend it. Providing the functionality as a userscript to those users who understand and accept the benefit/cost ratio is great, but I've found that the average user is going to have trouble with the increased load times.

cesbrandt
Community Champion

Overall, it sounds like you have a much better understanding on the limit and how to measure your progress to hitting it. When I've tried to look into it, it was mostly me fumbling about, so I wouldn't be surprised if my previous post made little-to-no sense. ^^'

To answer the questions at the end, though, I have noticed inconsistency between loading the larger subaccounts subsequently. On an account with 8,565 shells, I've had the initial load take under a minute with the subsequent load immediately after taking over 2 minutes and the load after being between 1 and 2 minutes.

I honestly think that the load time is as much affected by the client system as the server. I've found the same thing to be true regarding waiting to process the list until it is complete, and I suspect (I'm not an expert in this area of CIS) it's because of limited system resources on the client system. Those resources don't immediately become available once the process is done. Afterall, the completed list is stored there until the page is refreshed, and maybe even a little after due to the effectiveness of the browser. This means that while the server has it cached and ready-to-go, the client has less to work with and needs to go slower to prevent memory leaks and data drops.

As for the next versus last link, I know that there's always the risk that in the time it takes to run and process the calls enough new data could be added to force another page (unlikely since the requirements could be anywhere from 1 entry to 99 entries to do so), but there's only so much that can be accounted for when using these API calls. A synchronous calling would help mitigate it by ensuring the last call is made last and the most current set of data is available, but that also greatly slows down the process.

James
Community Champion
If you want to include it in the global JavaScript, it's perfectly doable as-is, though I would recommend it.

You do mean recommend against it, right? That's the way the rest of your comments read.

If someone were to put it in the custom JavaScript, I would recommend throwing the entire thing into an enclosure so that the variables don't pollute the global namespace. As far ask I can tell, there's no reason why courses needs to be saved as window.courses and it could clobber another script's variable. Canvas has been working on cleaning up the namespace on their end and while it's the right thing to do, it's been making it harder for people writing JavaScript to modify their Canvas to do things.

James
Community Champion

Another thing I started to write yesterday, but didn't, was that one technique I use to reduce memory usage is only store the part of the data that I actually use. The course object averaged out at about 720 bytes per course for me. While that's not huge for a modern machine, if you don't need it, you don't have to save it. So if your client systems are lower end, that might be a way to speed things up. In my PHP code, I added a filter system to just copy the values needed as it fetches it from the API.

We're taking different approaches in collecting the data though. Instead of doing a $.merge() on the data, I just copy the new data into the array containing the master list. Merge might be doing that same thing in one line of jQuery -- but I wasn't a JavaScript programmer when I started doing all this stuff for Canvas and I knew even less about jQuery. As time moves on, the more I know about jQuery, the more I try to do in pure JavaScript.

My machine was top of the line a couple of years ago when I bought it, so the bottleneck for me is usually the network (DSL) rather than the amount I store in memory. The other slowdown is Jive causing Firefox to consume more and more memory and 19% of the system resources and I have to kill it while I'm in the middle of writing a post to someone. That's a huge slowdown when it happens.

I ran some more timings this morning, this time with the default 10 per page. In Firefox, the first run took 30 seconds. I then switched to Chrome and made the same request about 1.5 minutes later -- that took 14 seconds. I ran it again in Chrome five minutes later while my daughter was watching YouTube. That took 14 seconds as well.

One factor in the speed may be that I'm not doing this within a user script. I open up Canvas to my dashboard and then paste the JavaScript into the browser's console. I'm also not adding saving the data. I just added the code to do that and re-ran it and it took 16 seconds. That 2 second increase could have easily been a result of network issues or even due to rounding since my resolution was only to the nearest second.

While not definitive, my initial research suggests that there might be some ways to speed up the process. At least there's enough there to say it's worth pursuing. Worth is subjective and arbitrary. I'm a math teacher and teach statistics, I love data -- but time is finite and this may not have been your initial plan when you made it available. But if nothing else comes out of this, I know I need to make use of the last page and make the calls in parallel.

The other thing I need to do is create a separate account for all the API work I do on the back-end. I've been using my account, but now that mobile is showing up in the Page View data, some of those API calls are showing up as well.

cesbrandt
Community Champion

Yes, my apologies. I would NOT recommend it. I'm blaming that I had used my phone on that one. xD

lorewap3
Community Member

Greetings Chris,

I commend you on a very nice script you have here. Saying it's more useful than the original interface is a huge understatement. 

I grabbed the code and tweaked it a little as requested by our director and thought I would briefly tell you the tweaks in case you're curious.

First, I re-ordered the filters to be 

Course Name -> Terms -> #custom filter -> Display x Shells -> Sort

It's just a personal preference, but to me having sort directions in the middle of filters are confusing to me. Since the sort isn't a 'filter' but simply a sort direction, I like putting them at the end, or in many cases of my own interfaces, separate them entirely so it's obvious to the user that this will not increase or reduce the number of results. I also like when people distinguish filter vs sort when communicate their desired changes. When I was requested to add a custom filter the sort was constantly being mentioned as a 'filter' and my programmer brain got slightly annoyed.

Anyway, I also a custom filter. It's a simple regex on the course code. We have development and pre-production shells that are identified by [DEV] and [PP] at the beginning of the course code. This dropdown allows you to select all, dev, pp, or none of the above as a filter. These are also separated into different sub-accounts which is really how you should be looking through them, but for the purpose of cleaning up old courses in the numerous sub-accounts we have it was easy to just drop an extra filter in there.

Ok, now finally to my question Smiley Wink. The entire table row for a course seems to be a click event for going straight to the course. Left and right clicking. I was trying to open a few courses in new tabs, and right clicking brought me straight to the course. I thought this was odd especially since the 'Links' drop-down can't be accessed since as soon as you click the gear icon it goes directly to the course without the option of clicking any of the drop-down items.

A super fancy down-the-road feature would be a nice right-click context menu to give various options pertaining to that course, but yes completely unnecessary. I really like tr row highlighting and mouse:cursor css that active on tr:hover so I'd like to keep the functionality of the entire row being clickable, but make right-clicking work as normal and fix the links drop-down. 

Thanks for the great script!

Will

cesbrandt
Community Champion

Yay! I get to do "question blocks"! xD

Question Blocks

First, I re-ordered the filters to be 

Course Name -> Terms -> #custom filter -> Display x Shells -> Sort

It's just a personal preference, but to me having sort directions in the middle of filters are confusing to me. Since the sort isn't a 'filter' but simply a sort direction, I like putting them at the end, or in many cases of my own interfaces, separate them entirely so it's obvious to the user that this will not increase or reduce the number of results.

  • Well, technically Display x Shells isn't a filter, either. As for labeling them, I agree, but it was decided to go with a minimalistic approach as the options are fairly self-explanatory.

Anyway, I also a custom filter. It's a simple regex on the course code. We have development and pre-production shells that are identified by [DEV] and [PP] at the beginning of the course code. This dropdown allows you to select all, dev, pp, or none of the above as a filter. These are also separated into different sub-accounts which is really how you should be looking through them, but for the purpose of cleaning up old courses in the numerous sub-accounts we have it was easy to just drop an extra filter in there.

  • We do the same labeling, but we use the Name field, instead. There's an LTI we use (I couldn't tell you which) that requires the Course Code to match their own database for pairing curriculum content. As a result, we don't modify the code to describe the shell purpose.

The entire table row for a course seems to be a click event for going straight to the course. Left and right clicking. I was trying to open a few courses in new tabs, and right clicking brought me straight to the course. I thought this was odd especially since the 'Links' drop-down can't be accessed since as soon as you click the gear icon it goes directly to the course without the option of clicking any of the drop-down items.

  • Did you pull down your copy before or after my update on Friday? This is one of the things that was identified and fixed. I tested the fix on Windows 10 in Internet Explorer 11, Edge, Firefox 53, and Chrome 58. I also tested it on OS X in Safari 9.1.3.
lorewap3
Community Member

Thank you Chris! 

You are correct, we grabbed it Friday. I got the new version and it works great. I'll do that next time before I cry wolf Smiley Wink

Our director is already wanting another filter to be added to separate the courses by type. I wouldn't be surprised if he later on wants an undergraduate/graduate filter as well. I already had to force the drop-down widths to a smaller size to keep it all on one line. If we end up adding more filters and have to drop the sorts to a second line I'll probably throw a label in there. 

It's a very small thing, but to make it easier for customization, I separated the html nodes for the filter into separate lines. I'm not sure if there's a reason for keeping them all in one line or not, but this has really helped adding the requested filters.

var filterOptionsHtml = '<div id="filterCourseListSelectorsContainer" class="span10">';
filterOptionsHtml += '<input type="text" id="filterByName" placeholder="Course Name"> ';
filterOptionsHtml += $('#enrollment_term_id').clone().attr({id: 'filterTermID'}).wrap('<div />').parent().html() + ' ';

filterOptionsHtml += '<select id="filterEnv" style="width:180px;">';
filterOptionsHtml += '<option value="" selected="selected">All</option>';
filterOptionsHtml += '<option value="DEV">Development</option>';
filterOptionsHtml += '<option value="PP">Pre-production</option>';
filterOptionsHtml += '<option value="PC">PC</option>';
filterOptionsHtml += '<option value="TEST">Test</option>';
filterOptionsHtml += '<option value="TEMPLATE">Template</option>';
filterOptionsHtml += '<option value="OTHER">None of the above</option>';
filterOptionsHtml += '</select>';

filterOptionsHtml += '<select id="filterDisplayNumber" style="width:180px;"><option value="50" selected="selected">Display x Shells</option><optgroup label="-----"><option value="10">10</option><option value="25">25</option><option value="50">50 (Default)</option><option value="100">100</option></optgroup><optgroup label="-----"><option value="0">All</option></optgroup></select>';

filterOptionsHtml += '<select id="filterSortBy" style="width:180px;"><option value="1">Course Code (A-Z)</option><option value="2">Course Code (Z-A)</option><option value="3">Name (A-Z)</option><option value="4">Name (Z-A)</option><option value="5">Oldest - Newest</option><option value="6" selected="selected">Newest - Oldest</option></select> ';

filterOptionsHtml += '</div><div id="filterCourseListSubmitContainer" class="span2 align-right"></div>';

var filter = $('<div />').addClass('ic-Action-header header-bar row-fluid pad-box-mini border border-trbl border-round-t').css({marginBottom: 0, paddingBottom: 0}).html(filterOptionsHtml);
cesbrandt
Community Champion

You've nothing to apologize for. I didn't really advertise the version change. I'll correct that when I update it for the functionality discussed with  @James ‌.

Regarding single-line vs. multi-lines, it's really just my preference. I try to maintain a clean look (usually), but I also like to keep variable declarations/modifications to one line per variable whenever possible, but I'll break them down for ease of readability.

James
Community Champion

Although the really long lines are hard to read, I also tend to prefer that over repeating the variable at the beginning of each line. I've done both and usually end up with whatever the code formatter for the language I'm writing in doesn't butcher.

But there may be a happy middle ground (definitely not saying to switch to it). But I just found out that this exists when looking for a preferred way in a JavaScript style guide (not that I follow all of the preferred suggestions anyway).

What you cannot do is embed newlines within single quotes.

var a = 'This
is a
test';
console.log(a);

 At least in Firefox's console, it strips out the embedded newlines and then throws an unterminated string literal error.

238351_pastedImage_1.png

But I just discovered Template Literals. Using a backtick instead of a single quote, you can embed newlines.

var a = `This
is a
test`
;
console.log(a);

It still combines it to a single line when it moves it from the input to the output sections, but now when you look at the console log, the embedded newlines are there.

238433_pastedImage_2.png

Plus, you can embed templates with a $ followed by braces: ${}

var timeLeft = 4;
console.log(`You have ${timeLeft} seconds remaining`);

When you look at the log, it shows up as:

238434_pastedImage_3.png

You don't have to just put variables in there, you can put JavaScript code as well.

var timeLeft = 4;
console.log(`You have ${timeLeft < 5 ? 'less than 5' : timeLeft } seconds remaining`);

Here is the output of that with timeLeft = 4 and timeLeft = 6.

238435_pastedImage_4.png

How cool is that?

But before you rush out to use it, this didn't become available until 2015 (ES6), so ask yourself if you need to support older browsers.

238436_pastedImage_5.png

238437_pastedImage_6.png

There's an awful lot of red there.

A nice write up on them is here ES6 in Depth: Template strings

cesbrandt
Community Champion

The lack of compatibility is why I will be sticking with the middleground between yours and what lorewap3‌ posted:

var a = 'This'
+ ' is a'
+ ' test';
console.log(a);

The trick then becomes remembering that spaces outside the quotes do not apply inside the quotes. I'm always tempted to wrap the quote right on the text, and that's wrong in this type of example. xD

cesbrandt
Community Champion

The script has been updated, with quite a few things, including a fairly important bug fix that was identified this morning by one of our developers.

Changelog

6/14/2017

  • Renamed the script file for easier installation to userscript managers
  • Added @updateURL to the header
  • Fixed filtering issue where two results in a row would result in the second result being ignored
  • Split all lines to be ~80 characters in length (tab = 4 spaces)
    • Note: I didn't use a script so that I could try to add readability as I went through and did this
    • I didn't enforce this if it was 1-2 character past
  • Fixed a few comments, primarily surrounding function returns
  • Added convertList function with option to toggle it or replaceList
  • Separated the list table building into the buildListTable function
  • Removed unused global variables
  • Added optional filtering/display labels
bfowler
Community Explorer

This is a valuable contribution. Every bit of functionality that we can choose to run in-browser is a big lift for the noncoders of the community. Thanks to Christopher for stepping in! Looking forward to see how this can develop.

kona
Community Champion

cesbrandt‌, I just tried to download this script and got an error message that it wasn't found. Can you direct me to the correct link to download this so I can give it a spin?

Thanks!

James
Community Champion

He renamed it so that it is recognized by the user script managers. You can either add .user before the .js so that it says .user.js or you can use the new direct link to install or the one to the GitHub page.

I'm sure Christopher will come along and update his blog post -- maybe even before I get this posted.

kona
Community Champion

Thanks  @James , I've got it up and working!

cesbrandt, some quick initial feedback.

  • It took a little while to load, but wasn't bad once I entered an actual course into the search.
  • Instructors aren't listed in the list of courses. This is really important because often I'm looking for a specific Instructor's course and the only thing I know is that they teach ENGL 101. Right now I have no idea which of the 10 different ENGL 101's is the course I'm looking for; side-note, I rarely know the course number or section number.
  • Speaking of which... it would be amazing if I could search by Instructor rather than just course or term. <--James hates it when I do this to him, but hey, I figured it can't hurt to ask... right? 😉

Overall this is a big improvement to the current Admin search interface and I greatly appreciate you sharing this with the Community!! Thank you!

bfowler
Community Explorer

HI Kona,

I am using tampermonkey.

I went to Cristopher’s github site and dl’d the zip file of the script, then used the tamper monkey dashboard>utilities> import zip feature.

It was the only way I could figger it….

James
Community Champion

When you say it took a while to load, you didn't happen to time it did you?

I don't mind you asking for features. It's the explaining when I've already explained that gets a little frustrating. By the way, here's what Christopher wrote about the teachers in his initial blog post.

Q: Is there anything else that was removed? Is so, why were they removed?
A: Yes. Yes this is.
Student Count: This information is not provided using the /api/v1/accounts/:account_id/courses API call.
Enrolled Teachers: This information is not provided using the /api/v1/accounts/:account_id/courses API call.

Yes, this information could easily be retrieved with additional calls, but that would significantly increase the number of API calls which then slows done the processing time required to generate the course list.

Q: You mentioned the speed at which the course list is generated would be slowed down by making the additional API calls needed to display more information. What are we talking about? 5-seconds? 10-seconds?
A: Minutes to hours; entirely dependent upon the connection, number of courses, and how many additional calls are required to pull all the desired information. I, honestly, have not looked into this beyond seeing that it is not provided right away.

James
Community Champion

 @bfowler  ,

Click on the file name on GitHub so it shows the code and then click the Raw button.

239283_pastedImage_2.png

kona
Community Champion
When you say it took a while to load, you didn't happen to time it did you?

It took around 30 seconds to initially load. That was without entering any course, term, etc information. Yet, once it loaded and I entered a course and selected a term and it loaded almost immediately.

Thanks for the faculty information. I obviously missed that! 😉

cesbrandt
Community Champion

Sorry about that,  @kona ‌ and  @James ‌. I've no idea why it hadn't occurred to me before that I needed to update that link. ^^'

It's fixed in the main post, now.

cesbrandt
Community Champion

 @bfowler ‌, my apologies for that. My best guess is that my mind was still in the clouds after my family reunion because it completely escaped me that I needed to update the link after I changed the filename. The link in the main post should work, now.

cesbrandt
Community Champion

Edit 10 Seconds After Original

I feel silly doing this, but I felt the information I had already typed would still be useful in understanding the reasoning why other data can't be included.

I decided to take another look at the courses API and, funny enough, I had completely missed that the API supported include[]=teachers, which would provide the desired data without any additional calls.  @James ‌, aren't you supposed to point out these things to me? :smileysilly:

So, expect an update tomorrow with this added!

Original

I've been tossing the idea of adding that additional information around as an optional feature, like enabling tabbed view with the original content, but I keep coming back to the same reasoning for leaving it out in the first place: this information would need to be pulled individually for each course in addition to pulling the generic course information.

Here's a basic idea of what the script current does for data retrieval:

Account 67: 893 Courses

API Calls: 90

/api/v1/accounts/67/courses?per_page=100&page=1

/api/v1/accounts/67/courses?per_page=100&page=2

/api/v1/accounts/67/courses?per_page=100&page=3

...

/api/v1/accounts/67/courses?per_page=100&page=89

/api/v1/accounts/67/courses?per_page=100&page=90

Runtime: ~15-30 seconds

Here's what it would look like with the single extra call needed to pull instructor information:

Account 67: 893 Courses

API Calls: 983

/api/v1/accounts/67/courses?per_page=100&page=1

/api/v1/accounts/67/courses?per_page=100&page=2

/api/v1/accounts/67/courses?per_page=100&page=3

...

/api/v1/accounts/67/courses?per_page=100&page=89

/api/v1/accounts/67/courses?per_page=100&page=90

/api/v1/courses/1/enrollments?per_page=100&page=1&role[]=TeacherEnrollment

/api/v1/courses/2/enrollments?per_page=100&page=1&role[]=TeacherEnrollment

/api/v1/courses/3/enrollments?per_page=100&page=1&role[]=TeacherEnrollment

...

/api/v1/courses/892/enrollments?per_page=100&page=1&role[]=TeacherEnrollment

/api/v1/courses/893/enrollments?per_page=100&page=1&role[]=TeacherEnrollment

Runtime: ~165-3300 seconds

Important Note: I'm not joking about the runtime of the second scenario. You have to remember that this data is stored entirely in your browser's memory as the full list is compiled. The more data that must be stored in the memory of the browser, the less it has to work with as it proceeds to the next call, slowing it's processing of the new data.

While, as  @James ‌ has mentioned, I could refine the data storage to retain only that data which is needed, the shear volume of API calls needed will significantly increase the execution time, even with no results for the enrollments.

No matter how I look at it, I simply cannot justify adding the teacher enrollments to the supplied data.

kona
Community Champion

Yay!!! Thank you for taking another look at that! 

James
Community Champion

I decided to take another look at the courses API and, funny enough, I had completely missed that the API supported include[]=teachers, which would provide the desired data without any additional calls. James Jones, aren't you supposed to point out these things to me?

A call to everyone to keep us honest. Sometimes we get so wrapped up in the details of getting some portion of the code just right that we overlook something that would make our life easier. I didn't even bother to check your API calls.

cesbrandt
Community Champion

Update ~3 Hours Later

Sorry, everyone, it looks like refactoring the code will have to wait until tomorrow. At the very least, I'll be able to start the weekend routine and that'll free me up to work on this.

Update ~2 Minutes Later

Yeah, it happened. I have a pretty time-consuming problem I need to address before I can work on this, so if I do get it done today it'll be later this evening.

Original

 @James , I just want to state, for the record, that when I called you out on it I was entirely jesting. It's really my fault. I should've caught it when I first wrote the script and was considering enabling all the information possible to make a Details feature. The idea was that each entry would be expandable to reveal a good bit more of the course settings retrieved from the API. In the interest is getting it done, though, dropped that portion of the concept.

As for the teacher data, I had hoped to have it out yesterday, but I ran into a little difficulty with setting up a filter, as  @kona ‌ requested. I got that sorted out and implemented on our userscript. Now I'll work on refactoring it to the public release equivalent. If I don't get sidetracked with other tasks, I should have it sometime in the next 2-3 hours.

James
Community Champion
when I called you out on it I was entirely jesting

I knew that at the time, but thanks for clarifying in case anyone was confused.

cesbrandt
Community Champion

I thought you had, the smiley should be a giveaway, but I also know that I'm not a good wordsmith. As such, I know my phrasing is often taken in the wrong tone. ^^'

cesbrandt
Community Champion

 @James ‌,  @kona ‌,  @bfowler ‌, lorewap3‌,  @Chris_Hofer ‌, 925024864

I've finally gotten to it! The script has been updated to include the teacher(s) on the list and filtering. I'm so sorry is took so long, things have just been a bit on the busy side of things. ^^'

I highly suggest reading the information in How It Works section of the blog post for details on how the filtering logic works.

kona
Community Champion

Thanks!! We're traveling to a family reunion (strangely enough), so I'm not sure when I'll get to the this out, but I'll let you know how it works! 🙂

cesbrandt
Community Champion

Don't worry about it. I just wanted to make sure everyone that has responded to the blog post knew that I finally got that update made. Have fun at your reunion. Smiley Happy

kona
Community Champion

Awesome! Just got the updated version installed and was SUPER excited to be able to search by Instructor! Yay!! Thank you! 🙂

cesbrandt
Community Champion

I'm glad you like it, but this is probably all that I will be able to give for a while. I need to set my focus on an unrelated project that'll take a while to complete. Smiley Sad

So, I hope everyone enjoys this while Instructure develops it properly. ^^

lorewap3
Community Member

Hey Christopher,

I know you're starting to work on an unrelated project, but when you get a chance I'd appreciate your insight on something. 

I forget when, but I updated our code to your newest version a week or two ago. My boss said that the sorting was working before but since the newer version isn't quite working as it used to. I can't say I paid enough attention to see whether it was or not before.

Basically the course code sorting seems to strip any numbers out of the string before doing the sorting. I'm guessing this is a design choice for the way your institution likes to sort? Ex. the following two courses display in this order:

"[PP] LSAL 5553 ASSESSMENT-BASED COACHING"

"[PP] LSAL 5513 FOUNDATIONS IN COACHING"

but we expect to see them in the reverse:

"[PP] LSAL 5513 FOUNDATIONS IN COACHING"

"[PP] LSAL 5553 ASSESSMENT-BASED COACHING"

I looked briefly and found the offending code around line 554

var course1Numeric = parseInt(course1SortBy, 10);
var course2Numeric = parseInt(course2SortBy, 10);

If I wanted to disable the stripping of the numbers so the sort works as intended, what would be the easiest way to modify the code to do so?

I apologize I haven't had the time recently to post feedback, but other than this the script works great. Our staff love the interface and find it an immense improvement especially with the new filters I was able to add easily. The indentation of the latest versions made it very easy. 

lorewap3
Community Member

Nevermind!

It didn't take long to figure out. I'm still curious as to why you wrote it this way, though. Most colleges have numbers in the course code so what's the scenario that you would want them stripped?

lorewap3
Community Member

Hey guys,

I'm trying to either find or put together a good canvas api javascript library. What I mean by that is basically I want a good standalone js script containing a class object I can instantiate in other scripts that I build that would handle all the error checking, validation, ajax, etc for api calls. 

I know plenty of people have had to develop something like this or at least started to before, so I thought I'd ask the experts. This script has api calls separated into functions, which is nice, but at first glance it seems a bit much for the library I'm thinking of. I just want something simple enough to do:

canvas = new CanvasAPI();

courses = canvas.get("/courses"); // All courses for current user

course = canvas.get("/courses/" + course_id); // Get course by course id

course.name = new_name;

canvas.put("/courses/" + course_id, course); // Put course object back into canvas

etc

Either of you come across something like that yet?

James
Community Champion

That would be cool, wouldn't it?

I haven't looked for any in the last couple of years, but back then, anything I found wasn't developed enough for me to use or consider using.  jQuery comes with AJAX built-in and with the exception of handling pagination, it's a relatively straight-forward process to just make the AJAX call through there.

cesbrandt
Community Champion

I actually have something like that in PHP: GitHub - cesbrandt/canvas-php-curl: This is a simplistic class designed specifically for use with In... 

Maybe after my current project finishes I'll make a JavaScript equivalent, though it may be some time before I am free to do so.