Skip navigation
All Places > Canvas Developers > Blog > Author: Gerald Q. Maguire

Canvas Developers

11 Posts authored by: Gerald Q. Maguire

For some time I have been running a local Canvas instance for development activities. This has enabled me to both peek under the covers and give a VM with a complete Canvas instance and programs that I have developed to students.

 

During the summer I noticed that after updating the code using the github Canvas sources that I had a flashing dashboard that would never render a static dashboard and that when I went to the assignments page I could not see the list of assignments.

When using the inspector in the browser I could see the results of the query return the JSON for the assignments in the course. However, nothing appeared.

After some looking at the page for assignments, I found that the class where I expected to see the assignments there was a div that included "hide-content-while-scripts-not-loaded" and then searching in the source code (using find) I found the following:

find . -type f -exec grep hide-content-while-scripts-not-loaded {} \; -print   @body_classes << 'hide-content-while-scripts-not-loaded' ./app/views/assignments/new_index.html.erb       @body_classes << 'hide-content-while-scripts-not-loaded' ./app/views/courses/show.html.erb   @body_classes << 'hide-content-while-scripts-not-loaded right-side-optional' ./app/views/announcements/index.html.erb   @body_classes << 'hide-content-while-scripts-not-loaded' ./app/views/discussion_topics/index.html.erb   @body_classes << "full-width no-page-block hide-content-while-scripts-not-loaded" ./app/views/calendars/show.html.erb

So this hiding of contents occurs in a number of places, but I could not find the CSS.
After a bit of searching, I found at https://code.vt.edu/griffc1/canvas-lms/blob/de9d56b7f0f8b1818d9f161c737c86744e17b756/app/stylesheets/base/_layout.sass

// This hides stuff till the javascript has done it's stuff .hide-content-while-scripts-not-loaded   #content, #right-side-wrapper     +single-transition(opacity, 0.3s)     +opacity(1) .scripts-not-loaded   #content, #right-side-wrapper     +opacity(0)

The above means that the results are purposely hidden until some javascript has been loaded.

Additionally, using the inspector in the brower I saw the following when trying to display the page for assignments for a course:

assignment_index.js:14 Uncaught (in promise) Error: Cannot find module '@instructure/js-utils'     at webpackMissingModule (assignment_index.js:14)     at eval (assignment_index.js:14)     at Module.sMe2 (assignment_index-c-9c2eac0849.js:1941)     at __webpack_require__ (main-e-a68344b004.js:64)

Going to the docker container where the webpack is built I did a yarn run webpack. In this I found:

ERROR in ./app/jsx/bundles/dashboard_card.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/app/jsx/bundles'  @ ./app/jsx/bundles/dashboard_card.js 22:0-65 40:33-39 40:40-56  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./app/jsx/bundles/assignment_index.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/app/jsx/bundles'  @ ./app/jsx/bundles/assignment_index.js 29:0-57 91:0-16  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./app/jsx/dashboard/DashboardHeader.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/app/jsx/dashboard'  @ ./app/jsx/dashboard/DashboardHeader.js 37:0-65 283:27-33 283:34-50  @ ./app/jsx/bundles/dashboard.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./app/jsx/discussions/apiClient.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/app/jsx/discussions'  @ ./app/jsx/discussions/apiClient.js 19:0-66 28:9-16 28:17-33  @ ./app/jsx/discussions/actions.js  @ ./app/jsx/discussions/components/DiscussionsIndex.js  @ ./app/jsx/discussions/index.js  @ ./app/jsx/bundles/discussion_topics_index_v2.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js

The above means that the js-utils are not found, despite the fact that this is a package as one can see from the output of the command "ls packages":

babel-preset-pretranslated-format-message canvas-planner canvas-rce canvas-supported-browsers jest-moxios-utils js-utils k5uploader old-copy-of-react-14-that-is-just-here-so-if-analytics-is-checked-out-it-doesnt-change-yarn.lock

Similar to the solution in posting https://github.com/instructure/canvas-lms/issues/1318 (Links to an external site.) 

The solution is to add in the docker-compose.override.yml file the following to the services -> jobs -> volumes key :
- js-utils:/usr/src/app/packages/js-utils

and then to the volumes key father down the file add this::
js-utils: {}

This fixes the problems with dashboard and assignments!

I also notice that another module ('canvas-planner') that is in packages also has problems during the yarn run webpack:

ERROR in ./packages/canvas-planner/lib/actions/index.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/packages/canvas-planner/lib/actions'  @ ./packages/canvas-planner/lib/actions/index.js 22:0-66 101:18-25 101:26-42  @ ./packages/canvas-planner/lib/index.js  @ ./app/jsx/dashboard/DashboardHeader.js  @ ./app/jsx/bundles/dashboard.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./packages/canvas-planner/lib/actions/loading-actions.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/packages/canvas-planner/lib/actions'  @ ./packages/canvas-planner/lib/actions/loading-actions.js 24:0-66 82:18-25 82:26-42 158:16-23 158:24-40  @ ./packages/canvas-planner/lib/actions/index.js  @ ./packages/canvas-planner/lib/index.js  @ ./app/jsx/dashboard/DashboardHeader.js  @ ./app/jsx/bundles/dashboard.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js

My hypothesis is that a similar approach can be used to solve this problem. However, since the output of the yarn run webpack also show the following (edited to reduce the mass of output)

ERROR in ./packages/canvas-planner/lib/actions/loading-actions.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/packages/canvas-planner/lib/actions'  @ ./packages/canvas-planner/lib/actions/loading-actions.js 24:0-66 82:18-25 82:26-42 158:16-23 158:24-40  @ ./packages/canvas-planner/lib/actions/index.js  @ ./packages/canvas-planner/lib/index.js  @ ./app/jsx/dashboard/DashboardHeader.js  @ ./app/jsx/bundles/dashboard.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./app/coffeescripts/media_comments/js_uploader.js Module not found: Error: Can't resolve '@instructure/k5uploader' in '/usr/src/app/app/coffeescripts/media_comments'  @ ./app/coffeescripts/media_comments/js_uploader.js 21:0-49 106:26-36 123:26-36  @ ./public/javascripts/media_comments.js  @ ./app/jsx/runOnEveryPageButDontBlockAnythingElse.js  @ ./app/jsx/main.js  ERROR in ./packages/canvas-rce/lib/bridge/Bridge.js Module not found: Error: Can't resolve '@instructure/k5uploader' in '/usr/src/app/packages/canvas-rce/lib/bridge'  @ ./packages/canvas-rce/lib/bridge/Bridge.js 21:0-49 69:38-48 ...  ERROR in ./packages/canvas-rce/lib/rce/ResizeHandle.js Module not found: Error: Can't resolve 'react-draggable' in '/usr/src/app/packages/canvas-rce/lib/rce'  @ ./packages/canvas-rce/lib/rce/ResizeHandle.js 22:0-48 65:27-40 ...    ModuleDependencyWarning: "export 'passthroughProps' was not found in '@instructure/ui-react-utils' ... ,   ModuleDependencyWarning: "export 'passthroughProps' was not found in '@instructure/ui-react-utils' ...  ]  98% after emitting SizeLimitsPlugin[ ModuleDependencyWarning: "export 'addInputModeListener' was not found in '@instructure/ui-dom-utils' ...,   ModuleDependencyWarning: "export 'passthroughProps' was not found in '@instructure/ui-react-utils' ...,   ModuleDependencyWarning: "export 'passthroughProps' was not found in '@instructure/ui-react-utils' ...

It makes me curious as to why all of these missing files include the path "@instructure". Is there some error in the configuration that leads to the packages not being found (despite the fact that doing a "yarn list" showed that "@instructure/js-utils" was installed)?.

 

I should note that I am a novice with respect to Javascript - so some of the problems might be operator error, but the Canvas source code was freshly installed via the quick start update script.

A couple of days ago I decided to re-examine an issue that has annoyed me several times, the lack of a Question Bank API. The process began with some postings to followup the question raised by Jared Chapman in https://community.canvaslms.com/thread/14766-where-is-the-question-bank-api

 

This lead to some Tampermoney scripts (such as https://kth.instructure.com/courses/11/pages/using-tampermonkey-to-add-question-banks-v2) that enabled me to add question banks. This success lead to a desire for further automation and this lead me to investigate the combination of Puppeteer (https://pptr.dev/) and Node to run Javascripts to create a question bank- the first result was Using Puppeteer to insert new question bank: Chip sandbox. After seeing what Puppeteer could do I expanded upon the program to have a program that would not only let me add question banks but would also output a JSON file of the resulting set of question banks (which of course could then be used with the Canvas API to insert questions into question banks!). The resulting program is documented at https://kth.instructure.com/courses/11/pages/using-puppeteer-to-create-new-question-banks-and-get-json-of-existing-banks

 

Some obvious programs that could be derived from this last script would be:

  • to read a JSON file or CSV file that contains a list of question banks that one wants to create along with the course_id to create them in
  • a program to simply output the JSON for the existing question banks in a course

Of course, the real solution is to add a Question bank API. Meanwhile, quite a lot can be done despite the lack of such an API.

 

Once again I wish to thank James Jones and the many others who have provided examples of JavaScript and Puppeteer scripts. I still do not know how to use Puppeteer (well) and lack confidence in navigating DOM structures (especially of pages that have elements that lack IDs or that dynamically modify the Javascript on a page).

New FERPA requirements for cross-listed courses! and others have commented on the problems of cross listing. However, the ability to do cross-listing is controlled by the same permission as being able to create/edit/delete sections. I find this to be odd. I think that the ability to cross-list should not be tied to the ability to utilize sections within a course.

 

As the app/controllers/sections_controller.rb has the following code (with the relevant line highlighted):

  # @API Cross-list a Section
  # Move the Section to another course.  The new course may be in a different account (department),
  # but must belong to the same root account (institution).
  #
  # @returns Section
  def crosslist
    @new_course = api_find(@section.root_account.all_courses.not_deleted, params[:new_course_id])
    if authorized_action(@section, @current_user, :update) && authorized_action(@new_course, @current_user, :manage)   
      @section.crosslist_to_course(@new_course, updating_user: @current_user)
      respond_to do |format|
        flash[:notice] = t('section_crosslisted', "Section successfully cross-listed!")
        format.html { redirect_to named_context_url(@new_course, :context_section_url, @section.id) }
        format.json { render :json => (api_request? ? section_json(@section, @current_user, session, []) : @section) }
      end
    end
  end

The check for authorized actions means that:

  • Unless the current user has the ability to update sections, the cross listing will not occur.
  • Unless the current user can manage the target course otherwise, the cross-listing will not occur.

Unfortunately, cross-listing provides a sneak path to add students to a course (as a teacher without administrative rights, but with the ability to create sections does a cross-listing - the students will be added to the target course - despite the fact that teacher does not have rights to add students to the course).

 

Moreover, if the current user can manually add students to the target course, then they can always add each of the students from a section to the target course. This means that there needs to be a check in the above code on whether the current user can enroll students in the target course. 

 

 

Therefore, the second test should be something similar to:

authorized_action(@new_course, @current_user, :manage)  && authorized_action(@new_course, @current_user, :manage_students) 

 

Where (based on looking at ./app/models/role_override.rb and spec/apis/v1/enrollments_api_spec.rb) :manage_students would enable the creation of student enrollments. Thus unless the user is allowed to enroll students in the target course, the cross-listing would not occur.

 

If the permission to add students ( permission: Users - add / remove students in courses) to imported courses (i.e., courses that are automatically created by SIS  imports) is disabled for the "Teacher" role, there should not be a problem in allowing teachers to create/edit/delete sections - while still meeting FERPA and other similar regulations (as there would not be any ability to cross-list a section worth of students and each section would only be within a single course). In fact, the use of sections could be used to reduce the visibility and interactions of students (and even teachers) to within their section, thus advancing student's privacy.

During 2019 I have been trying to use Canvas to help support the degree project process (for students, faculty, and administrators). One of the latest parts of this effort has been to look at some of the administrative decisions and actions that occur at the start of the process. A document about this can be found at https://github.com/gqmaguirejr/E-learning/blob/master/First-step-in-pictures-20190524.docx (a PDF is attached). The code can be found in SinatraTest21.rb at https://github.com/gqmaguirejr/E-learning. This code makes use of user custom data in conjunction with a dynamic survey (realized via an external LTI tool) and the administrative decision and action part of the process utilizes custom columns in the gradebook and automatically creating sections and adding a given student to the relevant section.

 

The Ruby code in LTI tool uses a token to access the Canvas API from the LTI tool to put values into the custom columns in the gradebook - this is probably not the best approach, but worked for the purpose of this prototype.

Two commonly used grading schemes in Sweden are A-F (i.e., A, B, C, D, E, and F) and P/F (i.e., Pass and Fail) together with Fx (an incomplete - this is not a final grade). To install these grading schemes into a Canvas course or account I wrote a simple python program: insert_AFPFFx_grading_standards.py (available at https://github.com/gqmaguirejr/Canvas-tools).

 

Note that the numeric values associated with the letter grades do not matter, as it is only the letter grade that will be recorded in the central grade register (called Ladok).

 

While we have been using Canvas as an LMS for several years, we still did not have such a grading scale available at the account level, so I made a program to make it easy to insert these schemes for a course.

In conjunction with experiments concerning using an LTI interface to realize a dynamic quiz, I built a version of Canvas that could run in a Virtual Machine (specifically an image that could be run by VirtualBox).

Advantages of this approach include:

  • easy to try things without any risk to the real Canvas infrastructures
  • easy to give an OVA image of the VM to students

The image was built using an ubuntu version of linux by following the Quick Start instructions at https://github.com/instructure/canvas-lms/wiki/Quick-Start.

Details of the operation of the different containers that comprise this system will be described in a forthcoming Bachelor's thesis.

I should note that when I do the docker-compose to bring up the Canvas instance, it takes a very long time.

In order to do some experiments with the dynamic quiz, I created some fake users and enrolled them in a course. Additionally, since one of the things that I would like the dynamic quiz to exploit is knowledge of what program of study these users are in I augments the user's custom data with details of what program they are in.

The initial version of the program (create-fake-users-in-course.py) can be found at  https://github.com/gqmaguirejr/Canvas-tools .

The result is a course with a set of fake users as can be seen in the list of user's in this course:

List of users in the course

An example of fake program data is:

custom data for user Ann FakeStudent is {'data': {'programs': [{'code': 'CINTE', 'name': 'Degree Programme in Information and Communication Technology', 'start': 2016}]}}

 

Some further details about the above can be found at Creating fake users and enrolling them into a course: Chip sandbox 

When faced with having to assign peer reviewing for multiple assignments, I realized that I needed a tool to copy peer reviewing assignments from one assignment to another. Then in a course with multiple assignments and multiple instructors (not all of whom were synchronized in making their peer reviewing assignment), I modified this program to allow the copying on a section basis (i.e., one can specify that only the peer reviewing assignment for a user in a given section is to be copied).

The results and the programs to do the above are at Copying peer reviewing assignments: Chip sandbox 

The current quiz API seems to be missing functionality to insert HTML answers, as one can only set an answer_text and not an answer_html.  This is despite the fact that the actual answers support an html element, as can be seen below:

"answers": [ { "id": 3603, "text": "answer1", "html": "", "comments": "", "comments_html": "", "weight": 0 }, { "id": 3070, "text": "answer2", "html": "", "comments": "", "comments_html": "", "weight": 100 }, ... ]

 

It is possible via the RCE GUI to cut the text and then switch to HTML mode and past it in. As shown in the example below:

[{'text': '', 'id': 2400, 'html': '<pre>sum(5,10)</pre>', 'weight': 0.0, 'comments_html': '', 'comments': ''}, {'text': '', 'id': 7243, 'html': '<pre>from math import *<br>sum(5,10,0)</pre>', 'weight': 0.0, 'comments_html': '', 'comments': ''}, {'text': '', 'id': 7766, 'html': '<pre>from math import<br>sum(5,10)</pre>', 'weight': 0.0, 'comments_html': '', 'comments': ''}, {'text': '', 'id': 5120, 'html': '<pre>from math import *<br>sum(5,10)</pre>', 'weight': 100.0, 'comments_html': '', 'comments': ''}]

 

I think that it should always be possible to set values via the API as opposed to having to use the GUI.  While it might take only a few minutes to do this for a single question, when there are large numbers of questions - this is when it should be possible to apply power tools.

 

[I am aware that there will be a new quiz engine, but as I do not have it - I have to work with what I have.]

I am a big fan of sections and have found that they are useful in a number of contexts. However, I really hate to use point and click user interfaces, so I decided it was time to make some programs to do the work for me.

This lead to a series of programs:

1.  list_sections_in_course.py to list the existing section information (produces a spreadsheet with two pages: Sections and Students)

2.  create_sections_in_course.py to create new sections by name

3.  delete_section_by_id.py to remove sections that I no longer want

4.  place_students_in_sections_in_course.py the real goal, it takes a course_id, spreadsheet file (such as an augmented version of the one produced by the first program), and a column name in this spreadsheet and adds then each student to the named section.

 

See Sections in courses: Chip sandbox 

 

An obvious extension is to randomly assign students to a set of N sections (with balancing over the set of sections). For example, this could be used to form sections for the purpose of assigning each section a different version of a quiz.

I just came from a workshop on "Language and Accessibility" (as a follow-upp to a seminar that we had a month ago on the topic - the slides from the seminar are available (in Swedish) from https://www.kth.se/social/files/58c17d95f276545c4bc556c8/Spr%C3%A5k-och-tillg%C3%A4nglighet.pdf ). One of the issues that came up is providing users with dyslexia with material presented in the Dyslexie font ( Dyslexie Font: The dyslexia font which eases the reading  ). Has anyone looked at being able to set a particular CSS file to be used for the user's Canvas UI based upon a user's profile? I imagine this to be an extension of the fact that the user can choose their UI language (for example, as commonly used here English or Swedish, based upon their user settings (as stored in the user's 'locale' attribute' in the format described in RFC 5646)). One approach would be to encode this desire for a special font in a subtag for the language and another would be to actually add a custom CSS attribute to the user object. Has anyone done anything like this?

 

Having an attribute that specifies a custom CSS file would probably be cleaner but would require a new field in the object. An interesting side benefit of the having both the ability to specify a custom CSS and font would be that one could have multiple language pages (ala Using tabs for content in three languages: Chip sandbox ) where the user's choice of language would the automatically the selected visible alternative.

 

Note that in the above when referring to a "custom CSS file" I am thinking in terms of choosing one from a set of files for the site, as opposed to having each user able to upload their own CSS file. 

I have been encouraged to make the contents of my sandbox more visible, it can be found at https://kth.instructure.com/courses/11  One of my goals has been to be able to use software to interact with Canvas, rather than being limited to cut and paste with RCE. Another of my goals has been to use programs to move content out of various systems that we are currently using and into Canvas. Another goal has been to move content in the other direction to other external systems.

My sandbox contains a lot of examples of things that I have been experimenting with, including computing readability statistics for the page of a course, adding citations to pages (via Zotero) using an RCE buttom to call on an LTI tool provider [a work in progress], and today's exporting of a spreadsheet of all assignments for a course (using Python's Pandas). I'm eager for feedback and opinions on this material.