Clean up MFE/Redux usage (#179)
* refactor: clean up/standardize selector usage * fix: fix eslint errors * chore: bump version to 1.4.25 Co-authored-by: Ben Warzeski <bwarzeski@edx.org>
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@edx/frontend-app-gradebook",
|
||||
"version": "1.4.24",
|
||||
"version": "1.4.25",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@edx/frontend-app-gradebook",
|
||||
"version": "1.4.24",
|
||||
"version": "1.4.25",
|
||||
"description": "edx editable gradebook-ui to manipulate grade overrides on subsections",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
} from '@edx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faDownload } from '@fortawesome/free-solid-svg-icons';
|
||||
import { configuration } from '../../config';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import { configuration } from '../../config';
|
||||
import { submitFileUploadFormData } from '../../data/actions/grades';
|
||||
import { getBulkManagementHistory } from '../../data/selectors/grades';
|
||||
|
||||
export class BulkManagement extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -183,14 +183,14 @@ BulkManagement.propTypes = {
|
||||
uploadSuccess: PropTypes.bool,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
bulkImportError: state.grades.bulkManagement
|
||||
&& state.grades.bulkManagement.errorMessages
|
||||
? `Errors while processing: ${state.grades.bulkManagement.errorMessages.join(', ')}`
|
||||
: '',
|
||||
bulkManagementHistory: getBulkManagementHistory(state),
|
||||
uploadSuccess: !!(state.grades.bulkManagement && state.grades.bulkManagement.uploadSuccess),
|
||||
});
|
||||
export const mapStateToProps = (state) => {
|
||||
const { grades } = selectors;
|
||||
return {
|
||||
bulkImportError: grades.bulkImportError(state),
|
||||
bulkManagementHistory: grades.bulkManagementHistoryEntries(state),
|
||||
uploadSuccess: grades.uploadSuccess(state),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
submitFileUploadFormData,
|
||||
|
||||
@@ -10,14 +10,18 @@ import {
|
||||
Table,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import {
|
||||
doneViewingAssignment,
|
||||
updateGrades,
|
||||
} from '../../data/actions/grades';
|
||||
|
||||
const GRADE_OVERRIDE_HISTORY_COLUMNS = [{ label: 'Date', key: 'date' }, { label: 'Grader', key: 'grader' },
|
||||
const GRADE_OVERRIDE_HISTORY_COLUMNS = [
|
||||
{ label: 'Date', key: 'date' },
|
||||
{ label: 'Grader', key: 'grader' },
|
||||
{ label: 'Reason', key: 'reason' },
|
||||
{ label: 'Adjusted grade', key: 'adjustedGrade' }];
|
||||
{ label: 'Adjusted grade', key: 'adjustedGrade' },
|
||||
];
|
||||
|
||||
export class EditModal extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -185,15 +189,18 @@ EditModal.propTypes = {
|
||||
updateGrades: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
gradeOverrides: state.grades.gradeOverrideHistoryResults,
|
||||
gradeOverrideCurrentEarnedGradedOverride: state.grades.gradeOverrideCurrentEarnedGradedOverride,
|
||||
gradeOverrideHistoryError: state.grades.gradeOverrideHistoryError,
|
||||
gradeOriginalEarnedGraded: state.grades.gradeOriginalEarnedGraded,
|
||||
grdaeOriginalPossibleGraded: state.grades.grdaeOriginalPossibleGraded,
|
||||
selectedCohort: state.filters.cohort,
|
||||
selectedTrack: state.filters.track,
|
||||
});
|
||||
export const mapStateToProps = (state) => {
|
||||
const { filters, grades } = selectors;
|
||||
return {
|
||||
gradeOverrides: grades.gradeOverrides(state),
|
||||
gradeOverrideCurrentEarnedGradedOverride: grades.gradeOverrideCurrentEarnedGradedOverride(state),
|
||||
gradeOverrideHistoryError: grades.gradeOverrideHistoryError(state),
|
||||
gradeOriginalEarnedGraded: grades.gradeOriginalEarnedGraded(state),
|
||||
gradeOriginalPossibleGraded: grades.gradeOriginalPossibleGraded(state),
|
||||
selectedCohort: filters.cohort(state),
|
||||
selectedTrack: filters.track(state),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
doneViewingAssignment,
|
||||
|
||||
@@ -3,9 +3,9 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { selectableAssignmentLabels } from 'data/selectors/filters';
|
||||
import * as gradesActions from 'data/actions/grades';
|
||||
import * as filterActions from 'data/actions/filters';
|
||||
import selectors from 'data/selectors';
|
||||
|
||||
import SelectGroup from '../SelectGroup';
|
||||
|
||||
@@ -85,13 +85,16 @@ AssignmentFilter.propTypes = {
|
||||
updateAssignmentFilter: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
assignmentFilterOptions: selectableAssignmentLabels(state),
|
||||
selectedAssignment: (state.filters.assignment || {}).label,
|
||||
selectedAssignmentType: state.filters.assignmentType,
|
||||
selectedCohort: state.filters.cohort,
|
||||
selectedTrack: state.filters.track,
|
||||
});
|
||||
export const mapStateToProps = (state) => {
|
||||
const { filters } = selectors;
|
||||
return {
|
||||
assignmentFilterOptions: filters.selectableAssignmentLabels(state),
|
||||
selectedAssignment: filters.selectedAssignmentLabel(state),
|
||||
selectedAssignmentType: filters.assignmentType(state),
|
||||
selectedCohort: filters.cohort(state),
|
||||
selectedTrack: filters.track(state),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
updateAssignmentFilter: filterActions.updateAssignmentFilter,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import { updateAssignmentFilter } from 'data/actions/filters';
|
||||
import { updateGradesIfAssignmentGradeFiltersSet } from 'data/actions/grades';
|
||||
import {
|
||||
@@ -9,12 +10,20 @@ import {
|
||||
mapDispatchToProps,
|
||||
} from '.';
|
||||
|
||||
jest.mock('data/selectors/filters', () => ({
|
||||
jest.mock('data/selectors', () => ({
|
||||
/** Mocking to use passed state for validation purposes */
|
||||
selectableAssignmentLabels: jest.fn().mockImplementation((state) => ({
|
||||
state,
|
||||
selectableLabels: 'selectableLabels',
|
||||
})),
|
||||
filters: {
|
||||
selectableAssignmentLabels: jest.fn(() => ([{
|
||||
label: 'assigNment',
|
||||
subsectionLabel: 'subsection',
|
||||
type: 'assignMentType',
|
||||
id: 'subsectionId',
|
||||
}])),
|
||||
selectedAssignmentLabel: jest.fn(() => 'assigNment'),
|
||||
assignmentType: jest.fn(() => 'assignMentType'),
|
||||
cohort: jest.fn(() => 'COhort'),
|
||||
track: jest.fn(() => 'traCK'),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AssignmentFilter', () => {
|
||||
@@ -103,51 +112,47 @@ describe('AssignmentFilter', () => {
|
||||
},
|
||||
};
|
||||
describe('assignmentFilterOptions', () => {
|
||||
it('is drawn from selectableAssignmentLabels', () => {
|
||||
expect(mapStateToProps(state).assignmentFilterOptions).toEqual({
|
||||
state,
|
||||
selectableLabels: 'selectableLabels',
|
||||
});
|
||||
it('is selected from filters.selectableAssignmentLabels', () => {
|
||||
expect(
|
||||
mapStateToProps(state).assignmentFilterOptions,
|
||||
).toEqual(
|
||||
selectors.filters.selectableAssignmentLabels(state),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('selectedAsssignment', () => {
|
||||
it('is undefined if no assignment is passed', () => {
|
||||
expect(
|
||||
mapStateToProps({ filters: {} }).selectedAssignment,
|
||||
).toEqual(undefined);
|
||||
});
|
||||
it('returns the label of selected assignment if there is one', () => {
|
||||
describe('selectedAssignment', () => {
|
||||
it('is selected from filters.selectedAssignmentLabel', () => {
|
||||
expect(
|
||||
mapStateToProps(state).selectedAssignment,
|
||||
).toEqual(
|
||||
state.filters.assignment.label,
|
||||
selectors.filters.selectedAssignmentLabel(state),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('selectedAssignmentType', () => {
|
||||
it('is drawn from state.filters.assignmentType', () => {
|
||||
it('is selected from filters.assignmentType', () => {
|
||||
expect(
|
||||
mapStateToProps(state).selectedAssignmentType,
|
||||
).toEqual(
|
||||
state.filters.assignmentType,
|
||||
selectors.filters.assignmentType(state),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('selectedCohort', () => {
|
||||
it('is drawn from state.filters.cohort', () => {
|
||||
it('is selected from filters.cohort', () => {
|
||||
expect(
|
||||
mapStateToProps(state).selectedCohort,
|
||||
).toEqual(
|
||||
state.filters.cohort,
|
||||
selectors.filters.cohort(state),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('selectedTrack', () => {
|
||||
it('is drawn from state.filters.track', () => {
|
||||
it('is selected from filters.track', () => {
|
||||
expect(
|
||||
mapStateToProps(state).selectedTrack,
|
||||
).toEqual(
|
||||
state.filters.track,
|
||||
selectors.filters.track(state),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Button } from '@edx/paragon';
|
||||
|
||||
import * as gradesActions from 'data/actions/grades';
|
||||
import * as filterActions from 'data/actions/filters';
|
||||
import selectors from 'data/selectors';
|
||||
|
||||
import PercentGroup from '../PercentGroup';
|
||||
|
||||
@@ -106,12 +107,15 @@ AssignmentGradeFilter.propTypes = {
|
||||
updateAssignmentLimits: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
selectedAssignment: (state.filters.assignment || {}).label,
|
||||
selectedAssignmentType: state.filters.assignmentType,
|
||||
selectedCohort: state.filters.cohort,
|
||||
selectedTrack: state.filters.track,
|
||||
});
|
||||
export const mapStateToProps = (state) => {
|
||||
const { filters } = selectors;
|
||||
return {
|
||||
selectedAssignment: filters.selectedAssignmentLabel(state),
|
||||
selectedAssignmentType: filters.assignmentType(state),
|
||||
selectedCohort: filters.cohort(state),
|
||||
selectedTrack: filters.track(state),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
getUserGrades: gradesActions.fetchGrades,
|
||||
|
||||
@@ -3,8 +3,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { selectableAssignmentLabels } from 'data/selectors/filters';
|
||||
import * as gradesActions from 'data/actions/grades';
|
||||
import selectors from 'data/selectors';
|
||||
|
||||
import SelectGroup from '../SelectGroup';
|
||||
|
||||
@@ -66,9 +66,9 @@ AssignmentTypeFilter.propTypes = {
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
assignmentTypes: state.assignmentTypes.results,
|
||||
assignmentFilterOptions: selectableAssignmentLabels(state),
|
||||
selectedAssignmentType: state.filters.assignmentType,
|
||||
assignmentTypes: selectors.assignmentTypes.allAssignmentTypes(state),
|
||||
assignmentFilterOptions: selectors.filters.selectableAssignmentLabels(state),
|
||||
selectedAssignmentType: selectors.filters.assignmentType(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import { filterAssignmentType } from 'data/actions/grades';
|
||||
|
||||
import {
|
||||
@@ -9,12 +10,20 @@ import {
|
||||
mapDispatchToProps,
|
||||
} from '.';
|
||||
|
||||
jest.mock('data/selectors/filters', () => ({
|
||||
jest.mock('data/selectors', () => ({
|
||||
/** Mocking to use passed state for validation purposes */
|
||||
selectableAssignmentLabels: jest.fn().mockImplementation((state) => ({
|
||||
state,
|
||||
selectableLabels: 'selectableLabels',
|
||||
})),
|
||||
assignmentTypes: {
|
||||
allAssignmentTypes: jest.fn(() => (['assignment', 'labs'])),
|
||||
},
|
||||
filters: {
|
||||
selectableAssignmentLabels: jest.fn(() => ([{
|
||||
label: 'assigNment',
|
||||
subsectionLabel: 'subsection',
|
||||
type: 'assignMentType',
|
||||
id: 'subsectionId',
|
||||
}])),
|
||||
assignmentType: jest.fn(() => 'assignMentType'),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AssignmentTypeFilter', () => {
|
||||
@@ -89,26 +98,29 @@ describe('AssignmentTypeFilter', () => {
|
||||
},
|
||||
};
|
||||
describe('assignmentTypes', () => {
|
||||
it('is drawn from assignmentTypes.results', () => {
|
||||
expect(mapStateToProps(state).assignmentTypes).toEqual(
|
||||
state.assignmentTypes.results,
|
||||
it('is selected from assignmentTypes.allAssignmentTypes', () => {
|
||||
expect(
|
||||
mapStateToProps(state).assignmentTypes,
|
||||
).toEqual(
|
||||
selectors.assignmentTypes.allAssignmentTypes(state),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('assignmentFilterOptions', () => {
|
||||
it('is drawn from selectableAssignmentLabels', () => {
|
||||
expect(mapStateToProps(state).assignmentFilterOptions).toEqual({
|
||||
state,
|
||||
selectableLabels: 'selectableLabels',
|
||||
});
|
||||
it('is selected from filters.selectableAssignmentLabels', () => {
|
||||
expect(
|
||||
mapStateToProps(state).assignmentFilterOptions,
|
||||
).toEqual(
|
||||
selectors.filters.selectableAssignmentLabels(state),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('selectedAssignmentType', () => {
|
||||
it('is drawn from state.filters.assignmentType', () => {
|
||||
it('is selected from filters.assignmentType', () => {
|
||||
expect(
|
||||
mapStateToProps(state).selectedAssignmentType,
|
||||
).toEqual(
|
||||
state.filters.assignmentType,
|
||||
selectors.filters.assignmentType(state),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
|
||||
import { updateCourseGradeFilter } from 'data/actions/filters';
|
||||
import { fetchGrades } from 'data/actions/grades';
|
||||
import selectors from 'data/selectors';
|
||||
import PercentGroup from '../PercentGroup';
|
||||
|
||||
export class CourseGradeFilter extends React.Component {
|
||||
@@ -118,11 +119,14 @@ CourseGradeFilter.propTypes = {
|
||||
updateFilter: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
selectedCohort: state.filters.cohort,
|
||||
selectedTrack: state.filters.track,
|
||||
selectedAssignmentType: state.filters.assignmentType,
|
||||
});
|
||||
export const mapStateToProps = (state) => {
|
||||
const { filters } = selectors;
|
||||
return {
|
||||
selectedCohort: filters.cohort(state),
|
||||
selectedTrack: filters.track(state),
|
||||
selectedAssignmentType: filters.assignmentType(state),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
updateFilter: updateCourseGradeFilter,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { fetchGrades } from 'data/actions/grades';
|
||||
import selectors from 'data/selectors';
|
||||
import SelectGroup from '../SelectGroup';
|
||||
|
||||
export class StudentGroupsFilter extends React.Component {
|
||||
@@ -134,13 +135,16 @@ StudentGroupsFilter.propTypes = {
|
||||
})),
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
cohorts: state.cohorts.results,
|
||||
selectedAssignmentType: state.filters.assignmentType,
|
||||
selectedCohort: state.filters.cohort,
|
||||
selectedTrack: state.filters.track,
|
||||
tracks: state.tracks.results,
|
||||
});
|
||||
export const mapStateToProps = (state) => {
|
||||
const { filters, cohorts, tracks } = selectors;
|
||||
return {
|
||||
cohorts: cohorts.allCohorts(state),
|
||||
selectedAssignmentType: filters.assignmentType(state),
|
||||
selectedCohort: filters.cohort(state),
|
||||
selectedTrack: filters.track(state),
|
||||
tracks: tracks.allTracks(state),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
getUserGrades: fetchGrades,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { connect } from 'react-redux';
|
||||
import { Collapsible, Form } from '@edx/paragon';
|
||||
|
||||
import * as filterActions from 'data/actions/filters';
|
||||
import selectors from 'data/selectors';
|
||||
|
||||
import AssignmentTypeFilter from './AssignmentTypeFilter';
|
||||
import AssignmentFilter from './AssignmentFilter';
|
||||
@@ -109,7 +110,7 @@ GradebookFilters.propTypes = {
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
includeCourseRoleMembers: state.filters.includeCourseRoleMembers,
|
||||
includeCourseRoleMembers: selectors.filters.includeCourseRoleMembers(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
Table, OverlayTrigger, Tooltip, Icon,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import { formatDateForDisplay } from '../../data/actions/utils';
|
||||
import { getHeadings } from '../../data/selectors/grades';
|
||||
import { fetchGradeOverrideHistory } from '../../data/actions/grades';
|
||||
import { EMAIL_HEADING, TOTAL_COURSE_GRADE_HEADING, USERNAME_HEADING } from '../../data/constants/grades';
|
||||
import selectors from '../../data/selectors';
|
||||
|
||||
const DECIMAL_PRECISION = 2;
|
||||
|
||||
@@ -215,12 +217,15 @@ GradebookTable.propTypes = {
|
||||
fetchGradeOverrideHistory: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
areGradesFrozen: state.assignmentTypes.areGradesFrozen,
|
||||
format: state.grades.gradeFormat,
|
||||
grades: state.grades.results,
|
||||
headings: getHeadings(state),
|
||||
});
|
||||
export const mapStateToProps = (state) => {
|
||||
const { assignmentTypes, grades, root } = selectors;
|
||||
return {
|
||||
areGradesFrozen: assignmentTypes.areGradesFrozen(state),
|
||||
format: grades.gradeFormat(state),
|
||||
grades: grades.allGrades(state),
|
||||
headings: root.getHeadings(state),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
fetchGradeOverrideHistory,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { Button, Icon, SearchField } from '@edx/paragon';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import {
|
||||
fetchGrades,
|
||||
fetchMatchingUserGrades,
|
||||
@@ -96,11 +97,14 @@ SearchControls.propTypes = {
|
||||
selectedTrack: PropTypes.string,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
selectedAssignmentType: state.filters.assignmentType,
|
||||
selectedTrack: state.filters.track,
|
||||
selectedCohort: state.filters.cohort,
|
||||
});
|
||||
export const mapStateToProps = (state) => {
|
||||
const { filters } = selectors;
|
||||
return {
|
||||
selectedAssignmentType: filters.assignmentType(state),
|
||||
selectedTrack: filters.track(state),
|
||||
selectedCohort: filters.cohort(state),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
getUserGrades: fetchGrades,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { StatusAlert } from '@edx/paragon';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import { closeBanner } from '../../data/actions/grades';
|
||||
|
||||
export const maxCourseGradeInvalidMessage = 'Maximum course grade value must be between 0 and 100. ';
|
||||
@@ -61,7 +62,7 @@ StatusAlerts.propTypes = {
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
showSuccessBanner: state.grades.showSuccess,
|
||||
showSuccessBanner: selectors.grades.showSuccess(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import selectors from 'data/selectors';
|
||||
import Gradebook from '../../components/Gradebook';
|
||||
import {
|
||||
fetchGradeOverrideHistory,
|
||||
@@ -19,96 +20,45 @@ import {
|
||||
updateAssignmentFilter,
|
||||
updateAssignmentLimits,
|
||||
} from '../../data/actions/filters';
|
||||
import stateHasMastersTrack from '../../data/selectors/tracks';
|
||||
import {
|
||||
getBulkManagementHistory,
|
||||
getHeadings,
|
||||
formatMinAssignmentGrade,
|
||||
formatMaxAssignmentGrade,
|
||||
formatMinCourseGrade,
|
||||
formatMaxCourseGrade,
|
||||
} from '../../data/selectors/grades';
|
||||
import { selectableAssignmentLabels } from '../../data/selectors/filters';
|
||||
import { hasSpecialBulkManagementAccess } from '../../data/selectors/special';
|
||||
import { getCohortNameById } from '../../data/selectors/cohorts';
|
||||
import { fetchAssignmentTypes } from '../../data/actions/assignmentTypes';
|
||||
import { getRoles } from '../../data/actions/roles';
|
||||
import LmsApiService from '../../data/services/LmsApiService';
|
||||
|
||||
function shouldShowSpinner(state) {
|
||||
if (state.roles.canUserViewGradebook === true) {
|
||||
return state.grades.showSpinner;
|
||||
} if (state.roles.canUserViewGradebook === false) {
|
||||
return false;
|
||||
} // canUserViewGradebook === null
|
||||
return true;
|
||||
}
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const {
|
||||
root,
|
||||
assignmentTypes,
|
||||
filters,
|
||||
grades,
|
||||
roles,
|
||||
} = selectors;
|
||||
|
||||
const mapStateToProps = (state, ownProps) => (
|
||||
{
|
||||
areGradesFrozen: state.assignmentTypes.areGradesFrozen,
|
||||
assignmentTypes: state.assignmentTypes.results,
|
||||
assignmentFilterOptions: selectableAssignmentLabels(state),
|
||||
bulkImportError: state.grades.bulkManagement
|
||||
&& state.grades.bulkManagement.errorMessages
|
||||
? `Errors while processing: ${state.grades.bulkManagement.errorMessages.join(', ')}`
|
||||
: '',
|
||||
bulkManagementHistory: getBulkManagementHistory(state),
|
||||
courseId: ownProps.match.params.courseId,
|
||||
canUserViewGradebook: state.roles.canUserViewGradebook,
|
||||
filteredUsersCount: state.grades.filteredUsersCount,
|
||||
format: state.grades.gradeFormat,
|
||||
gradeExportUrl: LmsApiService.getGradeExportCsvUrl(ownProps.match.params.courseId, {
|
||||
cohort: getCohortNameById(state, state.filters.cohort),
|
||||
track: state.filters.track,
|
||||
assignment: (state.filters.assignment || {}).id,
|
||||
assignmentType: state.filters.assignmentType,
|
||||
assignmentGradeMin: formatMinAssignmentGrade(
|
||||
state.filters.assignmentGradeMin,
|
||||
{ assignmentId: (state.filters.assignment || {}).id },
|
||||
),
|
||||
assignmentGradeMax: formatMaxAssignmentGrade(
|
||||
state.filters.assignmentGradeMax,
|
||||
{ assignmentId: (state.filters.assignment || {}).id },
|
||||
),
|
||||
courseGradeMin: formatMinCourseGrade(state.filters.courseGradeMin),
|
||||
courseGradeMax: formatMaxCourseGrade(state.filters.courseGradeMax),
|
||||
excludedCourseRoles: state.filters.includeCourseRoleMembers ? '' : 'all',
|
||||
}),
|
||||
grades: state.grades.results,
|
||||
headings: getHeadings(state),
|
||||
interventionExportUrl:
|
||||
LmsApiService.getInterventionExportCsvUrl(ownProps.match.params.courseId, {
|
||||
cohort: getCohortNameById(state, state.filters.cohort),
|
||||
assignment: (state.filters.assignment || {}).id,
|
||||
assignmentType: state.filters.assignmentType,
|
||||
assignmentGradeMin: formatMinAssignmentGrade(
|
||||
state.filters.assignmentGradeMin,
|
||||
{ assignmentId: (state.filters.assignment || {}).id },
|
||||
),
|
||||
assignmentGradeMax: formatMaxAssignmentGrade(
|
||||
state.filters.assignmentGradeMax,
|
||||
{ assignmentId: (state.filters.assignment || {}).id },
|
||||
),
|
||||
courseGradeMin: formatMinCourseGrade(state.filters.courseGradeMin),
|
||||
courseGradeMax: formatMaxCourseGrade(state.filters.courseGradeMax),
|
||||
}),
|
||||
const { courseId } = ownProps.match.params;
|
||||
return {
|
||||
courseId,
|
||||
areGradesFrozen: assignmentTypes.areGradesFrozen(state),
|
||||
assignmentTypes: assignmentTypes.allAssignmentTypes(state),
|
||||
assignmentFilterOptions: filters.selectableAssignmentLabels(state),
|
||||
bulkImportError: grades.bulkImportError(state),
|
||||
bulkManagementHistory: grades.bulkManagementHistoryEntries(state),
|
||||
canUserViewGradebook: roles.canUserViewGradebook(state),
|
||||
filteredUsersCount: grades.filteredUsersCount(state),
|
||||
format: grades.gradeFormat(state),
|
||||
gradeExportUrl: root.gradeExportUrl(state, { courseId }),
|
||||
grades: grades.allGrades(state),
|
||||
headings: root.getHeadings(state),
|
||||
interventionExportUrl: root.interventionExportUrl(state, { courseId }),
|
||||
nextPage: state.grades.nextPage,
|
||||
prevPage: state.grades.prevPage,
|
||||
selectedTrack: state.filters.track,
|
||||
selectedCohort: state.filters.cohort,
|
||||
selectedAssignmentType: state.filters.assignmentType,
|
||||
selectedAssignment: (state.filters.assignment || {}).label,
|
||||
showBulkManagement: (
|
||||
hasSpecialBulkManagementAccess(ownProps.match.params.courseId)
|
||||
|| (stateHasMastersTrack(state) && state.config.bulkManagementAvailable)
|
||||
),
|
||||
showSpinner: shouldShowSpinner(state),
|
||||
totalUsersCount: state.grades.totalUsersCount,
|
||||
uploadSuccess: !!(state.grades.bulkManagement
|
||||
&& state.grades.bulkManagement.uploadSuccess),
|
||||
}
|
||||
);
|
||||
selectedTrack: filters.track(state),
|
||||
selectedCohort: filters.cohort(state),
|
||||
selectedAssignmentType: filters.assignmentType(state),
|
||||
selectedAssignment: filters.selectedAssignmentLabel(state),
|
||||
showBulkManagement: root.showBulkManagement(state, { courseId }),
|
||||
showSpinner: root.shouldShowSpinner(state),
|
||||
totalUsersCount: grades.totalUsersCount(state),
|
||||
uploadSuccess: grades.uploadSuccess(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
downloadBulkGradesReport,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import filterSelectors from 'data/selectors/filters';
|
||||
import initialFilters from '../constants/filters';
|
||||
import {
|
||||
INITIALIZE_FILTERS,
|
||||
@@ -7,9 +8,10 @@ import {
|
||||
UPDATE_COURSE_GRADE_LIMITS,
|
||||
UPDATE_INCLUDE_COURSE_ROLE_MEMBERS,
|
||||
} from '../constants/actionTypes/filters';
|
||||
import { getFilters } from '../selectors/filters';
|
||||
import { fetchGrades } from './grades';
|
||||
|
||||
const { allFilters } = filterSelectors;
|
||||
|
||||
const initializeFilters = ({
|
||||
assignment = initialFilters.assignment,
|
||||
assignmentType = initialFilters.assignmentType,
|
||||
@@ -69,7 +71,7 @@ const updateIncludeCourseRoleMembersFilter = (includeCourseRoleMembers) => ({
|
||||
const updateIncludeCourseRoleMembers = includeCourseRoleMembers => (dispatch, getState) => {
|
||||
dispatch(updateIncludeCourseRoleMembersFilter(includeCourseRoleMembers));
|
||||
const state = getState();
|
||||
const { cohort, track, assignmentType } = getFilters(state);
|
||||
const { cohort, track, assignmentType } = allFilters(state);
|
||||
dispatch(fetchGrades(state.grades.courseId, cohort, track, assignmentType));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import gradesSelectors from 'data/selectors/grades';
|
||||
import filtersSelectors from 'data/selectors/filters';
|
||||
import {
|
||||
STARTED_FETCHING_GRADES,
|
||||
FINISHED_FETCHING_GRADES,
|
||||
@@ -27,10 +29,14 @@ import {
|
||||
import GRADE_OVERRIDE_HISTORY_ERROR_DEFAULT_MSG from '../constants/errors';
|
||||
import LmsApiService from '../services/LmsApiService';
|
||||
import { sortAlphaAsc, formatDateForDisplay } from './utils';
|
||||
import {
|
||||
formatMaxAssignmentGrade, formatMinAssignmentGrade, formatMaxCourseGrade, formatMinCourseGrade,
|
||||
} from '../selectors/grades';
|
||||
import { getFilters } from '../selectors/filters';
|
||||
|
||||
const {
|
||||
formatMaxAssignmentGrade,
|
||||
formatMinAssignmentGrade,
|
||||
formatMaxCourseGrade,
|
||||
formatMinCourseGrade,
|
||||
} = gradesSelectors;
|
||||
const { allFilters } = filtersSelectors;
|
||||
|
||||
const defaultAssignmentFilter = 'All';
|
||||
|
||||
@@ -142,7 +148,7 @@ const fetchGrades = (
|
||||
courseGradeMin,
|
||||
courseGradeMax,
|
||||
includeCourseRoleMembers,
|
||||
} = getFilters(getState());
|
||||
} = allFilters(getState());
|
||||
const { id: assignmentId } = assignment || {};
|
||||
const assignmentGradeMax = formatMaxAssignmentGrade(assignmentMax, { assignmentId });
|
||||
const assignmentGradeMin = formatMinAssignmentGrade(assignmentMin, { assignmentId });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import filtersSelectors from 'data/selectors/filters';
|
||||
import {
|
||||
GOT_ROLES,
|
||||
ERROR_FETCHING_ROLES,
|
||||
@@ -6,9 +7,10 @@ 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 { allFilters } = filtersSelectors;
|
||||
|
||||
const allowedRoles = ['staff', 'instructor', 'support'];
|
||||
|
||||
const gotRoles = (canUserViewGradebook, courseId) => ({
|
||||
@@ -26,7 +28,7 @@ const getRoles = courseId => (
|
||||
|| (response.roles.some(role => (role.course_id === courseId)
|
||||
&& allowedRoles.includes(role.role)));
|
||||
dispatch(gotRoles(canUserViewGradebook, courseId));
|
||||
const { cohort, track, assignmentType } = getFilters(getState());
|
||||
const { cohort, track, assignmentType } = allFilters(getState());
|
||||
if (canUserViewGradebook) {
|
||||
dispatch(fetchGrades(courseId, cohort, track, assignmentType));
|
||||
dispatch(fetchTracks(courseId));
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
/* eslint-disable camelcase */
|
||||
import tracksSelectors from 'data/selectors/tracks';
|
||||
import {
|
||||
STARTED_FETCHING_TRACKS,
|
||||
GOT_TRACKS,
|
||||
ERROR_FETCHING_TRACKS,
|
||||
} from '../constants/actionTypes/tracks';
|
||||
import { hasMastersTrack } from '../selectors/tracks';
|
||||
import { fetchBulkUpgradeHistory } from './grades';
|
||||
import LmsApiService from '../services/LmsApiService';
|
||||
|
||||
const { hasMastersTrack } = tracksSelectors;
|
||||
|
||||
const startedFetchingTracks = () => ({ type: STARTED_FETCHING_TRACKS });
|
||||
const errorFetchingTracks = () => ({ type: ERROR_FETCHING_TRACKS });
|
||||
const gotTracks = tracks => ({ type: GOT_TRACKS, tracks });
|
||||
const gotTracks = (tracks) => ({ type: GOT_TRACKS, tracks });
|
||||
|
||||
const fetchTracks = courseId => (
|
||||
const fetchTracks = (courseId) => (
|
||||
(dispatch) => {
|
||||
dispatch(startedFetchingTracks());
|
||||
return LmsApiService.fetchTracks(courseId)
|
||||
.then(response => response.data)
|
||||
.then((data) => {
|
||||
dispatch(gotTracks(data.course_modes));
|
||||
if (hasMastersTrack(data.course_modes)) {
|
||||
.then(({ data }) => data)
|
||||
.then(({ course_modes }) => {
|
||||
dispatch(gotTracks(course_modes));
|
||||
if (hasMastersTrack(course_modes)) {
|
||||
dispatch(fetchBulkUpgradeHistory(courseId));
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import filterSelectors from 'data/selectors/filters';
|
||||
import { GOT_GRADES, FILTER_BY_ASSIGNMENT_TYPE } from '../constants/actionTypes/grades';
|
||||
|
||||
import {
|
||||
INITIALIZE_FILTERS,
|
||||
UPDATE_ASSIGNMENT_FILTER,
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
RESET_FILTERS,
|
||||
UPDATE_INCLUDE_COURSE_ROLE_MEMBERS,
|
||||
} from '../constants/actionTypes/filters';
|
||||
|
||||
import initialFilters from '../constants/filters';
|
||||
|
||||
import { getAssignmentsFromResultsSubstate, chooseRelevantAssignmentData } from '../selectors/filters';
|
||||
|
||||
const { getAssignmentsFromResultsSubstate, chooseRelevantAssignmentData } = filterSelectors;
|
||||
const initialState = {};
|
||||
|
||||
const reducer = (state = initialState, action) => {
|
||||
|
||||
6
src/data/selectors/assignmentTypes.js
Normal file
6
src/data/selectors/assignmentTypes.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const selectors = {
|
||||
areGradesFrozen: ({ assignmentTypes }) => assignmentTypes.areGradesFrozen,
|
||||
allAssignmentTypes: ({ assignmentTypes }) => assignmentTypes.results,
|
||||
};
|
||||
|
||||
export default selectors;
|
||||
@@ -1,10 +1,16 @@
|
||||
const getCohorts = state => state.cohorts.results || [];
|
||||
const allCohorts = state => state.cohorts.results || [];
|
||||
|
||||
const getCohortById = (state, selectedCohortId) => {
|
||||
const cohort = getCohorts(state).find(coh => coh.id === selectedCohortId);
|
||||
const cohort = allCohorts(state).find(coh => coh.id === selectedCohortId);
|
||||
return cohort;
|
||||
};
|
||||
|
||||
const getCohortNameById = (state, selectedCohortId) => (getCohortById(state, selectedCohortId) || {}).name;
|
||||
|
||||
export { getCohortById, getCohortNameById, getCohorts };
|
||||
const selectors = {
|
||||
getCohortById,
|
||||
getCohortNameById,
|
||||
allCohorts,
|
||||
};
|
||||
|
||||
export default selectors;
|
||||
|
||||
@@ -1,38 +1,73 @@
|
||||
const getFilters = state => state.filters || {};
|
||||
import simpleSelectorFactory from '../utils';
|
||||
|
||||
const getAssignmentsFromResultsSubstate = results => (results[0] || {}).section_breakdown || [];
|
||||
const allFilters = (state) => state.filters || {};
|
||||
|
||||
const getAssignmentsFromResultsSubstate = (results) => (
|
||||
(results[0] || {}).section_breakdown || []
|
||||
);
|
||||
|
||||
const selectableAssignments = (state) => {
|
||||
const selectedAssignmentType = getFilters(state).assignmentType;
|
||||
const selectedAssignmentType = allFilters(state).assignmentType;
|
||||
const needToFilter = selectedAssignmentType && selectedAssignmentType !== 'All';
|
||||
const allAssignments = getAssignmentsFromResultsSubstate(state.grades.results);
|
||||
if (needToFilter) {
|
||||
return allAssignments.filter(assignment => assignment.category === selectedAssignmentType);
|
||||
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 chooseRelevantAssignmentData = ({
|
||||
label,
|
||||
subsection_name: subsectionLabel,
|
||||
category,
|
||||
module_id: id,
|
||||
}) => ({
|
||||
label, subsectionLabel, category, id,
|
||||
});
|
||||
|
||||
const selectableAssignmentLabels = state => selectableAssignments(state).map(chooseRelevantAssignmentData);
|
||||
const selectableAssignmentLabels = (state) => (
|
||||
selectableAssignments(state).map(chooseRelevantAssignmentData)
|
||||
);
|
||||
|
||||
const typeOfSelectedAssignment = (state) => {
|
||||
const selectedAssignmentLabel = getFilters(state).assignment;
|
||||
const selectedAssignmentLabel = allFilters(state).assignment;
|
||||
const sectionBreakdown = (state.grades.results[0] || {}).section_breakdown || [];
|
||||
const selectedAssignment = sectionBreakdown.find(section => section.label === selectedAssignmentLabel);
|
||||
const selectedAssignment = sectionBreakdown.find(
|
||||
({ label }) => label === selectedAssignmentLabel,
|
||||
);
|
||||
return selectedAssignment && selectedAssignment.category;
|
||||
};
|
||||
|
||||
export {
|
||||
const simpleSelectors = simpleSelectorFactory(
|
||||
({ filters }) => filters,
|
||||
[
|
||||
'assignment',
|
||||
'assignmentGradeMax',
|
||||
'assignmentGradeMin',
|
||||
'assignmentType',
|
||||
'cohort',
|
||||
'courseGradeMax',
|
||||
'courseGradeMin',
|
||||
'track',
|
||||
'includeCourseRoleMembers',
|
||||
],
|
||||
);
|
||||
const selectedAssignmentId = (state) => (simpleSelectors.assignment(state) || {}).id;
|
||||
const selectedAssignmentLabel = (state) => (simpleSelectors.assignment(state) || {}).label;
|
||||
|
||||
const selectors = {
|
||||
...simpleSelectors,
|
||||
selectedAssignmentId,
|
||||
selectedAssignmentLabel,
|
||||
|
||||
selectableAssignmentLabels,
|
||||
selectableAssignments,
|
||||
getFilters,
|
||||
allFilters,
|
||||
typeOfSelectedAssignment,
|
||||
chooseRelevantAssignmentData,
|
||||
getAssignmentsFromResultsSubstate,
|
||||
};
|
||||
|
||||
export default selectors;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatDateForDisplay } from '../actions/utils';
|
||||
import { getFilters } from './filters';
|
||||
import simpleSelectorFactory from '../utils';
|
||||
import { EMAIL_HEADING, TOTAL_COURSE_GRADE_HEADING, USERNAME_HEADING } from '../constants/grades';
|
||||
|
||||
const getRowsProcessed = (data) => {
|
||||
@@ -16,26 +16,25 @@ const getRowsProcessed = (data) => {
|
||||
};
|
||||
};
|
||||
|
||||
const transformHistoryEntry = (historyRow) => {
|
||||
const {
|
||||
modified,
|
||||
original_filename: originalFilename,
|
||||
data,
|
||||
...rest
|
||||
} = historyRow;
|
||||
const transformHistoryEntry = ({
|
||||
modified,
|
||||
original_filename: originalFilename,
|
||||
data,
|
||||
...rest
|
||||
}) => ({
|
||||
timeUploaded: formatDateForDisplay(new Date(modified)),
|
||||
originalFilename,
|
||||
summaryOfRowsProcessed: getRowsProcessed(data),
|
||||
...rest,
|
||||
});
|
||||
|
||||
const timeUploaded = formatDateForDisplay(new Date(modified));
|
||||
const summaryOfRowsProcessed = getRowsProcessed(data);
|
||||
const bulkManagementHistory = ({ grades: { bulkManagement } }) => (
|
||||
(bulkManagement.history || [])
|
||||
);
|
||||
|
||||
return {
|
||||
timeUploaded,
|
||||
originalFilename,
|
||||
summaryOfRowsProcessed,
|
||||
...rest,
|
||||
};
|
||||
};
|
||||
const getBulkManagementHistoryFromState = state => state.grades.bulkManagement.history || [];
|
||||
const getBulkManagementHistory = state => getBulkManagementHistoryFromState(state).map(transformHistoryEntry);
|
||||
const bulkManagementHistoryEntries = (state) => (
|
||||
bulkManagementHistory(state).map(transformHistoryEntry)
|
||||
);
|
||||
|
||||
const headingMapper = (category, label = 'All') => {
|
||||
const filters = {
|
||||
@@ -67,19 +66,9 @@ const headingMapper = (category, label = 'All') => {
|
||||
};
|
||||
};
|
||||
|
||||
const getExampleSectionBreakdown = state => (state.grades.results[0] || {}).section_breakdown || [];
|
||||
|
||||
const getHeadings = (state) => {
|
||||
const filters = getFilters(state) || {};
|
||||
const {
|
||||
assignmentType: selectedAssignmentType,
|
||||
assignment: selectedAssignment,
|
||||
} = filters;
|
||||
const assignments = getExampleSectionBreakdown(state);
|
||||
const type = selectedAssignmentType || 'All';
|
||||
const assignment = (selectedAssignment || {}).label || 'All';
|
||||
return headingMapper(type, assignment)(assignments);
|
||||
};
|
||||
const getExampleSectionBreakdown = ({ grades }) => (
|
||||
(grades.results[0] || {}).section_breakdown || []
|
||||
);
|
||||
|
||||
const composeFilters = (...predicates) => (percentGrade, options = {}) => predicates.reduce((accum, predicate) => {
|
||||
if (predicate(percentGrade, options)) {
|
||||
@@ -102,20 +91,56 @@ const assignmentIdIsDefined = (percentGrade, { assignmentId }) => (
|
||||
|
||||
const formatMaxCourseGrade = composeFilters(percentGradeIsMax);
|
||||
const formatMinCourseGrade = composeFilters(percentGradeIsMin);
|
||||
|
||||
const formatMaxAssignmentGrade = composeFilters(
|
||||
percentGradeIsMax,
|
||||
assignmentIdIsDefined,
|
||||
);
|
||||
|
||||
const formatMinAssignmentGrade = composeFilters(
|
||||
percentGradeIsMin,
|
||||
assignmentIdIsDefined,
|
||||
);
|
||||
|
||||
export {
|
||||
getBulkManagementHistory,
|
||||
getHeadings,
|
||||
const simpleSelectors = simpleSelectorFactory(
|
||||
({ grades }) => grades,
|
||||
[
|
||||
'filteredUsersCount',
|
||||
'totalUsersCount',
|
||||
'gradeFormat',
|
||||
'showSpinner',
|
||||
'gradeOverrideCurrentEarnedGradedOverride',
|
||||
'gradeOverrideHistoryError',
|
||||
'gradeOriginalEarnedGraded',
|
||||
'gradeOriginalPossibleGraded',
|
||||
'showSuccess',
|
||||
],
|
||||
);
|
||||
|
||||
const allGrades = ({ grades: { results } }) => results;
|
||||
const uploadSuccess = ({ grades: { bulkManagement } }) => (!!bulkManagement && bulkManagement.uploadSuccess);
|
||||
|
||||
const bulkImportError = ({ grades: { bulkManagement } }) => (
|
||||
(!!bulkManagement && bulkManagement.errorMessages)
|
||||
? `Errors while processing: ${bulkManagement.errorMessages.join(', ')}`
|
||||
: ''
|
||||
);
|
||||
const gradeOverrides = ({ grades }) => grades.gradeOverrideHistoryResults;
|
||||
|
||||
const selectors = {
|
||||
bulkImportError,
|
||||
formatMinAssignmentGrade,
|
||||
formatMaxAssignmentGrade,
|
||||
formatMaxCourseGrade,
|
||||
formatMinCourseGrade,
|
||||
getExampleSectionBreakdown,
|
||||
headingMapper,
|
||||
|
||||
...simpleSelectors,
|
||||
allGrades,
|
||||
uploadSuccess,
|
||||
bulkManagementHistoryEntries,
|
||||
gradeOverrides,
|
||||
};
|
||||
|
||||
export default selectors;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getBulkManagementHistory } from './grades';
|
||||
import selectors from './grades';
|
||||
|
||||
const genericHistoryRow = {
|
||||
id: 5,
|
||||
@@ -15,14 +15,14 @@ const genericHistoryRow = {
|
||||
},
|
||||
};
|
||||
|
||||
describe('getBulkManagementHistory', () => {
|
||||
describe('bulkManagementHistoryEntries', () => {
|
||||
it('handles history being as-yet unloaded', () => {
|
||||
const result = getBulkManagementHistory({ grades: { bulkManagement: {} } });
|
||||
const result = selectors.bulkManagementHistoryEntries({ grades: { bulkManagement: {} } });
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('formats dates for us', () => {
|
||||
const result = getBulkManagementHistory({
|
||||
const result = selectors.bulkManagementHistoryEntries({
|
||||
grades: {
|
||||
bulkManagement: {
|
||||
history: [
|
||||
@@ -37,7 +37,7 @@ describe('getBulkManagementHistory', () => {
|
||||
});
|
||||
|
||||
const exerciseGetRowsProcessed = (input, expectation) => {
|
||||
const result = getBulkManagementHistory({
|
||||
const result = selectors.bulkManagementHistoryEntries({
|
||||
grades: {
|
||||
bulkManagement: {
|
||||
history: [
|
||||
@@ -50,7 +50,7 @@ describe('getBulkManagementHistory', () => {
|
||||
expect(summaryOfRowsProcessed).toEqual(expect.objectContaining(expectation));
|
||||
};
|
||||
|
||||
it('calculates skippage', () => {
|
||||
it('calculates skipped rows', () => {
|
||||
exerciseGetRowsProcessed({
|
||||
total_rows: 100,
|
||||
processed_rows: 10,
|
||||
|
||||
71
src/data/selectors/index.js
Normal file
71
src/data/selectors/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import LmsApiService from 'data/services/LmsApiService';
|
||||
|
||||
import assignmentTypes from './assignmentTypes';
|
||||
import cohorts from './cohorts';
|
||||
import filters from './filters';
|
||||
import grades from './grades';
|
||||
import roles from './roles';
|
||||
import special from './special';
|
||||
import tracks from './tracks';
|
||||
|
||||
const lmsApiServiceArgs = (state) => ({
|
||||
cohort: cohorts.getCohortNameById(state, filters.cohort(state)),
|
||||
assignment: filters.selectedAssignmentId(state),
|
||||
assignmentType: filters.assignmentType(state),
|
||||
assignmentGradeMin: grades.formatMinAssignmentGrade(
|
||||
filters.assignmentGradeMin(state),
|
||||
{ assignmentId: filters.selectedAssignmentId(state) },
|
||||
),
|
||||
assignmentGradeMax: grades.formatMaxAssignmentGrade(
|
||||
filters.assignmentGradeMax(state),
|
||||
{ assignmentId: filters.selectedAssignmentId(state) },
|
||||
),
|
||||
courseGradeMin: grades.formatMinCourseGrade(filters.courseGradeMin(state)),
|
||||
courseGradeMax: grades.formatMaxCourseGrade(filters.courseGradeMax(state)),
|
||||
});
|
||||
|
||||
const gradeExportUrl = (state, { courseId }) => (
|
||||
LmsApiService.getGradeExportCsvUrl(courseId, {
|
||||
...lmsApiServiceArgs(state),
|
||||
excludeCourseRoles: filters.includeCourseRoleMembers(state) ? '' : 'all',
|
||||
})
|
||||
);
|
||||
|
||||
const interventionExportUrl = (state, { courseId }) => (
|
||||
LmsApiService.getInterventionExportCsvUrl(
|
||||
courseId,
|
||||
lmsApiServiceArgs(state),
|
||||
)
|
||||
);
|
||||
|
||||
const showBulkManagement = (state, { courseId }) => (
|
||||
special.hasSpecialBulkManagementAccess(courseId)
|
||||
|| (tracks.stateHasMastersTrack(state) && state.config.bulkManagementAvailable)
|
||||
);
|
||||
|
||||
const shouldShowSpinner = (state) => {
|
||||
const canView = roles.canUserViewGradebook(state);
|
||||
return canView && grades.showSpinner(state);
|
||||
};
|
||||
|
||||
const getHeadings = (state) => grades.headingMapper(
|
||||
filters.assignmentType(state) || 'All',
|
||||
filters.selectedAssignmentLabel(state) || 'All',
|
||||
)(grades.getExampleSectionBreakdown(state));
|
||||
|
||||
export default {
|
||||
root: {
|
||||
getHeadings,
|
||||
gradeExportUrl,
|
||||
interventionExportUrl,
|
||||
showBulkManagement,
|
||||
shouldShowSpinner,
|
||||
},
|
||||
assignmentTypes,
|
||||
cohorts,
|
||||
filters,
|
||||
grades,
|
||||
roles,
|
||||
special,
|
||||
tracks,
|
||||
};
|
||||
5
src/data/selectors/roles.js
Normal file
5
src/data/selectors/roles.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const selectors = {
|
||||
canUserViewGradebook: ({ roles }) => roles.canUserViewGradebook,
|
||||
};
|
||||
|
||||
export default selectors;
|
||||
@@ -3,10 +3,11 @@
|
||||
// Note that this does not affect whether or not the backend
|
||||
// LMS API will permit usage of the tool.
|
||||
|
||||
const hasSpecialBulkManagementAccess = courseId => {
|
||||
const specialIdList = process.env.BULK_MANAGEMENT_SPECIAL_ACCESS_COURSE_IDS || '';
|
||||
return specialIdList.split(',').includes(courseId);
|
||||
const selectors = {
|
||||
hasSpecialBulkManagementAccess: (courseId) => {
|
||||
const specialIdList = process.env.BULK_MANAGEMENT_SPECIAL_ACCESS_COURSE_IDS || '';
|
||||
return specialIdList.split(',').includes(courseId);
|
||||
},
|
||||
};
|
||||
|
||||
export { hasSpecialBulkManagementAccess };
|
||||
export default hasSpecialBulkManagementAccess;
|
||||
export default selectors;
|
||||
|
||||
@@ -3,10 +3,16 @@ const compose = (...fns) => {
|
||||
return (...args) => rest.reduce((accum, fn) => fn(accum), firstFunc(...args));
|
||||
};
|
||||
|
||||
const getTracks = state => state.tracks.results || [];
|
||||
const allTracks = state => state.tracks.results || [];
|
||||
const trackIsMasters = track => track.slug === 'masters';
|
||||
const hasMastersTrack = tracks => tracks.some(trackIsMasters);
|
||||
const stateHasMastersTrack = compose(hasMastersTrack, getTracks);
|
||||
const stateHasMastersTrack = compose(hasMastersTrack, allTracks);
|
||||
|
||||
export { hasMastersTrack, trackIsMasters };
|
||||
export default stateHasMastersTrack;
|
||||
const selectors = {
|
||||
allTracks,
|
||||
hasMastersTrack,
|
||||
stateHasMastersTrack,
|
||||
trackIsMasters,
|
||||
};
|
||||
|
||||
export default selectors;
|
||||
|
||||
19
src/data/utils.js
Normal file
19
src/data/utils.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Simple selector factory.
|
||||
* Takes a list of string keys, and returns a simple slector for each.
|
||||
*
|
||||
* @function
|
||||
* @param {Object|string[]} keys - If passed as object, Object.keys(keys) is used.
|
||||
* @return {Object} - object of `{[key]: ({key}) => key}`
|
||||
*/
|
||||
const simpleSelectorFactory = (transformer, keys) => {
|
||||
const selKeys = Array.isArray(keys) ? keys : Object.keys(keys);
|
||||
return selKeys.reduce(
|
||||
(obj, key) => ({
|
||||
...obj, [key]: (state) => transformer(state)[key],
|
||||
}),
|
||||
{ root: (state) => transformer(state) },
|
||||
);
|
||||
};
|
||||
|
||||
export default simpleSelectorFactory;
|
||||
27
src/data/utils.test.js
Normal file
27
src/data/utils.test.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import simpleSelectorFactory from './utils';
|
||||
|
||||
describe('Redux utilities - creators', () => {
|
||||
describe('simpleSelectors', () => {
|
||||
const data = { a: 1, b: 2, c: 3 };
|
||||
const state = {
|
||||
testGroup: data,
|
||||
other: 'stuff',
|
||||
};
|
||||
const transformer = ({ testGroup }) => testGroup;
|
||||
|
||||
test('given a list of strings, returns a dict w/ a simple selector per string', () => {
|
||||
const keys = ['a', 'b'];
|
||||
const selectors = simpleSelectorFactory(transformer, keys);
|
||||
expect(Object.keys(selectors)).toEqual(['root', ...keys]);
|
||||
expect(selectors.a(state)).toEqual(data.a);
|
||||
expect(selectors.b(state)).toEqual(data.b);
|
||||
});
|
||||
test('given an object for keys, returns a dict w/ simple selector per key', () => {
|
||||
const selectors = simpleSelectorFactory(transformer, data);
|
||||
expect(Object.keys(selectors)).toEqual(['root', ...Object.keys(data)]);
|
||||
expect(selectors.a(state)).toEqual(data.a);
|
||||
expect(selectors.b(state)).toEqual(data.b);
|
||||
expect(selectors.c(state)).toEqual(data.c);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user