From 11647efdeb1a058422999b2884149653ed6ed014 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Mon, 13 Nov 2017 21:58:23 +0100 Subject: [PATCH 01/12] Solution list data schema updated --- .../EvaluationFailedTableRow.js | 27 +++++++++---------- .../FailedSubmissionTableRow.js | 22 +++++++++------ .../NotEvaluatedSubmissionTableRow.js | 23 ++++++++++------ .../SubmissionsTable/SubmissionsTable.js | 2 +- .../SuccessfulSubmissionTableRow.js | 27 +++++++++++-------- .../SubmissionsTableContainer.js | 4 +-- src/redux/modules/groupResults.js | 4 +-- src/redux/modules/submissions.js | 4 +-- 8 files changed, 65 insertions(+), 48 deletions(-) diff --git a/src/components/Assignments/SubmissionsTable/EvaluationFailedTableRow.js b/src/components/Assignments/SubmissionsTable/EvaluationFailedTableRow.js index 5a5bec2fe..004697d64 100644 --- a/src/components/Assignments/SubmissionsTable/EvaluationFailedTableRow.js +++ b/src/components/Assignments/SubmissionsTable/EvaluationFailedTableRow.js @@ -4,23 +4,21 @@ import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; import AssignmentStatusIcon from '../Assignment/AssignmentStatusIcon'; -const EvaluationFailedTableRow = ({ link, note, submittedAt }) => ( +const EvaluationFailedTableRow = ({ link, note, solution: { createdAt } }) => - - + + + +   - + - - - - + - - - - - + - {note} @@ -33,13 +31,14 @@ const EvaluationFailedTableRow = ({ link, note, submittedAt }) => ( /> - -); + ; EvaluationFailedTableRow.propTypes = { link: PropTypes.string.isRequired, - submittedAt: PropTypes.number.isRequired, - note: PropTypes.string.isRequired + note: PropTypes.string.isRequired, + solution: PropTypes.shape({ + createdAt: PropTypes.number.isRequired + }).isRequired }; export default EvaluationFailedTableRow; diff --git a/src/components/Assignments/SubmissionsTable/FailedSubmissionTableRow.js b/src/components/Assignments/SubmissionsTable/FailedSubmissionTableRow.js index 9aaa4cbd6..17d02b17d 100644 --- a/src/components/Assignments/SubmissionsTable/FailedSubmissionTableRow.js +++ b/src/components/Assignments/SubmissionsTable/FailedSubmissionTableRow.js @@ -13,9 +13,10 @@ import Points from './Points'; const FailedSubmissionTableRow = ({ link, note, - submittedAt, + lastSubmission: { evaluation: { score, points } }, maxPoints, - evaluation: { score, points, bonusPoints }, + bonusPoints, + solution: { createdAt }, accepted }) => @@ -23,9 +24,9 @@ const FailedSubmissionTableRow = ({ - +   - + @@ -56,12 +57,17 @@ const FailedSubmissionTableRow = ({ FailedSubmissionTableRow.propTypes = { link: PropTypes.string.isRequired, - submittedAt: PropTypes.number.isRequired, note: PropTypes.string.isRequired, maxPoints: PropTypes.number.isRequired, - evaluation: PropTypes.shape({ - score: PropTypes.number.isRequired, - points: PropTypes.number.isRequired + bonusPoints: PropTypes.number.isRequired, + lastSubmission: PropTypes.shape({ + evaluation: PropTypes.shape({ + score: PropTypes.number.isRequired, + points: PropTypes.number.isRequired + }) + }).isRequired, + solution: PropTypes.shape({ + createdAt: PropTypes.number.isRequired }).isRequired, accepted: PropTypes.bool }; diff --git a/src/components/Assignments/SubmissionsTable/NotEvaluatedSubmissionTableRow.js b/src/components/Assignments/SubmissionsTable/NotEvaluatedSubmissionTableRow.js index dc1eeb37e..4824add96 100644 --- a/src/components/Assignments/SubmissionsTable/NotEvaluatedSubmissionTableRow.js +++ b/src/components/Assignments/SubmissionsTable/NotEvaluatedSubmissionTableRow.js @@ -4,13 +4,19 @@ import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; import AssignmentStatusIcon from '../Assignment/AssignmentStatusIcon'; -const NotEvaluatedSubmissionTableRow = ({ link, note, submittedAt }) => ( +const NotEvaluatedSubmissionTableRow = ({ + link, + note, + solution: { createdAt } +}) => - - + + + +   - + - - / - @@ -25,13 +31,14 @@ const NotEvaluatedSubmissionTableRow = ({ link, note, submittedAt }) => ( /> - -); + ; NotEvaluatedSubmissionTableRow.propTypes = { link: PropTypes.string.isRequired, - submittedAt: PropTypes.number.isRequired, - note: PropTypes.string.isRequired + note: PropTypes.string.isRequired, + solution: PropTypes.shape({ + createdAt: PropTypes.number.isRequired + }).isRequired }; export default NotEvaluatedSubmissionTableRow; diff --git a/src/components/Assignments/SubmissionsTable/SubmissionsTable.js b/src/components/Assignments/SubmissionsTable/SubmissionsTable.js index e32a2b179..dce54fba7 100644 --- a/src/components/Assignments/SubmissionsTable/SubmissionsTable.js +++ b/src/components/Assignments/SubmissionsTable/SubmissionsTable.js @@ -66,7 +66,7 @@ const SubmissionsTable = ({ const id = data.id; const link = SUBMISSION_DETAIL_URI_FACTORY(assignmentId, id); - switch (data.evaluationStatus) { + switch (data.lastSubmission.evaluationStatus) { case 'done': return ( ( +}) => - +   - + @@ -53,17 +54,21 @@ const SuccessfulSubmissionTableRow = ({ /> - -); + ; SuccessfulSubmissionTableRow.propTypes = { link: PropTypes.string.isRequired, - submittedAt: PropTypes.number.isRequired, note: PropTypes.string.isRequired, maxPoints: PropTypes.number.isRequired, - evaluation: PropTypes.shape({ - score: PropTypes.number.isRequired, - points: PropTypes.number.isRequired + bonusPoints: PropTypes.number.isRequired, + lastSubmission: PropTypes.shape({ + evaluation: PropTypes.shape({ + score: PropTypes.number.isRequired, + points: PropTypes.number.isRequired + }) + }).isRequired, + solution: PropTypes.shape({ + createdAt: PropTypes.number.isRequired }).isRequired, accepted: PropTypes.bool }; diff --git a/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js b/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js index 20132f8af..35ca5ff2d 100644 --- a/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js +++ b/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js @@ -22,8 +22,8 @@ class SubmissionsTableContainer extends Component { sortSubmissions(submissions) { return submissions.sort((a, b) => { - var aTimestamp = a.get('data').get('submittedAt'); - var bTimestamp = b.get('data').get('submittedAt'); + var aTimestamp = a.getIn(['data', 'solution', 'createdAt']); + var bTimestamp = b.getIn(['data', 'solution', 'createdAt']); return bTimestamp - aTimestamp; }); } diff --git a/src/redux/modules/groupResults.js b/src/redux/modules/groupResults.js index 6ace08a4c..56b277821 100644 --- a/src/redux/modules/groupResults.js +++ b/src/redux/modules/groupResults.js @@ -26,7 +26,7 @@ export const additionalActionTypes = { export const fetchBestSubmission = (userId, assignmentId) => createApiAction({ type: additionalActionTypes.BEST_SUBMISSION, - endpoint: `/exercise-assignments/${assignmentId}/users/${userId}/best-submission`, + endpoint: `/exercise-assignments/${assignmentId}/users/${userId}/best-solution`, method: 'GET', meta: { userId, assignmentId } }); @@ -34,7 +34,7 @@ export const fetchBestSubmission = (userId, assignmentId) => export const fetchBestSubmissions = assignmentId => createApiAction({ type: additionalActionTypes.BEST_SUBMISSION, - endpoint: `/exercise-assignments/${assignmentId}/best-submissions`, + endpoint: `/exercise-assignments/${assignmentId}/best-solutions`, method: 'GET', meta: { assignmentId } }); diff --git a/src/redux/modules/submissions.js b/src/redux/modules/submissions.js index 84bea781b..0631845b0 100644 --- a/src/redux/modules/submissions.js +++ b/src/redux/modules/submissions.js @@ -95,7 +95,7 @@ export const resubmitAllSubmissions = assignmentId => export const fetchUsersSubmissions = (userId, assignmentId) => actions.fetchMany({ type: additionalActionTypes.LOAD_USERS_SUBMISSIONS, - endpoint: `/exercise-assignments/${assignmentId}/users/${userId}/submissions`, + endpoint: `/exercise-assignments/${assignmentId}/users/${userId}/solutions`, meta: { assignmentId, userId @@ -103,7 +103,7 @@ export const fetchUsersSubmissions = (userId, assignmentId) => }); export const downloadResultArchive = downloadHelper({ - endpoint: id => `/submissions/${id}/download-result`, + endpoint: id => `/submissions/evaluation/${id}/download-result`, fetch: fetchSubmissionIfNeeded, actionType: additionalActionTypes.DOWNLOAD_RESULT_ARCHIVE, fileNameSelector: (id, state) => `${id}.zip`, From e2340195b36e35f6c64e01a52a390e5cddcffc4b Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Tue, 14 Nov 2017 21:17:56 +0100 Subject: [PATCH 02/12] Old behaviour, new data format inside --- .../Groups/ResultsTable/ResultsTableRow.js | 14 ++---- .../EvaluationDetail/EvaluationDetail.js | 23 ++++----- .../SubmissionDetail/SubmissionDetail.js | 48 ++++++++++--------- .../BonusPointsContainer.js | 11 ++--- src/redux/modules/submissions.js | 13 ++--- test/redux/modules/submissions-test.js | 2 +- 6 files changed, 53 insertions(+), 58 deletions(-) diff --git a/src/components/Groups/ResultsTable/ResultsTableRow.js b/src/components/Groups/ResultsTable/ResultsTableRow.js index bdc205149..22f8387cc 100644 --- a/src/components/Groups/ResultsTable/ResultsTableRow.js +++ b/src/components/Groups/ResultsTable/ResultsTableRow.js @@ -14,19 +14,11 @@ const ResultsTableRow = ({ userId, assignmentsIds, submissions }) => { .filter(s => s !== null) .filter(s => s.exerciseAssignmentId === assignmentId)[0]; const points = - submission && - submission !== null && - submission.evaluation && - submission.evaluation !== null - ? submission.evaluation.points + submission !== null + ? submission.lastSubmission.evaluation.points : '-'; const bonusPoints = - submission && - submission !== null && - submission.evaluation && - submission.evaluation !== null - ? submission.evaluation.bonusPoints - : 0; + submission && submission !== null ? submission.bonusPoints : 0; totalPoints += points !== '-' ? points : 0; totalPoints += bonusPoints; return ( diff --git a/src/components/Submissions/EvaluationDetail/EvaluationDetail.js b/src/components/Submissions/EvaluationDetail/EvaluationDetail.js index cff6fb46c..0273261d8 100644 --- a/src/components/Submissions/EvaluationDetail/EvaluationDetail.js +++ b/src/components/Submissions/EvaluationDetail/EvaluationDetail.js @@ -19,7 +19,8 @@ const EvaluationDetail = ({ evaluation, isCorrect, submittedAt, - maxPoints + maxPoints, + bonusPoints }) => {evaluation.points}/{maxPoints} - {evaluation.bonusPoints !== 0 && + {bonusPoints !== 0 && - + } - {evaluation.bonusPoints !== 0 && + {bonusPoints !== 0 && 0 + !isCorrect || evaluation.points + bonusPoints <= 0, + 'text-success': isCorrect && evaluation.points + bonusPoints > 0 })} > - {evaluation.points + evaluation.bonusPoints}/{maxPoints} + {evaluation.points + bonusPoints}/{maxPoints} } @@ -172,7 +172,8 @@ EvaluationDetail.propTypes = { isCorrect: PropTypes.bool.isRequired, submittedAt: PropTypes.number.isRequired, evaluation: PropTypes.object, - maxPoints: PropTypes.number.isRequired + maxPoints: PropTypes.number.isRequired, + bonusPoints: PropTypes.number.isRequired }; export default EvaluationDetail; diff --git a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js index ea01ac235..d735bd6fa 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -23,17 +23,17 @@ class SubmissionDetail extends Component { submission: { id, note = '', - evaluationStatus, - submittedAt, - userId, - submittedBy, + solution: { createdAt, userId, files, ...restSolution }, + lastSubmission: { + submittedBy, + evaluation, + isCorrect, + evaluationStatus + }, maxPoints, - files, - evaluation, + bonusPoints, accepted, - originalSubmissionId, - runtimeEnvironmentId, - isCorrect + runtimeEnvironmentId }, assignment, isSupervisor @@ -46,12 +46,12 @@ class SubmissionDetail extends Component { @@ -81,15 +81,16 @@ class SubmissionDetail extends Component { {isSupervisor && } ( +const BonusPointsContainer = ({ bonusPoints, setPoints }) => -); + initialvalues={{ points: bonusPoints }} + />; BonusPointsContainer.propTypes = { submissionId: PropTypes.string.isRequired, - evaluation: PropTypes.shape({ - bonusPoints: PropTypes.number.isRequired - }).isRequired, + bonusPoints: PropTypes.number.isRequired, setPoints: PropTypes.func.isRequired }; diff --git a/src/redux/modules/submissions.js b/src/redux/modules/submissions.js index 0631845b0..e9580378b 100644 --- a/src/redux/modules/submissions.js +++ b/src/redux/modules/submissions.js @@ -15,6 +15,7 @@ const needsRefetching = item => const { actions, actionTypes, reduceActions } = factory({ resourceName, + apiEndpointFactory: id => `/assignment-solutions/${id}`, needsRefetching }); @@ -53,7 +54,7 @@ export const fetchSubmissionIfNeeded = actions.fetchOneIfNeeded; export const setPoints = (submissionId, bonusPoints) => createApiAction({ type: additionalActionTypes.SET_BONUS_POINTS, - endpoint: `/submissions/${submissionId}`, + endpoint: `/assignment-solutions/${submissionId}/bonus-points`, method: 'POST', body: { bonusPoints }, meta: { submissionId, bonusPoints } @@ -63,7 +64,7 @@ export const acceptSubmission = id => createApiAction({ type: additionalActionTypes.ACCEPT, method: 'POST', - endpoint: `/submissions/${id}/set-accepted`, + endpoint: `/assignment-solutions/${id}/set-accepted`, meta: { id } }); @@ -71,7 +72,7 @@ export const unacceptSubmission = id => createApiAction({ type: additionalActionTypes.UNACCEPT, method: 'DELETE', - endpoint: `/submissions/${id}/unset-accepted`, + endpoint: `/assignment-solutions/${id}/unset-accepted`, meta: { id } }); @@ -79,7 +80,7 @@ export const resubmitSubmission = (id, isPrivate, isDebug = true) => createApiAction({ type: submissionActionTypes.SUBMIT, method: 'POST', - endpoint: `/submissions/${id}/resubmit`, + endpoint: `/assignment-solutions/${id}/resubmit`, body: { private: isPrivate, debug: isDebug }, meta: { id } }); @@ -103,7 +104,7 @@ export const fetchUsersSubmissions = (userId, assignmentId) => }); export const downloadResultArchive = downloadHelper({ - endpoint: id => `/submissions/evaluation/${id}/download-result`, + endpoint: id => `/assignment-solutions/evaluation/${id}/download-result`, fetch: fetchSubmissionIfNeeded, actionType: additionalActionTypes.DOWNLOAD_RESULT_ARCHIVE, fileNameSelector: (id, state) => `${id}.zip`, @@ -124,7 +125,7 @@ const reducer = handleActions( { meta: { submissionId, bonusPoints } } ) => state.setIn( - ['resources', submissionId, 'data', 'evaluation', 'bonusPoints'], + ['resources', submissionId, 'data', 'bonusPoints'], Number(bonusPoints) ), diff --git a/test/redux/modules/submissions-test.js b/test/redux/modules/submissions-test.js index a69a2f000..4c4ce27b2 100644 --- a/test/redux/modules/submissions-test.js +++ b/test/redux/modules/submissions-test.js @@ -16,7 +16,7 @@ describe('Submissions', () => { expect(action.request).to.eql({ type: additionalActionTypes.ACCEPT, method: 'POST', - endpoint: `/submissions/${id}/set-accepted`, + endpoint: `/assignment-solutions/${id}/set-accepted`, meta: { id } }); }); From b75be00d43d54edd9a2b8f51738f5afedfc74baa Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Tue, 14 Nov 2017 21:51:20 +0100 Subject: [PATCH 03/12] Fix reference solutions --- .../ReferenceSolutionDetail.js | 20 +++++++++---------- .../ReferenceSolutionEvaluationsContainer.js | 7 +++---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/components/ReferenceSolutions/ReferenceSolutionDetail/ReferenceSolutionDetail.js b/src/components/ReferenceSolutions/ReferenceSolutionDetail/ReferenceSolutionDetail.js index 1b674b077..bc63e83e0 100644 --- a/src/components/ReferenceSolutions/ReferenceSolutionDetail/ReferenceSolutionDetail.js +++ b/src/components/ReferenceSolutions/ReferenceSolutionDetail/ReferenceSolutionDetail.js @@ -10,10 +10,9 @@ import ExercisesNameContainer from '../../../containers/ExercisesNameContainer'; const ReferenceSolutionDetail = ({ description, - uploadedAt, - solution: { userId }, + solution: { userId, createdAt }, exerciseId -}) => ( +}) => - {description} + + {description} + @@ -64,9 +65,9 @@ const ReferenceSolutionDetail = ({ /> - +   - + @@ -85,14 +86,13 @@ const ReferenceSolutionDetail = ({ - -); + ; ReferenceSolutionDetail.propTypes = { description: PropTypes.string.isRequired, - uploadedAt: PropTypes.number.isRequired, solution: PropTypes.shape({ - userId: PropTypes.string.isRequired + userId: PropTypes.string.isRequired, + createdAt: PropTypes.number.isRequired }).isRequired, exerciseId: PropTypes.string.isRequired }; diff --git a/src/containers/ReferenceSolutionEvaluationsContainer/ReferenceSolutionEvaluationsContainer.js b/src/containers/ReferenceSolutionEvaluationsContainer/ReferenceSolutionEvaluationsContainer.js index e9f95faaf..4eb12774e 100644 --- a/src/containers/ReferenceSolutionEvaluationsContainer/ReferenceSolutionEvaluationsContainer.js +++ b/src/containers/ReferenceSolutionEvaluationsContainer/ReferenceSolutionEvaluationsContainer.js @@ -28,13 +28,12 @@ class ReferenceSolutionEvaluationsContainer extends Component { return ( - {(...evaluations) => ( + {(...evaluations) => - )} + />} ); } @@ -51,7 +50,7 @@ ReferenceSolutionEvaluationsContainer.propTypes = { export default connect( (state, { referenceSolution }) => ({ evaluations: getReferenceSolutionEvaluationsByIdsSelector( - referenceSolution.evaluations + referenceSolution.submissions )(state) }), (dispatch, { referenceSolutionId, exerciseId }) => ({ From 4d24d1203ac8d4cf7dffc3349143570d26023df8 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Tue, 14 Nov 2017 23:10:29 +0100 Subject: [PATCH 04/12] WIP: Show other student submissions to supervisors --- .../SubmissionDetail/SubmissionDetail.js | 14 ++++- .../SubmissionEvaluationsContainer.js | 60 +++++++++++++++++++ .../SubmissionEvaluationsContainer/index.js | 1 + .../modules/referenceSolutionEvaluations.js | 2 +- src/redux/modules/submissionEvaluations.js | 42 +++++++++++++ src/redux/reducer.js | 4 +- src/redux/selectors/submissionEvaluations.js | 23 +++++++ 7 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js create mode 100644 src/containers/SubmissionEvaluationsContainer/index.js create mode 100644 src/redux/modules/submissionEvaluations.js create mode 100644 src/redux/selectors/submissionEvaluations.js diff --git a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js index d735bd6fa..c06a9e61c 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -9,6 +9,7 @@ import BonusPointsContainer from '../../../containers/BonusPointsContainer'; import DownloadResultArchiveContainer from '../../../containers/DownloadResultArchiveContainer'; import CommentThreadContainer from '../../../containers/CommentThreadContainer'; import SourceCodeViewerContainer from '../../../containers/SourceCodeViewerContainer'; +import SubmissionEvaluationsContainer from '../../../containers/SubmissionEvaluationsContainer'; import EvaluationDetail from '../EvaluationDetail'; import CompilationLogs from '../CompilationLogs'; @@ -33,7 +34,8 @@ class SubmissionDetail extends Component { maxPoints, bonusPoints, accepted, - runtimeEnvironmentId + runtimeEnvironmentId, + submissions }, assignment, isSupervisor @@ -104,6 +106,16 @@ class SubmissionDetail extends Component { } + {isSupervisor && + + + + + } } diff --git a/src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js b/src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js new file mode 100644 index 000000000..e80f77322 --- /dev/null +++ b/src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js @@ -0,0 +1,60 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; + +import { fetchSubmissionEvaluationsForSolution } from '../../redux/modules/submissionEvaluations'; +import { getSubmissionEvaluationsByIdsSelector } from '../../redux/selectors/submissionEvaluations'; +import ResourceRenderer from '../../components/helpers/ResourceRenderer'; +import ReferenceSolutionEvaluations from '../../components/ReferenceSolutions/ReferenceSolutionEvaluations'; + +class SubmissionEvaluationsContainer extends Component { + componentWillMount() { + SubmissionEvaluationsContainer.loadData(this.props); + } + + componentWillReceiveProps(newProps) { + if (this.props.submissionId !== newProps.submissionId) { + SubmissionEvaluationsContainer.loadData(newProps); + } + } + + static loadData = ({ fetchEvaluationsOnLoad }) => { + fetchEvaluationsOnLoad(); + }; + + render() { + const { submissionId, assignmentId, evaluations } = this.props; + + return ( + + {(...evaluations) => + } + + ); + } +} + +SubmissionEvaluationsContainer.propTypes = { + submissionId: PropTypes.string.isRequired, + submission: PropTypes.object.isRequired, + assignmentId: PropTypes.string.isRequired, + evaluations: ImmutablePropTypes.map, + fetchEvaluationsOnLoad: PropTypes.func.isRequired +}; + +export default connect( + (state, { submission }) => ({ + evaluations: getSubmissionEvaluationsByIdsSelector(submission.submissions)( + state + ) + }), + (dispatch, { submissionId }) => ({ + fetchEvaluationsOnLoad: () => + dispatch(fetchSubmissionEvaluationsForSolution(submissionId)) + }) +)(SubmissionEvaluationsContainer); diff --git a/src/containers/SubmissionEvaluationsContainer/index.js b/src/containers/SubmissionEvaluationsContainer/index.js new file mode 100644 index 000000000..ef9342bc3 --- /dev/null +++ b/src/containers/SubmissionEvaluationsContainer/index.js @@ -0,0 +1 @@ +export default from './SubmissionEvaluationsContainer'; diff --git a/src/redux/modules/referenceSolutionEvaluations.js b/src/redux/modules/referenceSolutionEvaluations.js index a2b7fb3f6..18368f5a1 100644 --- a/src/redux/modules/referenceSolutionEvaluations.js +++ b/src/redux/modules/referenceSolutionEvaluations.js @@ -3,7 +3,7 @@ import { handleActions } from 'redux-actions'; import factory, { initialState } from '../helpers/resourceManager'; import { downloadHelper } from '../helpers/api/download'; -const resourceName = 'referenceSolutions'; +const resourceName = 'referenceSolutionEvaluations'; const { actions, reduceActions } = factory({ resourceName, apiEndpointFactory: evaluationId => diff --git a/src/redux/modules/submissionEvaluations.js b/src/redux/modules/submissionEvaluations.js new file mode 100644 index 000000000..ada6e18d6 --- /dev/null +++ b/src/redux/modules/submissionEvaluations.js @@ -0,0 +1,42 @@ +import { handleActions } from 'redux-actions'; + +import factory, { initialState } from '../helpers/resourceManager'; +import { downloadHelper } from '../helpers/api/download'; + +const resourceName = 'submissionEvaluations'; +const { actions, reduceActions } = factory({ + resourceName, + apiEndpointFactory: evaluationId => + `/assignment-solutions/evaluation/${evaluationId}` +}); + +/** + * Actions + */ + +export const additionalActionTypes = { + DOWNLOAD_EVALUATION_ARCHIVE: 'recodex/files/DOWNLOAD_EVALUATION_ARCHIVE' +}; + +export const fetchSubmissionEvaluation = actions.fetchResource; +export const fetchSubmissionEvaluationIfNeeded = actions.fetchOneIfNeeded; + +export const fetchSubmissionEvaluationsForSolution = solutionId => + actions.fetchMany({ + endpoint: `/assignment-solutions/${solutionId}/evaluations` + }); + +export const downloadEvaluationArchive = downloadHelper({ + actionType: additionalActionTypes.DOWNLOAD_EVALUATION_ARCHIVE, + fetch: fetchSubmissionEvaluationIfNeeded, + endpoint: id => `/assignment-solutions/evaluation/${id}/download-result`, + fileNameSelector: (id, state) => `${id}.zip`, + contentType: 'application/zip' +}); + +/** + * Reducer + */ + +const reducer = handleActions(reduceActions, initialState); +export default reducer; diff --git a/src/redux/reducer.js b/src/redux/reducer.js index e5966d26b..9b7185bb4 100644 --- a/src/redux/reducer.js +++ b/src/redux/reducer.js @@ -46,6 +46,7 @@ import sisSubscribedGroups from './modules/sisSubscribedGroups'; import sisSupervisedCourses from './modules/sisSupervisedCourses'; import sisPossibleParents from './modules/sisPossibleParents'; import referenceSolutionEvaluations from './modules/referenceSolutionEvaluations'; +import submissionEvaluations from './modules/submissionEvaluations'; const createRecodexReducers = token => ({ auth: auth(token), @@ -91,7 +92,8 @@ const createRecodexReducers = token => ({ sisStatus, sisSubscribedGroups, sisSupervisedCourses, - sisPossibleParents + sisPossibleParents, + submissionEvaluations }); const librariesReducers = { diff --git a/src/redux/selectors/submissionEvaluations.js b/src/redux/selectors/submissionEvaluations.js new file mode 100644 index 000000000..586fe8522 --- /dev/null +++ b/src/redux/selectors/submissionEvaluations.js @@ -0,0 +1,23 @@ +import { createSelector } from 'reselect'; +import { isReady } from '../helpers/resourceManager'; + +const getSubmissionEvaluations = state => state.submissionEvaluations; +const getResources = submissionEvaluations => + submissionEvaluations.get('resources'); + +export const submissionEvaluationsSelector = createSelector( + getSubmissionEvaluations, + getResources +); + +export const submissionEvaluationSelector = evaluationId => + createSelector(submissionEvaluationsSelector, submissionEvaluations => + submissionEvaluations.get(evaluationId) + ); + +export const getSubmissionEvaluationsByIdsSelector = ids => + createSelector(submissionEvaluationsSelector, evaluations => + evaluations + .filter(isReady) + .filter(evaluation => ids.indexOf(evaluation.getIn(['data', 'id'])) >= 0) + ); From 13bf271c2261113d4665ed6a572cdcab21bf7d7a Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Wed, 15 Nov 2017 16:54:53 +0100 Subject: [PATCH 05/12] Fix download of results archive --- .../SubmissionDetail/SubmissionDetail.js | 7 +++- .../SubmissionStatus/SubmissionStatus.js | 39 ++++--------------- .../DownloadResultArchiveContainer.js | 9 ++--- src/redux/modules/submissions.js | 9 ----- 4 files changed, 17 insertions(+), 47 deletions(-) diff --git a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js index c06a9e61c..a23791449 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -29,7 +29,8 @@ class SubmissionDetail extends Component { submittedBy, evaluation, isCorrect, - evaluationStatus + evaluationStatus, + ...restLastSub }, maxPoints, bonusPoints, @@ -103,7 +104,9 @@ class SubmissionDetail extends Component { {isSupervisor && - + } {isSupervisor && diff --git a/src/components/Submissions/SubmissionStatus/SubmissionStatus.js b/src/components/Submissions/SubmissionStatus/SubmissionStatus.js index 60a4e8099..7ab66a299 100644 --- a/src/components/Submissions/SubmissionStatus/SubmissionStatus.js +++ b/src/components/Submissions/SubmissionStatus/SubmissionStatus.js @@ -1,12 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Table } from 'react-bootstrap'; -import { Link } from 'react-router'; import Icon from 'react-fontawesome'; import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'; import Box from '../../widgets/Box'; -import AssignmentStatusIcon - from '../../Assignments/Assignment/AssignmentStatusIcon'; +import AssignmentStatusIcon from '../../Assignments/Assignment/AssignmentStatusIcon'; import UsersNameContainer from '../../../containers/UsersNameContainer'; import withLinks from '../../../hoc/withLinks'; @@ -20,7 +18,7 @@ const SubmissionStatus = ({ originalSubmissionId = null, assignmentId, links: { SUBMISSION_DETAIL_URI_FACTORY } -}) => ( +}) => @@ -42,7 +40,9 @@ const SubmissionStatus = ({ defaultMessage="Note:" /> - {note} + + {note} + } @@ -82,36 +82,14 @@ const SubmissionStatus = ({ } - {originalSubmissionId && - - - - - - - - - - {originalSubmissionId} - - - } @@ -155,8 +133,7 @@ const SubmissionStatus = ({ - -); + ; SubmissionStatus.propTypes = { evaluationStatus: PropTypes.string.isRequired, diff --git a/src/containers/DownloadResultArchiveContainer/DownloadResultArchiveContainer.js b/src/containers/DownloadResultArchiveContainer/DownloadResultArchiveContainer.js index c4380cde1..73ca5770d 100644 --- a/src/containers/DownloadResultArchiveContainer/DownloadResultArchiveContainer.js +++ b/src/containers/DownloadResultArchiveContainer/DownloadResultArchiveContainer.js @@ -2,17 +2,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { downloadResultArchive } from '../../redux/modules/submissions'; +import { downloadEvaluationArchive } from '../../redux/modules/submissionEvaluations'; import ResultArchiveInfoBox from '../../components/Submissions/ResultArchiveInfoBox'; const DownloadResultArchiveContainer = ({ submissionId, downloadResultArchive -}) => ( +}) => - -); + ; DownloadResultArchiveContainer.propTypes = { submissionId: PropTypes.string.isRequired, @@ -24,7 +23,7 @@ export default connect( (dispatch, { submissionId }) => ({ downloadResultArchive: e => { e.preventDefault(); - dispatch(downloadResultArchive(submissionId)); + dispatch(downloadEvaluationArchive(submissionId)); } }) )(DownloadResultArchiveContainer); diff --git a/src/redux/modules/submissions.js b/src/redux/modules/submissions.js index e9580378b..ba1f00fbf 100644 --- a/src/redux/modules/submissions.js +++ b/src/redux/modules/submissions.js @@ -6,7 +6,6 @@ import factory, { defaultNeedsRefetching } from '../helpers/resourceManager'; import { actionTypes as submissionActionTypes } from './submission'; -import { downloadHelper } from '../helpers/api/download'; const resourceName = 'submissions'; const needsRefetching = item => @@ -103,14 +102,6 @@ export const fetchUsersSubmissions = (userId, assignmentId) => } }); -export const downloadResultArchive = downloadHelper({ - endpoint: id => `/assignment-solutions/evaluation/${id}/download-result`, - fetch: fetchSubmissionIfNeeded, - actionType: additionalActionTypes.DOWNLOAD_RESULT_ARCHIVE, - fileNameSelector: (id, state) => `${id}.zip`, - contentType: 'application/zip' -}); - /** * Reducer */ From ae88f7a880aaf65d65878c64c6ac8a89c2b0467b Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Wed, 15 Nov 2017 21:13:21 +0100 Subject: [PATCH 06/12] WIP: show all submissions of a solution --- .../EvaluationTable/EvaluationTable.js | 31 +------- .../ReferenceSolutionEvaluations.js | 79 ++++++++++--------- .../ReferenceSolutionsEvaluationsResults.js | 0 .../index.js | 0 .../SubmissionDetail/SubmissionDetail.js | 44 ++++++----- .../SubmissionEvaluations.js | 63 +++++++++++++++ .../SubmissionEvaluations/index.js | 1 + .../HardwareGroupFields.js | 20 +++-- .../SubmissionEvaluationsContainer.js | 20 +++-- src/pages/Submission/Submission.js | 56 +++++++++---- src/redux/modules/submissionEvaluations.js | 5 +- src/redux/selectors/submissionEvaluations.js | 6 ++ 12 files changed, 210 insertions(+), 115 deletions(-) rename src/components/{Submissions => ReferenceSolutions}/ReferenceSolutionsEvaluationsResults/ReferenceSolutionsEvaluationsResults.js (100%) rename src/components/{Submissions => ReferenceSolutions}/ReferenceSolutionsEvaluationsResults/index.js (100%) create mode 100644 src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js create mode 100644 src/components/Submissions/SubmissionEvaluations/index.js diff --git a/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.js b/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.js index f0f58e6c5..e86a1a692 100644 --- a/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.js +++ b/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.js @@ -7,18 +7,11 @@ import { FormattedNumber } from 'react-intl'; import { Table } from 'react-bootstrap'; -import { Link } from 'react-router'; import classnames from 'classnames'; -import withLinks from '../../../hoc/withLinks'; import AssignmentStatusIcon from '../../Assignments/Assignment/AssignmentStatusIcon'; -const EvaluationTable = ({ - evaluations, - referenceSolutionId, - exerciseId, - links: { REFERENCE_SOLUTION_EVALUATION_URI_FACTORY } -}) => +const EvaluationTable = ({ evaluations, renderButtons }) => @@ -81,21 +74,7 @@ const EvaluationTable = ({ /> } - + {renderButtons && renderButtons(e.id)} )} @@ -113,9 +92,7 @@ const EvaluationTable = ({ EvaluationTable.propTypes = { evaluations: PropTypes.array.isRequired, - referenceSolutionId: PropTypes.string.isRequired, - exerciseId: PropTypes.string.isRequired, - links: PropTypes.object + renderButtons: PropTypes.func }; -export default withLinks(EvaluationTable); +export default EvaluationTable; diff --git a/src/components/ReferenceSolutions/ReferenceSolutionEvaluations/ReferenceSolutionEvaluations.js b/src/components/ReferenceSolutions/ReferenceSolutionEvaluations/ReferenceSolutionEvaluations.js index 50d4fd787..a2de2ca12 100644 --- a/src/components/ReferenceSolutions/ReferenceSolutionEvaluations/ReferenceSolutionEvaluations.js +++ b/src/components/ReferenceSolutions/ReferenceSolutionEvaluations/ReferenceSolutionEvaluations.js @@ -1,48 +1,55 @@ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, intlShape, injectIntl } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; import Box from '../../widgets/Box'; import EvaluationTable from '../EvaluationTable'; +import withLinks from '../../../hoc/withLinks'; -const messages = defineMessages({ - title: { - id: 'app.referenceSolutionEvaluation.title', - defaultMessage: 'Evaluations of reference solution' - } -}); - -class ReferenceSolutionEvaluations extends Component { - render() { - const { - evaluations, - referenceSolutionId, - exerciseId, - intl: { formatMessage } - } = this.props; - - return ( - - - - ); - } -} +const ReferenceSolutionEvaluations = ({ + evaluations, + referenceSolutionId, + exerciseId, + links: { REFERENCE_SOLUTION_EVALUATION_URI_FACTORY } +}) => + + } + noPadding={true} + collapsable={true} + isOpen={true} + > + + } + /> + ; ReferenceSolutionEvaluations.propTypes = { evaluations: PropTypes.array.isRequired, referenceSolutionId: PropTypes.string.isRequired, exerciseId: PropTypes.string.isRequired, - intl: intlShape.isRequired + links: PropTypes.object }; -export default injectIntl(ReferenceSolutionEvaluations); +export default withLinks(ReferenceSolutionEvaluations); diff --git a/src/components/Submissions/ReferenceSolutionsEvaluationsResults/ReferenceSolutionsEvaluationsResults.js b/src/components/ReferenceSolutions/ReferenceSolutionsEvaluationsResults/ReferenceSolutionsEvaluationsResults.js similarity index 100% rename from src/components/Submissions/ReferenceSolutionsEvaluationsResults/ReferenceSolutionsEvaluationsResults.js rename to src/components/ReferenceSolutions/ReferenceSolutionsEvaluationsResults/ReferenceSolutionsEvaluationsResults.js diff --git a/src/components/Submissions/ReferenceSolutionsEvaluationsResults/index.js b/src/components/ReferenceSolutions/ReferenceSolutionsEvaluationsResults/index.js similarity index 100% rename from src/components/Submissions/ReferenceSolutionsEvaluationsResults/index.js rename to src/components/ReferenceSolutions/ReferenceSolutionsEvaluationsResults/index.js diff --git a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js index a23791449..7506b90f4 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -15,23 +15,22 @@ import EvaluationDetail from '../EvaluationDetail'; import CompilationLogs from '../CompilationLogs'; class SubmissionDetail extends Component { - state = { openFileId: null }; + state = { openFileId: null, activeSubmissionId: null }; openFile = id => this.setState({ openFileId: id }); hideFile = () => this.setState({ openFileId: null }); + componentWillMount() { + this.setState({ + activeSubmissionId: this.props.submission.lastSubmission.id + }); + } + render() { const { submission: { id, note = '', solution: { createdAt, userId, files, ...restSolution }, - lastSubmission: { - submittedBy, - evaluation, - isCorrect, - evaluationStatus, - ...restLastSub - }, maxPoints, bonusPoints, accepted, @@ -39,9 +38,17 @@ class SubmissionDetail extends Component { submissions }, assignment, - isSupervisor + isSupervisor, + evaluations } = this.props; - const { openFileId } = this.state; + const { openFileId, activeSubmissionId } = this.state; + const { + submittedBy, + evaluation, + isCorrect, + evaluationStatus, + ...restSub + } = evaluations.toJS()[activeSubmissionId].data; return (
@@ -104,9 +111,7 @@ class SubmissionDetail extends Component { {isSupervisor &&
- + } {isSupervisor && @@ -116,6 +121,8 @@ class SubmissionDetail extends Component { submissionId={id} submission={{ submissions }} assignmentId={assignment.id} + activeSubmissionId={activeSubmissionId} + onSelect={id => this.setState({ activeSubmissionId: id })} /> } @@ -136,12 +143,8 @@ SubmissionDetail.propTypes = { submission: PropTypes.shape({ id: PropTypes.string.isRequired, note: PropTypes.string, - lastSubmission: PropTypes.shape({ - evaluationStatus: PropTypes.string.isRequired, - submittedBy: PropTypes.string, - evaluation: PropTypes.object, - isCorrect: PropTypes.bool - }).isRequired, + lastSubmission: PropTypes.shape({ id: PropTypes.string.isRequired }) + .isRequired, solution: PropTypes.shape({ createdAt: PropTypes.number.isRequired, userId: PropTypes.string.isRequired, @@ -151,7 +154,8 @@ SubmissionDetail.propTypes = { runtimeEnvironmentId: PropTypes.string }).isRequired, assignment: PropTypes.object.isRequired, - isSupervisor: PropTypes.bool + isSupervisor: PropTypes.bool, + evaluations: PropTypes.object.isRequired }; export default SubmissionDetail; diff --git a/src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js b/src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js new file mode 100644 index 000000000..e9dc0fc67 --- /dev/null +++ b/src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js @@ -0,0 +1,63 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import Button from '../../widgets/FlatButton'; + +import Box from '../../widgets/Box'; +import EvaluationTable from '../../ReferenceSolutions/EvaluationTable'; +import withLinks from '../../../hoc/withLinks'; + +const SubmissionEvaluations = ({ + evaluations, + submissionId, + assignmentId, + activeSubmissionId, + onSelect, + links: { REFERENCE_SOLUTION_EVALUATION_URI_FACTORY } +}) => + + } + noPadding={true} + collapsable={true} + isOpen={true} + > + + } + /> + ; + +SubmissionEvaluations.propTypes = { + evaluations: PropTypes.array.isRequired, + submissionId: PropTypes.string.isRequired, + assignmentId: PropTypes.string.isRequired, + activeSubmissionId: PropTypes.string.isRequired, + onSelect: PropTypes.func.isRequired, + links: PropTypes.object +}; + +export default withLinks(SubmissionEvaluations); diff --git a/src/components/Submissions/SubmissionEvaluations/index.js b/src/components/Submissions/SubmissionEvaluations/index.js new file mode 100644 index 000000000..64e38b193 --- /dev/null +++ b/src/components/Submissions/SubmissionEvaluations/index.js @@ -0,0 +1 @@ +export default from './SubmissionEvaluations'; diff --git a/src/components/forms/EditHardwareGroupLimits/HardwareGroupFields.js b/src/components/forms/EditHardwareGroupLimits/HardwareGroupFields.js index ec16d48d9..3ba67a3e3 100644 --- a/src/components/forms/EditHardwareGroupLimits/HardwareGroupFields.js +++ b/src/components/forms/EditHardwareGroupLimits/HardwareGroupFields.js @@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl'; import { Field } from 'redux-form'; import { Row, Col, HelpBlock } from 'react-bootstrap'; import { KiloBytesTextField, SecondsTextField } from '../Fields'; -import ReferenceSolutionsEvaluationsResults from '../../Submissions/ReferenceSolutionsEvaluationsResults'; +import ReferenceSolutionsEvaluationsResults from '../../ReferenceSolutions/ReferenceSolutionsEvaluationsResults'; const sortTests = tests => { return tests.sort((a, b) => a.localeCompare(b)); @@ -21,7 +21,7 @@ const HardwareGroupFields = ({ referenceSolutionsEvaluations[hardwareGroup]; return ( - {sortTests(Object.keys(tests)).map((testName, j) => ( + {sortTests(Object.keys(tests)).map((testName, j) =>

{' '} {testName}

- {Object.keys(tests[testName]).map((taskId, k) => ( + {Object.keys(tests[testName]).map((taskId, k) =>
- {referenceSolutionsEvaluationsResults && ( + {referenceSolutionsEvaluationsResults && - )} + />} - {!referenceSolutionsEvaluationsResults && ( + {!referenceSolutionsEvaluationsResults && - - )} + }
- ))} + )} - ))} + )} ); }; diff --git a/src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js b/src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js index e80f77322..a360a6a1f 100644 --- a/src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js +++ b/src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import { fetchSubmissionEvaluationsForSolution } from '../../redux/modules/submissionEvaluations'; import { getSubmissionEvaluationsByIdsSelector } from '../../redux/selectors/submissionEvaluations'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; -import ReferenceSolutionEvaluations from '../../components/ReferenceSolutions/ReferenceSolutionEvaluations'; +import SubmissionEvaluations from '../../components/Submissions/SubmissionEvaluations'; class SubmissionEvaluationsContainer extends Component { componentWillMount() { @@ -24,15 +24,23 @@ class SubmissionEvaluationsContainer extends Component { }; render() { - const { submissionId, assignmentId, evaluations } = this.props; + const { + submissionId, + assignmentId, + evaluations, + activeSubmissionId, + onSelect + } = this.props; return ( {(...evaluations) => - } ); @@ -44,6 +52,8 @@ SubmissionEvaluationsContainer.propTypes = { submission: PropTypes.object.isRequired, assignmentId: PropTypes.string.isRequired, evaluations: ImmutablePropTypes.map, + activeSubmissionId: PropTypes.string.isRequired, + onSelect: PropTypes.func.isRequired, fetchEvaluationsOnLoad: PropTypes.func.isRequired }; diff --git a/src/pages/Submission/Submission.js b/src/pages/Submission/Submission.js index 10cfaffad..c3e26a37c 100644 --- a/src/pages/Submission/Submission.js +++ b/src/pages/Submission/Submission.js @@ -12,10 +12,12 @@ import SubmissionDetail, { import AcceptSolutionContainer from '../../containers/AcceptSolutionContainer'; import ResubmitSolutionContainer from '../../containers/ResubmitSolutionContainer'; import HierarchyLineContainer from '../../containers/HierarchyLineContainer'; +import FetchManyResourceRenderer from '../../components/helpers/FetchManyResourceRenderer'; import { fetchGroupsStats } from '../../redux/modules/stats'; import { fetchAssignmentIfNeeded } from '../../redux/modules/assignments'; import { fetchSubmissionIfNeeded } from '../../redux/modules/submissions'; +import { fetchSubmissionEvaluationsForSolution } from '../../redux/modules/submissionEvaluations'; import { getSubmission } from '../../redux/selectors/submissions'; import { getAssignment } from '../../redux/selectors/assignments'; import { @@ -24,11 +26,16 @@ import { isSuperAdmin } from '../../redux/selectors/users'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; +import { + getSubmissionEvaluationsByIdsSelector, + fetchManyStatus +} from '../../redux/selectors/submissionEvaluations'; class Submission extends Component { static loadAsync = ({ submissionId, assignmentId }, dispatch) => Promise.all([ dispatch(fetchSubmissionIfNeeded(submissionId)), + dispatch(fetchSubmissionEvaluationsForSolution(submissionId)), dispatch(fetchAssignmentIfNeeded(assignmentId)) .then(res => res.value) .then(assignment => dispatch(fetchGroupsStats(assignment.groupId))) @@ -49,7 +56,9 @@ class Submission extends Component { assignment, submission, params: { assignmentId }, - isSupervisorOrMore + isSupervisorOrMore, + evaluations, + fetchStatus } = this.props; return ( @@ -120,11 +129,16 @@ class Submission extends Component { isDebug={false} />

} - + {console.log(fetchStatus)} + + {() => + } + } @@ -141,18 +155,30 @@ Submission.propTypes = { children: PropTypes.element, submission: PropTypes.object, loadAsync: PropTypes.func.isRequired, - isSupervisorOrMore: PropTypes.func.isRequired + isSupervisorOrMore: PropTypes.func.isRequired, + evaluations: PropTypes.object, + fetchStatus: PropTypes.string.isRequired }; export default connect( - (state, { params: { submissionId, assignmentId } }) => ({ - submission: getSubmission(submissionId)(state), - assignment: getAssignment(assignmentId)(state), - isSupervisorOrMore: groupId => - isSupervisorOf(loggedInUserIdSelector(state), groupId)(state) || - isAdminOf(loggedInUserIdSelector(state), groupId)(state) || - isSuperAdmin(loggedInUserIdSelector(state))(state) - }), + (state, { params: { submissionId, assignmentId } }) => { + const submission = getSubmission(submissionId)(state); + return { + submission, + assignment: getAssignment(assignmentId)(state), + isSupervisorOrMore: groupId => + isSupervisorOf(loggedInUserIdSelector(state), groupId)(state) || + isAdminOf(loggedInUserIdSelector(state), groupId)(state) || + isSuperAdmin(loggedInUserIdSelector(state))(state), + evaluations: + submission && submission.toJS().data != null + ? getSubmissionEvaluationsByIdsSelector( + submission.toJS().data.submissions + )(state) + : null, + fetchStatus: fetchManyStatus(submissionId)(state) + }; + }, (dispatch, { params }) => ({ loadAsync: () => Submission.loadAsync(params, dispatch) }) diff --git a/src/redux/modules/submissionEvaluations.js b/src/redux/modules/submissionEvaluations.js index ada6e18d6..74dbf2628 100644 --- a/src/redux/modules/submissionEvaluations.js +++ b/src/redux/modules/submissionEvaluations.js @@ -21,9 +21,12 @@ export const additionalActionTypes = { export const fetchSubmissionEvaluation = actions.fetchResource; export const fetchSubmissionEvaluationIfNeeded = actions.fetchOneIfNeeded; +export const fetchManyEndpoint = id => + `/assignment-solutions/${id}/evaluations`; + export const fetchSubmissionEvaluationsForSolution = solutionId => actions.fetchMany({ - endpoint: `/assignment-solutions/${solutionId}/evaluations` + endpoint: fetchManyEndpoint(solutionId) }); export const downloadEvaluationArchive = downloadHelper({ diff --git a/src/redux/selectors/submissionEvaluations.js b/src/redux/selectors/submissionEvaluations.js index 586fe8522..8cdb34bc3 100644 --- a/src/redux/selectors/submissionEvaluations.js +++ b/src/redux/selectors/submissionEvaluations.js @@ -1,5 +1,6 @@ import { createSelector } from 'reselect'; import { isReady } from '../helpers/resourceManager'; +import { fetchManyEndpoint } from '../modules/submissionEvaluations'; const getSubmissionEvaluations = state => state.submissionEvaluations; const getResources = submissionEvaluations => @@ -21,3 +22,8 @@ export const getSubmissionEvaluationsByIdsSelector = ids => .filter(isReady) .filter(evaluation => ids.indexOf(evaluation.getIn(['data', 'id'])) >= 0) ); + +export const fetchManyStatus = id => + createSelector(getSubmissionEvaluations, state => + state.getIn(['fetchManyStatus', fetchManyEndpoint(id)]) + ); From 5150502fd4f07c6ad50768008c30dc3a7a76a5d4 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Thu, 16 Nov 2017 15:43:03 +0100 Subject: [PATCH 07/12] Fix submission loading error --- .../EvaluationTable/EvaluationTable.css | 3 + .../EvaluationTable/EvaluationTable.js | 9 +-- .../SubmissionDetail/SubmissionDetail.js | 24 ++++--- .../SubmissionEvaluations.js | 5 +- .../ResubmitSolution/ResubmitSolution.js | 39 +++++++---- .../ResubmitSolutionContainer.js | 2 +- .../SubmissionEvaluationsContainer.js | 70 ------------------- .../SubmissionEvaluationsContainer/index.js | 1 - src/pages/Submission/Submission.js | 7 +- 9 files changed, 55 insertions(+), 105 deletions(-) create mode 100644 src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.css delete mode 100644 src/containers/SubmissionEvaluationsContainer/SubmissionEvaluationsContainer.js delete mode 100644 src/containers/SubmissionEvaluationsContainer/index.js diff --git a/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.css b/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.css new file mode 100644 index 000000000..68d1b8f5d --- /dev/null +++ b/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.css @@ -0,0 +1,3 @@ +.activeRow { + background-color: #eee; +} diff --git a/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.js b/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.js index e86a1a692..59e686720 100644 --- a/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.js +++ b/src/components/ReferenceSolutions/EvaluationTable/EvaluationTable.js @@ -8,10 +8,10 @@ import { } from 'react-intl'; import { Table } from 'react-bootstrap'; import classnames from 'classnames'; - import AssignmentStatusIcon from '../../Assignments/Assignment/AssignmentStatusIcon'; +import './EvaluationTable.css'; -const EvaluationTable = ({ evaluations, renderButtons }) => +const EvaluationTable = ({ evaluations, renderButtons, selectedRowId = '' }) =>
- - - -
+ + + +
+ {id === activeSubmissionId + ? + : } +
@@ -40,7 +40,7 @@ const EvaluationTable = ({ evaluations, renderButtons }) => return b.evaluation.evaluatedAt - a.evaluation.evaluatedAt; }) .map(e => - + - this.setState({ activeSubmissionId: id })} - /> + + {(...evaluations) => + + this.setState({ activeSubmissionId: id })} + />} + } } diff --git a/src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js b/src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js index e9dc0fc67..32f13047e 100644 --- a/src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js +++ b/src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js @@ -28,13 +28,14 @@ const SubmissionEvaluations = ({ > + + + + + - + {evaluation && + } - {isSupervisor && + {evaluation && + isSupervisor && } - + {evaluation && + } - {isSupervisor && + {evaluation && + isSupervisor &&
EvaluationTable.propTypes = { evaluations: PropTypes.array.isRequired, - renderButtons: PropTypes.func + renderButtons: PropTypes.func, + selectedRowId: PropTypes.string }; export default EvaluationTable; diff --git a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js index 7506b90f4..ac2ef4e0f 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -9,7 +9,8 @@ import BonusPointsContainer from '../../../containers/BonusPointsContainer'; import DownloadResultArchiveContainer from '../../../containers/DownloadResultArchiveContainer'; import CommentThreadContainer from '../../../containers/CommentThreadContainer'; import SourceCodeViewerContainer from '../../../containers/SourceCodeViewerContainer'; -import SubmissionEvaluationsContainer from '../../../containers/SubmissionEvaluationsContainer'; +import SubmissionEvaluations from '../SubmissionEvaluations'; +import ResourceRenderer from '../../helpers/ResourceRenderer'; import EvaluationDetail from '../EvaluationDetail'; import CompilationLogs from '../CompilationLogs'; @@ -34,8 +35,7 @@ class SubmissionDetail extends Component { maxPoints, bonusPoints, accepted, - runtimeEnvironmentId, - submissions + runtimeEnvironmentId }, assignment, isSupervisor, @@ -117,13 +117,17 @@ class SubmissionDetail extends Component { {isSupervisor &&
{id === activeSubmissionId ? : ; + id="app.resubmitSolution.confirm" + defaultMessage="Are you sure you want to resubmit this solution?" + /> + } + > + + ; ResubmitSolution.propTypes = { + id: PropTypes.string.isRequired, resubmit: PropTypes.func.isRequired, isDebug: PropTypes.bool.isRequired }; diff --git a/src/containers/ResubmitSolutionContainer/ResubmitSolutionContainer.js b/src/containers/ResubmitSolutionContainer/ResubmitSolutionContainer.js index 88c4b409f..ef78c712f 100644 --- a/src/containers/ResubmitSolutionContainer/ResubmitSolutionContainer.js +++ b/src/containers/ResubmitSolutionContainer/ResubmitSolutionContainer.js @@ -24,7 +24,7 @@ const ResubmitSolutionContainer = ({ }) => { return ( - + { - fetchEvaluationsOnLoad(); - }; - - render() { - const { - submissionId, - assignmentId, - evaluations, - activeSubmissionId, - onSelect - } = this.props; - - return ( - - {(...evaluations) => - } - - ); - } -} - -SubmissionEvaluationsContainer.propTypes = { - submissionId: PropTypes.string.isRequired, - submission: PropTypes.object.isRequired, - assignmentId: PropTypes.string.isRequired, - evaluations: ImmutablePropTypes.map, - activeSubmissionId: PropTypes.string.isRequired, - onSelect: PropTypes.func.isRequired, - fetchEvaluationsOnLoad: PropTypes.func.isRequired -}; - -export default connect( - (state, { submission }) => ({ - evaluations: getSubmissionEvaluationsByIdsSelector(submission.submissions)( - state - ) - }), - (dispatch, { submissionId }) => ({ - fetchEvaluationsOnLoad: () => - dispatch(fetchSubmissionEvaluationsForSolution(submissionId)) - }) -)(SubmissionEvaluationsContainer); diff --git a/src/containers/SubmissionEvaluationsContainer/index.js b/src/containers/SubmissionEvaluationsContainer/index.js deleted file mode 100644 index ef9342bc3..000000000 --- a/src/containers/SubmissionEvaluationsContainer/index.js +++ /dev/null @@ -1 +0,0 @@ -export default from './SubmissionEvaluationsContainer'; diff --git a/src/pages/Submission/Submission.js b/src/pages/Submission/Submission.js index c3e26a37c..a69218e29 100644 --- a/src/pages/Submission/Submission.js +++ b/src/pages/Submission/Submission.js @@ -121,15 +121,14 @@ class Submission extends Component {

} - {console.log(fetchStatus)} {() => Date: Thu, 16 Nov 2017 16:15:05 +0100 Subject: [PATCH 08/12] Better submission evaluations selector --- src/pages/Submission/Submission.js | 30 +++++++------------- src/redux/selectors/submissionEvaluations.js | 19 +++++++++---- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/pages/Submission/Submission.js b/src/pages/Submission/Submission.js index a69218e29..f2b325d47 100644 --- a/src/pages/Submission/Submission.js +++ b/src/pages/Submission/Submission.js @@ -27,7 +27,7 @@ import { } from '../../redux/selectors/users'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; import { - getSubmissionEvaluationsByIdsSelector, + evaluationsForSubmissionSelector, fetchManyStatus } from '../../redux/selectors/submissionEvaluations'; @@ -160,24 +160,16 @@ Submission.propTypes = { }; export default connect( - (state, { params: { submissionId, assignmentId } }) => { - const submission = getSubmission(submissionId)(state); - return { - submission, - assignment: getAssignment(assignmentId)(state), - isSupervisorOrMore: groupId => - isSupervisorOf(loggedInUserIdSelector(state), groupId)(state) || - isAdminOf(loggedInUserIdSelector(state), groupId)(state) || - isSuperAdmin(loggedInUserIdSelector(state))(state), - evaluations: - submission && submission.toJS().data != null - ? getSubmissionEvaluationsByIdsSelector( - submission.toJS().data.submissions - )(state) - : null, - fetchStatus: fetchManyStatus(submissionId)(state) - }; - }, + (state, { params: { submissionId, assignmentId } }) => ({ + submission: getSubmission(submissionId)(state), + assignment: getAssignment(assignmentId)(state), + isSupervisorOrMore: groupId => + isSupervisorOf(loggedInUserIdSelector(state), groupId)(state) || + isAdminOf(loggedInUserIdSelector(state), groupId)(state) || + isSuperAdmin(loggedInUserIdSelector(state))(state), + evaluations: evaluationsForSubmissionSelector(submissionId)(state), + fetchStatus: fetchManyStatus(submissionId)(state) + }), (dispatch, { params }) => ({ loadAsync: () => Submission.loadAsync(params, dispatch) }) diff --git a/src/redux/selectors/submissionEvaluations.js b/src/redux/selectors/submissionEvaluations.js index 8cdb34bc3..7dee2e0a3 100644 --- a/src/redux/selectors/submissionEvaluations.js +++ b/src/redux/selectors/submissionEvaluations.js @@ -1,6 +1,7 @@ import { createSelector } from 'reselect'; import { isReady } from '../helpers/resourceManager'; import { fetchManyEndpoint } from '../modules/submissionEvaluations'; +import { getSubmission } from './submissions'; const getSubmissionEvaluations = state => state.submissionEvaluations; const getResources = submissionEvaluations => @@ -16,11 +17,19 @@ export const submissionEvaluationSelector = evaluationId => submissionEvaluations.get(evaluationId) ); -export const getSubmissionEvaluationsByIdsSelector = ids => - createSelector(submissionEvaluationsSelector, evaluations => - evaluations - .filter(isReady) - .filter(evaluation => ids.indexOf(evaluation.getIn(['data', 'id'])) >= 0) +export const evaluationsForSubmissionSelector = submissionId => + createSelector( + [getSubmission(submissionId), submissionEvaluationsSelector], + (submission, evaluations) => + evaluations + .filter(isReady) + .filter( + evaluation => + submission + .get('data') + .get('submissions') + .indexOf(evaluation.getIn(['data', 'id'])) >= 0 + ) ); export const fetchManyStatus = id => From 94679a38fca610626f322f4a465c4c0ab9d97253 Mon Sep 17 00:00:00 2001 From: Martin Polanka Date: Fri, 17 Nov 2017 16:35:16 +0100 Subject: [PATCH 09/12] Submission can be null in results table --- src/components/Groups/ResultsTable/ResultsTableRow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Groups/ResultsTable/ResultsTableRow.js b/src/components/Groups/ResultsTable/ResultsTableRow.js index 22f8387cc..b4ecc1c2a 100644 --- a/src/components/Groups/ResultsTable/ResultsTableRow.js +++ b/src/components/Groups/ResultsTable/ResultsTableRow.js @@ -14,7 +14,7 @@ const ResultsTableRow = ({ userId, assignmentsIds, submissions }) => { .filter(s => s !== null) .filter(s => s.exerciseAssignmentId === assignmentId)[0]; const points = - submission !== null + submission && submission !== null ? submission.lastSubmission.evaluation.points : '-'; const bonusPoints = From f7e121eeee1b9d033b2fc835d3ba90602da46670 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Fri, 17 Nov 2017 17:05:16 +0100 Subject: [PATCH 10/12] Various fixes --- .../ReferenceSolutionsList.js | 7 +++-- .../Groups/ResultsTable/ResultsTableRow.js | 5 +++- .../forms/EditGroupForm/EditGroupForm.js | 9 ------- .../SidebarContainer/SidebarContainer.js | 6 ++--- src/pages/Assignment/Assignment.js | 6 ++--- src/pages/Dashboard/Dashboard.js | 18 ++++++------- .../EditExerciseConfig/EditExerciseConfig.js | 27 +++++++++++-------- src/pages/Exercises/Exercises.js | 7 +++-- src/pages/Group/Group.js | 4 +-- src/pages/Instance/Instance.js | 4 +-- src/pages/Submission/Submission.js | 4 +-- src/pages/User/User.js | 4 +-- src/pages/Users/Users.js | 7 +++-- src/redux/selectors/users.js | 14 +++++----- 14 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/components/Exercises/ReferenceSolutionsList/ReferenceSolutionsList.js b/src/components/Exercises/ReferenceSolutionsList/ReferenceSolutionsList.js index 9d6788593..7494313da 100644 --- a/src/components/Exercises/ReferenceSolutionsList/ReferenceSolutionsList.js +++ b/src/components/Exercises/ReferenceSolutionsList/ReferenceSolutionsList.js @@ -36,10 +36,9 @@ const ReferenceSolutionsList = ({ .map( ({ id, - uploadedAt, description, permissionHints, - solution: { userId } + solution: { userId, createdAt } }) =>
@@ -49,8 +48,8 @@ const ReferenceSolutionsList = ({ {description} -  {' '} - +  {' '} + diff --git a/src/components/Groups/ResultsTable/ResultsTableRow.js b/src/components/Groups/ResultsTable/ResultsTableRow.js index 22f8387cc..d0be83bee 100644 --- a/src/components/Groups/ResultsTable/ResultsTableRow.js +++ b/src/components/Groups/ResultsTable/ResultsTableRow.js @@ -14,7 +14,10 @@ const ResultsTableRow = ({ userId, assignmentsIds, submissions }) => { .filter(s => s !== null) .filter(s => s.exerciseAssignmentId === assignmentId)[0]; const points = - submission !== null + submission && + submission !== null && + submission.lastSubmission.evaluation && + submission.lastSubmission.evaluation.points ? submission.lastSubmission.evaluation.points : '-'; const bonusPoints = diff --git a/src/components/forms/EditGroupForm/EditGroupForm.js b/src/components/forms/EditGroupForm/EditGroupForm.js index 867d9f50a..938287880 100644 --- a/src/components/forms/EditGroupForm/EditGroupForm.js +++ b/src/components/forms/EditGroupForm/EditGroupForm.js @@ -227,15 +227,6 @@ const validate = ({ localizedTexts = [], threshold }) => { /> ); } - - if (!localizedTexts[i].description) { - localeErrors['description'] = ( - - ); - } } localizedTextsErrors[i] = localeErrors; diff --git a/src/containers/SidebarContainer/SidebarContainer.js b/src/containers/SidebarContainer/SidebarContainer.js index b537d72eb..72528347b 100644 --- a/src/containers/SidebarContainer/SidebarContainer.js +++ b/src/containers/SidebarContainer/SidebarContainer.js @@ -8,8 +8,8 @@ import { } from '../../redux/selectors/groups'; import { notificationsSelector, - isSuperAdmin, - isSupervisor + isSupervisor, + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; const mapStateToProps = state => { @@ -17,7 +17,7 @@ const mapStateToProps = state => { return { instances: memberOfInstances(userId)(state), studentOf: studentOfSelector(userId)(state), - isAdmin: isSuperAdmin(userId)(state), + isAdmin: isLoggedAsSuperAdmin(state), isSupervisor: isSupervisor(userId)(state), supervisorOf: supervisorOfSelector(userId)(state), notifications: notificationsSelector(state) diff --git a/src/pages/Assignment/Assignment.js b/src/pages/Assignment/Assignment.js index d25c00f06..6ba0ea88c 100644 --- a/src/pages/Assignment/Assignment.js +++ b/src/pages/Assignment/Assignment.js @@ -27,9 +27,9 @@ import { canSubmitSolution } from '../../redux/selectors/canSubmit'; import { isSubmitting } from '../../redux/selectors/submission'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; import { - isSuperAdmin, isStudentOf, - isSupervisorOf + isSupervisorOf, + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import { runtimeEnvironmentSelector } from '../../redux/selectors/runtimeEnvironments'; @@ -356,7 +356,7 @@ export default withLinks( ), userId, loggedInUserId, - isSuperAdmin: isSuperAdmin(loggedInUserId)(state), + isSuperAdmin: isLoggedAsSuperAdmin(state), isStudentOf: groupId => isStudentOf(loggedInUserId, groupId)(state), isSupervisorOf: groupId => isSupervisorOf(loggedInUserId, groupId)(state), diff --git a/src/pages/Dashboard/Dashboard.js b/src/pages/Dashboard/Dashboard.js index 167113ca7..984cf3c05 100644 --- a/src/pages/Dashboard/Dashboard.js +++ b/src/pages/Dashboard/Dashboard.js @@ -30,7 +30,7 @@ import { supervisorOfGroupsIdsSelector, isStudent, isSupervisor, - isSuperAdmin + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; @@ -73,7 +73,7 @@ class Dashboard extends Component { const state = getState(); const user = getJsData(getUser(userId)(state)); const groups = user.groups.studentOf.concat(user.groups.supervisorOf); - const isAdmin = isSuperAdmin(userId)(state); + const isAdmin = isLoggedAsSuperAdmin(state); return dispatch(fetchGroupsIfNeeded(...groups)).then(groups => Promise.all( @@ -103,11 +103,11 @@ class Dashboard extends Component { supervisor, supervisorOf, supervisorOfGroupsIds, + superadmin, groupAssignments, groupStatistics, usersStatistics, allGroups, - isAdmin, links: { GROUP_URI_FACTORY }, intl: { locale } } = this.props; @@ -265,8 +265,10 @@ class Dashboard extends Component { } } - {(supervisor || isAdmin) && - + {(supervisor || superadmin) && + {(...groups) => } } @@ -343,7 +345,6 @@ Dashboard.propTypes = { groupStatistics: PropTypes.func.isRequired, usersStatistics: PropTypes.func.isRequired, allGroups: PropTypes.array, - isAdmin: PropTypes.bool, links: PropTypes.object, intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired }; @@ -356,7 +357,7 @@ export default withLinks( userId, student: isStudent(userId)(state), supervisor: isSupervisor(userId)(state), - superadmin: isSuperAdmin(userId)(state), + superadmin: isLoggedAsSuperAdmin(state), user: getUser(userId)(state), studentOfGroupsIds: studentOfGroupsIdsSelector(userId)(state).toArray(), studentOf: studentOfSelector(userId)(state).toArray(), @@ -368,8 +369,7 @@ export default withLinks( groupStatistics: groupId => createGroupsStatsSelector(groupId)(state), usersStatistics: statistics => statistics.find(stat => stat.userId === userId) || {}, - allGroups: groupsSelector(state).toArray(), - isAdmin: isSuperAdmin(userId)(state) + allGroups: groupsSelector(state).toArray() }; }, (dispatch, { params }) => ({ diff --git a/src/pages/EditExerciseConfig/EditExerciseConfig.js b/src/pages/EditExerciseConfig/EditExerciseConfig.js index d4ab3d3de..373e8b19a 100644 --- a/src/pages/EditExerciseConfig/EditExerciseConfig.js +++ b/src/pages/EditExerciseConfig/EditExerciseConfig.js @@ -11,7 +11,7 @@ import Box from '../../components/widgets/Box'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; import { LocalizedExerciseName } from '../../components/helpers/LocalizedNames'; -// import EditExerciseConfigForm from '../../components/forms/EditExerciseConfigForm/EditExerciseConfigForm'; +import EditExerciseConfigForm from '../../components/forms/EditExerciseConfigForm/EditExerciseConfigForm'; import EditEnvironmentConfigForm from '../../components/forms/EditEnvironmentConfigForm'; import EditScoreConfigForm from '../../components/forms/EditScoreConfigForm'; import EditSimpleLimitsBox from '../../components/Exercises/EditSimpleLimitsBox'; @@ -51,6 +51,7 @@ import { simpleLimitsSelector } from '../../redux/selectors/simpleLimits'; import withLinks from '../../hoc/withLinks'; import { getLocalizedName } from '../../helpers/getLocalizedData'; +import { isLoggedAsSuperAdmin } from '../../redux/selectors/users'; class EditExerciseConfig extends Component { componentWillMount = () => this.props.loadAsync(); @@ -87,19 +88,20 @@ class EditExerciseConfig extends Component { params: { exerciseId }, exercise, editEnvironmentConfigs, - // setConfig, + setConfig, runtimeEnvironments, environmentFormValues, exerciseConfig, exerciseEnvironmentConfig, exerciseScoreConfig, editEnvironmentSimpleLimits, - // pipelines, + pipelines, limits, setHorizontally, setVertically, setAll, editScoreConfig, + superadmin, intl: { locale } } = this.props; @@ -200,13 +202,14 @@ class EditExerciseConfig extends Component { > {(config, ...runtimeEnvironments) =>
- {/* */} + {superadmin && + } - simpleLimitsSelector(exerciseId, runtimeEnvironmentId)(state) + simpleLimitsSelector(exerciseId, runtimeEnvironmentId)(state), + superadmin: isLoggedAsSuperAdmin(state) }; }, (dispatch, { params: { exerciseId } }) => ({ diff --git a/src/pages/Exercises/Exercises.js b/src/pages/Exercises/Exercises.js index b2ff3c38e..7f53e45fd 100644 --- a/src/pages/Exercises/Exercises.js +++ b/src/pages/Exercises/Exercises.js @@ -13,7 +13,10 @@ import PageContent from '../../components/layout/PageContent'; import Box from '../../components/widgets/Box'; import { AddIcon, EditIcon } from '../../components/icons'; import { fetchManyStatus } from '../../redux/selectors/exercises'; -import { canEditExercise, isSuperAdmin } from '../../redux/selectors/users'; +import { + canEditExercise, + isLoggedAsSuperAdmin +} from '../../redux/selectors/users'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; import { fetchExercises, @@ -201,7 +204,7 @@ export default withLinks( const userId = loggedInUserIdSelector(state); return { fetchStatus: fetchManyStatus(state), - isSuperAdmin: isSuperAdmin(userId)(state), + isSuperAdmin: isLoggedAsSuperAdmin(state), isAuthorOfExercise: exerciseId => canEditExercise(userId, exerciseId)(state) }; diff --git a/src/pages/Group/Group.js b/src/pages/Group/Group.js index 94f671b29..cb4baece4 100644 --- a/src/pages/Group/Group.js +++ b/src/pages/Group/Group.js @@ -45,7 +45,7 @@ import { isStudentOf, isSupervisorOf, isAdminOf, - isSuperAdmin + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import { @@ -331,7 +331,7 @@ const mapStateToProps = (state, { params: { groupId } }) => { isStudent: isStudentOf(userId, groupId)(state), isSupervisor: isSupervisorOf(userId, groupId)(state), isAdmin: isAdminOf(userId, groupId)(state), - isSuperAdmin: isSuperAdmin(userId)(state) + isSuperAdmin: isLoggedAsSuperAdmin(state) }; }; diff --git a/src/pages/Instance/Instance.js b/src/pages/Instance/Instance.js index d1a888f02..f38a7e492 100644 --- a/src/pages/Instance/Instance.js +++ b/src/pages/Instance/Instance.js @@ -24,7 +24,7 @@ import { createGroup } from '../../redux/modules/groups'; import { fetchInstancePublicGroups } from '../../redux/modules/publicGroups'; import { publicGroupsSelectors } from '../../redux/selectors/publicGroups'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; -import { isSuperAdmin } from '../../redux/selectors/users'; +import { isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import withLinks from '../../hoc/withLinks'; @@ -152,7 +152,7 @@ export default withLinks( instance: instanceSelector(state, instanceId), groups: publicGroupsSelectors(state), isAdmin: isAdminOfInstance(userId, instanceId)(state), - isSuperAdmin: isSuperAdmin(userId)(state), + isSuperAdmin: isLoggedAsSuperAdmin(state), formValues: getFormValues('editGroup')(state) }; }, diff --git a/src/pages/Submission/Submission.js b/src/pages/Submission/Submission.js index f2b325d47..b647febf2 100644 --- a/src/pages/Submission/Submission.js +++ b/src/pages/Submission/Submission.js @@ -23,7 +23,7 @@ import { getAssignment } from '../../redux/selectors/assignments'; import { isSupervisorOf, isAdminOf, - isSuperAdmin + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; import { @@ -166,7 +166,7 @@ export default connect( isSupervisorOrMore: groupId => isSupervisorOf(loggedInUserIdSelector(state), groupId)(state) || isAdminOf(loggedInUserIdSelector(state), groupId)(state) || - isSuperAdmin(loggedInUserIdSelector(state))(state), + isLoggedAsSuperAdmin(state), evaluations: evaluationsForSubmissionSelector(submissionId)(state), fetchStatus: fetchManyStatus(submissionId)(state) }), diff --git a/src/pages/User/User.js b/src/pages/User/User.js index a691a169d..5c62d5e7b 100644 --- a/src/pages/User/User.js +++ b/src/pages/User/User.js @@ -25,7 +25,7 @@ import { getUser, studentOfGroupsIdsSelector, isStudent, - isSuperAdmin + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import { getProfile } from '../../redux/selectors/publicProfiles'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; @@ -275,7 +275,7 @@ export default withLinks( connect( (state, { params: { userId } }) => { const loggedInUserId = loggedInUserIdSelector(state); - const isSuperadmin = isSuperAdmin(loggedInUserId)(state); + const isSuperadmin = isLoggedAsSuperAdmin(state); const studentOfArray = studentOfSelector2(userId)(state) .toList() diff --git a/src/pages/Users/Users.js b/src/pages/Users/Users.js index f3b12d029..17eb8fe49 100644 --- a/src/pages/Users/Users.js +++ b/src/pages/Users/Users.js @@ -15,11 +15,10 @@ import Box from '../../components/widgets/Box'; import UsersList from '../../components/Users/UsersList'; import SearchContainer from '../../containers/SearchContainer'; import { - isSuperAdmin, fetchManyStatus, - loggedInUserSelector + loggedInUserSelector, + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; -import { loggedInUserIdSelector } from '../../redux/selectors/auth'; import { fetchAllUsers } from '../../redux/modules/users'; import { takeOver } from '../../redux/modules/auth'; import { searchPeople } from '../../redux/modules/search'; @@ -172,7 +171,7 @@ export default withLinks( connect( state => { return { - isSuperAdmin: isSuperAdmin(loggedInUserIdSelector(state))(state), + isSuperAdmin: isLoggedAsSuperAdmin(state), fetchStatus: fetchManyStatus(state), user: loggedInUserSelector(state) }; diff --git a/src/redux/selectors/users.js b/src/redux/selectors/users.js index df2f50f2d..5ebfe323b 100644 --- a/src/redux/selectors/users.js +++ b/src/redux/selectors/users.js @@ -59,9 +59,6 @@ export const isStudent = userId => export const isSupervisor = userId => createSelector(getRole(userId), role => role === 'supervisor'); -export const isSuperAdmin = userId => - createSelector(getRole(userId), role => role === 'superadmin'); - export const getUserSettings = userId => createSelector( getUser(userId), @@ -73,6 +70,11 @@ export const loggedInUserSelector = createSelector( (users, id) => users.get(id) ); +export const isLoggedAsSuperAdmin = createSelector( + [usersSelector, loggedInUserIdSelector], + (users, id) => users.get(id).getIn(['data', 'role']) === 'superadmin' +); + export const memberOfInstancesIdsSelector = userId => createSelector( getUser(userId), @@ -116,7 +118,7 @@ export const isSupervisorOf = (userId, groupId) => export const isAdminOf = (userId, groupId) => createSelector( - [groupSelector(groupId), isSuperAdmin(userId)], + [groupSelector(groupId), isLoggedAsSuperAdmin], (group, isSuperAdmin) => isSuperAdmin === true || (group && @@ -142,7 +144,7 @@ export const usersGroupsIds = userId => export const canEditExercise = (userId, exerciseId) => createSelector( - [exerciseSelector(exerciseId), isSuperAdmin(userId)], + [exerciseSelector(exerciseId), isLoggedAsSuperAdmin], (exercise, isSuperAdmin) => isSuperAdmin || (exercise && @@ -152,7 +154,7 @@ export const canEditExercise = (userId, exerciseId) => export const canEditPipeline = (userId, pipelineId) => createSelector( - [pipelineSelector(pipelineId), isSuperAdmin(userId)], + [pipelineSelector(pipelineId), isLoggedAsSuperAdmin], (pipeline, isSuperAdmin) => isSuperAdmin || (pipeline && From c811d140f10d14c74d3ff0f2f6989cd89056d485 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Fri, 17 Nov 2017 17:36:38 +0100 Subject: [PATCH 11/12] Show number of submitted solututions Fixes #127 --- .../AssignmentDetails/AssignmentDetails.js | 22 ++++++-- .../SubmissionsTableContainer.js | 5 +- src/pages/Assignment/Assignment.js | 50 ++++++++++++++----- src/redux/selectors/assignments.js | 30 ++++++----- 4 files changed, 73 insertions(+), 34 deletions(-) diff --git a/src/components/Assignments/Assignment/AssignmentDetails/AssignmentDetails.js b/src/components/Assignments/Assignment/AssignmentDetails/AssignmentDetails.js index 73ede757a..25865dad0 100644 --- a/src/components/Assignments/Assignment/AssignmentDetails/AssignmentDetails.js +++ b/src/components/Assignments/Assignment/AssignmentDetails/AssignmentDetails.js @@ -27,7 +27,8 @@ const AssignmentDetails = ({ isBonus, runtimeEnvironments, canSubmit, - pointsPercentualThreshold + pointsPercentualThreshold, + alreadySubmitted }) =>
- +
+ + + + + {alreadySubmitted} +
@@ -204,7 +219,8 @@ AssignmentDetails.propTypes = { isBonus: PropTypes.bool, runtimeEnvironments: PropTypes.array, canSubmit: ImmutablePropTypes.map, - pointsPercentualThreshold: PropTypes.number + pointsPercentualThreshold: PropTypes.number, + alreadySubmitted: PropTypes.number.isRequired }; export default AssignmentDetails; diff --git a/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js b/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js index 35ca5ff2d..eaadc208e 100644 --- a/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js +++ b/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js @@ -6,7 +6,7 @@ import { List } from 'immutable'; import { fetchUsersSubmissions } from '../../redux/modules/submissions'; import SubmissionsTable from '../../components/Assignments/SubmissionsTable'; -import { createGetUsersSubmissionsForAssignment } from '../../redux/selectors/assignments'; +import { getUserSubmissions } from '../../redux/selectors/assignments'; class SubmissionsTableContainer extends Component { componentWillMount = () => this.props.loadAsync(); @@ -62,10 +62,9 @@ SubmissionsTableContainer.propTypes = { export default connect( (state, { userId, assignmentId }) => { - const getSubmissions = createGetUsersSubmissionsForAssignment(); return { userId, - submissions: getSubmissions(state, userId, assignmentId) + submissions: getUserSubmissions(userId, assignmentId)(state) }; }, (dispatch, { userId, assignmentId }) => ({ diff --git a/src/pages/Assignment/Assignment.js b/src/pages/Assignment/Assignment.js index 6ba0ea88c..cf3718d2d 100644 --- a/src/pages/Assignment/Assignment.js +++ b/src/pages/Assignment/Assignment.js @@ -17,11 +17,13 @@ import { init, submitAssignmentSolution as submitSolution } from '../../redux/modules/submission'; +import { fetchUsersSubmissions } from '../../redux/modules/submissions'; import { fetchRuntimeEnvironments } from '../../redux/modules/runtimeEnvironments'; import { getAssignment, - runtimeEnvironmentsSelector + runtimeEnvironmentsSelector, + getUserSubmissions } from '../../redux/selectors/assignments'; import { canSubmitSolution } from '../../redux/selectors/canSubmit'; import { isSubmitting } from '../../redux/selectors/submission'; @@ -44,25 +46,29 @@ import { EditIcon, ResultsIcon } from '../../components/icons'; import LocalizedTexts from '../../components/helpers/LocalizedTexts'; import SubmitSolutionButton from '../../components/Assignments/SubmitSolutionButton'; import SubmitSolutionContainer from '../../containers/SubmitSolutionContainer'; -import SubmissionsTableContainer from '../../containers/SubmissionsTableContainer'; +import SubmissionsTable from '../../components/Assignments/SubmissionsTable'; import withLinks from '../../hoc/withLinks'; class Assignment extends Component { - static loadAsync = ({ assignmentId }, dispatch) => + static loadAsync = ({ assignmentId }, dispatch, userId) => Promise.all([ dispatch(fetchAssignmentIfNeeded(assignmentId)), dispatch(fetchRuntimeEnvironments()), - dispatch(canSubmit(assignmentId)) + dispatch(canSubmit(assignmentId)), + dispatch(fetchUsersSubmissions(userId, assignmentId)) ]); componentWillMount() { - this.props.loadAsync(); + this.props.loadAsync(this.props.userId); } componentWillReceiveProps(newProps) { - if (this.props.params.assignmentId !== newProps.params.assignmentId) { - newProps.loadAsync(); + if ( + this.props.params.assignmentId !== newProps.params.assignmentId || + this.props.userId !== newProps.userId + ) { + newProps.loadAsync(newProps.userId); } } @@ -70,6 +76,14 @@ class Assignment extends Component { return unixTime * 1000 < Date.now(); }; + sortSubmissions(submissions) { + return submissions.sort((a, b) => { + var aTimestamp = a.getIn(['data', 'solution', 'createdAt']); + var bTimestamp = b.getIn(['data', 'solution', 'createdAt']); + return bTimestamp - aTimestamp; + }); + } + render() { const { assignment, @@ -83,6 +97,7 @@ class Assignment extends Component { canSubmit, runtimeEnvironments, exerciseSync, + submissions, links: { ASSIGNMENT_EDIT_URI_FACTORY, SUPERVISOR_STATS_URI_FACTORY } } = this.props; @@ -278,6 +293,7 @@ class Assignment extends Component { )} canSubmit={canSubmit} runtimeEnvironments={runtimes} + alreadySubmitted={submissions.count()} /> {isStudentOf(assignment.groupId) && @@ -307,8 +323,15 @@ class Assignment extends Component { {(isStudentOf(assignment.groupId) || isSupervisorOf(assignment.groupId) || isSuperAdmin) && - + } userId={userId} + submissions={this.sortSubmissions(submissions)} assignmentId={assignment.id} />} } @@ -336,7 +359,8 @@ Assignment.propTypes = { loadAsync: PropTypes.func.isRequired, links: PropTypes.object.isRequired, runtimeEnvironments: PropTypes.array, - exerciseSync: PropTypes.func.isRequired + exerciseSync: PropTypes.func.isRequired, + submissions: ImmutablePropTypes.list.isRequired }; export default withLinks( @@ -360,12 +384,14 @@ export default withLinks( isStudentOf: groupId => isStudentOf(loggedInUserId, groupId)(state), isSupervisorOf: groupId => isSupervisorOf(loggedInUserId, groupId)(state), - canSubmit: canSubmitSolution(assignmentId)(state) + canSubmit: canSubmitSolution(assignmentId)(state), + submissions: getUserSubmissions(userId, assignmentId)(state) }; }, - (dispatch, { params: { assignmentId } }) => ({ + (dispatch, { params: { assignmentId, userId } }) => ({ init: userId => () => dispatch(init(userId, assignmentId)), - loadAsync: () => Assignment.loadAsync({ assignmentId }, dispatch), + loadAsync: userId => + Assignment.loadAsync({ assignmentId }, dispatch, userId), exerciseSync: () => dispatch(syncWithExercise(assignmentId)) }) )(Assignment) diff --git a/src/redux/selectors/assignments.js b/src/redux/selectors/assignments.js index 9a38e4e66..85784f5d7 100644 --- a/src/redux/selectors/assignments.js +++ b/src/redux/selectors/assignments.js @@ -22,21 +22,19 @@ export const runtimeEnvironmentsSelector = id => : List() ); -export const getUsersSubmissionIds = (state, userId, assignmentId) => { - const submissions = getAssignments(state).getIn([ - 'submissions', - assignmentId, - userId - ]); - if (!submissions) { - return List(); - } - - return submissions; -}; - -export const createGetUsersSubmissionsForAssignment = () => +export const getUserSubmissions = (userId, assignmentId) => createSelector( - [getUsersSubmissionIds, getSubmissions], - (submissionIds, submissions) => submissionIds.map(id => submissions.get(id)) + [getSubmissions, getAssignments], + (submissions, assignments) => { + const assignmentSubmissions = assignments.getIn([ + 'submissions', + assignmentId, + userId + ]); + if (!assignmentSubmissions) { + return List(); + } + + return assignmentSubmissions.map(id => submissions.get(id)); + } ); From 5cd9cce8c11c50c1ef7c8cec013d853132288055 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Fri, 17 Nov 2017 17:56:13 +0100 Subject: [PATCH 12/12] Allow switching on failed submissions too --- .../SubmissionDetail/SubmissionDetail.js | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js index ac2ef4e0f..c11c426df 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -86,29 +86,33 @@ class SubmissionDetail extends Component { - {evaluation && + {evaluations &&