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) {