prefetch user_ids in gradebook

closes TALLY-749
flag = prefetch_gradebook_user_ids

test plan:
 * Enable the prefetch_gradebook_user_ids release flag
 * Visit Gradebook
 * Open the network tab in dev tools
 * Refresh the page
 * Verify user_ids are requested early
   (within the first handful of XHR requests)
 * Verify user_ids are not requested twice
 * Change the section or enrollment filters
 * Verify user_ids are requested again

Change-Id: I3969d988f3328dfb6ac15be96fa7cb18d7609c82
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/230246
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Spencer Olson <solson@instructure.com>
Reviewed-by: Gary Mei <gmei@instructure.com>
QA-Review: Robin Kuss <rkuss@instructure.com>
Product-Review: Spencer Olson <solson@instructure.com>
This commit is contained in:
Jeremy Neander 2020-03-17 11:52:10 -05:00
parent f49eb28966
commit 664090f1df
5 changed files with 112 additions and 2 deletions

View File

@ -233,6 +233,11 @@ class GradebooksController < ApplicationController
@course_is_concluded = @context.completed?
@post_grades_tools = post_grades_tools
# Optimize initial data loading
if Account.site_admin.feature_enabled?(:prefetch_gradebook_user_ids)
prefetch_xhr(user_ids_course_gradebook_url(@context), id: 'user_ids')
end
render_gradebook
end
end
@ -339,6 +344,8 @@ class GradebooksController < ApplicationController
last_exported_attachment = @last_exported_gradebook_csv.try(:attachment)
grading_standard = @context.grading_standard_or_default
{
prefetch_gradebook_user_ids: Account.site_admin.feature_enabled?(:prefetch_gradebook_user_ids),
GRADEBOOK_OPTIONS: {
api_max_per_page: per_page,
chunk_size: Setting.get('gradebook2.submissions_chunk_size', '10').to_i,

View File

@ -18,10 +18,25 @@
import $ from 'jquery'
import {asJson, consumePrefetchedXHR} from '@instructure/js-utils'
import NaiveRequestDispatch from '../shared/network/NaiveRequestDispatch'
import StudentContentDataLoader from './default_gradebook/DataLoader/StudentContentDataLoader'
function getStudentIds(courseId) {
if (ENV.prefetch_gradebook_user_ids) {
/*
* When user ids have been prefetched, the data is only known valid for the
* first request. Consume it by pulling it out of the prefetch store, which
* will force all subsequent requests for user ids to call through the
* network.
*/
const promise = consumePrefetchedXHR('user_ids')
if (promise) {
return asJson(promise)
}
}
const url = `/courses/${courseId}/gradebook/user_ids`
return $.ajaxJSON(url, 'GET', {})
}

View File

@ -18,6 +18,8 @@
import sinon from 'sinon'
import {clearPrefetchedXHRs, getPrefetchedXHR, setPrefetchedXHR} from '@instructure/js-utils'
import FakeServer, {
paramsFromRequest,
pathFromRequest
@ -179,11 +181,41 @@ describe('Gradebook DataLoader', () => {
})
it('resolves .gotStudentIds with the user ids', async () => {
let loadedStudentIds
await loadGradebookData()
dataLoader.gotStudentIds.then(studentIds => (loadedStudentIds = studentIds))
const loadedStudentIds = await dataLoader.gotStudentIds
expect(loadedStudentIds).toEqual({user_ids: exampleData.studentIds})
})
describe('when student ids have been prefetched', () => {
beforeEach(() => {
ENV.prefetch_gradebook_user_ids = true
const jsonString = JSON.stringify({user_ids: exampleData.studentIds})
const response = new Response(jsonString)
setPrefetchedXHR('user_ids', Promise.resolve(response))
})
afterEach(() => {
clearPrefetchedXHRs()
delete ENV.prefetch_gradebook_user_ids
})
it('does not sends the request using the given course id', async () => {
await loadGradebookData()
const requests = server.filterRequests(urls.userIds)
expect(requests).toHaveLength(0)
})
it('resolves .gotStudentIds with the user ids', async () => {
await loadGradebookData()
const loadedStudentIds = await dataLoader.gotStudentIds
expect(loadedStudentIds).toEqual({user_ids: exampleData.studentIds})
})
it('removes the prefetch request', async () => {
await loadGradebookData()
expect(getPrefetchedXHR('user_ids')).toBeUndefined()
})
})
})
describe('loading grading period assignments', () => {

View File

@ -56,3 +56,8 @@ include_speed_grader_in_assignment_header_menu:
applies_to: SiteAdmin
display_name: Include SpeedGrader in Assignment Header Menu
description: Includes a link to SpeedGrader in the assignment header menu in Gradebook.
prefetch_gradebook_user_ids:
state: hidden
applies_to: SiteAdmin
display_name: Prefetch User IDs in Gradebook
description: Load user ids upon page load so that Gradebook loads more quickly.

View File

@ -18,10 +18,61 @@
// These are helpful methods you can use along side the ruby ApplicationHelper::prefetch_xhr helper method in canvas
/**
* Retrieve the fetch response promise assigned to the given id. This will
* return `undefined` if there are no prefetched requests or there is no
* request assigned to the given id. When a request is assigned to the given
* id, it will remain stored for subsequent retrievals, if needed.
*
* @param {String} id
* @returns {Promise<Response>} fetchResponsePromise
*/
export function getPrefetchedXHR(id) {
return window.prefetched_xhrs && window.prefetched_xhrs[id]
}
/**
* Retrieve the fetch response promise assigned to the given id. This will
* return `undefined` if there are no prefetched requests or there is no
* request assigned to the given id. When a request is assigned to the given
* id, it will be removed from request storage. This is important for requests
* which are expected to return fresh results upon each request.
*
* @param {String} id
* @returns {Promise<Response>} fetchResponsePromise
*/
export function consumePrefetchedXHR(id) {
if (window.prefetched_xhrs) {
const value = window.prefetched_xhrs[id]
delete window.prefetched_xhrs[id]
return value
}
return undefined
}
/**
* Store a fetch response promise assigned to the given id. This function is
* intended for specs so that they do not need awareness of the implementation
* details for how prefetched requests are managed.
*
* @param {String} id
* @param {Promise<Response>} fetchResponsePromise
*/
export function setPrefetchedXHR(id, fetchResponsePromise) {
window.prefetched_xhrs = window.prefetched_xhrs || {}
window.prefetched_xhrs[id] = fetchResponsePromise
}
/**
* Remove all prefetched requests from storage. This function is intended for
* specs so that they can clean up after assertions without needing awareness
* of the implementation details for how prefetched requests are managed.
*/
export function clearPrefetchedXHRs() {
delete window.prefetched_xhrs
}
/**
* Transforms a `fetch` request into something that looks like an `axios` response
* with a `.data` and `.headers` property, so you can pass it to our parseLinkHeaders stuff