add View Ungraded as 0 view option to Gradebook

closes EVAL-622

Test Plan
- Enable the Feature Flag as a site admin.
- Verify that the Gradebook View menu includes "View Ungraded as 0".
- Select "View Ungraded as 0". Verify that this selection persists
  across page reloads of Gradebook.
- De-select "View Ungraded as 0". Verify that this selection persists
  across page reloads of Gradebook.
- Disable the Feature Flag as a site admin. Verify that the Gradebook
  View menu no longer includes "View Ungraded as 0".

Change-Id: I49792de1408a428955f42f272604a87b42d6d8d9
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/248205
Reviewed-by: Adrian Packel <apackel@instructure.com>
Reviewed-by: Kai Bjorkman <kbjorkman@instructure.com>
Reviewed-by: Spencer Olson <solson@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Product-Review: Syed Hussain <shussain@instructure.com>
QA-Review: Gary Mei <gmei@instructure.com>
This commit is contained in:
Gary Mei 2020-09-22 10:55:34 -05:00
parent 558dfc18c1
commit 1adcb0bc1e
9 changed files with 159 additions and 6 deletions

View File

@ -141,7 +141,8 @@ export default do ->
getCourseFeaturesFromOptions = (options) ->
{
finalGradeOverrideEnabled: options.final_grade_override_enabled
finalGradeOverrideEnabled: options.final_grade_override_enabled,
allowViewUngradedAsZero: !!options.allow_view_ungraded_as_zero
}
## Gradebook Display Settings
@ -196,6 +197,7 @@ export default do ->
commentsLoaded: false
commentsUpdating: false
editedCommentId: null
viewUngradedAsZero: settings.view_ungraded_as_zero == 'true'
}
## Gradebook Application State
@ -1362,6 +1364,9 @@ export default do ->
onSelectShowUnpublishedAssignments: @toggleUnpublishedAssignments
onSelectShowStatusesModal: =>
@statusesModal.open()
onSelectViewUngradedAsZero: @toggleViewUngradedAsZero
viewUngradedAsZero: @gridDisplaySettings.viewUngradedAsZero
allowViewUngradedAsZero: @courseFeatures.allowViewUngradedAsZero
renderViewOptionsMenu: =>
mountPoint = document.querySelector("[data-component='ViewOptionsMenu']")
@ -1848,6 +1853,7 @@ export default do ->
studentColumnDisplayAs = @getSelectedPrimaryInfo(),
studentColumnSecondaryInfo = @getSelectedSecondaryInfo(),
sortRowsBy = @getSortRowsBySetting(),
viewUngradedAsZero = @gridDisplaySettings.viewUngradedAsZero,
colors = @getGridColors()
} = {}, successFn, errorFn) =>
selectedViewOptionsFilters.push('') unless selectedViewOptionsFilters.length > 0
@ -1866,6 +1872,7 @@ export default do ->
sort_rows_by_column_id: sortRowsBy.columnId
sort_rows_by_setting_key: sortRowsBy.settingKey
sort_rows_by_direction: sortRowsBy.direction
view_ungraded_as_zero: viewUngradedAsZero
colors: colors
$.ajaxJSON(@options.settings_update_url, 'PUT', data, successFn, errorFn)
@ -2484,6 +2491,18 @@ export default do ->
toggleableAction
)
toggleViewUngradedAsZero: =>
toggleableAction = =>
@gridDisplaySettings.viewUngradedAsZero = !@gridDisplaySettings.viewUngradedAsZero
@updateColumnsAndRenderViewOptionsMenu()
toggleableAction()
@saveSettings(
{ viewUngradedAsZero: @gridDisplaySettings.viewUngradedAsZero },
() =>, # on success, do nothing since the render happened earlier
toggleableAction
)
setAssignmentsLoaded: (loaded) =>
@contentLoadStates.assignmentsLoaded = loaded

View File

@ -56,6 +56,7 @@ class GradebookSettingsController < ApplicationController
:sort_rows_by_column_id,
:sort_rows_by_setting_key,
:sort_rows_by_direction,
:view_ungraded_as_zero,
{ colors: [ :late, :missing, :resubmitted, :dropped, :excused ] }
)
gradebook_settings_params[:enter_grades_as] = params[:gradebook_settings][:enter_grades_as]

View File

@ -482,7 +482,8 @@ class GradebooksController < ApplicationController
submissions_url: api_v1_course_student_submissions_url(@context, grouped: '1'),
teacher_notes: teacher_notes && custom_gradebook_column_json(teacher_notes, @current_user, session),
user_asset_string: @current_user&.asset_string,
version: params.fetch(:version, nil)
version: params.fetch(:version, nil),
allow_view_ungraded_as_zero: Account.site_admin.feature_enabled?(:view_ungraded_as_zero)
}
js_env({

View File

@ -65,7 +65,10 @@ class ViewOptionsMenu extends React.Component {
}).isRequired,
onSelectShowStatusesModal: func.isRequired,
showUnpublishedAssignments: bool.isRequired,
onSelectShowUnpublishedAssignments: func.isRequired
onSelectShowUnpublishedAssignments: func.isRequired,
onSelectViewUngradedAsZero: func.isRequired,
viewUngradedAsZero: bool.isRequired,
allowViewUngradedAsZero: bool.isRequired
}
onFilterSelect = (_event, filters) => {
@ -215,6 +218,17 @@ class ViewOptionsMenu extends React.Component {
{I18n.t('Statuses…')}
</Menu.Item>
{this.props.allowViewUngradedAsZero && (
<Menu.Group label={I18n.t('View Options')}>
<Menu.Item
onSelect={this.props.onSelectViewUngradedAsZero}
selected={this.props.viewUngradedAsZero}
>
{I18n.t('View Ungraded as 0')}
</Menu.Item>
</Menu.Group>
)}
<Menu.Separator />
<Menu.Group allowMultiple label={I18n.t('Columns')}>

View File

@ -63,3 +63,9 @@ responsive_student_grades_page:
applies_to: RootAccount
display_name: Responsive Student Grades Page
description: Makes the student grades page responsive.
view_ungraded_as_zero:
state: hidden
applies_to: SiteAdmin
display_name: View Ungraded as Zero View in Gradebook
description: The Gradebook will factor in ungraded submissions as if they were given a score of zero for
calculations. This is just a view for the teacher, and does not affect actual scoring.

View File

@ -50,6 +50,7 @@ RSpec.describe GradebookSettingsController, type: :controller do
"sort_rows_by_column_id" => "student",
"sort_rows_by_setting_key" => "sortable_name",
"sort_rows_by_direction" => "descending",
"view_ungraded_as_zero" => "true",
"colors" => {
"late" => "#000000",
"missing" => "#000001",
@ -96,8 +97,9 @@ RSpec.describe GradebookSettingsController, type: :controller do
it { is_expected.to include 'sort_rows_by_column_id' => 'student' }
it { is_expected.to include 'sort_rows_by_setting_key' => 'sortable_name' }
it { is_expected.to include 'sort_rows_by_direction' => 'descending' }
it { is_expected.to include 'view_ungraded_as_zero' => 'true' }
it { is_expected.not_to include 'colors' }
it { is_expected.to have(12).items } # ensure we add specs for new additions
it { is_expected.to have(13).items } # ensure we add specs for new additions
context 'colors' do
subject { json_parse.fetch('gradebook_settings').fetch('colors') }

View File

@ -843,6 +843,19 @@ describe GradebooksController do
end
end
describe "view ungraded as zero" do
it "sets allow_view_ungraded_as_zero in the ENV to true if the feature is enabled" do
Account.site_admin.enable_feature!(:view_ungraded_as_zero)
get :show, params: { course_id: @course.id }
expect(gradebook_options.fetch(:allow_view_ungraded_as_zero)).to be true
end
it "sets allow_view_ungraded_as_zero in the ENV to false if the feature is not enabled" do
get :show, params: { course_id: @course.id }
expect(gradebook_options.fetch(:allow_view_ungraded_as_zero)).to be false
end
end
describe "dataloader_improvements" do
# TODO: remove this entire block with TALLY-831

View File

@ -2436,6 +2436,32 @@ QUnit.module('Gradebook#getViewOptionsMenuProps', () => {
const {showUnpublishedAssignments} = createGradebook({settings}).getViewOptionsMenuProps()
strictEqual(showUnpublishedAssignments, false)
})
test('viewUngradedAsZero is true when settings.view_ungraded_as_zero is "true"', () => {
const settings = {view_ungraded_as_zero: 'true'}
const {viewUngradedAsZero} = createGradebook({settings}).getViewOptionsMenuProps()
strictEqual(viewUngradedAsZero, true)
})
test('viewUngradedAsZero is false when settings.view_ungraded_as_zero is "false"', () => {
const settings = {view_ungraded_as_zero: 'false'}
const {viewUngradedAsZero} = createGradebook({settings}).getViewOptionsMenuProps()
strictEqual(viewUngradedAsZero, false)
})
test('allowViewUngradedAsZero is true when allow_view_ungraded_as_zero is true', () => {
const {allowViewUngradedAsZero} = createGradebook({
allow_view_ungraded_as_zero: true
}).getViewOptionsMenuProps()
strictEqual(allowViewUngradedAsZero, true)
})
test('allowViewUngradedAsZero is false when allow_view_ungraded_as_zero is false', () => {
const {allowViewUngradedAsZero} = createGradebook({
allow_view_ungraded_as_zero: false
}).getViewOptionsMenuProps()
strictEqual(allowViewUngradedAsZero, false)
})
})
QUnit.module('Gradebook#createTeacherNotes', {

View File

@ -45,7 +45,10 @@ function defaultProps({props, filterSettings} = {}) {
},
onSelectShowStatusesModal() {},
onSelectShowUnpublishedAssignments() {},
onSelectViewUngradedAsZero() {},
showUnpublishedAssignments: false,
viewUngradedAsZero: false,
allowViewUngradedAsZero: false,
finalGradeOverrideEnabled: false,
teacherNotes: {
disabled: false,
@ -234,6 +237,74 @@ test('onSelect is called with list of selected filters upon any selection change
strictEqual(onSelect.calledWithExactly(['assignmentGroups', 'sections', 'gradingPeriods']), true)
})
QUnit.module('ViewOptionsMenu - view ungraded as 0', {
mountViewOptionsMenu({
viewUngradedAsZero = false,
allowViewUngradedAsZero = false,
onSelectViewUngradedAsZero = () => {}
} = {}) {
const props = defaultProps()
return mount(
<ViewOptionsMenu
{...props}
viewUngradedAsZero={viewUngradedAsZero}
allowViewUngradedAsZero={allowViewUngradedAsZero}
onSelectViewUngradedAsZero={onSelectViewUngradedAsZero}
/>
)
},
teardown() {
if (this.wrapper) {
this.wrapper.unmount()
}
}
})
test('"View Ungraded As 0" is shown when allowViewUngradedAsZero is true', function() {
this.wrapper = this.mountViewOptionsMenu({allowViewUngradedAsZero: true})
this.wrapper.find('button').simulate('click')
ok(getMenuItem(this.wrapper.instance().menuContent, 'View Ungraded as 0'))
})
test('"View Ungraded As 0" is not shown when allowViewUngradedAsZero is false', function() {
this.wrapper = this.mountViewOptionsMenu({allowViewUngradedAsZero: false})
this.wrapper.find('button').simulate('click')
notOk(getMenuItem(this.wrapper.instance().menuContent, 'View Ungraded as 0'))
})
test('"View Ungraded As 0" is selected when viewUngradedAsZero is true', function() {
this.wrapper = this.mountViewOptionsMenu({
viewUngradedAsZero: true,
allowViewUngradedAsZero: true
})
this.wrapper.find('button').simulate('click')
const menuItem = getMenuItem(this.wrapper.instance().menuContent, 'View Ungraded as 0')
strictEqual(menuItem.getAttribute('aria-checked'), 'true')
})
test('"View Ungraded As 0" is not selected when viewUngradedAsZero is false', function() {
this.wrapper = this.mountViewOptionsMenu({
viewUngradedAsZero: false,
allowViewUngradedAsZero: true
})
this.wrapper.find('button').simulate('click')
const menuItem = getMenuItem(this.wrapper.instance().menuContent, 'View Ungraded as 0')
strictEqual(menuItem.getAttribute('aria-checked'), 'false')
})
test('onSelectViewUngradedAsZero is called when selected', function() {
const onSelectViewUngradedAsZeroStub = sinon.stub()
this.wrapper = this.mountViewOptionsMenu({
viewUngradedAsZero: false,
allowViewUngradedAsZero: true,
onSelectViewUngradedAsZero: onSelectViewUngradedAsZeroStub
})
this.wrapper.find('button').simulate('click')
getMenuItem(this.wrapper.instance().menuContent, 'View Ungraded as 0').click()
strictEqual(onSelectViewUngradedAsZeroStub.callCount, 1)
})
QUnit.module('ViewOptionsMenu - unpublished assignments', {
mountViewOptionsMenu({
showUnpublishedAssignments = true,
@ -345,7 +416,7 @@ test('Due Date - Oldest to Newest is selected when criterion is due_date and dir
strictEqual(menuItem.getAttribute('aria-checked'), 'true')
})
test('Due Date - Oldest to Newest is selected when criterion is due_date and direction is ascending', function() {
test('Due Date - Newest to Oldest is selected when criterion is due_date and direction is descending', function() {
const wrapper = mountAndOpenOptions(this.props('due_date', 'descending'))
const menuItem = getMenuItem(
wrapper.instance().menuContent,
@ -365,7 +436,7 @@ test('Points - Lowest to Highest is selected when criterion is points and direct
strictEqual(menuItem.getAttribute('aria-checked'), 'true')
})
test('Points - Lowest to Highest is selected when criterion is points and direction is ascending', function() {
test('Points - Highest to Lowest is selected when criterion is points and direction is descending', function() {
const wrapper = mountAndOpenOptions(this.props('points', 'descending'))
const menuItem = getMenuItem(
wrapper.instance().menuContent,