Compare commits

..

11 Commits

Author SHA1 Message Date
Alex Dusenbery
f0aada7794 Get ws communication working with lms 2019-03-25 17:08:52 -04:00
Richard I Reilly
09e482e893 Merge pull request #87 from edx/rir/remove-sorting
Remove remove all unneeded sorting
2019-02-04 10:45:29 -05:00
Rick Reilly
d4421d47fc Remove remove all unneeded sorting 2019-02-04 10:39:49 -05:00
Kyle McCormick
0ef8e773cc Merge pull request #85 from edx/kdmccormick/page-size
EDUCATOR-3936 Increase users per page from 10 to 25
2019-01-25 11:46:38 -05:00
Kyle McCormick
1dac20b866 EDUCATOR-3936 Increase users per page from 10 to 25 2019-01-25 11:39:38 -05:00
Zachary Hancock
ed2d715ce0 Merge pull request #84 from edx/zhancock/assignment-type-filter
persist assignment type filter
2019-01-24 16:47:22 -05:00
Zach Hancock
c82c49ea59 persist assignment type filter 2019-01-24 16:42:57 -05:00
Richard I Reilly
a9f8aec5f9 Merge pull request #86 from edx/rir/search-affordance
Cosmetic changes to give search more affordance
2019-01-24 16:20:52 -05:00
Rick Reilly
a63e9a5347 Cosmetic changes to give search more affordance 2019-01-24 16:06:53 -05:00
Richard I Reilly
2581812118 Merge pull request #82 from edx/rir/cleanup
Remove the 'is_graded' filter. The api will ensure all subsection gra…
2019-01-23 14:23:55 -05:00
Rick Reilly
c4fe803a95 Remove the 'is_graded' filter. The api will ensure all subsection grades we get are 'is_graded=true' 2019-01-23 13:34:50 -05:00
14 changed files with 181 additions and 166 deletions

View File

@@ -81,3 +81,6 @@
}
}
.mb-85 {
margin-bottom: 85px;
}

View File

@@ -25,12 +25,41 @@ export default class Gradebook extends React.Component {
updateVal: 0,
updateModuleId: null,
updateUserId: null,
socket: null,
websocketMsg: {
visible: false,
},
};
}
componentDidMount() {
const urlQuery = queryString.parse(this.props.location.search);
this.props.getRoles(this.props.match.params.courseId, urlQuery);
const socket = new WebSocket('ws://localhost:8765/ws/gradebook/course-v1:edX+DemoX+Demo_Course/');
socket.onmessage = this.socketMessageFunction;
}
socketMessageFunction = (event) => {
var data = JSON.parse(event.data);
console.log(data);
const userIndex = this.props.grades.findIndex((entry) => entry.user_id == data.user_id);
const username = this.props.grades[userIndex].username;
const subsectionIndex = this.props.grades[userIndex].section_breakdown.findIndex((entry) => entry.module_id = data.subsection_id);
const subsectionName = this.props.grades[userIndex].section_breakdown[subsectionIndex].label;
let subsectionGrade = this.props.grades[userIndex].section_breakdown[subsectionIndex];
subsectionGrade.score_earned = data.override.earned_graded_override;
subsectionGrade.score_possible = data.override.possible_graded_override;
const updatedMsg = {
visible: true,
username: username,
subsectionName: subsectionName,
};
this.setState({ websocketMsg: updatedMsg });
this.props.gradeUpdateSuccess(this.props.match.params.courseId, this.props.grades);
}
setNewModalState = (userEntry, subsection) => {
@@ -120,6 +149,8 @@ export default class Gradebook extends React.Component {
updateAssignmentTypes = (event) => {
this.props.filterColumns(event, this.props.grades[0]);
const updatedQueryStrings = this.updateQueryParams('assignmentType', event);
this.props.history.push(updatedQueryStrings);
}
updateTracks = (event) => {
@@ -132,8 +163,10 @@ export default class Gradebook extends React.Component {
this.props.match.params.courseId,
this.props.selectedCohort,
selectedTrackSlug,
this.props.selectedAssignmentType,
);
this.updateQueryParams('track', selectedTrackSlug);
const updatedQueryStrings = this.updateQueryParams('track', selectedTrackSlug);
this.props.history.push(updatedQueryStrings);
};
updateCohorts = (event) => {
@@ -146,19 +179,11 @@ export default class Gradebook extends React.Component {
this.props.match.params.courseId,
selectedCohortId,
this.props.selectedTrack,
this.props.selectedAssignmentType,
);
this.updateQueryParams('cohort', selectedCohortId);
};
mapSelectedAssignmentTypeEntry = (entry) => {
const selectedAssignmentTypeEntry = this.props.assignmentTypes
.find(x => x.id === parseInt(entry, 10));
if (selectedAssignmentTypeEntry) {
return selectedAssignmentTypeEntry.name;
}
return 'All';
};
mapSelectedCohortEntry = (entry) => {
const selectedCohortEntry = this.props.cohorts.find(x => x.id === parseInt(entry, 10));
if (selectedCohortEntry) {
@@ -181,7 +206,6 @@ export default class Gradebook extends React.Component {
percent: (entries, areGradesFrozen) => entries.map((entry) => {
const results = { username: entry.username };
const assignments = entry.section_breakdown
.filter(section => section.is_graded)
.reduce((acc, subsection) => {
if (areGradesFrozen) {
acc[subsection.label] = `${this.roundGrade(subsection.percent * 100)} %`;
@@ -203,7 +227,6 @@ export default class Gradebook extends React.Component {
absolute: (entries, areGradesFrozen) => entries.map((entry) => {
const results = { username: entry.username };
const assignments = entry.section_breakdown
.filter(section => section.is_graded)
.reduce((acc, subsection) => {
const scoreEarned = this.roundGrade(subsection.score_earned);
const scorePossible = this.roundGrade(subsection.score_possible);
@@ -298,7 +321,7 @@ export default class Gradebook extends React.Component {
<InputSelect
name="assignment-types"
ariaLabel="Assignment Types"
value="All"
value={this.props.selectedAssignmentType}
options={this.mapAssignmentTypeEntries(this.props.assignmentTypes)}
onChange={this.updateAssignmentTypes}
/>
@@ -328,7 +351,7 @@ export default class Gradebook extends React.Component {
</div>
<div>
<div style={{ marginLeft: '10px', marginBottom: '10px' }}>
<a href={`${this.lmsInstructorDashboardUrl(this.props.match.params.courseId)}#view-data_download`}>Generate Grade Report</a>
<a className="btn btn-outline-primary mb-85" href={`${this.lmsInstructorDashboardUrl(this.props.match.params.courseId)}#view-data_download`}>Generate Grade Report</a>
</div>
<SearchField
onSubmit={value =>
@@ -337,14 +360,17 @@ export default class Gradebook extends React.Component {
value,
this.props.selectedCohort,
this.props.selectedTrack,
this.props.selectedAssignmentType,
)
}
inputLabel="Search Username:"
onChange={filterValue => this.setState({ filterValue })}
onClear={() =>
this.props.getUserGrades(
this.props.match.params.courseId,
this.props.selectedCohort,
this.props.selectedTrack,
this.props.selectedAssignmentType,
)
}
value={this.state.filterValue}
@@ -358,6 +384,12 @@ export default class Gradebook extends React.Component {
onClose={() => this.props.updateBanner(false)}
open={this.props.showSuccess}
/>
<StatusAlert
alertType="success"
dialog={`Grade for user ${this.state.websocketMsg.username} in ${this.state.websocketMsg.subsectionName} was updated.`}
onClose={() => this.setState({ websocketMsg : false })}
open={this.state.websocketMsg.visible}
/>
{PageButtons(this.props)}
<div className="gbook">
<Table
@@ -366,9 +398,6 @@ export default class Gradebook extends React.Component {
this.props.grades,
this.props.areGradesFrozen,
)}
tableSortable
defaultSortDirection="asc"
defaultSortedColumn="username"
rowHeaderColumnKey="username"
/>
</div>
@@ -425,6 +454,7 @@ Gradebook.defaultProps = {
},
selectedCohort: null,
selectedTrack: null,
selectedAssignmentType: 'All',
showSpinner: false,
tracks: [],
};
@@ -443,7 +473,6 @@ Gradebook.propTypes = {
section_breakdown: PropTypes.arrayOf(PropTypes.shape({
attempted: PropTypes.bool,
category: PropTypes.string,
is_graded: PropTypes.bool,
label: PropTypes.string,
module_id: PropTypes.string,
percent: PropTypes.number,
@@ -457,9 +486,10 @@ Gradebook.propTypes = {
headings: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string,
key: PropTypes.string,
columnSortable: PropTypes.bool,
onSort: PropTypes.func,
})).isRequired,
history: PropTypes.shape({
push: PropTypes.func,
}).isRequired,
location: PropTypes.shape({
search: PropTypes.string,
}),
@@ -469,6 +499,7 @@ Gradebook.propTypes = {
}),
}),
searchForUser: PropTypes.func.isRequired,
selectedAssignmentType: PropTypes.string,
selectedCohort: PropTypes.shape({
name: PropTypes.string,
}),

View File

@@ -10,7 +10,7 @@ exports[`PageButtons prev not null, next not null 1`] = `
}
>
<button
className="btn btn-primary"
className="btn btn-outline-primary"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
@@ -25,7 +25,7 @@ exports[`PageButtons prev not null, next not null 1`] = `
Previous Page
</button>
<button
className="btn btn-primary"
className="btn btn-outline-primary"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
@@ -52,7 +52,7 @@ exports[`PageButtons prev not null, next null 1`] = `
}
>
<button
className="btn btn-primary"
className="btn btn-outline-primary"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
@@ -67,7 +67,7 @@ exports[`PageButtons prev not null, next null 1`] = `
Previous Page
</button>
<button
className="btn btn-primary"
className="btn btn-outline-primary"
disabled={true}
onBlur={[Function]}
onClick={[Function]}
@@ -94,7 +94,7 @@ exports[`PageButtons prev null, next not null 1`] = `
}
>
<button
className="btn btn-primary"
className="btn btn-outline-primary"
disabled={true}
onBlur={[Function]}
onClick={[Function]}
@@ -109,7 +109,7 @@ exports[`PageButtons prev null, next not null 1`] = `
Previous Page
</button>
<button
className="btn btn-primary"
className="btn btn-outline-primary"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
@@ -136,7 +136,7 @@ exports[`PageButtons prev null, next null 1`] = `
}
>
<button
className="btn btn-primary"
className="btn btn-outline-primary"
disabled={true}
onBlur={[Function]}
onClick={[Function]}
@@ -151,7 +151,7 @@ exports[`PageButtons prev null, next null 1`] = `
Previous Page
</button>
<button
className="btn btn-primary"
className="btn btn-outline-primary"
disabled={true}
onBlur={[Function]}
onClick={[Function]}

View File

@@ -4,7 +4,8 @@ import { Button } from '@edx/paragon';
export default function PageButtons({
prevPage, nextPage, selectedTrack, selectedCohort, getPrevNextGrades, match,
prevPage, nextPage, selectedTrack, selectedCohort, selectedAssignmentType,
getPrevNextGrades, match,
}) {
return (
<div
@@ -14,27 +15,29 @@ export default function PageButtons({
<Button
label="Previous Page"
style={{ margin: '20px' }}
buttonType="primary"
buttonType="outline-primary"
disabled={!prevPage}
onClick={() =>
getPrevNextGrades(
prevPage,
match.params.courseId,
selectedCohort,
selectedTrack,
match.params.courseId,
selectedAssignmentType,
)}
/>
<Button
label="Next Page"
style={{ margin: '20px' }}
buttonType="primary"
buttonType="outline-primary"
disabled={!nextPage}
onClick={() =>
getPrevNextGrades(
nextPage,
match.params.courseId,
selectedCohort,
selectedTrack,
match.params.courseId,
selectedAssignmentType,
)}
/>
</div>
@@ -51,6 +54,7 @@ PageButtons.defaultProps = {
prevPage: '',
selectedCohort: null,
selectedTrack: null,
selectedAssignmentType: null,
};
PageButtons.propTypes = {
@@ -62,6 +66,7 @@ PageButtons.propTypes = {
}),
nextPage: PropTypes.string,
prevPage: PropTypes.string,
selectedAssignmentType: PropTypes.string,
selectedCohort: PropTypes.shape({
name: PropTypes.string,
}),

View File

@@ -5,6 +5,7 @@ import {
fetchGrades,
fetchMatchingUserGrades,
fetchPrevNextGrades,
gradeUpdateSuccess,
updateGrades,
toggleGradeFormat,
filterColumns,
@@ -32,6 +33,7 @@ const mapStateToProps = state => (
cohorts: state.cohorts.results,
selectedTrack: state.grades.selectedTrack,
selectedCohort: state.grades.selectedCohort,
selectedAssignmentType: state.grades.selectedAssignmentType,
format: state.grades.gradeFormat,
showSuccess: state.grades.showSuccess,
prevPage: state.grades.prevPage,
@@ -45,14 +47,14 @@ const mapStateToProps = state => (
const mapDispatchToProps = dispatch => (
{
getUserGrades: (courseId, cohort, track) => {
dispatch(fetchGrades(courseId, cohort, track));
getUserGrades: (courseId, cohort, track, assignmentType) => {
dispatch(fetchGrades(courseId, cohort, track, assignmentType));
},
searchForUser: (courseId, searchText, cohort, track) => {
dispatch(fetchMatchingUserGrades(courseId, searchText, cohort, track, false));
searchForUser: (courseId, searchText, cohort, track, assignmentType) => {
dispatch(fetchMatchingUserGrades(courseId, searchText, cohort, track, assignmentType, false));
},
getPrevNextGrades: (endpoint, cohort, track, courseId) => {
dispatch(fetchPrevNextGrades(endpoint, cohort, track, courseId));
getPrevNextGrades: (endpoint, courseId, cohort, track, assignmentType) => {
dispatch(fetchPrevNextGrades(endpoint, courseId, cohort, track, assignmentType));
},
getCohorts: (courseId) => {
dispatch(fetchCohorts(courseId));
@@ -78,6 +80,9 @@ const mapDispatchToProps = dispatch => (
getRoles: (matchParams, urlQuery) => {
dispatch(getRoles(matchParams, urlQuery));
},
gradeUpdateSuccess: (courseId, data) => {
dispatch(gradeUpdateSuccess(courseId, data));
},
}
);

View File

@@ -7,36 +7,24 @@ import {
GRADE_UPDATE_SUCCESS,
GRADE_UPDATE_FAILURE,
TOGGLE_GRADE_FORMAT,
SORT_GRADES,
FILTER_COLUMNS,
UPDATE_BANNER,
} from '../constants/actionTypes/grades';
import LmsApiService from '../services/LmsApiService';
import store from '../store';
import { headingMapper, gradeSortMap, sortAlphaAsc } from './utils';
import { headingMapper, sortAlphaAsc } from './utils';
import apiClient from '../apiClient';
const defaultAssignmentFilter = 'All';
const sortGrades = (columnName, direction) => {
const sortFn = gradeSortMap(columnName, direction);
const { results } = store.getState().grades;
results.sort(sortFn);
/* have to make a copy of results or React wont know there was
* a change and wont trigger a re-render
*/
return ({ type: SORT_GRADES, results: [...results] });
};
const startedFetchingGrades = () => ({ type: STARTED_FETCHING_GRADES });
const finishedFetchingGrades = () => ({ type: FINISHED_FETCHING_GRADES });
const errorFetchingGrades = () => ({ type: ERROR_FETCHING_GRADES });
const gotGrades = (grades, cohort, track, headings, prev, next, courseId) => ({
const gotGrades = (grades, cohort, track, assignmentType, headings, prev, next, courseId) => ({
type: GOT_GRADES,
grades,
cohort,
track,
assignmentType,
headings,
prev,
next,
@@ -61,13 +49,13 @@ const toggleGradeFormat = formatType => ({ type: TOGGLE_GRADE_FORMAT, formatType
const filterColumns = (filterType, exampleUser) => (
dispatch => dispatch({
type: FILTER_COLUMNS,
headings: headingMapper(filterType)(dispatch, exampleUser),
headings: headingMapper(filterType)(exampleUser),
})
);
const updateBanner = showSuccess => ({ type: UPDATE_BANNER, showSuccess });
const fetchGrades = (courseId, cohort, track, showSuccess) => (
const fetchGrades = (courseId, cohort, track, assignmentType, showSuccess) => (
(dispatch) => {
dispatch(startedFetchingGrades());
return LmsApiService.fetchGradebookData(courseId, null, cohort, track)
@@ -77,7 +65,8 @@ const fetchGrades = (courseId, cohort, track, showSuccess) => (
data.results.sort(sortAlphaAsc),
cohort,
track,
headingMapper(defaultAssignmentFilter)(dispatch, data.results[0]),
assignmentType,
headingMapper(assignmentType || defaultAssignmentFilter)(data.results[0]),
data.previous,
data.next,
courseId,
@@ -91,7 +80,14 @@ const fetchGrades = (courseId, cohort, track, showSuccess) => (
}
);
const fetchMatchingUserGrades = (courseId, searchText, cohort, track, showSuccess) => (
const fetchMatchingUserGrades = (
courseId,
searchText,
cohort,
track,
assignmentType,
showSuccess,
) => (
(dispatch) => {
dispatch(startedFetchingGrades());
return LmsApiService.fetchGradebookData(courseId, searchText, cohort, track)
@@ -101,7 +97,8 @@ const fetchMatchingUserGrades = (courseId, searchText, cohort, track, showSucces
data.results.sort(sortAlphaAsc),
cohort,
track,
headingMapper(defaultAssignmentFilter)(dispatch, data.results[0]),
assignmentType,
headingMapper(assignmentType || defaultAssignmentFilter)(data.results[0]),
data.previous,
data.next,
courseId,
@@ -115,7 +112,7 @@ const fetchMatchingUserGrades = (courseId, searchText, cohort, track, showSucces
}
);
const fetchPrevNextGrades = (endpoint, cohort, track, courseId) => (
const fetchPrevNextGrades = (endpoint, courseId, cohort, track, assignmentType) => (
(dispatch) => {
dispatch(startedFetchingGrades());
return apiClient.get(endpoint)
@@ -125,7 +122,8 @@ const fetchPrevNextGrades = (endpoint, cohort, track, courseId) => (
data.results.sort(sortAlphaAsc),
cohort,
track,
headingMapper(defaultAssignmentFilter)(dispatch, data.results[0]),
assignmentType,
headingMapper(assignmentType || defaultAssignmentFilter)(data.results[0]),
data.previous,
data.next,
courseId,
@@ -138,7 +136,6 @@ const fetchPrevNextGrades = (endpoint, cohort, track, courseId) => (
}
);
const updateGrades = (courseId, updateData, searchText, cohort, track) => (
(dispatch) => {
dispatch(gradeUpdateRequest());
@@ -146,7 +143,14 @@ const updateGrades = (courseId, updateData, searchText, cohort, track) => (
.then(response => response.data)
.then((data) => {
dispatch(gradeUpdateSuccess(courseId, data));
dispatch(fetchMatchingUserGrades(courseId, searchText, cohort, track, true));
// dispatch(fetchMatchingUserGrades(
// courseId,
// searchText,
// cohort,
// track,
// defaultAssignmentFilter,
// true,
// ));
})
.catch((error) => {
dispatch(gradeUpdateFailure(courseId, error));
@@ -167,7 +171,6 @@ export {
gradeUpdateFailure,
updateGrades,
toggleGradeFormat,
sortGrades,
filterColumns,
updateBanner,
};

View File

@@ -27,7 +27,8 @@ describe('actions', () => {
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const expectedCohort = 1;
const expectedTrack = 'verified';
const fetchGradesURL = `${configuration.LMS_BASE_URL}/api/grades/v1/gradebook/${courseId}/?page_size=10&cohort_id=${expectedCohort}&enrollment_mode=${expectedTrack}`;
const expectedAssignmentType = 'Exam';
const fetchGradesURL = `${configuration.LMS_BASE_URL}/api/grades/v1/gradebook/${courseId}/?page_size=25&cohort_id=${expectedCohort}&enrollment_mode=${expectedTrack}`;
const responseData = {
next: `${fetchGradesURL}&cursor=2344fda`,
previous: null,
@@ -94,18 +95,15 @@ describe('actions', () => {
grades: responseData.results.sort(sortAlphaAsc),
cohort: expectedCohort,
track: expectedTrack,
assignmentType: expectedAssignmentType,
headings: [
{
columnSortable: true,
key: 'username',
label: 'Username',
onSort: expect.anything(),
},
{
columnSortable: true,
key: 'total',
label: 'Total',
onSort: expect.anything(),
},
],
prev: responseData.previous,
@@ -120,10 +118,15 @@ describe('actions', () => {
axiosMock.onGet(fetchGradesURL)
.replyOnce(200, JSON.stringify(responseData));
return store.dispatch(fetchGrades(courseId, expectedCohort, expectedTrack, false))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
return store.dispatch(fetchGrades(
courseId,
expectedCohort,
expectedTrack,
expectedAssignmentType,
false,
)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('dispatches failure action after fetching grades', () => {
@@ -136,10 +139,53 @@ describe('actions', () => {
axiosMock.onGet(fetchGradesURL)
.replyOnce(500, JSON.stringify({}));
return store.dispatch(fetchGrades(courseId, expectedCohort, expectedTrack, false))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
return store.dispatch(fetchGrades(
courseId,
expectedCohort,
expectedTrack,
expectedAssignmentType,
false,
)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('dispatches success action on empty response after fetching grades', () => {
const emptyResponseData = {
next: responseData.next,
previous: responseData.previous,
results: [],
};
const expectedActions = [
{ type: STARTED_FETCHING_GRADES },
{
type: GOT_GRADES,
grades: [],
cohort: expectedCohort,
track: expectedTrack,
assignmentType: expectedAssignmentType,
headings: [],
prev: responseData.previous,
next: responseData.next,
courseId,
},
{ type: FINISHED_FETCHING_GRADES },
{ type: UPDATE_BANNER, showSuccess: false },
];
const store = mockStore();
axiosMock.onGet(fetchGradesURL)
.replyOnce(200, JSON.stringify(emptyResponseData));
return store.dispatch(fetchGrades(
courseId,
expectedCohort,
expectedTrack,
expectedAssignmentType,
false,
)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});
});

View File

@@ -26,7 +26,7 @@ const getRoles = (courseId, urlQuery) => (
&& allowedRoles.includes(role.role)));
dispatch(gotRoles(canUserViewGradebook, courseId));
if (canUserViewGradebook) {
dispatch(fetchGrades(courseId, urlQuery.cohort, urlQuery.track));
dispatch(fetchGrades(courseId, urlQuery.cohort, urlQuery.track, urlQuery.assignmentType));
dispatch(fetchTracks(courseId));
dispatch(fetchCohorts(courseId));
dispatch(fetchAssignmentTypes(courseId));

View File

@@ -1,5 +1,3 @@
import { sortGrades } from './grades';
const sortAlphaAsc = (gradeRowA, gradeRowB) => {
const a = gradeRowA.username.toUpperCase();
const b = gradeRowB.username.toUpperCase();
@@ -12,78 +10,24 @@ const sortAlphaAsc = (gradeRowA, gradeRowB) => {
return 0;
};
const sortAlphaDesc = (gradeRowA, gradeRowB) => {
const a = gradeRowA.username.toUpperCase();
const b = gradeRowB.username.toUpperCase();
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
return 0;
};
const sortNumerically = (colKey, direction) => {
function getPercents(gradeRowA, gradeRowB) {
if (colKey !== 'total') {
return {
a: gradeRowA.section_breakdown.find(x => x.label === colKey).percent,
b: gradeRowB.section_breakdown.find(x => x.label === colKey).percent,
};
}
return {
a: gradeRowA.percent,
b: gradeRowB.percent,
};
}
function sortNumAsc(gradeRowA, gradeRowB) {
const { a, b } = getPercents(gradeRowA, gradeRowB);
return a - b;
}
function sortNumDesc(gradeRowA, gradeRowB) {
const { a, b } = getPercents(gradeRowA, gradeRowB);
return b - a;
}
return direction === 'desc' ? sortNumDesc : sortNumAsc;
};
function gradeSortMap(columnName, direction) {
if (columnName === 'username' && direction === 'desc') {
return sortAlphaDesc;
} else if (columnName === 'username') {
return sortAlphaAsc;
}
return sortNumerically(columnName, direction);
}
const headingMapper = (filterKey) => {
function all(dispatch, entry) {
function all(entry) {
if (entry) {
const results = [{
label: 'Username',
key: 'username',
columnSortable: true,
onSort: (direction) => { dispatch(sortGrades('username', direction)); },
}];
const assignmentHeadings = entry.section_breakdown
.filter(section => section.is_graded && section.label)
.filter(section => section.label)
.map(s => ({
label: s.label,
key: s.label,
columnSortable: true,
onSort: direction => dispatch(sortGrades(s.label, direction)),
}));
const totals = [{
label: 'Total',
key: 'total',
columnSortable: true,
onSort: direction => dispatch(sortGrades('total', direction)),
}];
return results.concat(assignmentHeadings).concat(totals);
@@ -91,28 +35,24 @@ const headingMapper = (filterKey) => {
return [];
}
function some(dispatch, entry) {
function some(entry) {
if (!entry) return [];
const results = [{
label: 'Username',
key: 'username',
columnSortable: true,
onSort: (direction) => { dispatch(sortGrades('username', direction)); },
}];
const assignmentHeadings = entry.section_breakdown
.filter(section => section.is_graded && section.label && section.category === filterKey)
.filter(section => section.label && section.category === filterKey)
.map(s => ({
label: s.label,
key: s.label,
columnSortable: false,
onSort: (direction) => { this.sortNumerically(s.label, direction); },
}));
const totals = [{
label: 'Total',
key: 'total',
columnSortable: true,
onSort: direction => dispatch(sortGrades('total', direction)),
}];
return results.concat(assignmentHeadings).concat(totals);
@@ -121,5 +61,5 @@ const headingMapper = (filterKey) => {
return filterKey === 'All' ? all : some;
};
export { headingMapper, gradeSortMap, sortAlphaAsc };
export { headingMapper, sortAlphaAsc };

View File

@@ -8,7 +8,6 @@ const GRADE_UPDATE_SUCCESS = 'GRADE_UPDATE_SUCCESS';
const GRADE_UPDATE_FAILURE = 'GRADE_UPDATE_FAILURE';
const TOGGLE_GRADE_FORMAT = 'TOGGLE_GRADE_FORMAT';
const SORT_GRADES = 'SORT_GRADES';
const FILTER_COLUMNS = 'FILTER_COLUMNS';
const UPDATE_BANNER = 'UPDATE_BANNER';
@@ -21,7 +20,6 @@ export {
GRADE_UPDATE_SUCCESS,
GRADE_UPDATE_FAILURE,
TOGGLE_GRADE_FORMAT,
SORT_GRADES,
FILTER_COLUMNS,
UPDATE_BANNER,
};

View File

@@ -5,7 +5,6 @@ import {
TOGGLE_GRADE_FORMAT,
FILTER_COLUMNS,
UPDATE_BANNER,
SORT_GRADES,
} from '../constants/actionTypes/grades';
const initialState = {
@@ -32,6 +31,7 @@ const grades = (state = initialState, action) => {
errorFetching: false,
selectedTrack: action.track,
selectedCohort: action.cohort,
selectedAssignmentType: action.assignmentType,
prevPage: action.prev,
nextPage: action.next,
showSpinner: false,
@@ -65,11 +65,6 @@ const grades = (state = initialState, action) => {
...state,
showSuccess: action.showSuccess,
};
case SORT_GRADES:
return {
...state,
results: action.results,
};
default:
return state;
}

View File

@@ -6,7 +6,6 @@ import {
TOGGLE_GRADE_FORMAT,
FILTER_COLUMNS,
UPDATE_BANNER,
SORT_GRADES,
} from '../constants/actionTypes/grades';
const initialState = {
@@ -164,17 +163,6 @@ describe('grades reducer', () => {
})).toEqual(expected);
});
it('updates sort grades state success', () => {
const expected = {
...initialState,
results: gradesData,
};
expect(grades(undefined, {
type: SORT_GRADES,
results: gradesData,
})).toEqual(expected);
});
it('updates fetch grades failure state', () => {
const expected = {
...initialState,

View File

@@ -3,7 +3,7 @@ import { configuration } from '../../config';
class LmsApiService {
static baseUrl = configuration.LMS_BASE_URL;
static pageSize = 10
static pageSize = 25
static fetchGradebookData(courseId, searchText, cohort, track) {
let gradebookUrl = `${LmsApiService.baseUrl}/api/grades/v1/gradebook/${courseId}/`;

View File

@@ -24,6 +24,7 @@ const eventsMap = {
courseId: action.courseId,
track: action.track,
cohort: action.cohort,
assignmentType: action.assignmentType,
prev: action.prev,
next: action.next,
},