Skip to content

Commit 751d84b

Browse files
committed
Implementing redux module and selectors for handling solution reviews.
1 parent 4371fe3 commit 751d84b

File tree

4 files changed

+180
-112
lines changed

4 files changed

+180
-112
lines changed

src/pages/SolutionSourceCodes/SolutionSourceCodes.js

Lines changed: 128 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ import { fetchRuntimeEnvironments } from '../../redux/modules/runtimeEnvironment
3131
import { fetchAssignmentIfNeeded } from '../../redux/modules/assignments';
3232
import { fetchSolutionIfNeeded, fetchUsersSolutions } from '../../redux/modules/solutions';
3333
import { fetchAssignmentSolutionFilesIfNeeded } from '../../redux/modules/solutionFiles';
34+
import { fetchSolutionReviewIfNeeded } from '../../redux/modules/solutionReviews';
3435
import { download } from '../../redux/modules/files';
3536
import { fetchContentIfNeeded } from '../../redux/modules/filesContent';
3637
import { getSolution } from '../../redux/selectors/solutions';
3738
import { getSolutionFiles } from '../../redux/selectors/solutionFiles';
39+
import { getSolutionReviewComments } from '../../redux/selectors/solutionReviews';
3840
import {
3941
getAssignment,
4042
getUserSolutionsSortedData,
@@ -201,6 +203,7 @@ class SolutionSourceCodes extends Component {
201203
registerSolutionVisit(solution);
202204
return Promise.all([dispatch(fetchUsersSolutions(solution.authorId, assignmentId))]);
203205
}),
206+
dispatch(fetchSolutionReviewIfNeeded(solutionId)),
204207
dispatch(fetchAssignmentIfNeeded(assignmentId)),
205208
dispatch(fetchAssignmentSolutionFilesIfNeeded(solutionId))
206209
.then(res => preprocessFiles(res.value))
@@ -326,6 +329,7 @@ class SolutionSourceCodes extends Component {
326329
secondSolution,
327330
files,
328331
secondFiles,
332+
reviewComments,
329333
fileContentsSelector,
330334
download,
331335
userSolutionsSelector,
@@ -453,120 +457,130 @@ class SolutionSourceCodes extends Component {
453457
</Row>
454458
)}
455459

456-
<ResourceRenderer resource={files} bulkyLoading>
457-
{filesRaw => (
458-
<ResourceRenderer resource={secondFiles || []} bulkyLoading>
459-
{(secondFilesRaw = null) => {
460-
const secondFiles = secondFilesRaw && preprocessFiles(secondFilesRaw);
461-
const files = associateFilesForDiff(
462-
preprocessFiles(filesRaw),
463-
secondFiles,
464-
this.state.diffMappings
465-
);
466-
const revertedIndex = files && secondFiles && getRevertedMapping(files);
467-
return (
468-
<>
469-
{files.map(file => (
470-
<SourceCodeBox
471-
key={file.id}
472-
{...file}
473-
download={download}
474-
fileContentsSelector={fileContentsSelector}
475-
diffMode={diffMode}
476-
adjustDiffMapping={this.openMappingDialog}
477-
/>
478-
))}
479-
480-
{diffMode && secondFiles && (
481-
<Modal
482-
show={this.state.mappingDialogOpenFile !== null}
483-
backdrop="static"
484-
onHide={this.closeDialogs}
485-
size="xl">
486-
<Modal.Header closeButton>
487-
<Modal.Title>
488-
<FormattedMessage
489-
id="app.solutionSourceCodes.mappingModal.title"
490-
defaultMessage="Adjust mapping of compared files"
460+
<ResourceRenderer resource={reviewComments} bulkyLoading>
461+
{reviewCommentsRaw =>
462+
console.log(reviewCommentsRaw) || (
463+
<ResourceRenderer resource={files} bulkyLoading>
464+
{filesRaw => (
465+
<ResourceRenderer resource={secondFiles || []} bulkyLoading>
466+
{(secondFilesRaw = null) => {
467+
const secondFiles = secondFilesRaw && preprocessFiles(secondFilesRaw);
468+
const files = associateFilesForDiff(
469+
preprocessFiles(filesRaw),
470+
secondFiles,
471+
this.state.diffMappings
472+
);
473+
const revertedIndex = files && secondFiles && getRevertedMapping(files);
474+
return (
475+
<>
476+
{files.map(file => (
477+
<SourceCodeBox
478+
key={file.id}
479+
{...file}
480+
download={download}
481+
fileContentsSelector={fileContentsSelector}
482+
diffMode={diffMode}
483+
adjustDiffMapping={this.openMappingDialog}
491484
/>
492-
</Modal.Title>
493-
</Modal.Header>
494-
<Modal.Body>
495-
<h5 className="mb-3">
496-
<code className="mr-2">
497-
{this.state.mappingDialogOpenFile && this.state.mappingDialogOpenFile.name}
498-
</code>{' '}
499-
<FormattedMessage
500-
id="app.solutionSourceCodes.mappingModal.fileIsAssociatedWith"
501-
defaultMessage="file (on the left) is associated with..."
502-
/>
503-
</h5>
504-
505-
<InsetPanel>
506-
{this.state.mappingDialogOpenFile && (
507-
<FormattedMessage
508-
id="app.solutionSourceCodes.mappingModal.explain"
509-
defaultMessage="Select a file from second solution that will be compared with ''<code>{name}</code>'' file from the first solution. Note that changing a mapping between two files may affect how other files are mapped."
510-
values={{
511-
name: this.state.mappingDialogOpenFile.name,
512-
code: content => <code>{content}</code>,
513-
}}
514-
/>
515-
)}
516-
</InsetPanel>
517-
518-
<Table hover>
519-
<tbody>
520-
{secondFiles.map(file => {
521-
const selected =
522-
this.state.mappingDialogDiffWith &&
523-
file.id === this.state.mappingDialogDiffWith.id;
524-
return (
525-
<tr
526-
key={file.id}
527-
className={selected ? 'table-primary' : ''}
528-
onClick={
529-
selected
530-
? null
531-
: () => this.adjustDiffMapping(this.state.mappingDialogOpenFile.id, file.id)
532-
}>
533-
<td className="shrink-col">
534-
<CircleIcon selected={selected} />
535-
</td>
536-
<td>{file.name}</td>
537-
<td className="shrink-col text-muted text-nowrap small">
538-
{revertedIndex && revertedIndex[file.id] && (
539-
<>
540-
<CodeCompareIcon gapRight />
541-
{revertedIndex[file.id].name}
542-
</>
543-
)}
544-
</td>
545-
</tr>
546-
);
547-
})}
548-
</tbody>
549-
</Table>
550-
551-
{this.state.diffMappings && !isEmptyObject(this.state.diffMappings) && (
552-
<div className="text-center">
553-
<Button variant="danger" onClick={this.resetDiffMappings}>
554-
<RefreshIcon gapRight />
555-
<FormattedMessage
556-
id="app.solutionSourceCodes.mappingModal.resetButton"
557-
defaultMessage="Reset mapping"
558-
/>
559-
</Button>
560-
</div>
485+
))}
486+
487+
{diffMode && secondFiles && (
488+
<Modal
489+
show={this.state.mappingDialogOpenFile !== null}
490+
backdrop="static"
491+
onHide={this.closeDialogs}
492+
size="xl">
493+
<Modal.Header closeButton>
494+
<Modal.Title>
495+
<FormattedMessage
496+
id="app.solutionSourceCodes.mappingModal.title"
497+
defaultMessage="Adjust mapping of compared files"
498+
/>
499+
</Modal.Title>
500+
</Modal.Header>
501+
<Modal.Body>
502+
<h5 className="mb-3">
503+
<code className="mr-2">
504+
{this.state.mappingDialogOpenFile && this.state.mappingDialogOpenFile.name}
505+
</code>{' '}
506+
<FormattedMessage
507+
id="app.solutionSourceCodes.mappingModal.fileIsAssociatedWith"
508+
defaultMessage="file (on the left) is associated with..."
509+
/>
510+
</h5>
511+
512+
<InsetPanel>
513+
{this.state.mappingDialogOpenFile && (
514+
<FormattedMessage
515+
id="app.solutionSourceCodes.mappingModal.explain"
516+
defaultMessage="Select a file from second solution that will be compared with ''<code>{name}</code>'' file from the first solution. Note that changing a mapping between two files may affect how other files are mapped."
517+
values={{
518+
name: this.state.mappingDialogOpenFile.name,
519+
code: content => <code>{content}</code>,
520+
}}
521+
/>
522+
)}
523+
</InsetPanel>
524+
525+
<Table hover>
526+
<tbody>
527+
{secondFiles.map(file => {
528+
const selected =
529+
this.state.mappingDialogDiffWith &&
530+
file.id === this.state.mappingDialogDiffWith.id;
531+
return (
532+
<tr
533+
key={file.id}
534+
className={selected ? 'table-primary' : ''}
535+
onClick={
536+
selected
537+
? null
538+
: () =>
539+
this.adjustDiffMapping(
540+
this.state.mappingDialogOpenFile.id,
541+
file.id
542+
)
543+
}>
544+
<td className="shrink-col">
545+
<CircleIcon selected={selected} />
546+
</td>
547+
<td>{file.name}</td>
548+
<td className="shrink-col text-muted text-nowrap small">
549+
{revertedIndex && revertedIndex[file.id] && (
550+
<>
551+
<CodeCompareIcon gapRight />
552+
{revertedIndex[file.id].name}
553+
</>
554+
)}
555+
</td>
556+
</tr>
557+
);
558+
})}
559+
</tbody>
560+
</Table>
561+
562+
{this.state.diffMappings && !isEmptyObject(this.state.diffMappings) && (
563+
<div className="text-center">
564+
<Button variant="danger" onClick={this.resetDiffMappings}>
565+
<RefreshIcon gapRight />
566+
<FormattedMessage
567+
id="app.solutionSourceCodes.mappingModal.resetButton"
568+
defaultMessage="Reset mapping"
569+
/>
570+
</Button>
571+
</div>
572+
)}
573+
</Modal.Body>
574+
</Modal>
561575
)}
562-
</Modal.Body>
563-
</Modal>
564-
)}
565-
</>
566-
);
567-
}}
568-
</ResourceRenderer>
569-
)}
576+
</>
577+
);
578+
}}
579+
</ResourceRenderer>
580+
)}
581+
</ResourceRenderer>
582+
)
583+
}
570584
</ResourceRenderer>
571585

572586
<Modal show={this.state.diffDialogOpen} backdrop="static" onHide={this.closeDialogs} size="xl">
@@ -650,6 +664,7 @@ SolutionSourceCodes.propTypes = {
650664
solution: ImmutablePropTypes.map,
651665
secondSolution: ImmutablePropTypes.map,
652666
files: ImmutablePropTypes.map,
667+
reviewComments: ImmutablePropTypes.map,
653668
secondFiles: ImmutablePropTypes.map,
654669
fileContentsSelector: PropTypes.func.isRequired,
655670
userSolutionsSelector: PropTypes.func.isRequired,
@@ -680,6 +695,7 @@ export default withLinks(
680695
solution: getSolution(state, solutionId),
681696
secondSolution,
682697
files: getSolutionFiles(state, solutionId),
698+
reviewComments: getSolutionReviewComments(state, solutionId),
683699
secondFiles:
684700
secondSolutionId && secondSolutionId !== solutionId ? getSolutionFiles(state, secondSolutionId) : null,
685701
fileContentsSelector: getFilesContentSelector(state),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { handleActions, createAction } from 'redux-actions';
2+
import { fromJS } from 'immutable';
3+
4+
import { createApiAction } from '../middleware/apiMiddleware';
5+
import factory, { initialState, createRecord, resourceStatus } from '../helpers/resourceManager';
6+
import { actionTypes as submissionActionTypes } from './submission';
7+
import { actionTypes as submissionEvaluationActionTypes } from './submissionEvaluations';
8+
import { getAssignmentSolversLastUpdate } from '../selectors/solutions';
9+
import { objectFilter } from '../../helpers/common';
10+
11+
const resourceName = 'solutionReviews';
12+
13+
const apiEndpointFactory = id => `/assignment-solutions/${id}/review`;
14+
const { actions, actionTypes, reduceActions } = factory({
15+
resourceName,
16+
apiEndpointFactory,
17+
});
18+
19+
/**
20+
* Actions
21+
*/
22+
export { actionTypes };
23+
export const additionalActionTypes = {};
24+
25+
export const fetchSolutionReview = actions.fetchResource;
26+
export const fetchSolutionReviewIfNeeded = actions.fetchOneIfNeeded;
27+
export const deleteSolutionReview = actions.removeResource;
28+
29+
/**
30+
* Reducer
31+
*/
32+
33+
const reducer = handleActions(
34+
Object.assign({}, reduceActions, {
35+
[actionTypes.FETCH_FULFILLED]: (state, { meta: { id }, payload: { reviewComments: data } }) =>
36+
state.setIn(['resources', id], createRecord({ state: resourceStatus.FULFILLED, data })),
37+
}),
38+
initialState
39+
);
40+
41+
export default reducer;

src/redux/reducer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import sisSupervisedCourses from './modules/sisSupervisedCourses';
4545
import sisTerms from './modules/sisTerms';
4646
import solutions from './modules/solutions';
4747
import solutionFiles from './modules/solutionFiles';
48+
import solutionReviews from './modules/solutionReviews';
4849
import stats from './modules/stats';
4950
import submission from './modules/submission';
5051
import submissionEvaluations from './modules/submissionEvaluations';
@@ -103,6 +104,7 @@ const createRecodexReducers = (token, instanceId, lang) => ({
103104
sisTerms,
104105
solutions,
105106
solutionFiles,
107+
solutionReviews,
106108
stats,
107109
submission,
108110
submissionEvaluations,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createSelector } from 'reselect';
2+
3+
const getSolutionsReviewsResources = state => state.solutionReviews.get('resources');
4+
const getParam = (_, id) => id;
5+
6+
export const getSolutionReviewComments = createSelector(
7+
[getSolutionsReviewsResources, getParam],
8+
(reviewComments, id) => reviewComments.get(id)
9+
);

0 commit comments

Comments
 (0)