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:
parent
f49eb28966
commit
664090f1df
|
@ -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,
|
||||
|
|
|
@ -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', {})
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue