fix: separate local and static grade data

This commit is contained in:
Ben Warzeski
2021-11-09 15:49:20 -05:00
parent e620bc0f59
commit 595148a82b
13 changed files with 144 additions and 82 deletions

View File

@@ -29,7 +29,10 @@ export class CriterionFeedback extends React.Component {
render() {
const {
config, isGrading, value, valueIsInvalid,
config,
isGrading,
value,
valueIsInvalid,
} = this.props;
if (config === feedbackRequirement.disabled) {
return null;
@@ -57,7 +60,7 @@ export class CriterionFeedback extends React.Component {
}
CriterionFeedback.defaultProps = {
value: '',
value: { local: '', review: '' },
};
CriterionFeedback.propTypes = {

View File

@@ -35,7 +35,7 @@ describe('Criterion Feedback', () => {
orderNum: 1,
config: 'config string',
isGrading: true,
value: 'some value',
value: { grading: 'grading value', review: 'review value' },
gradeStatus: gradeStatuses.ungraded,
setValue: jest.fn().mockName('this.props.setValue'),
valueIsInvalid: false,

View File

@@ -32,9 +32,10 @@ export class RadioCriterion extends React.Component {
isGrading,
radioIsInvalid,
} = this.props;
const value = isGrading ? data.grading : data.review;
return (
<>
<Form.RadioSet name={config.name} value={data.selectedOption || ''}>
<Form.RadioSet name={config.name} value={value}>
{config.options.map((option) => (
<Form.Radio
className="criteria-option"
@@ -60,8 +61,8 @@ export class RadioCriterion extends React.Component {
RadioCriterion.defaultProps = {
data: {
selectedOption: '',
feedback: '',
grading: '',
review: '',
},
};
@@ -86,8 +87,8 @@ RadioCriterion.propTypes = {
),
}).isRequired,
data: PropTypes.shape({
selectedOption: PropTypes.string,
feedback: PropTypes.string,
grading: PropTypes.string,
review: PropTypes.string,
}),
setCriterionOption: PropTypes.func.isRequired,
radioIsInvalid: PropTypes.bool.isRequired,

View File

@@ -18,8 +18,8 @@ jest.mock('data/redux/app/selectors', () => ({
}));
jest.mock('data/redux/grading/selectors', () => ({
selected: {
criterionGradeData: jest.fn((...args) => ({
selectedCriterionGradeData: args,
criterionSelectedOption: jest.fn((...args) => ({
selectedCriterionSelectedOption: args,
})),
criterionSelectedIsInvalid: jest.fn((...args) => ({
selectedCriterionSelectedIsInvalid: args,
@@ -54,8 +54,8 @@ describe('Radio Criterion Container', () => {
],
},
data: {
selectedOption: 'selected option',
feedback: 'data feedback',
review: 'selected review option',
grading: 'selected grading option',
},
setCriterionOption: jest.fn().mockName('this.props.setCriterionOption'),
radioIsInvalid: false,
@@ -142,9 +142,9 @@ describe('Radio Criterion Container', () => {
);
});
test('selectors.grading.selected.criterionGradeData', () => {
test('selectors.grading.selected.criterionSelectedOption', () => {
expect(mapped.data).toEqual(
selectors.grading.selected.criterionGradeData(testState, ownProps),
selectors.grading.selected.criterionSelectedOption(testState, ownProps),
);
});
test('selectors.grading.selected.criterionSelectedIsInvalid', () => {

View File

@@ -21,28 +21,4 @@ exports[`Criterion Feedback snapshot feedback value is invalid 1`] = `
exports[`Criterion Feedback snapshot is configure to disabled 1`] = `null`;
exports[`Criterion Feedback snapshot is graded 1`] = `
<Form.Group>
<Form.Control
as="input"
className="criterion-feedback feedback-input"
disabled={true}
floatingLabel="Comments"
onChange={[MockFunction this.onChange]}
value="some value"
/>
</Form.Group>
`;
exports[`Criterion Feedback snapshot is grading 1`] = `
<Form.Group>
<Form.Control
as="input"
className="criterion-feedback feedback-input"
disabled={false}
floatingLabel="Add comments"
onChange={[MockFunction this.onChange]}
value="some value"
/>
</Form.Group>
`;
exports[`Criterion Feedback snapshot is graded 1`] = ``;

View File

@@ -4,7 +4,7 @@ exports[`Radio Criterion Container snapshot is grading 1`] = `
<React.Fragment>
<Form.RadioSet
name="random name"
value="selected option"
value="selected grading option"
>
<Form.Radio
className="criteria-option"
@@ -32,7 +32,7 @@ exports[`Radio Criterion Container snapshot is not grading 1`] = `
<React.Fragment>
<Form.RadioSet
name="random name"
value="selected option"
value="selected review option"
>
<Form.Radio
className="criteria-option"

View File

@@ -56,7 +56,7 @@ export class RubricFeedback extends React.Component {
as="input"
className="rubric-feedback feedback-input"
floatingLabel={this.inputLabel}
value={value}
value={isGrading ? value.grading : value.review}
onChange={this.onChange}
disabled={!isGrading}
/>
@@ -71,7 +71,7 @@ export class RubricFeedback extends React.Component {
}
RubricFeedback.defaultProps = {
value: '',
value: { grading: '', review: '' },
};
RubricFeedback.propTypes = {

View File

@@ -89,7 +89,7 @@ describe('Rubric Feedback component', () => {
expect(el.isEmptyRender()).toEqual(false);
const input = el.find('.rubric-feedback.feedback-input');
expect(input.prop('disabled')).toEqual(false);
expect(input.prop('value')).toEqual(props.value);
expect(input.prop('value')).toEqual(props.value.grading);
});
test('is graded (the input are disabled)', () => {
@@ -100,7 +100,7 @@ describe('Rubric Feedback component', () => {
expect(el.isEmptyRender()).toEqual(false);
const input = el.find('.rubric-feedback.feedback-input');
expect(input.prop('disabled')).toEqual(true);
expect(input.prop('value')).toEqual(props.value);
expect(input.prop('value')).toEqual(props.value.review);
});
test('is having invalid feedback (feedback get render)', () => {

View File

@@ -69,7 +69,7 @@ exports[`Rubric Feedback component snapshot is graded 1`] = `
disabled={true}
floatingLabel="Comments"
onChange={[MockFunction this.onChange]}
value="some value"
value="review value"
/>
</Form.Group>
`;
@@ -100,7 +100,7 @@ exports[`Rubric Feedback component snapshot is grading 1`] = `
disabled={false}
floatingLabel="Add comments"
onChange={[MockFunction this.onChange]}
value="some value"
value="grading value"
/>
</Form.Group>
`;

View File

@@ -28,6 +28,18 @@ const initialState = {
* }
*/
},
localGradeData: {
/**
* <submissionId>: {
* overallFeedback: '',
* criteria: [{
* orderNum: 0,
* points: 0,
* comments: '',
* }],
* }
*/
},
activeIndex: null,
current: {
/**
@@ -59,35 +71,49 @@ const initialState = {
};
/**
* Updates the given state's gradeData entry for the seleted submission,
* overlaying the passed data on top of the existing data for the that
* submission.
* Updates the given state's gradeData entry for the seleted submission.
* @return {object} - new state
*/
export const updateGradeData = (state, data) => ({
export const loadGradeData = (state, data) => ({
...state,
gradeData: {
...state.gradeData,
[state.current.submissionId]: {
...state.gradeData[state.current.submissionId],
...data,
},
[state.current.submissionId]: { ...data },
},
});
/**
* Updates the given state's gradeData entry for the seleted submission,
* Updates the state's localGradeData entry for the seleted submission,
* overlaying the passed data on top of the existing data for the that
* submission.
* @return {object} - new state
*/
export const updateLocalGradeData = (state, data) => {
const currentId = state.current.submissionId;
return {
...state,
localGradeData: {
...state.localGradeData,
[currentId]: state.localGradeData[currentId]
? { ...state.localGradeData[currentId], ...data }
: { ...data },
},
};
};
/**
* Updates the given state's localGradeData entry for the seleted submission,
* overlaying the passed data on top of the existing data for the criterion
* at the given index (orderNum) for the rubric.
* @return {object} - new state
*/
export const updateCriterion = (state, orderNum, data) => {
const entry = state.gradeData[state.current.submissionId];
const entry = state.localGradeData[state.current.submissionId];
const criteria = {
...entry.criteria,
[orderNum]: { ...entry.criteria[orderNum], ...data },
};
return updateGradeData(state, { ...entry, criteria });
return updateLocalGradeData(state, { ...entry, criteria });
};
// eslint-disable-next-line no-unused-vars
@@ -129,7 +155,7 @@ const grading = createSlice({
selected: payload,
activeIndex: 0,
}),
startGrading: (state, { payload }) => updateGradeData(
startGrading: (state, { payload }) => updateLocalGradeData(
{
...state,
current: { ...state.current, lockStatus: lockStatuses.inProgress },
@@ -137,7 +163,7 @@ const grading = createSlice({
{ ...payload },
),
setRubricFeedback: (state, { payload }) => (
updateGradeData(state, { overallFeedback: payload })
updateLocalGradeData(state, { overallFeedback: payload })
),
setCriterionOption: (state, { payload: { orderNum, value } }) => (
updateCriterion(state, orderNum, { selectedOption: value })
@@ -166,12 +192,12 @@ const grading = createSlice({
lockStatus: lockStatuses.unlocked,
},
}),
clearGrade: (state) => {
const gradeData = { ...state.gradeData };
delete gradeData[state.current.submissionId];
stopGrading: (state) => {
const localGradeData = { ...state.localGradeData };
delete localGradeData[state.current.submissionId];
return {
...state,
gradeData,
localGradeData,
current: {
...state.current,
lockStatus: lockStatuses.unlocked,

View File

@@ -10,6 +10,7 @@ export const simpleSelectors = {
activeIndex: state => state.grading.activeIndex,
current: state => state.grading.current,
gradeData: state => state.grading.gradeData,
localGradeData: state => state.grading.localGradeData,
};
/**
@@ -121,12 +122,39 @@ selected.gradeData = createSelector(
);
/**
* Returns list of criterion grade data for the current selection
* @return {obj[]} criterion grade data entries
* Returns the local grade data for the selected submission
* @return {obj} local grade data
* { score, overallFeedback, criteria }
*/
selected.localGradeData = createSelector(
[module.selected.submissionId, module.simpleSelectors.localGradeData],
(submissionId, localGradeData) => localGradeData[submissionId],
);
/**
* Returns list of criterion grade data for the current selection for review
* and grading views.
* @return {obj} criterion grade data entries ({ review: [{}], grading: [{}] })
*/
selected.criteriaGradeData = createSelector(
[module.selected.gradeData],
(data) => (data ? data.criteria : []),
[module.selected.gradeData, module.selected.localGradeData],
(data, localData) => ({
grading: (localData ? localData.criteria : []),
review: (data ? data.criteria : []),
}),
);
/**
* Returns list of criterion grade data for the current selection for both
* review and grading views.
* @return {obj} criterion grade data entries ({ review: [{}], grading: [{}] })
*/
selected.criteriaGradeData = createSelector(
[module.selected.gradeData, module.selected.localGradeData],
(data, localData) => ({
grading: (localData ? localData.criteria : {}),
review: (data ? data.criteria : {}),
}),
);
/**
@@ -139,12 +167,17 @@ selected.score = createSelector(
);
/**
* Returns the rubric-level feedback for the selected submission
* @return {string} selected submission's associated rubric-level feedback
* Returns the rubric-level feedback for the selected submission for both review
* and grading views.
* @return {obj} selected submission's associated rubric-level feedback
* ({ review: '', grading: '' })
*/
selected.overallFeedback = createSelector(
[module.selected.gradeData],
(data) => (data ? data.overallFeedback : ''),
[module.selected.gradeData, module.selected.localGradeData],
(data, localData) => ({
review: (data ? data.overallFeedback : ''),
grading: (localData ? localData.overallFeedback : ''),
}),
);
/**
@@ -170,24 +203,47 @@ selected.isValidForSubmit = createSelector(
/**
* Returns the grade data for the given criterion of the current
* selection
* selection for both review and grading views.
* @param {number} orderNum - criterion orderNum (and index)
* @return {obj} - Grade Data associated with the criterion
* ({ review: {}, grading: {} })
*/
selected.criterionGradeData = (state, { orderNum }) => {
const data = module.selected.criteriaGradeData(state);
return data ? data[orderNum] : {};
const { grading, review } = module.selected.criteriaGradeData(state);
return {
review: review ? review[orderNum] : {},
grading: grading ? grading[orderNum] : {},
};
};
/**
* Returns the selected option for the given criterion of the current selection for
* both review and grading views.
* @param {number} orderNum - criterion orderNum (and index)
* @return {obj} - selected option associated with the criterion
* ({ review: '', grading: '' })
*/
selected.criterionSelectedOption = (state, { orderNum }) => {
const { grading, review } = module.selected.criterionGradeData(state, { orderNum });
return {
grading: grading ? grading.selectedOption : '',
review: review ? review.selectedOption : '',
};
};
/**
* Returns the critierion-level feedback for the selected submission, given the
* orderNum of the criterion.
* orderNum of the criterion for both review and grading views.
* @param {number} orderNum - criterion index
* @return {string} - criterion-level feedback response for the given criterion.
* @return {obj} - criterion-level feedback response for the given criterion.
* ({ review: '', grading: '' }),
*/
selected.criterionFeedback = (state, { orderNum }) => {
const data = module.selected.criterionGradeData(state, { orderNum });
return data ? data.feedback : '';
const { grading, review } = module.selected.criterionGradeData(state, { orderNum });
return {
grading: grading ? grading.feedback : '',
review: review ? review.feedback : '',
};
};
/**

View File

@@ -159,7 +159,7 @@ export const cancelGrading = () => (dispatch, getState) => {
* to False
*/
export const stopGrading = () => (dispatch) => {
dispatch(actions.grading.clearGrade());
dispatch(actions.grading.stopGrading());
dispatch(actions.app.setGrading(false));
};

View File

@@ -339,10 +339,10 @@ describe('grading thunkActions', () => {
});
describe('stopGrading', () => {
it('dispatches grading.clearGrade and app.setGrading(false)', () => {
it('dispatches grading.stopGrading and app.setGrading(false)', () => {
thunkActions.stopGrading()(dispatch, getState);
expect(dispatch.mock.calls).toEqual([
[actions.grading.clearGrade()],
[actions.grading.stopGrading()],
[actions.app.setGrading(false)],
]);
});