add date info to grading period filter

When filtering by grading period in the gradebook, it would often be
helpful to teachers if they knew the start, end, and close dates of the
grading period. This change adds that information to the grading period
filter.

closes EVAL-3936
flag=grading_periods_filter_dates

test plan:
 - Have the release flag for this feature turned on
 - Have a course in a term with multiple grading periods
 - Go to the gradebook
 - Filter by grading period
 - Note the correct dates for start, end, and close for each grading
   period in the Select
 - repeat the above steps with enhanced gradebook filters enabled

Change-Id: If111fce30567ae0ec0342c529cb810a968229ba2
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/339745
Reviewed-by: Spencer Olson <solson@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Derek Williams <derek.williams@instructure.com>
Product-Review: Ravi Koll <ravi.koll@instructure.com>
This commit is contained in:
Keith T. Garner 2024-02-05 13:16:53 -06:00 committed by Keith Garner
parent 0b3d0ba99a
commit f801c44dba
10 changed files with 117 additions and 16 deletions

View File

@ -578,6 +578,7 @@ class GradebooksController < ApplicationController
user_asset_string: @current_user&.asset_string,
version: params.fetch(:version, nil),
assignment_missing_shortcut: Account.site_admin.feature_enabled?(:assignment_missing_shortcut),
grading_periods_filter_dates_enabled: Account.site_admin.feature_enabled?(:grading_periods_filter_dates),
}
js_env({

View File

@ -351,3 +351,13 @@ platform_service_speedgrader:
state: allowed
development:
state: allowed
grading_periods_filter_dates:
state: hidden
applies_to: SiteAdmin
display_name: Add dates to Gradebook Grading Period Filter
description: This feature adds the start, end, and close dates of grading periods to the grading period filter.
environments:
ci:
state: allowed_on
development:
state: allowed_on

View File

@ -187,6 +187,8 @@ QUnit.module('Gradebook Grid Column Filtering', suiteHooks => {
fakeENV.setup({
current_user_id: '1101',
// TODO: remove this when we remove the release flag
GRADEBOOK_OPTIONS: {grading_periods_filter_dates_enabled: true},
})
createAssignments()
@ -495,8 +497,8 @@ QUnit.module('Gradebook Grid Column Filtering', suiteHooks => {
id: '1501',
display_totals_for_all_grading_periods: true,
grading_periods: [
{id: '1401', title: 'GP1'},
{id: '1402', title: 'GP2'},
{id: '1401', title: 'GP1', start_date: Date(), end_date: Date(), close_date: Date()},
{id: '1402', title: 'GP2', start_date: Date(), end_date: Date(), close_date: Date()},
],
},
})

View File

@ -131,6 +131,7 @@ QUnit.module('Gradebook Grid Columns', suiteHooks => {
fakeENV.setup({
current_user_id: '1101',
GRADEBOOK_OPTIONS: {grading_periods_filter_dates_enabled: true},
})
createAssignments()
@ -654,8 +655,20 @@ QUnit.module('Gradebook Grid Columns', suiteHooks => {
id: '1301',
display_totals_for_all_grading_periods: true,
grading_periods: [
{id: '1401', title: 'Grading Period 1'},
{id: '1402', title: 'Grading Period 2'},
{
id: '1401',
title: 'Grading Period 1',
start_date: Date(),
end_date: Date(),
close_date: Date(),
},
{
id: '1402',
title: 'Grading Period 2',
start_date: Date(),
end_date: Date(),
close_date: Date(),
},
],
},
settings: {

View File

@ -29,6 +29,11 @@ describe "Enhanced Gradebook Filters" do
include GradebookSetup
include_context "late_policy_course_setup"
def format_grading_period_title_with_date(grp)
dates = [grp.start_date, grp.end_date, grp.close_date].map { |d| format_date_for_view(d, "%-m/%-d/%y") }
"#{grp.title}: #{dates[0]} - #{dates[1]} | #{dates[2]}"
end
before(:once) do
Account.site_admin.enable_feature!(:enhanced_gradebook_filters)
Account.site_admin.enable_feature!(:custom_gradebook_statuses)
@ -173,12 +178,13 @@ describe "Enhanced Gradebook Filters" do
end
it "can filter and unfilter by grading period" do
title = format_grading_period_title_with_date(@gp_closed)
Gradebook.apply_filters_button.click
Gradebook.select_filter_type_menu_item("Grading Periods")
Gradebook.select_filter_menu_item(@gp_closed.title)
Gradebook.select_filter_menu_item(title)
expect(Gradebook.fetch_assignment_names).to eq [@a1.name]
Gradebook.select_filter_menu_item(@gp_closed.title)
Gradebook.select_filter_menu_item(title)
expect(Gradebook.fetch_assignment_names).to eq [@a4.name, @a5.name, @a6.name, @a2.name, @a3.name]
end
@ -387,18 +393,19 @@ describe "Enhanced Gradebook Filters" do
Gradebook.select_filter_type_menu_item("Modules")
Gradebook.select_filter_menu_item("module1")
Gradebook.select_filter_dropdown_back_button
Gradebook.select_filter_type_menu_item("Grading Periods")
Gradebook.select_filter_menu_item(@gp_closed.title)
Gradebook.select_filter_dropdown_back_button
Gradebook.select_filter_type_menu_item("Submissions")
Gradebook.select_filter_menu_item("Has Submissions")
Gradebook.select_filter_dropdown_back_button
Gradebook.select_filter_type_menu_item("Student Groups")
Gradebook.select_sorted_filter_menu_item("group1")
Gradebook.select_filter_dropdown_back_button
Gradebook.select_filter_type_menu_item("Grading Periods")
gp_title = format_grading_period_title_with_date(@gp_closed)
Gradebook.select_filter_menu_item(gp_title)
expect(Gradebook.fetch_assignment_names).to eq [@a1.name]
expect(Gradebook.fetch_student_names).to eq [@student1.name]
Gradebook.clear_filter("GP Closed")
Gradebook.clear_filter(gp_title)
Gradebook.clear_filter("module1")
expect(Gradebook.fetch_assignment_names).to eq [@a3.name]
expect(Gradebook.fetch_student_names).to eq [@student1.name]

View File

@ -50,7 +50,8 @@ module GradebookSetup
{
title: "GP Closed",
start_date: 3.weeks.ago(now),
end_date: 2.weeks.ago(now)
end_date: 2.weeks.ago(now),
close_date: 2.weeks.ago(now)
}
end
@ -67,7 +68,8 @@ module GradebookSetup
{
title: "GP Current",
start_date: 1.day.ago(now),
end_date: 2.weeks.from_now
end_date: 2.weeks.from_now,
close_date: 2.weeks.from_now
}
end

View File

@ -358,7 +358,10 @@ export const getLabelForFilter = (
return assignmentGroups.find(a => a.id === filter.value)?.name || I18n.t('Assignment Group')
} else if (filter.type === 'grading-period') {
if (filter.value === '0') return I18n.t('All Grading Periods')
return gradingPeriods.find(g => g.id === filter.value)?.title || I18n.t('Grading Period')
return (
formatGradingPeriodTitleForDisplay(gradingPeriods.find(g => g.id === filter.value)) ||
I18n.t('Grading Period')
)
} else if (filter.type === 'student-group') {
const studentGroups: StudentGroup[] = Object.values(studentGroupCategories)
.map((c: StudentGroupCategory) => c.groups)
@ -798,3 +801,28 @@ export const filterAssignmentsBySubmissionsFn = (
return result
}
}
export function formatGradingPeriodTitleForDisplay(
gradingPeriod: GradingPeriod | undefined | null
) {
if (!gradingPeriod) return null
let title = gradingPeriod.title
if (ENV.GRADEBOOK_OPTIONS?.grading_periods_filter_dates_enabled) {
const formatter = Intl.DateTimeFormat(I18n.currentLocale(), {
year: '2-digit',
month: 'numeric',
day: 'numeric',
timezone: ENV.TIMEZONE,
})
title = I18n.t('%{title}: %{start} - %{end} | %{closed}', {
title: gradingPeriod.title,
start: formatter.format(gradingPeriod.startDate),
end: formatter.format(gradingPeriod.endDate),
closed: formatter.format(gradingPeriod.closeDate),
})
}
return title
}

View File

@ -35,13 +35,14 @@ import {
otherGradingPeriodAssignmentIds,
sectionList,
getLabelForFilter,
formatGradingPeriodTitleForDisplay,
} from '../Gradebook.utils'
import {isDefaultSortOrder, localeSort} from '../Gradebook.sorting'
import {createGradebook} from './GradebookSpecHelper'
import {fireEvent, screen, waitFor} from '@testing-library/dom'
import type {FilterPreset, Filter} from '../gradebook.d'
import type {SlickGridKeyboardEvent} from '../grid.d'
import type {Submission, Student, Enrollment} from '../../../../../api.d'
import type {Submission, Student, Enrollment, GradingPeriod} from '../../../../../api.d'
import {enrollment, student, enrollmentFilter, appliedFilters, student2} from './fixtures'
const unsubmittedSubmission: Submission = {
@ -749,3 +750,38 @@ describe('filterStudentBySectionFn', () => {
})
})
})
describe('formatGradingPeriodTitleForDisplay', () => {
ENV.GRADEBOOK_OPTIONS = {grading_periods_filter_dates_enabled: true}
const gp: GradingPeriod = {
id: '1',
title: 'GP1',
startDate: new Date('2021-01-01'),
endDate: new Date('2021-01-31'),
closeDate: new Date('2021-02-01'),
}
it('returns null if handed a null grading period', () => {
const result = formatGradingPeriodTitleForDisplay(null)
expect(result).toBeNull()
})
it('returns null if handed an undefined grading period', () => {
const result = formatGradingPeriodTitleForDisplay(undefined)
expect(result).toBeNull()
})
// TODO: remove "with the feature flag" from the test description when the feature flag is removed
it('returns the grading period title with the start, end, and close dates with the feature flag', () => {
ENV.GRADEBOOK_OPTIONS = {grading_periods_filter_dates_enabled: true}
const result = formatGradingPeriodTitleForDisplay(gp)
expect(result).toEqual('GP1: 1/1/21 - 1/31/21 | 2/1/21')
})
// TODO: remove this test when we remove the feature flag
it('returns only the grading period title without the feature flag', () => {
ENV.GRADEBOOK_OPTIONS = {grading_periods_filter_dates_enabled: false}
const result = formatGradingPeriodTitleForDisplay(gp)
expect(result).toEqual('GP1')
})
})

View File

@ -25,6 +25,7 @@ import {useScope as useI18nScope} from '@canvas/i18n'
import natcompare from '@canvas/util/natcompare'
import {
doFiltersMatch,
formatGradingPeriodTitleForDisplay,
getCustomStatusIdStrings,
isFilterNotEmpty,
mapCustomStatusToIdString,
@ -152,7 +153,7 @@ function useFilterDropdownData({
if (gradingPeriods.length > 0) {
const gradingPeriodItems: FilterDrilldownMenuItem[] = gradingPeriods.map(a => ({
id: a.id,
name: a.title,
name: formatGradingPeriodTitleForDisplay(a),
isSelected: appliedFilters.some(c => c.type === 'grading-period' && c.value === a.id),
onToggle: () => {
const filter: Filter = {

View File

@ -19,6 +19,7 @@
import React from 'react'
import {arrayOf, shape, string, bool, func} from 'prop-types'
import {formatGradingPeriodTitleForDisplay} from '../../Gradebook.utils'
import {useScope as useI18nScope} from '@canvas/i18n'
import ContentFilter from '@canvas/gradebook-content-filters/react/ContentFilter'
@ -30,7 +31,7 @@ const I18n = useI18nScope(
function normalizeGradingPeriods(gradingPeriods) {
return gradingPeriods.map(gradingPeriod => ({
id: gradingPeriod.id,
name: gradingPeriod.title,
name: formatGradingPeriodTitleForDisplay(gradingPeriod),
}))
}