Skip to content

Commit

Permalink
Adding edit dialog for description of reference solution. Optimizing …
Browse files Browse the repository at this point in the history
…related pages (redux selectors) to prevent unnecessary re-rendering.
  • Loading branch information
krulis-martin committed Jun 12, 2024
1 parent fb5a903 commit 50181ef
Show file tree
Hide file tree
Showing 15 changed files with 133 additions and 126 deletions.
3 changes: 1 addition & 2 deletions src/components/Solutions/SolutionDetail/SolutionDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ class SolutionDetail extends Component {
submittedAt={createdAt}
userId={authorId}
submittedBy={submittedBy}
note={note}
description={description}
note={referenceSolution ? description : note}
editNote={editNote}
accepted={accepted}
review={review}
Expand Down
81 changes: 41 additions & 40 deletions src/components/Solutions/SolutionStatus/SolutionStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ class SolutionStatus extends Component {
userId,
submittedBy,
note,
description,
visibility = null,
accepted,
review = null,
Expand Down Expand Up @@ -172,48 +171,44 @@ class SolutionStatus extends Component {
</tr>
)}

{referenceSolution ? (
{(referenceSolution || note.length > 0 || Boolean(editNote)) && (
<tr>
<td className="text-center text-muted shrink-col px-2">
<EditIcon />
<NoteIcon />
</td>
<th>
<FormattedMessage id="generic.description" defaultMessage="Description" />:
<th className="text-nowrap">
{referenceSolution ? (
<>
<FormattedMessage id="generic.description" defaultMessage="Description" />:
</>
) : (
<>
<FormattedMessage id="app.solution.note" defaultMessage="Note:" />
<Explanation id="note">
<FormattedMessage
id="app.solution.explanations.note"
defaultMessage="Short note left by the author of the solution that can be used to distinguish between solutions of one assignment. The note is also visible by teachers, so it can be used to pass brief information to them (however, comments are more suitable for elaborate conversations)."
/>
</Explanation>
</>
)}
</th>
<td>{description}</td>
</tr>
) : (
(note.length > 0 || Boolean(editNote)) && (
<tr>
<td className="text-center text-muted shrink-col px-2">
<NoteIcon />
</td>
<th className="text-nowrap">
<FormattedMessage id="app.solution.note" defaultMessage="Note:" />
<Explanation id="note">
<FormattedMessage
id="app.solution.explanations.note"
defaultMessage="Short note left by the author of the solution that can be used to distinguish between solutions of one assignment. The note is also visible by teachers, so it can be used to pass brief information to them (however, comments are more suitable for elaborate conversations)."
/>
</Explanation>
</th>
<td>
{note.length > 0 ? (
note
) : (
<em className="text-muted small">
<FormattedMessage id="app.solution.emptyNote" defaultMessage="empty" />
</em>
)}
<td>
{referenceSolution || note.length > 0 ? (
note
) : (
<em className="text-muted small">
<FormattedMessage id="app.solution.emptyNote" defaultMessage="empty" />
</em>
)}

{Boolean(editNote) && (
<span className="float-right text-warning mx-2">
<EditIcon onClick={this.openEditDialog} />
</span>
)}
</td>
</tr>
)
{Boolean(editNote) && (
<span className="float-right text-warning mx-2">
<EditIcon onClick={this.openEditDialog} />
</span>
)}
</td>
</tr>
)}

<tr>
Expand Down Expand Up @@ -541,7 +536,14 @@ class SolutionStatus extends Component {
<Modal show={this.state.editDialogOpen} backdrop="static" onHide={this.closeDialog} size="xl">
<Modal.Header closeButton>
<Modal.Title>
<FormattedMessage id="app.solution.editNoteModalTitle" defaultMessage="Edit Solution Note" />
{referenceSolution ? (
<FormattedMessage
id="app.referenceSolution.editDescriptionModalTitle"
defaultMessage="Edit Solution Description"
/>
) : (
<FormattedMessage id="app.solution.editNoteModalTitle" defaultMessage="Edit Solution Note" />
)}
</Modal.Title>
</Modal.Header>
<Modal.Body>
Expand Down Expand Up @@ -728,7 +730,6 @@ SolutionStatus.propTypes = {
}),
evaluation: PropTypes.object,
note: PropTypes.string,
description: PropTypes.string,
visibility: PropTypes.number,
accepted: PropTypes.bool,
review: PropTypes.shape({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default connect(
(state, { id }) => {
return {
currentUserId: loggedInUserIdSelector(state),
solution: getReferenceSolution(id)(state),
solution: getReferenceSolution(state, id),
visibilityPending: getReferenceSolutionSetVisibilityStatus(state, id),
};
},
Expand Down
2 changes: 1 addition & 1 deletion src/locales/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1530,14 +1530,14 @@
"app.referenceSolution.actions.setPromoted": "Propagovat",
"app.referenceSolution.actions.setPublic": "Změnit na veřejnou",
"app.referenceSolution.actions.unsetPromoted": "Degradovat",
"app.referenceSolution.editDescriptionModalTitle": "Upravit popis řešení",
"app.referenceSolution.exerciseBroken": "Úloha je rozbitá, a proto není v tuto chvíli možné nechat toto vzorové řešení znovu vyhodnotit.",
"app.referenceSolution.exerciseNoLongerHasEnvironment": "Úloha již nepodporuje běhové prostředí, pod kterým bylo toto řešení odevzdáno. Řešení není možné znovu vyhodnotit.",
"app.referenceSolution.title": "Podrobnosti referenčního řešení",
"app.referenceSolution.visibility.private": "Řešení můžete vidět pouze vy.",
"app.referenceSolution.visibility.promoted": "Řešení je prpagováno autorem úlohy (ostatním vedoucím je doporučeno se s ním seznámit).",
"app.referenceSolution.visibility.public": "Řešení je viditelné všem vedoucím, kteří vidí detaily úlohy.",
"app.referenceSolutionDetail.comments.additionalSwitchNote": "(učitelé, kteří smí vidět toto referenční řešení)",
"app.referenceSolutionDetail.exercise": "Úloha",
"app.referenceSolutionDetail.title.details": "Detail referenčního řešení",
"app.referenceSolutionDetail.visibility.private": "Privátní",
"app.referenceSolutionDetail.visibility.privateExplanation": "Privátní řešení jsou viditelná pouze jejich autorům. Krátkodobé experimenty s úlohou je vhodné udržovat jako privátní řešení, aby ostatní vedoucí nebyli příliš zahlceni nezajímavými zdrojovými kódy.",
Expand Down
2 changes: 1 addition & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1530,14 +1530,14 @@
"app.referenceSolution.actions.setPromoted": "Promote",
"app.referenceSolution.actions.setPublic": "Make public",
"app.referenceSolution.actions.unsetPromoted": "Demote",
"app.referenceSolution.editDescriptionModalTitle": "Edit Solution Description",
"app.referenceSolution.exerciseBroken": "The exercise is broken. This reference solution may not be resubmitted at the moment.",
"app.referenceSolution.exerciseNoLongerHasEnvironment": "The exercise no longer supports the environment for which this solution was evaluated. Resubmission is not possible.",
"app.referenceSolution.title": "Reference Solution Detail",
"app.referenceSolution.visibility.private": "The solution is visible only to you.",
"app.referenceSolution.visibility.promoted": "The solution is promoted by the auhor of the exercise (recommended for reading to other supervisors)",
"app.referenceSolution.visibility.public": "The solution is visible to all supervisors who can access this exercise.",
"app.referenceSolutionDetail.comments.additionalSwitchNote": "(teachers who can see this reference solution)",
"app.referenceSolutionDetail.exercise": "Exercise",
"app.referenceSolutionDetail.title.details": "Reference Solution Detail",
"app.referenceSolutionDetail.visibility.private": "Private",
"app.referenceSolutionDetail.visibility.privateExplanation": "Private solutions are visible only to their author. Experimental and temporary submissions should be kept private so other suprevisors are not overwhelmed with abundance of irrelevant source codes.",
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Assignment/Assignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { fetchRuntimeEnvironments } from '../../redux/modules/runtimeEnvironment

import {
getAssignment,
assignmentEnvironmentsSelector,
getAssignmentEnvironments,
getUserSolutionsSortedData,
} from '../../redux/selectors/assignments';
import { canSubmitSolution } from '../../redux/selectors/canSubmit';
Expand Down Expand Up @@ -324,7 +324,7 @@ export default injectIntl(
return {
assignment: getAssignment(state, assignmentId),
submitting: isSubmitting(state),
runtimeEnvironments: assignmentEnvironmentsSelector(state)(assignmentId),
runtimeEnvironments: getAssignmentEnvironments(state, assignmentId),
userId,
loggedInUserId,
currentUser: loggedInUserSelector(state),
Expand Down
4 changes: 2 additions & 2 deletions src/pages/AssignmentSolutions/AssignmentSolutions.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { groupSelector } from '../../redux/selectors/groups';
import { studentsIdsOfGroup } from '../../redux/selectors/usersGroups';
import {
getAssignment,
assignmentEnvironmentsSelector,
getAssignmentEnvironments,
getUserSolutionsSortedData,
getAssignmentSolutions,
} from '../../redux/selectors/assignments';
Expand Down Expand Up @@ -698,7 +698,7 @@ export default withLinks(
getUserSolutions: userId => getUserSolutionsSortedData(state)(userId, assignmentId),
assignmentSolutions: getAssignmentSolutions(state, assignmentId),
getGroup: id => groupSelector(state, id),
runtimeEnvironments: assignmentEnvironmentsSelector(state)(assignmentId),
runtimeEnvironments: getAssignmentEnvironments(state, assignmentId),
fetchManyStatus: fetchManyAssignmentSolutionsStatus(assignmentId)(state),
assignmentSolversLoading: isAssignmentSolversLoading(state),
assignmentSolverSelector: getAssignmentSolverSelector(state),
Expand Down
14 changes: 11 additions & 3 deletions src/pages/ReferenceSolution/ReferenceSolution.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import { TheButtonGroup } from '../../components/widgets/TheButton';
import Callout from '../../components/widgets/Callout';
import { ReferenceSolutionIcon } from '../../components/icons';

import { fetchReferenceSolutionIfNeeded, fetchReferenceSolution } from '../../redux/modules/referenceSolutions';
import {
fetchReferenceSolutionIfNeeded,
fetchReferenceSolution,
setDescription,
} from '../../redux/modules/referenceSolutions';
import { fetchReferenceSolutionFilesIfNeeded } from '../../redux/modules/solutionFiles';
import { download } from '../../redux/modules/files';
import { fetchExerciseIfNeeded } from '../../redux/modules/exercises';
Expand Down Expand Up @@ -70,6 +74,7 @@ class ReferenceSolution extends Component {
exercise,
fetchStatus,
evaluations,
editNote,
refreshSolutionEvaluations,
deleteEvaluation,
scoreConfigSelector,
Expand Down Expand Up @@ -144,6 +149,7 @@ class ReferenceSolution extends Component {
runtimeEnvironments={exercise.runtimeEnvironments}
currentUser={currentUser}
exercise={exercise}
editNote={hasPermissions(referenceSolution, 'update') ? editNote : null}
scoreConfigSelector={scoreConfigSelector}
download={download}
deleteEvaluation={deleteEvaluation}
Expand Down Expand Up @@ -173,6 +179,7 @@ ReferenceSolution.propTypes = {
referenceSolution: ImmutablePropTypes.map,
files: ImmutablePropTypes.map,
exercise: ImmutablePropTypes.map,
editNote: PropTypes.func.isRequired,
refreshSolutionEvaluations: PropTypes.func,
deleteEvaluation: PropTypes.func.isRequired,
download: PropTypes.func.isRequired,
Expand All @@ -186,16 +193,17 @@ export default injectIntl(
connect(
(state, { params: { exerciseId, referenceSolutionId } }) => ({
currentUser: loggedInUserSelector(state),
referenceSolution: getReferenceSolution(referenceSolutionId)(state),
referenceSolution: getReferenceSolution(state, referenceSolutionId),
files: getSolutionFiles(state, referenceSolutionId),
exercise: getExercise(exerciseId)(state),
evaluations: evaluationsForReferenceSolutionSelector(referenceSolutionId)(state),
evaluations: evaluationsForReferenceSolutionSelector(state, referenceSolutionId),
fetchStatus: fetchManyStatus(referenceSolutionId)(state),
scoreConfigSelector: referenceSubmissionScoreConfigSelector(state),
}),
(dispatch, { params }) => ({
loadAsync: () => ReferenceSolution.loadAsync(params, dispatch),
fetchScoreConfigIfNeeded: submissionId => dispatch(fetchReferenceSubmissionScoreConfigIfNeeded(submissionId)),
editNote: note => dispatch(setDescription(params.referenceSolutionId, note)),
refreshSolutionEvaluations: () => {
dispatch(fetchReferenceSolution(params.referenceSolutionId));
dispatch(fetchReferenceSolutionEvaluationsForSolution(params.referenceSolutionId));
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Solution/Solution.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import { getSolution, isAssignmentSolversLoading, getAssignmentSolverSelector }
import { getSolutionFiles } from '../../redux/selectors/solutionFiles';
import {
getAssignment,
assignmentEnvironmentsSelector,
getAssignmentEnvironments,
getUserSolutionsSortedData,
} from '../../redux/selectors/assignments';
import { evaluationsForSubmissionSelector, fetchManyStatus } from '../../redux/selectors/submissionEvaluations';
Expand Down Expand Up @@ -338,7 +338,7 @@ export default connect(
canSubmit: canSubmitSolution(assignmentId)(state),
assignment: getAssignment(state, assignmentId),
evaluations: evaluationsForSubmissionSelector(state, solutionId),
runtimeEnvironments: assignmentEnvironmentsSelector(state)(assignmentId),
runtimeEnvironments: getAssignmentEnvironments(state, assignmentId),
fetchStatus: fetchManyStatus(solutionId)(state),
scoreConfigSelector: assignmentSubmissionScoreConfigSelector(state),
assignmentSolversLoading: isAssignmentSolversLoading(state),
Expand Down
4 changes: 2 additions & 2 deletions src/pages/SolutionSourceCodes/SolutionSourceCodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { getSolutionReviewComments } from '../../redux/selectors/solutionReviews
import {
getAssignment,
getUserSolutionsSortedData,
assignmentEnvironmentsSelector,
getAssignmentEnvironments,
} from '../../redux/selectors/assignments';
import { getFilesContentSelector } from '../../redux/selectors/files';
import { getLoggedInUserEffectiveRole, loggedInUserSelector } from '../../redux/selectors/users';
Expand Down Expand Up @@ -733,7 +733,7 @@ export default withRouter(
loggedUserId: loggedInUserIdSelector(state),
currentUser: loggedInUserSelector(state),
effectiveRole: getLoggedInUserEffectiveRole(state),
runtimeEnvironments: assignmentEnvironmentsSelector(state)(assignmentId),
runtimeEnvironments: getAssignmentEnvironments(state, assignmentId),
isPrimaryAdminOf: loggedUserIsPrimaryAdminOfSelector(state),
};
},
Expand Down
19 changes: 19 additions & 0 deletions src/redux/modules/referenceSolutions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const additionalActionTypes = {
RESUBMIT: 'recodex/referenceSolutions/RESUBMIT',
FETCHALL: 'recodex/referenceSolutions/FETCHALL',
FETCHALL_FULFILLED: 'recodex/referenceSolutions/FETCHALL_FULFILLED',
...createActionsWithPostfixes('SET_NOTE', 'recodex/referenceSolutions'),
...createActionsWithPostfixes('SET_VISIBILITY', 'recodex/referenceSolutions'),
};

Expand Down Expand Up @@ -52,6 +53,15 @@ export const resubmitReferenceSolution = (solutionId, progressObserverId = null,
},
});

export const setDescription = (solutionId, note) =>
createApiAction({
type: additionalActionTypes.SET_NOTE,
endpoint: `/reference-solutions/${solutionId}`,
method: 'POST',
body: { note },
meta: { solutionId },
});

export const setVisibility = (solutionId, visibility = 1) =>
createApiAction({
type: additionalActionTypes.SET_VISIBILITY,
Expand Down Expand Up @@ -101,6 +111,15 @@ const reducer = handleActions(
)
: state,

[additionalActionTypes.SET_NOTE_FULFILLED]: (state, { payload }) =>
state.setIn(
['resources', payload.id],
createRecord({
state: resourceStatus.FULFILLED,
data: payload,
})
),

[additionalActionTypes.SET_VISIBILITY_PENDING]: (state, { meta: { solutionId } }) =>
state.setIn(['resources', solutionId, 'visibility'], true),
[additionalActionTypes.SET_VISIBILITY_REJECTED]: (state, { meta: { solutionId } }) =>
Expand Down
Loading

0 comments on commit 50181ef

Please sign in to comment.