Extract final grade override data loading
Test plan: - Open gradebook settings and enable final grade override - Open gradebook and verify that final grade override is visible - Override final grade for a student - Open gradebook settings and disable final grade override - Verify that final grade override is not visible Refs EVAL-1934 flag=none Change-Id: If3a5c26275c4d237fbccf8b924e49025b795f06e Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/305599 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Cameron Ray <cameron.ray@instructure.com> Reviewed-by: Kai Bjorkman <kbjorkman@instructure.com> QA-Review: Cameron Ray <cameron.ray@instructure.com> Product-Review: Cameron Ray <cameron.ray@instructure.com>
This commit is contained in:
parent
b1f865bd0e
commit
4ac41dda0a
|
@ -68,58 +68,39 @@ QUnit.module('Gradebook CourseSettings', suiteHooks => {
|
|||
QUnit.module('#handleUpdated()', hooks => {
|
||||
hooks.beforeEach(() => {
|
||||
buildGradebook()
|
||||
|
||||
sinon.stub(gradebook.finalGradeOverrides, 'loadFinalGradeOverrides')
|
||||
sinon.stub(gradebook, 'updateColumns')
|
||||
})
|
||||
|
||||
QUnit.module('when "allow final grade override" becomes enabled', contextHooks => {
|
||||
contextHooks.beforeEach(() => {
|
||||
gradebook.courseSettings.setAllowFinalGradeOverride(false)
|
||||
gradebook.courseSettings.handleUpdated({
|
||||
allowFinalGradeOverride: true,
|
||||
})
|
||||
gradebook.courseSettings.handleUpdated(
|
||||
{
|
||||
allowFinalGradeOverride: true,
|
||||
},
|
||||
() => {}
|
||||
)
|
||||
})
|
||||
|
||||
test('updates columns in the Gradebook grid', () => {
|
||||
strictEqual(gradebook.updateColumns.callCount, 1)
|
||||
})
|
||||
|
||||
test('loads final grade overrides', () => {
|
||||
strictEqual(gradebook.finalGradeOverrides.loadFinalGradeOverrides.callCount, 1)
|
||||
})
|
||||
})
|
||||
|
||||
QUnit.module('when "allow final grade override" becomes disabled', contextHooks => {
|
||||
contextHooks.beforeEach(() => {
|
||||
gradebook.courseSettings.setAllowFinalGradeOverride(true)
|
||||
gradebook.courseSettings.handleUpdated({
|
||||
allowFinalGradeOverride: false,
|
||||
})
|
||||
gradebook.courseSettings.handleUpdated(
|
||||
{
|
||||
allowFinalGradeOverride: false,
|
||||
},
|
||||
() => {}
|
||||
)
|
||||
})
|
||||
|
||||
test('updates columns in the Gradebook grid', () => {
|
||||
strictEqual(gradebook.updateColumns.callCount, 1)
|
||||
})
|
||||
|
||||
test('does not load final grade overrides', () => {
|
||||
strictEqual(gradebook.finalGradeOverrides.loadFinalGradeOverrides.callCount, 0)
|
||||
})
|
||||
})
|
||||
|
||||
QUnit.module('when "allow final grade override" is not changed', contextHooks => {
|
||||
contextHooks.beforeEach(() => {
|
||||
gradebook.courseSettings.setAllowFinalGradeOverride(true)
|
||||
gradebook.courseSettings.handleUpdated({})
|
||||
})
|
||||
|
||||
test('does not update columns in the Gradebook grid', () => {
|
||||
strictEqual(gradebook.updateColumns.callCount, 0)
|
||||
})
|
||||
|
||||
test('does not load final grade overrides', () => {
|
||||
strictEqual(gradebook.finalGradeOverrides.loadFinalGradeOverrides.callCount, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -273,36 +273,6 @@ QUnit.module('Gradebook > DataLoader > StudentContentDataLoader', suiteHooks =>
|
|||
})
|
||||
})
|
||||
|
||||
QUnit.module('loading final grade overrides', () => {
|
||||
test('optionally requests final grade overrides', async () => {
|
||||
await load()
|
||||
strictEqual(FinalGradeOverrideApi.getFinalGradeOverrides.callCount, 1)
|
||||
})
|
||||
|
||||
test('optionally does not request final grade overrides', async () => {
|
||||
gradebook.courseSettings.setAllowFinalGradeOverride(false)
|
||||
await load()
|
||||
strictEqual(FinalGradeOverrideApi.getFinalGradeOverrides.callCount, 0)
|
||||
})
|
||||
|
||||
test('uses the given course id when loading final grade overrides', async () => {
|
||||
await load()
|
||||
const [courseId] = FinalGradeOverrideApi.getFinalGradeOverrides.lastCall.args
|
||||
strictEqual(courseId, '1201')
|
||||
})
|
||||
|
||||
test('updates Gradebook when the final grade overrides have loaded', async () => {
|
||||
await load()
|
||||
strictEqual(gradebook.finalGradeOverrides.setGrades.callCount, 1)
|
||||
})
|
||||
|
||||
test('updates Gradebook with the loaded final grade overrides', async () => {
|
||||
await load()
|
||||
const [finalGradeOverrides] = gradebook.finalGradeOverrides.setGrades.lastCall.args
|
||||
deepEqual(finalGradeOverrides, exampleData.finalGradeOverrides)
|
||||
})
|
||||
})
|
||||
|
||||
QUnit.module('when submissions return before related students', contextHooks => {
|
||||
let events
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export default class CourseSettings {
|
|||
this._settings.allowFinalGradeOverride = allow
|
||||
}
|
||||
|
||||
handleUpdated(settings: Settings) {
|
||||
handleUpdated(settings: Settings, fetchFinalGradeOverrides: () => Promise<void>) {
|
||||
const previousSettings = {...this._settings}
|
||||
this._settings = {
|
||||
...this._settings,
|
||||
|
@ -53,7 +53,7 @@ export default class CourseSettings {
|
|||
this._gradebook.updateColumns()
|
||||
|
||||
if (this._settings.allowFinalGradeOverride) {
|
||||
this._gradebook.finalGradeOverrides?.loadFinalGradeOverrides()
|
||||
fetchFinalGradeOverrides()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,20 @@ import {useScope as useI18nScope} from '@canvas/i18n'
|
|||
import type Gradebook from '../Gradebook'
|
||||
import type {RequestDispatch} from '@canvas/network'
|
||||
import type PerformanceControls from '../PerformanceControls'
|
||||
import type {Student} from '../../../../../api.d'
|
||||
|
||||
import {showFlashAlert} from '@canvas/alerts/react/FlashAlert'
|
||||
|
||||
const I18n = useI18nScope('gradebook')
|
||||
|
||||
type Options = {
|
||||
dispatch: RequestDispatch
|
||||
gradebook: Gradebook
|
||||
submissionsChunkSize: number
|
||||
courseId: string
|
||||
submissionsPerPage: number
|
||||
}
|
||||
|
||||
const submissionsParams = {
|
||||
exclude_response_fields: ['preview_url'],
|
||||
grouped: 1,
|
||||
|
@ -75,13 +84,7 @@ function flashSubmissionLoadError(): void {
|
|||
|
||||
function ignoreFailure() {}
|
||||
|
||||
function getStudentsChunk(
|
||||
courseId: string,
|
||||
studentIds: string[],
|
||||
options: {
|
||||
dispatch: RequestDispatch
|
||||
}
|
||||
) {
|
||||
function getStudentsChunk(courseId: string, studentIds: string[], options: Options) {
|
||||
const url = `/api/v1/courses/${courseId}/users`
|
||||
const params = {
|
||||
enrollment_state: ['active', 'completed', 'inactive', 'invited'],
|
||||
|
@ -91,17 +94,22 @@ function getStudentsChunk(
|
|||
user_ids: studentIds,
|
||||
}
|
||||
|
||||
return options.dispatch.getJSON(url, params)
|
||||
return options.dispatch.getJSON<Student[]>(url, params)
|
||||
}
|
||||
|
||||
function getSubmissionsForStudents(options, studentIds, allEnqueued, dispatch) {
|
||||
function getSubmissionsForStudents(
|
||||
options: Options,
|
||||
studentIds: string[],
|
||||
allEnqueued,
|
||||
dispatch: RequestDispatch
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {courseId, submissionsPerPage} = options
|
||||
const url = `/api/v1/courses/${courseId}/students/submissions`
|
||||
const params = {...submissionsParams, student_ids: studentIds, per_page: submissionsPerPage}
|
||||
|
||||
dispatch
|
||||
.getDepaginated(url, params, undefined, allEnqueued)
|
||||
.getDepaginated<Student[]>(url, params, undefined, allEnqueued)
|
||||
.then(resolve)
|
||||
.catch(() => {
|
||||
flashSubmissionLoadError()
|
||||
|
@ -110,15 +118,7 @@ function getSubmissionsForStudents(options, studentIds, allEnqueued, dispatch) {
|
|||
})
|
||||
}
|
||||
|
||||
function getContentForStudentIdChunk(
|
||||
studentIds: string[],
|
||||
options: {
|
||||
dispatch: RequestDispatch
|
||||
gradebook: Gradebook
|
||||
submissionsChunkSize: number
|
||||
courseId: string
|
||||
}
|
||||
) {
|
||||
function getContentForStudentIdChunk(studentIds: string[], options: Options) {
|
||||
const {dispatch, gradebook, submissionsChunkSize} = options
|
||||
|
||||
let resolveEnqueued
|
||||
|
@ -230,15 +230,8 @@ export default class StudentContentDataLoader {
|
|||
getNextChunk()
|
||||
})
|
||||
.then(() => {
|
||||
const {courseSettings, finalGradeOverrides} = gradebook
|
||||
|
||||
let finalGradeOverridesRequest
|
||||
if (courseSettings.allowFinalGradeOverride && finalGradeOverrides) {
|
||||
finalGradeOverridesRequest = finalGradeOverrides.loadFinalGradeOverrides()
|
||||
}
|
||||
|
||||
// wait for all student, submission, and final grade override requests to return
|
||||
return Promise.all([...studentRequests, ...submissionRequests, finalGradeOverridesRequest])
|
||||
// wait for all student, submission requests to return
|
||||
return Promise.all([...studentRequests, ...submissionRequests])
|
||||
})
|
||||
.then(() => {
|
||||
gradebook.updateStudentsLoaded(true)
|
||||
|
|
|
@ -428,72 +428,4 @@ describe('Gradebook FinalGradeOverrides', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#loadFinalGradeOverrides()', () => {
|
||||
beforeEach(() => {
|
||||
grades = {
|
||||
1101: {
|
||||
courseGrade: {
|
||||
percentage: 88.1,
|
||||
},
|
||||
},
|
||||
1102: {
|
||||
courseGrade: {
|
||||
percentage: 91.1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sinon
|
||||
.stub(FinalGradeOverrideApi, 'getFinalGradeOverrides')
|
||||
.returns(Promise.resolve({finalGradeOverrides: grades}))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
FinalGradeOverrideApi.getFinalGradeOverrides.restore()
|
||||
})
|
||||
|
||||
it('optionally requests final grade overrides', async () => {
|
||||
await finalGradeOverrides.loadFinalGradeOverrides()
|
||||
expect(FinalGradeOverrideApi.getFinalGradeOverrides.callCount).toEqual(1)
|
||||
})
|
||||
|
||||
it('uses the course id from Gradebook when loading final grade overrides', async () => {
|
||||
await finalGradeOverrides.loadFinalGradeOverrides()
|
||||
const [courseId] = FinalGradeOverrideApi.getFinalGradeOverrides.lastCall.args
|
||||
expect(courseId).toEqual('1201')
|
||||
})
|
||||
|
||||
it('stores the given final grade overrides in the Gradebook', async () => {
|
||||
await finalGradeOverrides.loadFinalGradeOverrides()
|
||||
expect(finalGradeOverrides.getGradeForUser('1101')).toEqual(grades[1101].courseGrade)
|
||||
})
|
||||
|
||||
it('updates row cells for each related student', async () => {
|
||||
await finalGradeOverrides.loadFinalGradeOverrides()
|
||||
expect(gradebook.gradebookGrid.updateRowCell.callCount).toEqual(2)
|
||||
})
|
||||
|
||||
it('includes the user id when updating column cells', async () => {
|
||||
await finalGradeOverrides.loadFinalGradeOverrides()
|
||||
const calls = [0, 1].map(index => gradebook.gradebookGrid.updateRowCell.getCall(index))
|
||||
const studentIds = calls.map(call => call.args[0])
|
||||
expect(studentIds).toEqual(['1101', '1102'])
|
||||
})
|
||||
|
||||
it('includes the column id when updating column cells', async () => {
|
||||
await finalGradeOverrides.loadFinalGradeOverrides()
|
||||
const calls = [0, 1].map(index => gradebook.gradebookGrid.updateRowCell.getCall(index))
|
||||
const columnIds = calls.map(call => call.args[1])
|
||||
expect(columnIds).toEqual(['total_grade_override', 'total_grade_override'])
|
||||
})
|
||||
|
||||
it('updates row cells after storing final grade overrides', async () => {
|
||||
gradebook.gradebookGrid.updateRowCell.callsFake(() => {
|
||||
// final grade overrides will have already been updated by this time
|
||||
expect(finalGradeOverrides.getGradeForUser('1101')).toEqual(grades[1101].courseGrade)
|
||||
})
|
||||
await finalGradeOverrides.loadFinalGradeOverrides()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -18,10 +18,7 @@
|
|||
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
import {showFlashAlert} from '@canvas/alerts/react/FlashAlert'
|
||||
import {
|
||||
getFinalGradeOverrides,
|
||||
updateFinalGradeOverride,
|
||||
} from '@canvas/grading/FinalGradeOverrideApi'
|
||||
import {updateFinalGradeOverride} from '@canvas/grading/FinalGradeOverrideApi'
|
||||
import FinalGradeOverrideDatastore from './FinalGradeOverrideDatastore'
|
||||
import type Gradebook from '../Gradebook'
|
||||
|
||||
|
@ -104,12 +101,4 @@ export default class FinalGradeOverrides {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
loadFinalGradeOverrides() {
|
||||
return getFinalGradeOverrides(this._gradebook.course.id).then(({finalGradeOverrides}) => {
|
||||
if (finalGradeOverrides) {
|
||||
this.setGrades(finalGradeOverrides)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ import type {
|
|||
PendingGradeInfo,
|
||||
SubmissionFilterValue,
|
||||
} from './gradebook.d'
|
||||
import type {CamelizedGradingPeriodSet} from '@canvas/grading/grading.d'
|
||||
import type {CamelizedGradingPeriodSet, FinalGradeOverrideMap} from '@canvas/grading/grading.d'
|
||||
import type {
|
||||
GridColumn,
|
||||
GridData,
|
||||
|
@ -232,9 +232,11 @@ export type GradebookProps = {
|
|||
currentUserId: string
|
||||
customColumns: CustomColumn[]
|
||||
dispatch: RequestDispatch
|
||||
fetchFinalGradeOverrides: () => Promise<void>
|
||||
fetchGradingPeriodAssignments: () => Promise<GradingPeriodAssignmentMap>
|
||||
fetchStudentIds: () => Promise<string[]>
|
||||
filterNavNode: HTMLElement
|
||||
finalGradeOverrides: FinalGradeOverrideMap
|
||||
flashAlerts: FlashAlertType[]
|
||||
flashMessageContainer: HTMLElement
|
||||
gradebookEnv: GradebookOptions
|
||||
|
@ -2053,7 +2055,7 @@ class Gradebook extends React.Component<GradebookProps, GradebookState> {
|
|||
return this.gradebookSettingsModalButton.current?.focus()
|
||||
},
|
||||
onCourseSettingsUpdated: settings => {
|
||||
return this.courseSettings.handleUpdated(settings)
|
||||
return this.courseSettings.handleUpdated(settings, this.props.fetchFinalGradeOverrides)
|
||||
},
|
||||
onLatePolicyUpdate: this.onLatePolicyUpdate,
|
||||
postPolicies: this.postPolicies,
|
||||
|
@ -4605,6 +4607,14 @@ class Gradebook extends React.Component<GradebookProps, GradebookState> {
|
|||
)
|
||||
}
|
||||
|
||||
// final grade overrides
|
||||
if (
|
||||
prevProps.finalGradeOverrides !== this.props.finalGradeOverrides &&
|
||||
this.props.finalGradeOverrides
|
||||
) {
|
||||
this.finalGradeOverrides?.setGrades(this.props.finalGradeOverrides)
|
||||
}
|
||||
|
||||
// modules
|
||||
if (
|
||||
prevProps.isModulesLoading !== this.props.isModulesLoading &&
|
||||
|
|
|
@ -68,6 +68,9 @@ export default function GradebookData(props: Props) {
|
|||
const isCustomColumnsLoading = useStore(state => state.isCustomColumnsLoading)
|
||||
const fetchCustomColumns = useStore(state => state.fetchCustomColumns)
|
||||
|
||||
const finalGradeOverrides = useStore(state => state.finalGradeOverrides)
|
||||
const fetchFinalGradeOverrides = useStore(state => state.fetchFinalGradeOverrides)
|
||||
|
||||
const studentIds = useStore(state => state.studentIds, shallow)
|
||||
const isStudentIdsLoading = useStore(state => state.isStudentIdsLoading)
|
||||
const fetchStudentIds = useStore(state => state.fetchStudentIds)
|
||||
|
@ -90,6 +93,7 @@ export default function GradebookData(props: Props) {
|
|||
dispatch: dispatch.current,
|
||||
performanceControls: performanceControls.current,
|
||||
hasModules: props.gradebookEnv.has_modules,
|
||||
allowFinalGradeOverride: props.gradebookEnv.course_settings.allow_final_grade_override,
|
||||
})
|
||||
initializeAppliedFilters(
|
||||
props.gradebookEnv.settings.filter_rows_by || {},
|
||||
|
@ -101,6 +105,7 @@ export default function GradebookData(props: Props) {
|
|||
props.gradebookEnv.settings.filter_rows_by,
|
||||
props.gradebookEnv.settings.filter_columns_by,
|
||||
props.gradebookEnv.has_modules,
|
||||
props.gradebookEnv.course_settings.allow_final_grade_override,
|
||||
initializeAppliedFilters,
|
||||
])
|
||||
|
||||
|
@ -112,16 +117,21 @@ export default function GradebookData(props: Props) {
|
|||
if (props.gradebookEnv.has_modules) {
|
||||
fetchModules()
|
||||
}
|
||||
if (props.gradebookEnv.course_settings.allow_final_grade_override) {
|
||||
fetchFinalGradeOverrides()
|
||||
}
|
||||
fetchCustomColumns()
|
||||
}, [
|
||||
fetchFilters,
|
||||
fetchModules,
|
||||
fetchCustomColumns,
|
||||
fetchFilters,
|
||||
fetchFinalGradeOverrides,
|
||||
fetchModules,
|
||||
initializeStagedFilters,
|
||||
props.gradebookEnv.course_settings.allow_final_grade_override,
|
||||
props.gradebookEnv.enhanced_gradebook_filters,
|
||||
props.gradebookEnv.has_modules,
|
||||
initializeStagedFilters,
|
||||
props.gradebookEnv.settings.filter_rows_by,
|
||||
props.gradebookEnv.settings.filter_columns_by,
|
||||
props.gradebookEnv.settings.filter_rows_by,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -152,6 +162,7 @@ export default function GradebookData(props: Props) {
|
|||
{...props}
|
||||
appliedFilters={appliedFilters}
|
||||
customColumns={customColumns}
|
||||
fetchFinalGradeOverrides={fetchFinalGradeOverrides}
|
||||
fetchGradingPeriodAssignments={fetchGradingPeriodAssignments}
|
||||
fetchStudentIds={fetchStudentIds}
|
||||
flashAlerts={flashMessages}
|
||||
|
@ -162,6 +173,7 @@ export default function GradebookData(props: Props) {
|
|||
isGradingPeriodAssignmentsLoading={isGradingPeriodAssignmentsLoading}
|
||||
isModulesLoading={isModulesLoading}
|
||||
isStudentIdsLoading={isStudentIdsLoading}
|
||||
finalGradeOverrides={finalGradeOverrides}
|
||||
modules={modules}
|
||||
recentlyLoadedAssignmentGroups={recentlyLoadedAssignmentGroups}
|
||||
studentIds={studentIds}
|
||||
|
|
|
@ -29,6 +29,9 @@ const defaultProps = {
|
|||
gradebookEnv: {
|
||||
context_id: '1',
|
||||
enhanced_gradebook_filters: false,
|
||||
course_settings: {
|
||||
allow_final_grade_override: true,
|
||||
},
|
||||
settings: {
|
||||
filter_rows_by: {
|
||||
section_id: null,
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute test and/or modify test under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that test will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {NetworkFake} from '@canvas/network/NetworkFake/index'
|
||||
import store from '../index'
|
||||
|
||||
describe('Gradebook > store > fetchFinalGradeOverrides', () => {
|
||||
const url = '/courses/0/gradebook/final_grade_overrides'
|
||||
|
||||
let exampleData
|
||||
let network
|
||||
|
||||
beforeEach(() => {
|
||||
exampleData = {
|
||||
finalGradeOverrides: {
|
||||
'1': {
|
||||
courseGrade: {
|
||||
percentage: 88.1,
|
||||
},
|
||||
gradingPeriodGrades: {
|
||||
'2': {
|
||||
percentage: 90,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('#fetchFinalGradeOverrides()', () => {
|
||||
beforeEach(() => {
|
||||
network = new NetworkFake()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
network.restore()
|
||||
})
|
||||
|
||||
function resolveRequest() {
|
||||
const [request] = getRequests()
|
||||
request.response.setJson({
|
||||
final_grade_overrides: {
|
||||
'1': {
|
||||
course_grade: {
|
||||
percentage: 88.1,
|
||||
},
|
||||
grading_period_grades: {
|
||||
'2': {
|
||||
percentage: 90,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
request.response.send()
|
||||
}
|
||||
|
||||
function getRequests() {
|
||||
return network.getRequests(request => request.url === url)
|
||||
}
|
||||
|
||||
test('sends the request', async () => {
|
||||
store.getState().fetchFinalGradeOverrides()
|
||||
await network.allRequestsReady()
|
||||
const requests = getRequests()
|
||||
expect(requests.length).toStrictEqual(1)
|
||||
})
|
||||
|
||||
test('saves final grade overrides to the store', async () => {
|
||||
const promise = store.getState().fetchFinalGradeOverrides()
|
||||
await network.allRequestsReady()
|
||||
resolveRequest()
|
||||
await promise
|
||||
expect(store.getState().finalGradeOverrides).toStrictEqual(exampleData.finalGradeOverrides)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {GetState, SetState} from 'zustand'
|
||||
import {getFinalGradeOverrides} from '@canvas/grading/FinalGradeOverrideApi'
|
||||
import type {GradebookStore} from './index'
|
||||
import type {FinalGradeOverrideMap} from '@canvas/grading/grading.d'
|
||||
|
||||
export type FinalGradeOverrideState = {
|
||||
allowFinalGradeOverride: boolean
|
||||
areFinalGradeOverridesLoaded: boolean
|
||||
finalGradeOverrides: FinalGradeOverrideMap
|
||||
fetchFinalGradeOverrides: () => Promise<void>
|
||||
}
|
||||
|
||||
export default (
|
||||
set: SetState<GradebookStore>,
|
||||
get: GetState<GradebookStore>
|
||||
): FinalGradeOverrideState => ({
|
||||
allowFinalGradeOverride: false,
|
||||
|
||||
finalGradeOverrides: {},
|
||||
|
||||
areFinalGradeOverridesLoaded: false,
|
||||
|
||||
fetchFinalGradeOverrides: (): Promise<void> => {
|
||||
return getFinalGradeOverrides(get().courseId).then(data => {
|
||||
if (data?.finalGradeOverrides) {
|
||||
set({
|
||||
finalGradeOverrides: data?.finalGradeOverrides,
|
||||
areFinalGradeOverridesLoaded: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
|
@ -22,6 +22,7 @@ import modules, {ModulesState} from './modulesState'
|
|||
import students, {StudentsState} from './studentsState'
|
||||
import assignments, {AssignmentsState} from './assignmentsState'
|
||||
import customColumns, {CustomColumnsState} from './customColumnsState'
|
||||
import finalGradeOverrides, {FinalGradeOverrideState} from './finalGradeOverrides'
|
||||
import {RequestDispatch} from '@canvas/network'
|
||||
import PerformanceControls from '../PerformanceControls'
|
||||
import type {FlashMessage} from '../gradebook.d'
|
||||
|
@ -44,7 +45,8 @@ export type GradebookStore = State &
|
|||
FiltersState &
|
||||
ModulesState &
|
||||
StudentsState &
|
||||
AssignmentsState
|
||||
AssignmentsState &
|
||||
FinalGradeOverrideState
|
||||
|
||||
const store = create<GradebookStore>((set, get) => ({
|
||||
performanceControls: defaultPerformanceControls,
|
||||
|
@ -64,6 +66,8 @@ const store = create<GradebookStore>((set, get) => ({
|
|||
...students(set, get),
|
||||
|
||||
...assignments(set, get),
|
||||
|
||||
...finalGradeOverrides(set, get),
|
||||
}))
|
||||
|
||||
export default store
|
||||
|
|
|
@ -19,13 +19,15 @@
|
|||
import axios from '@canvas/axios'
|
||||
import {camelize} from 'convert-case'
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
|
||||
import {createClient, gql} from '@canvas/apollo'
|
||||
import {showFlashAlert} from '@canvas/alerts/react/FlashAlert'
|
||||
import type {FinalGradeOverrideMap} from './grading.d'
|
||||
|
||||
const I18n = useI18nScope('finalGradeOverrideApi')
|
||||
|
||||
export function getFinalGradeOverrides(courseId: string) {
|
||||
export function getFinalGradeOverrides(
|
||||
courseId: string
|
||||
): Promise<void | {finalGradeOverrides: FinalGradeOverrideMap}> {
|
||||
const url = `/courses/${courseId}/gradebook/final_grade_overrides`
|
||||
|
||||
return axios
|
||||
|
@ -36,7 +38,10 @@ export function getFinalGradeOverrides(courseId: string) {
|
|||
for (const studentId in response.data.final_grade_overrides) {
|
||||
const responseOverrides = response.data.final_grade_overrides[studentId]
|
||||
const studentOverrides: {
|
||||
courseGrade?: string
|
||||
courseGrade?: {
|
||||
percentage: number | null
|
||||
schemeKey: string | null
|
||||
}
|
||||
gradingPeriodGrades?: {[gradingPeriodId: string]: string}
|
||||
} = (data.finalGradeOverrides[studentId] = {})
|
||||
|
||||
|
|
|
@ -220,3 +220,14 @@ export type CamelizedSubmissionWithOriginalityReport = CamelizedSubmission & {
|
|||
turnitinData?: PlagiarismDataMap
|
||||
vericiteData?: {provider: 'vericite'} & PlagiarismDataMap
|
||||
}
|
||||
|
||||
export type FinalGradeOverride = {
|
||||
courseGrade?: string
|
||||
gradingPeriodGrades?: {
|
||||
[gradingPeriodId: string]: string
|
||||
}
|
||||
}
|
||||
|
||||
export type FinalGradeOverrideMap = {
|
||||
[userId: string]: FinalGradeOverride
|
||||
}
|
||||
|
|
|
@ -130,9 +130,9 @@ export default class RequestDispatch {
|
|||
return request.deferred.promise
|
||||
}
|
||||
|
||||
getJSON(url: string, params?) {
|
||||
getJSON<T>(url: string, params?) {
|
||||
const request = {
|
||||
deferred: deferPromise(),
|
||||
deferred: deferPromise<T>(),
|
||||
start: () => {},
|
||||
active: false,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue