diff --git a/src/components/Gradebook/index.jsx b/src/components/Gradebook/index.jsx index 05ccf1c..c31f54f 100644 --- a/src/components/Gradebook/index.jsx +++ b/src/components/Gradebook/index.jsx @@ -41,7 +41,8 @@ export default class Gradebook extends React.Component { componentDidMount() { const urlQuery = queryString.parse(this.props.location.search); - this.props.getRoles(this.props.courseId, urlQuery); + this.props.initializeFilters(urlQuery); + this.props.getRoles(this.props.courseId); this.overrideReasonInput.focus(); } @@ -87,6 +88,17 @@ export default class Gradebook extends React.Component { return ['Grades']; }; + getAssignmentFilterOptions = () => [ + { label: 'All', value: '' }, + ...this.props.assignmentFilterOptions.map((assignment) => { + const { label, subsectionLabel } = assignment; + return { + label: `${label}: ${subsectionLabel}`, + value: label, + }; + }), + ]; + handleAdjustedGradeClick = () => { this.props.updateGrades( this.props.courseId, [ @@ -113,9 +125,23 @@ export default class Gradebook extends React.Component { }); } + handleAssignmentFilterChange = (assignment) => { + const selectedFilterOption = this.props.assignmentFilterOptions.find(assig => + assig.label === assignment); + const { type, id } = selectedFilterOption || {}; + const typedValue = { label: assignment, type, id }; + this.props.updateAssignmentFilter(typedValue); + const updatedQueryStrings = this.updateQueryParams('assignment', assignment); + this.props.history.push(updatedQueryStrings); + } + updateQueryParams = (queryKey, queryValue) => { const parsed = queryString.parse(this.props.location.search); - parsed[queryKey] = queryValue; + if (queryValue) { + parsed[queryKey] = queryValue; + } else { + delete parsed[queryKey]; + } return `?${queryString.stringify(parsed)}`; }; @@ -124,7 +150,7 @@ export default class Gradebook extends React.Component { id: entry, label: entry, })); - mapped.unshift({ id: 0, label: 'All' }); + mapped.unshift({ id: 0, label: 'All', value: '' }); return mapped; }; @@ -182,9 +208,9 @@ export default class Gradebook extends React.Component { return { resultsSummary, filename, ...rest }; }; - updateAssignmentTypes = (event) => { - this.props.filterColumns(event, this.props.grades[0]); - const updatedQueryStrings = this.updateQueryParams('assignmentType', event); + updateAssignmentTypes = (assignmentType) => { + this.props.filterAssignmentType(assignmentType); + const updatedQueryStrings = this.updateQueryParams('assignmentType', assignmentType); this.props.history.push(updatedQueryStrings); } @@ -216,7 +242,8 @@ export default class Gradebook extends React.Component { this.props.selectedTrack, this.props.selectedAssignmentType, ); - this.updateQueryParams('cohort', selectedCohortId); + const updatedQueryStrings = this.updateQueryParams('cohort', selectedCohortId); + this.props.history.push(updatedQueryStrings); }; handleClickExportGrades = () => { @@ -379,20 +406,32 @@ export default class Gradebook extends React.Component { options={[{ label: 'Percent', value: 'percent' }, { label: 'Absolute', value: 'absolute' }]} onChange={this.props.toggleFormat} /> - {this.props.assignmentTypes.length > 0 && -
- - Assignment Types: - - -
- } +
+ + Assignment Types: + + +
+
+ + Assignment: + + +
Student Groups: @@ -643,6 +682,7 @@ export default class Gradebook extends React.Component { Gradebook.defaultProps = { areGradesFrozen: false, assignmentTypes: [], + assignmentFilterOptions: [], canUserViewGradebook: false, cohorts: [], grades: [], @@ -655,7 +695,8 @@ Gradebook.defaultProps = { courseId: '', selectedCohort: null, selectedTrack: null, - selectedAssignmentType: 'All', + selectedAssignmentType: '', + selectedAssignment: '', showSpinner: false, tracks: [], bulkImportError: '', @@ -671,9 +712,17 @@ Gradebook.defaultProps = { Gradebook.propTypes = { areGradesFrozen: PropTypes.bool, assignmentTypes: PropTypes.arrayOf(PropTypes.string), + assignmentFilterOptions: PropTypes.arrayOf(PropTypes.shape({ + label: PropTypes.string, + subsectionLabel: PropTypes.string, + })), canUserViewGradebook: PropTypes.bool, - cohorts: PropTypes.arrayOf(PropTypes.string), - filterColumns: PropTypes.func.isRequired, + cohorts: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string, + id: PropTypes.number, + })), + filterAssignmentType: PropTypes.func.isRequired, + updateAssignmentFilter: PropTypes.func.isRequired, format: PropTypes.string.isRequired, getRoles: PropTypes.func.isRequired, getUserGrades: PropTypes.func.isRequired, @@ -711,9 +760,8 @@ Gradebook.propTypes = { courseId: PropTypes.string, searchForUser: PropTypes.func.isRequired, selectedAssignmentType: PropTypes.string, - selectedCohort: PropTypes.shape({ - name: PropTypes.string, - }), + selectedAssignment: PropTypes.string, + selectedCohort: PropTypes.number, selectedTrack: PropTypes.string, showSpinner: PropTypes.bool, showSuccess: PropTypes.bool.isRequired, @@ -744,4 +792,5 @@ Gradebook.propTypes = { totalUsersCount: PropTypes.number, filteredUsersCount: PropTypes.number, showDownloadButtons: PropTypes.bool, + initializeFilters: PropTypes.func.isRequired, }; diff --git a/src/containers/GradebookPage/index.jsx b/src/containers/GradebookPage/index.jsx index 5e77a4f..978dcfd 100644 --- a/src/containers/GradebookPage/index.jsx +++ b/src/containers/GradebookPage/index.jsx @@ -8,14 +8,17 @@ import { fetchPrevNextGrades, updateGrades, toggleGradeFormat, - filterColumns, + filterAssignmentType, closeBanner, submitFileUploadFormData, } from '../../data/actions/grades'; import { fetchCohorts } from '../../data/actions/cohorts'; import { fetchTracks } from '../../data/actions/tracks'; +import { initializeFilters, updateAssignmentFilter } from '../../data/actions/filters'; import stateHasMastersTrack from '../../data/selectors/tracks'; -import getBulkManagementHistory from '../../data/selectors/grades'; +import { getBulkManagementHistory, getHeadings } from '../../data/selectors/grades'; +import { selectableAssignmentLabels } from '../../data/selectors/filters'; +import { getCohortNameById } from '../../data/selectors/cohorts'; import { fetchAssignmentTypes } from '../../data/actions/assignmentTypes'; import { getRoles } from '../../data/actions/roles'; import LmsApiService from '../../data/services/LmsApiService'; @@ -40,24 +43,28 @@ const mapStateToProps = (state, ownProps) => ( gradeOverrideCurrentPossibleGradedOverride: state.grades.gradeOverrideCurrentPossibleGradedOverride, gradeOriginalEarnedGraded: state.grades.gradeOriginalEarnedGraded, - headings: state.grades.headings, + headings: getHeadings(state), tracks: state.tracks.results, cohorts: state.cohorts.results, - selectedTrack: state.grades.selectedTrack, - selectedCohort: state.grades.selectedCohort, - selectedAssignmentType: state.grades.selectedAssignmentType, + selectedTrack: state.filters.track, + selectedCohort: state.filters.cohort, + selectedAssignmentType: state.filters.assignmentType, + selectedAssignment: (state.filters.assignment || {}).label, format: state.grades.gradeFormat, showSuccess: state.grades.showSuccess, errorFetchingGradeOverrideHistory: state.grades.errorFetchingOverrideHistory, prevPage: state.grades.prevPage, nextPage: state.grades.nextPage, assignmentTypes: state.assignmentTypes.results, + assignmentFilterOptions: selectableAssignmentLabels(state), areGradesFrozen: state.assignmentTypes.areGradesFrozen, showSpinner: shouldShowSpinner(state), canUserViewGradebook: state.roles.canUserViewGradebook, gradeExportUrl: LmsApiService.getGradeExportCsvUrl(ownProps.match.params.courseId, { - cohort: state.grades.selectedCohort, - track: state.grades.selectedTrack, + cohort: getCohortNameById(state, state.filters.cohort), + track: state.filters.track, + assignment: (state.filters.assignment || {}).id, + assignmentType: state.filters.assignmentType, }), interventionExportUrl: LmsApiService.getInterventionExportCsvUrl(ownProps.match.params.courseId), @@ -85,10 +92,12 @@ const mapDispatchToProps = { getAssignmentTypes: fetchAssignmentTypes, updateGrades, toggleFormat: toggleGradeFormat, - filterColumns, + filterAssignmentType, closeBanner, getRoles, submitFileUploadFormData, + initializeFilters, + updateAssignmentFilter, }; const GradebookPage = connect( diff --git a/src/data/actions/filters.js b/src/data/actions/filters.js new file mode 100644 index 0000000..e1eaa0e --- /dev/null +++ b/src/data/actions/filters.js @@ -0,0 +1,23 @@ +import { INITIALIZE_FILTERS, UPDATE_ASSIGNMENT_FILTER } from '../constants/actionTypes/filters'; + +const initializeFilters = ({ + assignment = '', + assignmentType = '', + track = '', + cohort = '', +}) => ({ + type: INITIALIZE_FILTERS, + data: { + assignment: { label: assignment }, + assignmentType, + track, + cohort, + }, +}); + +const updateAssignmentFilter = assignment => ({ + type: UPDATE_ASSIGNMENT_FILTER, + data: assignment, +}); + +export { initializeFilters, updateAssignmentFilter }; diff --git a/src/data/actions/grades.js b/src/data/actions/grades.js index d784f5e..751243b 100644 --- a/src/data/actions/grades.js +++ b/src/data/actions/grades.js @@ -7,7 +7,7 @@ import { GRADE_UPDATE_SUCCESS, GRADE_UPDATE_FAILURE, TOGGLE_GRADE_FORMAT, - FILTER_COLUMNS, + FILTER_BY_ASSIGNMENT_TYPE, OPEN_BANNER, CLOSE_BANNER, START_UPLOAD, @@ -19,7 +19,7 @@ import { ERROR_FETCHING_GRADE_OVERRIDE_HISTORY, } from '../constants/actionTypes/grades'; import LmsApiService from '../services/LmsApiService'; -import { headingMapper, sortAlphaAsc, formatDateForDisplay } from './utils'; +import { sortAlphaAsc, formatDateForDisplay } from './utils'; import apiClient from '../apiClient'; const defaultAssignmentFilter = 'All'; @@ -85,10 +85,10 @@ const gradeUpdateFailure = (courseId, error) => ({ const toggleGradeFormat = formatType => ({ type: TOGGLE_GRADE_FORMAT, formatType }); -const filterColumns = (filterType, exampleUser) => ( +const filterAssignmentType = filterType => ( dispatch => dispatch({ - type: FILTER_COLUMNS, - headings: headingMapper(filterType)(exampleUser), + type: FILTER_BY_ASSIGNMENT_TYPE, + filterType, }) ); @@ -112,7 +112,6 @@ const fetchGrades = ( cohort, track, assignmentType, - headings: headingMapper(assignmentType || defaultAssignmentFilter)(data.results[0]), prev: data.previous, next: data.next, courseId, @@ -185,7 +184,6 @@ const fetchPrevNextGrades = (endpoint, courseId, cohort, track, assignmentType) cohort, track, assignmentType, - headings: headingMapper(assignmentType || defaultAssignmentFilter)(data.results[0]), prev: data.previous, next: data.next, courseId, @@ -259,7 +257,7 @@ export { gradeUpdateFailure, updateGrades, toggleGradeFormat, - filterColumns, + filterAssignmentType, closeBanner, submitFileUploadFormData, fetchBulkUpgradeHistory, diff --git a/src/data/actions/grades.test.js b/src/data/actions/grades.test.js index 8c9904b..3c483ba 100644 --- a/src/data/actions/grades.test.js +++ b/src/data/actions/grades.test.js @@ -97,11 +97,6 @@ describe('actions', () => { cohort: expectedCohort, track: expectedTrack, assignmentType: expectedAssignmentType, - headings: [ - 'Username', - 'Email', - 'Total', - ], prev: responseData.previous, next: responseData.next, courseId, @@ -159,7 +154,6 @@ describe('actions', () => { cohort: expectedCohort, track: expectedTrack, assignmentType: expectedAssignmentType, - headings: [], prev: responseData.previous, next: responseData.next, courseId, diff --git a/src/data/actions/roles.js b/src/data/actions/roles.js index f66f88a..6d81634 100644 --- a/src/data/actions/roles.js +++ b/src/data/actions/roles.js @@ -6,6 +6,7 @@ import { fetchGrades } from './grades'; import { fetchTracks } from './tracks'; import { fetchCohorts } from './cohorts'; import { fetchAssignmentTypes } from './assignmentTypes'; +import { getFilters } from '../selectors/filters'; import LmsApiService from '../services/LmsApiService'; const allowedRoles = ['staff', 'instructor', 'support']; @@ -17,16 +18,17 @@ const gotRoles = (canUserViewGradebook, courseId) => ({ }); const errorFetchingRoles = () => ({ type: ERROR_FETCHING_ROLES }); -const getRoles = (courseId, urlQuery) => ( - dispatch => LmsApiService.fetchUserRoles(courseId) +const getRoles = courseId => ( + (dispatch, getState) => LmsApiService.fetchUserRoles(courseId) .then(response => response.data) .then((response) => { const canUserViewGradebook = response.is_staff || (response.roles.some(role => (role.course_id === courseId) && allowedRoles.includes(role.role))); dispatch(gotRoles(canUserViewGradebook, courseId)); + const { cohort, track, assignmentType } = getFilters(getState()); if (canUserViewGradebook) { - dispatch(fetchGrades(courseId, urlQuery.cohort, urlQuery.track, urlQuery.assignmentType)); + dispatch(fetchGrades(courseId, cohort, track, assignmentType)); dispatch(fetchTracks(courseId)); dispatch(fetchCohorts(courseId)); dispatch(fetchAssignmentTypes(courseId)); diff --git a/src/data/actions/utils.js b/src/data/actions/utils.js index 70e8872..1c78d0c 100644 --- a/src/data/actions/utils.js +++ b/src/data/actions/utils.js @@ -26,29 +26,5 @@ const sortAlphaAsc = (gradeRowA, gradeRowB) => { return 0; }; -const headingMapper = (filterKey) => { - const filters = { - all: section => section.label, - some: section => section.label && section.category === filterKey, - }; - - const filter = filterKey === 'All' ? 'all' : 'some'; - - return (entry) => { - if (entry) { - const results = ['Username', 'Email']; - - const assignmentHeadings = entry.section_breakdown - .filter(filters[filter]) - .map(s => s.label); - - const totals = ['Total']; - - return results.concat(assignmentHeadings).concat(totals); - } - return []; - }; -}; - -export { headingMapper, sortAlphaAsc, formatDateForDisplay }; +export { sortAlphaAsc, formatDateForDisplay }; diff --git a/src/data/constants/actionTypes/filters.js b/src/data/constants/actionTypes/filters.js new file mode 100644 index 0000000..9d14480 --- /dev/null +++ b/src/data/constants/actionTypes/filters.js @@ -0,0 +1,4 @@ +const INITIALIZE_FILTERS = 'INITIALIZE_FILTERS'; +const UPDATE_ASSIGNMENT_FILTER = 'UPDATE_ASSIGNMENT_FILTER'; + +export { INITIALIZE_FILTERS, UPDATE_ASSIGNMENT_FILTER }; diff --git a/src/data/constants/actionTypes/grades.js b/src/data/constants/actionTypes/grades.js index ab920d1..c69e0e4 100644 --- a/src/data/constants/actionTypes/grades.js +++ b/src/data/constants/actionTypes/grades.js @@ -10,7 +10,7 @@ const GRADE_UPDATE_SUCCESS = 'GRADE_UPDATE_SUCCESS'; const GRADE_UPDATE_FAILURE = 'GRADE_UPDATE_FAILURE'; const TOGGLE_GRADE_FORMAT = 'TOGGLE_GRADE_FORMAT'; -const FILTER_COLUMNS = 'FILTER_COLUMNS'; +const FILTER_BY_ASSIGNMENT_TYPE = 'FILTER_BY_ASSIGNMENT_TYPE'; const CLOSE_BANNER = 'CLOSE_BANNER'; const OPEN_BANNER = 'OPEN_BANNER'; @@ -29,7 +29,7 @@ export { GRADE_UPDATE_SUCCESS, GRADE_UPDATE_FAILURE, TOGGLE_GRADE_FORMAT, - FILTER_COLUMNS, + FILTER_BY_ASSIGNMENT_TYPE, OPEN_BANNER, CLOSE_BANNER, START_UPLOAD, diff --git a/src/data/reducers/filters.js b/src/data/reducers/filters.js new file mode 100644 index 0000000..9d8f34f --- /dev/null +++ b/src/data/reducers/filters.js @@ -0,0 +1,55 @@ +import { GOT_GRADES, FILTER_BY_ASSIGNMENT_TYPE } from '../constants/actionTypes/grades'; + +import { INITIALIZE_FILTERS, UPDATE_ASSIGNMENT_FILTER } from '../constants/actionTypes/filters'; + +import { getAssignmentsFromResultsSubstate, chooseRelevantAssignmentData } from '../selectors/filters'; + +const initialState = {}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case FILTER_BY_ASSIGNMENT_TYPE: + return { + ...state, + assignmentType: action.filterType, + assignment: ( + action.filterType !== '' && + (state.assignment || {}).type !== action.filterType) + ? '' : state.assignment, + }; + case INITIALIZE_FILTERS: + return { + ...state, + ...action.data, + }; + case GOT_GRADES: { + const { assignment } = state; + const { label, type } = assignment || {}; + if (!type) { + const relevantAssignment = getAssignmentsFromResultsSubstate(action.grades) + .map(chooseRelevantAssignmentData) + .find(assig => assig.label === label); + return { + ...state, + track: action.track, + cohort: action.cohort, + assignment: relevantAssignment, + }; + } + return { + ...state, + track: action.track, + cohort: action.cohort, + }; + } + case UPDATE_ASSIGNMENT_FILTER: + return { + ...state, + assignment: action.data, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/src/data/reducers/grades.js b/src/data/reducers/grades.js index 51db139..5668680 100644 --- a/src/data/reducers/grades.js +++ b/src/data/reducers/grades.js @@ -3,7 +3,7 @@ import { ERROR_FETCHING_GRADES, GOT_GRADES, TOGGLE_GRADE_FORMAT, - FILTER_COLUMNS, + FILTER_BY_ASSIGNMENT_TYPE, OPEN_BANNER, CLOSE_BANNER, START_UPLOAD, @@ -48,9 +48,6 @@ const grades = (state = initialState, action) => { headings: action.headings, finishedFetching: true, errorFetching: false, - selectedTrack: action.track, - selectedCohort: action.cohort, - selectedAssignmentType: action.assignmentType, prevPage: action.prev, nextPage: action.next, showSpinner: false, @@ -98,9 +95,10 @@ const grades = (state = initialState, action) => { ...state, gradeFormat: action.formatType, }; - case FILTER_COLUMNS: + case FILTER_BY_ASSIGNMENT_TYPE: return { ...state, + selectedAssignmentType: action.filterType, headings: action.headings, }; case OPEN_BANNER: diff --git a/src/data/reducers/grades.test.js b/src/data/reducers/grades.test.js index e7a2f42..e14c5d2 100644 --- a/src/data/reducers/grades.test.js +++ b/src/data/reducers/grades.test.js @@ -4,7 +4,7 @@ import { ERROR_FETCHING_GRADES, GOT_GRADES, TOGGLE_GRADE_FORMAT, - FILTER_COLUMNS, + FILTER_BY_ASSIGNMENT_TYPE, OPEN_BANNER, } from '../constants/actionTypes/grades'; @@ -94,8 +94,6 @@ describe('grades reducer', () => { headings: headingsData, errorFetching: false, finishedFetching: true, - selectedTrack: expectedTrack, - selectedCohort: expectedCohortId, prevPage: expectedPrev, nextPage: expectedNext, showSpinner: false, @@ -137,7 +135,7 @@ describe('grades reducer', () => { headings: expectedHeadings, }; expect(grades(undefined, { - type: FILTER_COLUMNS, + type: FILTER_BY_ASSIGNMENT_TYPE, headings: expectedHeadings, })).toEqual(expected); }); diff --git a/src/data/reducers/index.js b/src/data/reducers/index.js index 392f013..e5330ec 100755 --- a/src/data/reducers/index.js +++ b/src/data/reducers/index.js @@ -5,6 +5,7 @@ import grades from './grades'; import tracks from './tracks'; import assignmentTypes from './assignmentTypes'; import roles from './roles'; +import filters from './filters'; const rootReducer = combineReducers({ grades, @@ -12,6 +13,7 @@ const rootReducer = combineReducers({ tracks, assignmentTypes, roles, + filters, }); export default rootReducer; diff --git a/src/data/selectors/cohorts.js b/src/data/selectors/cohorts.js new file mode 100644 index 0000000..8463aba --- /dev/null +++ b/src/data/selectors/cohorts.js @@ -0,0 +1,12 @@ + +const getCohorts = state => state.cohorts.results || []; + +const getCohortById = (state, selectedCohortId) => { + const cohort = getCohorts(state).find(coh => coh.id === selectedCohortId); + return cohort; +}; + +const getCohortNameById = (state, selectedCohortId) => + (getCohortById(state, selectedCohortId) || {}).name; + +export { getCohortById, getCohortNameById, getCohorts }; diff --git a/src/data/selectors/filters.js b/src/data/selectors/filters.js new file mode 100644 index 0000000..257cecf --- /dev/null +++ b/src/data/selectors/filters.js @@ -0,0 +1,41 @@ +const getFilters = state => state.filters || {}; + +const getAssignmentsFromResultsSubstate = results => + (results[0] || {}).section_breakdown || []; + +const selectableAssignments = (state) => { + const selectedAssignmentType = getFilters(state).assignmentType; + const needToFilter = selectedAssignmentType && selectedAssignmentType !== 'All'; + const allAssignments = getAssignmentsFromResultsSubstate(state.grades.results); + if (needToFilter) { + return allAssignments.filter(assignment => assignment.category === selectedAssignmentType); + } + return allAssignments; +}; + +const chooseRelevantAssignmentData = assignment => ({ + label: assignment.label, + subsectionLabel: assignment.subsection_name, + type: assignment.category, + id: assignment.module_id, +}); + +const selectableAssignmentLabels = state => + selectableAssignments(state).map(chooseRelevantAssignmentData); + +const typeOfSelectedAssignment = (state) => { + const selectedAssignmentLabel = getFilters(state).assignment; + const sectionBreakdown = (state.grades.results[0] || {}).section_breakdown || []; + const selectedAssignment = sectionBreakdown.find(section => + section.label === selectedAssignmentLabel); + return selectedAssignment && selectedAssignment.category; +}; + +export { + selectableAssignmentLabels, + selectableAssignments, + getFilters, + typeOfSelectedAssignment, + chooseRelevantAssignmentData, + getAssignmentsFromResultsSubstate, +}; diff --git a/src/data/selectors/grades.js b/src/data/selectors/grades.js index a8b4d42..792bfee 100644 --- a/src/data/selectors/grades.js +++ b/src/data/selectors/grades.js @@ -1,4 +1,5 @@ -import { formatDateForDisplay } from '../../data/actions/utils'; +import { formatDateForDisplay } from '../actions/utils'; +import { getFilters } from './filters'; const getRowsProcessed = (data) => { const { @@ -37,4 +38,46 @@ const getBulkManagementHistoryFromState = state => const getBulkManagementHistory = state => getBulkManagementHistoryFromState(state).map(transformHistoryEntry); -export default getBulkManagementHistory; +const headingMapper = (category, label = 'All') => { + const filters = { + all: section => section.label, + byCategory: section => section.label && section.category === category, + byLabel: section => section.label && section.label === label, + }; + + let filter; + if (label === 'All') { + filter = category === 'All' ? 'all' : 'byCategory'; + } else { + filter = 'byLabel'; + } + + return (entry) => { + if (entry) { + const results = ['Username', 'Email']; + + const assignmentHeadings = entry + .filter(filters[filter]) + .map(s => s.label); + + const totals = ['Total']; + + return results.concat(assignmentHeadings).concat(totals); + } + return []; + }; +}; + +const getHeadings = (state) => { + const filters = getFilters(state) || {}; + const { + assignmentType: selectedAssignmentType, + assignment: selectedAssignment, + } = filters; + const assignments = (state.grades.results[0] || {}).section_breakdown || []; + const type = selectedAssignmentType || 'All'; + const assignment = (selectedAssignment || {}).label || 'All'; + return headingMapper(type, assignment)(assignments); +}; + +export { getBulkManagementHistory, getHeadings }; diff --git a/src/data/selectors/grades.test.js b/src/data/selectors/grades.test.js index 676902a..17ef29b 100644 --- a/src/data/selectors/grades.test.js +++ b/src/data/selectors/grades.test.js @@ -1,4 +1,4 @@ -import getBulkManagementHistory from './grades'; +import { getBulkManagementHistory } from './grades'; const genericHistoryRow = { id: 5, diff --git a/src/data/services/LmsApiService.js b/src/data/services/LmsApiService.js index ad52097..c20b3ad 100644 --- a/src/data/services/LmsApiService.js +++ b/src/data/services/LmsApiService.js @@ -70,11 +70,12 @@ class LmsApiService { } static getGradeExportCsvUrl(courseId, options = {}) { - const trackQueryParam = options.track ? [`track=${options.track}`] : []; - const cohortQueryParam = options.cohort ? [`cohort=${options.cohort}`] : []; - const queryParams = [...trackQueryParam, ...cohortQueryParam].join('&'); - const downloadUrl = `${LmsApiService.baseUrl}/api/bulk_grades/course/${courseId}/?${queryParams}`; - return downloadUrl; + const queryParams = ['track', 'cohort', 'assignment', 'assignmentType'] + .filter(opt => options[opt] && + options[opt] !== 'All') + .map(opt => `${opt}=${encodeURIComponent(options[opt])}`) + .join('&'); + return `${LmsApiService.baseUrl}/api/bulk_grades/course/${courseId}/?${queryParams}`; } static getInterventionExportCsvUrl(courseId) {