Sort gradebook filters alphabetically

This ignore GradingPeriodFilter since these would need to be sorted by
start_date.

Natcompare was used so this can sort as expected in all locales.

Closes: APG-101

Test Plan:
- Given New Gradebook is enabled
- Given assignment groups, modules, sections, and student groups that
  were created in non alphabetical order (e.g. 'Section 2' was created
  before 'Section 1')
- Given the Gradebook page
- When using any of the filters except for grading periods
- Then the contents are in alphabetical order

Change-Id: I6b85da1f503eea1074fcff2d1885dc794d96e639
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/213374
Tested-by: Jenkins
Reviewed-by: Spencer Olson <solson@instructure.com>
Reviewed-by: Gary Mei <gmei@instructure.com>
QA-Review: Derek Bender <djbender@instructure.com>
Product-Review: Jonathan Fenton <jfenton@instructure.com>
This commit is contained in:
Derek Bender 2019-10-16 13:12:01 -05:00
parent af22a1e9de
commit 6349c6e2cc
11 changed files with 75 additions and 49 deletions

View File

@ -35,6 +35,7 @@ export default function AssignmentGroupFilter(props) {
items={assignmentGroups}
label={I18n.t('Assignment Group Filter')}
selectedItemId={selectedAssignmentGroupId || ALL_ITEMS_ID}
sortAlphabetically
/>
)
}

View File

@ -21,6 +21,7 @@ import {arrayOf, bool, func, shape, string} from 'prop-types'
import {ScreenReaderContent} from '@instructure/ui-a11y'
import {Select} from '@instructure/ui-select'
import natcompare from 'compiled/util/natcompare'
import I18n from 'i18n!gradezilla_default_gradebook_components_content_filters_content_filter'
function renderItem(option, {disabled, highlightedItemId, selectedItemId}) {
@ -85,7 +86,12 @@ export default function ContentFilter(props) {
}
}
const options = [{id: allItemsId, name: allItemsLabel}].concat(items)
let options = [{id: allItemsId, name: allItemsLabel}]
if (props.sortAlphabetically) {
options = options.concat(items.sort(natcompare.byKey('name')))
} else {
options = options.concat(items)
}
return (
<Select
@ -129,9 +135,11 @@ ContentFilter.propTypes = {
).isRequired,
onSelect: func.isRequired,
selectedItemId: string
selectedItemId: string,
sortAlphabetically: bool
}
ContentFilter.defaultProps = {
selectedItemId: null
selectedItemId: null,
sortAlphabetically: false
}

View File

@ -33,6 +33,7 @@ export default function ModuleFilter(props) {
items={modules}
label={I18n.t('Module Filter')}
selectedItemId={selectedModuleId}
sortAlphabetically
/>
)
}

View File

@ -33,6 +33,7 @@ export default function SectionFilter(props) {
items={sections}
label={I18n.t('Section Filter')}
selectedItemId={selectedSectionId}
sortAlphabetically
/>
)
}

View File

@ -19,12 +19,14 @@
import React from 'react'
import {arrayOf, shape, string} from 'prop-types'
import natcompare from 'compiled/util/natcompare'
import I18n from 'i18n!gradezilla_default_gradebook_components_content_filters_student_group_filter'
import ContentFilter from './ContentFilter'
function normalizeStudentGroupSets(studentGroupSets) {
return studentGroupSets.map(category => ({
children: [...category.groups].sort((a, b) => a.id - b.id),
children: [...category.groups].sort(natcompare.byKey('name')),
id: category.id,
name: category.name
}))
@ -41,6 +43,7 @@ export default function StudentGroupFilter(props) {
items={normalizeStudentGroupSets(studentGroupSets)}
label={I18n.t('Student Group Filter')}
selectedItemId={selectedStudentGroupId}
sortAlphabetically
/>
)
}

View File

@ -5580,8 +5580,8 @@ QUnit.module('Gradebook#updateCurrentAssignmentGroup', {
}
})
this.gradebook.setAssignmentGroups({
'1': {id: '1'},
'2': {id: '2'}
'1': {id: '1', name: 'First'},
'2': {id: '2', name: 'Second'}
})
sinon.spy(this.gradebook, 'setFilterColumnsBySetting')
sandbox.spy($, 'ajaxJSON')

View File

@ -76,11 +76,11 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
equal($allItemsOption.textContent.trim(), 'All Assignment Groups')
})
test('labels each option using the related assignment group name', () => {
test('labels each option using the related assignment group name in alphabetical order', () => {
renderComponent()
filter.clickToExpand()
const labels = filter.$options.slice(1).map($option => $option.textContent.trim())
deepEqual(labels, ['In-Class', 'Homework'])
deepEqual(labels, ['Homework', 'In-Class'])
})
test('disables non-selected options when the filter is disabled', () => {

View File

@ -102,6 +102,19 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
deepEqual(filter.optionLabels.slice(1), ['Item 1', 'Item 2'])
})
QUnit.module('when sortAlphabetically is enabled', hooks => {
hooks.beforeEach(() => {
props.sortAlphabetically = true
props.items = [{id: '2', name: 'Item 2'}, {id: '1', name: 'Item 1'}]
})
test('labels each item option using the related item .name in alphabetical order', () => {
renderComponent()
filter.clickToExpand()
deepEqual(filter.optionLabels.slice(1), ['Item 1', 'Item 2'])
})
})
QUnit.module('when using option groups', hooks => {
hooks.beforeEach(() => {
props.items = [

View File

@ -19,11 +19,11 @@
import React from 'react'
import {render} from '@testing-library/react'
import SectionFilter from 'jsx/gradezilla/default_gradebook/components/content-filters/SectionFilter'
import ModuleFilter from 'jsx/gradezilla/default_gradebook/components/content-filters/ModuleFilter'
import ContentFilterDriver from './ContentFilterDriver'
QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', () => {
QUnit.module('SectionFilter', suiteHooks => {
QUnit.module('ModuleFilter', suiteHooks => {
let $container
let component
let filter
@ -34,9 +34,9 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
props = {
disabled: false,
sections: [{id: '2001', name: 'Section 1'}, {id: '2002', name: 'Section 2'}],
modules: [{id: '2002', name: 'Module 2'}, {id: '2001', name: 'Module 1'}],
onSelect: sinon.stub(),
selectedSectionId: '0'
selectedModuleId: '0'
}
component = null
@ -48,46 +48,46 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
})
function renderComponent() {
component = render(<SectionFilter {...props} />, {container: $container})
filter = ContentFilterDriver.findWithLabelText('Section Filter', $container)
component = render(<ModuleFilter {...props} />, {container: $container})
filter = ContentFilterDriver.findWithLabelText('Module Filter', $container)
}
test('labels the filter with "Section Filter"', () => {
test('labels the filter with "Module Filter"', () => {
renderComponent()
equal(filter.labelText, 'Section Filter')
equal(filter.labelText, 'Module Filter')
})
test('displays the name of the selected section as the value', () => {
props.selectedSectionId = '2002'
test('displays the name of the selected module as the value', () => {
props.selectedModuleId = '2002'
renderComponent()
equal(filter.selectedItemLabel, 'Section 2')
equal(filter.selectedItemLabel, 'Module 2')
})
test('displays "All Sections" as the value when selected', () => {
test('displays "All Modules" as the value when selected', () => {
renderComponent()
equal(filter.selectedItemLabel, 'All Sections')
equal(filter.selectedItemLabel, 'All Modules')
})
QUnit.module('sections list', () => {
test('labels the "all items" option with "All Sections"', () => {
QUnit.module('modules list', () => {
test('labels the "all items" option with "All Modules"', () => {
renderComponent()
filter.clickToExpand()
const $allItemsOption = filter.$options[0]
equal($allItemsOption.textContent.trim(), 'All Sections')
equal($allItemsOption.textContent.trim(), 'All Modules')
})
test('labels each option using the related section name', () => {
test('labels each option using the related module name in alphabetical order', () => {
renderComponent()
filter.clickToExpand()
const labels = filter.$options.slice(1).map($option => $option.textContent.trim())
deepEqual(labels, ['Section 1', 'Section 2'])
deepEqual(labels, ['Module 1', 'Module 2'])
})
test('disables non-selected options when the filter is disabled', () => {
props.disabled = true
renderComponent()
filter.clickToExpand()
const $option = filter.getOptionWithLabel('Section 2')
const $option = filter.getOptionWithLabel('Module 2')
strictEqual($option.getAttribute('aria-disabled'), 'true')
})
})
@ -96,25 +96,25 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
test('calls the .onSelect callback', () => {
renderComponent()
filter.clickToExpand()
filter.clickToSelectOption('Section 1')
filter.clickToSelectOption('Module 1')
strictEqual(props.onSelect.callCount, 1)
})
test('includes the section id when calling the .onSelect callback', () => {
test('includes the module id when calling the .onSelect callback', () => {
renderComponent()
filter.clickToExpand()
filter.clickToSelectOption('Section 1')
const [selectedSectionId] = props.onSelect.lastCall.args
strictEqual(selectedSectionId, '2001')
filter.clickToSelectOption('Module 1')
const [selectedModuleId] = props.onSelect.lastCall.args
strictEqual(selectedModuleId, '2001')
})
test('includes "0" when the "All Sections" is clicked', () => {
props.selectedSectionId = '2001'
test('includes "0" when the "All Modules" is clicked', () => {
props.selectedModuleId = '2001'
renderComponent()
filter.clickToExpand()
filter.clickToSelectOption('All Sections')
const [selectedSectionId] = props.onSelect.lastCall.args
strictEqual(selectedSectionId, '0')
filter.clickToSelectOption('All Modules')
const [selectedModuleId] = props.onSelect.lastCall.args
strictEqual(selectedModuleId, '0')
})
})
})

View File

@ -34,7 +34,7 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
props = {
disabled: false,
sections: [{id: '2001', name: 'Section 1'}, {id: '2002', name: 'Section 2'}],
sections: [{id: '2002', name: 'Section 2'}, {id: '2001', name: 'Section 1'}],
onSelect: sinon.stub(),
selectedSectionId: '0'
}
@ -76,7 +76,7 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
equal($allItemsOption.textContent.trim(), 'All Sections')
})
test('labels each option using the related section name', () => {
test('labels each option using the related section name in alphabetical order', () => {
renderComponent()
filter.clickToExpand()
const labels = filter.$options.slice(1).map($option => $option.textContent.trim())

View File

@ -36,16 +36,15 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
disabled: false,
studentGroupSets: [
{
groups: [{id: '2101', name: 'Group A1'}, {id: '2102', name: 'Group A2'}],
id: '2151',
name: 'Group Set A'
},
{
groups: [{id: '2103', name: 'Group B1'}, {id: '2104', name: 'Group B2'}],
id: '2152',
name: 'Group Set B'
},
{
groups: [{id: '2101', name: 'Group A2'}, {id: '2102', name: 'Group A1'}],
id: '2151',
name: 'Group Set A'
}
],
@ -72,7 +71,7 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
})
test('displays the name of the selected student group as the value', () => {
props.selectedStudentGroupId = '2102'
props.selectedStudentGroupId = '2101'
renderComponent()
equal(filter.selectedItemLabel, 'Group A2')
})
@ -83,7 +82,7 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
})
QUnit.module('student group sets', () => {
test('labels each group set option group using the related name', () => {
test('labels each group set option group using the related name in alphabetical order', () => {
renderComponent()
filter.clickToExpand()
const labels = filter.optionGroupLabels
@ -99,7 +98,7 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
equal($allItemsOption.textContent.trim(), 'All Student Groups')
})
test('labels each option using the related student group name', () => {
test('labels each option using the related student group name in alphabetical order', () => {
renderComponent()
filter.clickToExpand()
const labels = filter.$options.slice(1).map($option => $option.textContent.trim())
@ -128,7 +127,7 @@ QUnit.module('Gradebook > Default Gradebook > Components > Content Filters', ()
filter.clickToExpand()
filter.clickToSelectOption('Group A1')
const [selectedStudentGroupId] = props.onSelect.lastCall.args
strictEqual(selectedStudentGroupId, '2101')
strictEqual(selectedStudentGroupId, '2102')
})
test('includes "0" when the "All Student Groups" is clicked', () => {