Adding Login and SIS IDs to Admin User Search

James
Community Champion
28
9331

I've written a user script that will add the Login ID and SIS User ID (if you're using them) to the User Search from the Admin page in Canvas.

Quick Install

For those power users who are impatient, here are the quick install steps.

  1. Install a browser add-on: Greasemonkey for Firefox or Tampermonkey for Chrome/Safari
  2. Install the Admin User Search Details user script.

When you are searching for a User from the Account page, it will automatically display the extra information.

It has been tested with Firefox, Chrome, and Safari.

Introduction

I had a discussion with my Canvas Admin a few weeks ago about the Admin User Search. I was working on something, did a user search, noticed there was a duplicate name entry with no way to tell them apart and wondered what information would be useful. I was aware that the search functionality for users has long been a sore spot , but I don't use it on a regular basis, so I hadn't devoted much time to it.

I have recently made some advancements in my JavaScript knowledge and was ready to make an attempt to do something about this. I whipped something up one night, coming up with different ways that it could be enhanced. I showed  @kona ​ the working model the next day and asked her what she thought. She initially said it was wonderful, then before I ease the pain from patting myself on the back, she came back with a laundry list of things that it should do.

I looked into her list, which included the ability to filter users by term and decided that with the current API, there was little that I could do to speed up the process short of writing code that would query our local copy of the SIS database instead of the one maintained by Canvas'. While most of that information is available locally, there's something nice to be said about having it directly available within Canvas and part of the user interface. Besides, I already have a lookup tool that our local admins could use to find a student SIS ID and then search by that to get the results quickly within Canvas.

The bottleneck is the Canvas search. At our institution, with only about 13,500 users, when you search for "Jones" or "Smith", you get about a 25 second delay while Canvas searches for that information and returns a paginated list of 118 users (Jones) or 169 users (Smith). Subsequent reloads within a short time or moves to the next or previous pages are much faster. But it's that initial delay that is the killer and there's nothing I can do to speed that part up.

So, deciding I wouldn't be able to help Kona with her dream implementation, I focused on what was available.

Sometimes, Canvas returns information on the page but just hides it from the user. I held out hope, because the API call to search for users returns lots of information like the full name, sortable name, SIS user ID, and login ID. In some lists of users, you can get the email address, the enrollments, and the last login. Unfortunately, no matter what was being fetched to generate the page, the only thing being stored on the page was the name of the user and the URL to the user's page.

Considering some of the incredible markup and nested divs that Canvas uses in a lot of places like the modules page, it was actually very boring markup: a simple unordered list that was returned as part of the page itself, rather than an AJAX call as becoming more frequent for loading information.

After the 9+ To Do fiasco of February 20, I had written Restoring the Needs Graded count​  (by the way, if you installed that script, you should disable it) and now knew how to fetch information about each item on a page. So, I took what I had learned there and fetched the user information for all 30 users that was listed on the page. I then appended the Login ID and SIS User ID to the user's name and I formatted it to look more like a table than a list.

Kona influenced the design, so if you don't like it ... Anyway, I had three ideas for displaying it. 1) look for duplicate names and only fetch the information for those names, 2) wait until a user mouses over a name and then fetch it, displaying the information as a tool-tip if they hovered over the name, and 3) load the information for all users on the page, possibly with a delay of a few seconds in case the name was easily located.

Kona said she wanted the information for all of the users, even if there wasn't a duplicate name, she didn't like the tool-tip, it was extra work on her part, and she wanted the information immediately. So that gives us what I'm announcing today.

Canvas is Doing Something

Kona told me that Canvas was working on something and that it would hopefully be coming soon.  @Renee_Carney ​ wrote on March 7, 2016, that it is currently in code review and that hopefully it would be appearing in one of the next few releases​. It didn't show up in the March 12 release and after code review, it has to go through Beta before making it into Production, so it's at least a few weeks off. I asked Kona whether I should go ahead and release this or just wait for Canvas to do something. She said to go ahead and release it.

As with much of what I write, I hope that it is temporary and that Canvas will do something much better and this can be retired. As mentioned earlier, they are the only ones that can speed up the search.

Why Not List the Courses?

When developing this, I thought about adding currently enrolled courses to the list. The enrollments API will filter enrollments by state, but we don't conclude our courses at the end of the term and so the "active" courses included all of the previous ones as well. Then the names of the courses weren't included in the enrollments, just the Canvas and SIS IDs for them. You can get a list of courses for a user (if you have admin privileges, which shouldn't be a problem here, but it might be specific to masquerade permissions), but that might not return the dates of the course if your school puts them on the section instead and it doesn't include the user information. For the same reason, the email addresses were an additional API call when it wasn't already their Login ID. Basically, adding anything extra boiled down to a lot more complexity and time (network API calls) involved to get all of that information and I wanted something that would be reasonably quick. Canvas displays just 30 names at a time, so it makes 30 calls to the API to fetch the user information and it's pretty quick about it -- although you'll be able to see it update the screen as it fetches the information.

Stop Babbling and Show Me!

Fair enough.

I did a search for Michael Smith since he is our most popular name (having a local copy of the database made that easy to find).

Native Search Results

This is what it currently looks like:

175345_pastedImage_16.png

Enhanced Search Results

After installing the user script, you get this:

2016-03-13_22-23-35.png

Note: Your screen won't actually contain the blur, that's done for FERPA reasons.

Customization

The script should run as-is on any site that doesn't use a custom domain name. If your Canvas instance doesn't end in instructure.com, then you'll need to edit the \\ @include on line 5.

The source code contains a configuration variable with a columns array inside of it. This contains the information to display, the order to display it in, and any extra CSS classes you would like to apply to that column.

Currently, it displays the login_id first and then the sis_user_id. Our sis_user_id values are numeric, so I applied the text-right class to that column so they are right-aligned. If you have alphanumeric SIS codes, then you could take out the class value (and the comma on the line before it).

Other values besides login_id and sis_user_id that you might use include:

  • id: The Canvas User ID. You can get this now by mousing over the URL
  • sis_login_id: We use the same thing for our Canvas Login ID, so I didn't include it, but others might have them be separate values
  • avatar_url: If you wanted to display the location of their Avatar, you could include this. You probably don't want the location, though, you would want the actual avatar to show up. That's extra functionality that would have to be programmed into the system.
  • permissions: This is a really incomplete list of what the user can do. For example, my user record has can_update_name: false and can_update_avatar: true.

There are different variations of the name. name, sortable_name, and short_name.

Canvancements

This script is a Canvancement and designed to improve the user's experience with Canvas. It is up to the user to decide whether or not to use it. The source code is available on the Canvancement website.

When Canvas releases their improved user search, you will probably want to disable this one. In the meantime, I hope someone can benefit from it.

28 Comments
kona
Community Champion

Yes, yes, yes, I personally take all responsibility and blame if people don't like the way this looks and/or functions... Smiley Wink

YET, both my colleague and I have this downloaded on our computers and LOVE it! Takes a tad bit longer to load (if we are searching for something with a lot of hits - ex: "Smith"), but it's well worth the wait. Thank you  @James ​!!

kona
Community Champion

PS - Not sure how *nice* it is to call out your pregnant wife in the Community... Smiley Wink

PregnantMeme.jpg

James
Community Champion

The long delay on "Jones" or "Smith" is not because of my user script. It is all Canvas delay. Today, from work, that's sitting at about 7.5 seconds -- which is 3 times faster than from home last night. My program displays the basic information as soon as it comes in from Canvas and then starts fetching the information and updating the screen. That updating and fetching is pretty quick -- a couple of runs came in at 1.1 s and 1.2 s. -- since it only fetches the information for 30 users at a time. Because Firefox allows for concurrent requests (up to 5 at a time for me), it essentially only takes 6 blocks of 200 ms each.

But to be crystal clear, that 1.2 s is after Canvas has already delivered its data and it has been displayed.

And if your search only comes back with 5 names, it only has to look up 5 names, so it's really quick.

kona
Community Champion

Thank you for the clarification on that!

dgrobani
Community Champion

Thanks for this, James!

I installed it on Firefox 45.0 and searched for names from the account page several times. Unfortunately, though the hyperlinked user names are displayed in those nice rows, the SIS ID doesn't appear.

I tried this out because I thought it was a neat idea, not because it solves a problem I'm having. If my case is an outlier, it's probably not worth your time or attention to try to resolve it, but if other folks are having this problem, I'd be happy to help you debug it in any way I can.

James
Community Champion

dgrobani​,

The first questions that come to mind are does your site use SIS IDs and do you have permissions to use access them?

To check this, can you take the Canvas User ID for someone who you think should have the SIS User ID and do a query for me? Log into Canvas and pull up the user page. Then click on a user. You should have an URL ending in /accounts/XXX/users/YYY. Replace the /accounts/XXX by /api/v1 so it ends in /api/v1/users/YYY. Then hit enter to load the page.

Look for a field called sis_user_id and/or sis_login_id. If they aren't there, or they have null or "" in them, then my script won't display them.

I did check the script with the Old UI and the new UI, so that's not the issue. I also tested it with Chrome, Firefox, and Safari (on the old UI), so that's probably not the issue. If there is another field present that you would rather use instead of sis_user_id, you just need to make sure that it's there.

I also just noticed that I was using Firefox 44.0.2, but when I went to check it, it just offered to update it. If it fails on Firefox 45.0, there's a chance they broke something on the upgrade (they like to do that) or that updates need made. I'll know more once I restart (I'm in the middle of writing this in FF right now).

Also, you mentioned no SIS IDs, but did the Login IDs appear? I wasn't clear from your post whether it was just the SIS User IDs missing or there was nothing else other than the boxes.

James
Community Champion

I just restarted and am now in FF 45.0 and the script continues to run for me. Let me know what the check to make sure the sis_user_id shows.

buddyhall
Community Champion

Thank you! Thank you! Thank you!  @James ​ is amazing! This worked flawlessly for me in Safari 9 and the new UI.

jthoms
Community Novice

This is way cool,  @James ​!

I'm a little late to the party on this one, but am glad to learn about your other Canvancements too.

Nice work :smileycool:

sriddle
Community Novice

Good info....will help me....thank So!

mnieckoski
Community Novice

This is a great script and I've been using it for a few weeks now.  However, I'm having an issue with the columns ... it seems to have started with the Aug 27 Production update.  The name column aligns left, but the login and SISID columns are smashed together: 

Smith, Michael     msmith0577840  

All the information is there, but there is not even a space separating the columns. 

I'm wondering if it's happening in just my instance, or is this happening elsewhere?  I'm not a programmer, but if you can identify the area in the script where I could insert a few spaces (or revive the columns), I'm sure I can do it.

Thanks for a GREAT script, very helpful!

--mike

Michael Nieckoski

Keene State College

James
Community Champion

I had based the code off the modules page, where it used to truncate long text. Those are now, after the last update, on two lines and don't go ellipsis anymore.

I didn't even have this script turned on and hardly ever use this search, so I hadn't noticed it. I'll see if I can find another way to get the columns together.

In the meantime, you can add some space by editing line 50 (look for the code around line 50).

change the line from

e = $('<div>').addClass('ellipses').text(user[col.field]);

to (add the part in bold)

e = $('<div>').addClass('ellipses').text(user[col.field]).css('padding-left','2em');

James
Community Champion

I've updated the script to version 3.

  • It takes advantage of the multi-row items that Canvas now uses.
  • Since the data is no longer in columns, I added labels for the Login and SIS ID that appear on the second row.
  • Added a quick link "Become" that will allow you to masquerade as a user.
  • Behind the scenes, I reworked most of the code to avoid jQuery and use pure JavaScript, so the script will appear much longer than it was. This wasn't needed, but I've been writing so much code without jQuery recently that it would have taken me longer to figure out how to do it in jQuery than it would to rewrite it.

Hopefully your script managers will automatically detect the update, but it may take a while. You can force an update within the script manager (Greasemonkey: Manage User Scripts > Admin Cog or Tampermonkey: Check for userscript updates)

mnieckoski
Community Novice

Thanks so much, James.  The new script works great, didn't take any time to update, and the masquerade feature is very handy!

--mike

Michael Nieckoski

Keene State College

James
Community Champion

Thanks. I just updated it to version 4, which adds a "Grades" link to show all the grades for that student. Sometimes you just can't think of everything you want to do at 3 in the morning.

dgrobani
Community Champion

 @James ​,

Many months later, many thanks for the helpful diagnostic info and for updating the script.

I'm seeing the name and the new Grades & Become links but no IDs. It looks like you nailed the cause, as I don't see sis_user_id or sis_login_id in the JSON returned by the call to the users endpoint, even though we use them. This puzzles me, as I'm our account admin.

I'd be grateful for any further thoughts on this.

James
Community Champion

dgrobani​,

I experienced the same issue today while I was showing  @kona ​ the new interface. She was not seeing the Login or SIS ID, but I was. I sat down at her computer, added some console.log(); statements to the code, and found that the SIS ID information is not coming through on her account, either. We are both admins at the same institution so we should be getting the same information, right? Except I'm getting the SIS ID, another Canvas admin is getting the information, but Kona isn't. It's just not returned in the API call for her while it is for us.

My working theory right now is that what differentiates Kona is that she has two accounts listed in her Admin menu. Our institution and another one for another place.

Can you help us track down the issue? How many accounts show up when you click on the Admin menu? Is it more than one? I'm hoping you are, otherwise I have no idea where to proceed.

baxl
Community Contributor

This is great!  I used your other version, but this is even better!  Thanks for sharing! Smiley Happy

dgrobani
Community Champion

Ack, I only have one account. I tried the API call from Chrome after wiping all history and got the same unfortunate result. How bizarre!

dgrobani
Community Champion

I just made the same call via Postman passing a token and still no SIS info.

dgrobani
Community Champion

And I made the same call passing a token via a Python script and still no SIS info.

James
Community Champion

Thanks. I'm not ready to file it as a bug report yet, but it sure sounds fishy.

I may have to find another API call that consistently works.

cesbrandt
Community Champion

 @James ​, we were looking at this but found a point of concern that we've concluded is too great for use.

The Grades and Become links are loaded regardless of if the user view the list has the appropriate permissions. Of course, Canvas does the right thing and denies the user access to the pages, however this raises concerns about reports of non-functioning links from users that should never have been able to see that information to begin with.

One wouldn't think this would be a major issue, however we have several Account Admin roles whose permissions allow the viewing of the list of users but have no administrative permissions over them. Some of these include the ability to view user Grades, others do not. As for the Become, this is restricted only to the highest role, System Admins.

The biggest concern is sight. If a user sees something, they expect that they would be allowed to use it. Some things stand out and they can realize that it's a mistake, but the circumstances varying. It's not unreasonable to think a support role would be able to Become another user with the intent of troubleshooting an issue, but due to the lack of ability to restrict what users they would be able to Become, this is not the case.

Since API calls are already being utilized, it may be worth looking into restricting the generation of these links based upon the permissions the user actually has with regards to their location (i.e., a user may be able to Become users of sub-account a but not of b).

For us, we're hoping to hear something about the idea you referenced that has been under development since last November.

James
Community Champion

cesbrandt​,

You didn't say how you were using it (or not as the case is), but if you are using the user script version, then there is a configuration variable in the source code that will solve your problem. It's towards the top and there's a line that says

     'features' : [ 'grades', 'masquerade' ]

Just remove the one(s) that you don't want to be there. You don't have to have either one, but it will only add the buttons for those that are listed.

Since user scripts are loaded on a per-user basis, you can distribute your own version to those users that don't need things.

If you're using it in your custom JavaScript file, then you'll need to take efforts to restrict who can see it.

My plan when I wrote it was to make something quick so our institution could tell the users apart. I relied on the Canvas to only allow people to do things they can do.

Looking at the Roles API, determining what a person can do is not as simple as you might think it is. Canvas provides all of the roles the user is anywhere in the system in the ENV.current_user_roles variable, and not just the ones they are within the current context. However, the names there don't match the names of the roles, and they are the generic roles that other things are based off of -- like "admin", "teacher", "student", "observer", and "user", not the specific role like "Tutor".  I'm having trouble locating where you can find account roles for a specific user. They are there for enrollments, but that doesn't help on the admin page when you're not in a course or group. There is an API call to list the account admins, but that only lists the admins specific to that account and so I would have to traverse up the chain looking for admin users and then see whether or not that particular permission is one that cascades down to the account they were in.

Frankly, that's all way too complicated for what little this does. I opted for a configuration variable instead. I don't see me adding role-checking (too many other things going on and I'm not even sure I can). But you're free to use or not use the script. Everything I put out there is meant to inspire people to go further (or possibly inspire Canvas).

What have I been working on since November? Is that the due-date spreadsheet or the enhancements to SpeedGrader? The SpeedGrader things is close -- we're making videos and I need to write up documentation, but I've only been on that since about March or April.

cesbrandt
Community Champion

My apologies if you felt I was going after your work or time. I know you contribute a lot to the community. I only wished to provide feedback regarding what we perceived as being something that could help others. Your script is great for what it was designed for.

Regarding our tested deployment of your script, we pursued use as part of the global JavaScript. We see no reason to require individual users to have to manage/maintain the script themselves when it could easily be done by us.

With regards to the identification of whether a user has sufficient access, I've built that logic in the past: Account Role Detection, rather I've built out the ability to determine if the role of admin in the ENV constant (which is applied to all users with an account-level role, regardless of if it is within the path to their access location [according to our testing]) is applicable to the path of that account/course for the user. I'll take a look at adapting it (into jQuery to match your work) for use with determining if the user has those two permissions. Most of the logic is already there, but I'll adapt it so that it escapes out of the search if all needed permissions are identified.

James
Community Champion

You can mix and match jQuery and pure Javascript, so your logic to decide whether or not to run or what the roles are is independent. I think the only jQuery I use is the AJAX call and you already seem to have that part figured out in JavaScript, so it would be easier to port just that part if you want to remain just JavaScript.

cesbrandt
Community Champion

As it turns out, the code I wrote is useless for this extended purpose. It works great for just determining if the admin role in ENV is valid for the path, but it can't give someone permission to check the capabilities of a role if they can't do that.

So, I ended up going a simpler route. I used $.ajax() to pull up the masquerade and grades pages (not the become_user_id page that redirects to the masquerade page or Dashboard) and then build the config.features array based upon the results. If if succeeds, then the user it allowed to access them, otherwise they aren't.

I wrapped most of you code into a function of its own, but had to pick a few little pieces to stay global (variables). Here's the revised version that will dynamically determine whether or not to generate the Grades and Become links:

// ==UserScript==

// @name        Admin User Search Details

// @namespace   https://github.com/jamesjonesmath/canvancement

// @description Show the Login ID and SIS User Id for users from the Admin Search Page

// @include     /^https://.*\.instructure\.com/accounts/[0-9]+/users(\?|$)/

// @version     4

// @grant       none

// ==/UserScript==

(function() {

  var url = window.location.href;

  if(url.indexOf('/accounts/') != -1 && url.substr(url.lastIndexOf('/') + 1, 5) == 'users') {

    'use strict';

    var config = {

      'columns' : [ {

        'field' : 'login_id',

        'name' : 'Login'

      }, {

        'field' : 'sis_user_id',

        'name' : 'SIS ID'

      } ],

      'features' : []

    },

        roleCheckXHR = 2,

        id = url.split('/')[url.split('/').indexOf('accounts') + 1],

        userData = {};

    var userList = $('ul.users').children();

    if(userList.length > 0) {

      checkURL = '/users/' + userList.first().attr('id').replace(/\D/g,'') + '/';

      $.ajax(checkURL + 'grades').done(function() {

        config.features.unshift('grades');

      }).always(function() {

        roleCheckXHR--;

      });

      $.ajax(checkURL + 'masquerade').done(function() {

        config.features.push('masquerade');

      }).always(function() {

        roleCheckXHR--;

      });

      var wait = setInterval(function() {

        if(roleCheckXHR <= 0) {

          console.log(config.features);

          updateUserList();

          clearInterval(wait);

        }

      }, 500);

    }

  }

  function updateUserList() {

    var userRegex = new RegExp('/accounts/([0-9]+)/users/([0-9]+)');

    try {

      var nodes = document.querySelectorAll('ul.users li.user');

      if (nodes.length === 0) {

        return;

      }

      document.querySelector('ul.users').classList.add('ig-list');

      for (var k = 0; k < nodes.length; k++) {

        var e = nodes[k].querySelector('a');

        var match = userRegex.exec(e.href);

        if (!match) {

          continue;

        }

        var accountId = match[1];

        var userId = match[2];

        e.classList.add('ig-title');

        var row = document.createElement('div');

        row.classList.add('ig-row');

        var layout = document.createElement('div');

        layout.classList.add('ig-row__layout');

        var info = document.createElement('div');

        info.classList.add('ig-info');

        info.appendChild(e);

        var details = document.createElement('div');

        details.classList.add('ig-details');

        info.appendChild(details);

        layout.appendChild(info);

        if (typeof config.features !== 'undefined' && config.features.length > 0) {

          var hasLinks = false;

          var admin = document.createElement('div');

          admin.classList.add('ig-admin');

          var links = document.createElement('span');

          links.classList.add('links');

          for (var j = 0; j < config.features.length; j++) {

            var link = document.createElement('a');

            switch (config.features[j]) {

              case 'masquerade':

                link.href = '/?become_user_id=' + userId;

                link.textContent = 'Become';

                link.title = 'Masquerade as ' + e.textContent;

                break;

              case 'grades':

                link.href = '/users/' + userId + '/grades';

                link.textContent = 'Grades';

                link.title = 'Show all grades for ' + e.textContent;

                break;

            }

            if (link.href) {

              if (hasLinks) {

                links.appendChild(document.createTextNode(' | '));

              } else {

                hasLinks = true;

              }

              links.appendChild(link);

            }

          }

          if (hasLinks) {

            admin.appendChild(links);

            layout.appendChild(admin);

          }

        }

        row.appendChild(layout);

        nodes[k].appendChild(row);

        getUser(accountId, userId);

      }

    } catch (e) {

      console.log(e);

    }

  }

  function getUser(accountId, userId) {

    var url = '/api/v1/users/' + userId;

    $.getJSON(url, function(data, status, jqXHR) {

      var userId = data.id;

      userData[userId] = data;

      updateInfo(data);

    });

  }

  function updateInfo(user) {

    var userId = user.id;

    var info = [];

    var i, e, col;

    var node = document.querySelector('li#user_' + userId + ' div.ig-details');

    if (!node) {

      return;

    }

    for (i = 0; i < config.columns.length; i++) {

      col = config.columns[i];

      if (typeof user[col.field] !== 'undefined' && user[col.field]) {

        var item = document.createElement('div');

        item.classList.add('ig-details__item');

        item.textContent = col.name + ': ' + user[col.field];

        node.appendChild(item);

      }

    }

  }

})();

rconroy
Instructure Alumni
Instructure Alumni

Since the last CanvasLIVE event with User Access reports, I'm now coming to the light of all these amazing #canvancements and I think its literally changing my life. Seriously, you guys need a chocolate bar or something at InstrucureCon if I run across you. hah!