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/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/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 bdc205149..d0be83bee 100644 --- a/src/components/Groups/ResultsTable/ResultsTableRow.js +++ b/src/components/Groups/ResultsTable/ResultsTableRow.js @@ -16,17 +16,12 @@ const ResultsTableRow = ({ userId, assignmentsIds, submissions }) => { const points = submission && submission !== null && - submission.evaluation && - submission.evaluation !== null - ? submission.evaluation.points + submission.lastSubmission.evaluation && + submission.lastSubmission.evaluation.points + ? 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/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 f0f58e6c5..59e686720 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'; +import './EvaluationTable.css'; -const EvaluationTable = ({ - evaluations, - referenceSolutionId, - exerciseId, - links: { REFERENCE_SOLUTION_EVALUATION_URI_FACTORY } -}) => +const EvaluationTable = ({ evaluations, renderButtons, selectedRowId = '' }) => @@ -47,7 +40,7 @@ const EvaluationTable = ({ return b.evaluation.evaluatedAt - a.evaluation.evaluatedAt; }) .map(e => - + } - + {renderButtons && renderButtons(e.id)} )} @@ -113,9 +92,8 @@ const EvaluationTable = ({ EvaluationTable.propTypes = { evaluations: PropTypes.array.isRequired, - referenceSolutionId: PropTypes.string.isRequired, - exerciseId: PropTypes.string.isRequired, - links: PropTypes.object + renderButtons: PropTypes.func, + selectedRowId: PropTypes.string }; -export default withLinks(EvaluationTable); +export default EvaluationTable; 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 -}) => ( +}) => - + @@ -85,14 +86,13 @@ const ReferenceSolutionDetail = ({
- - - -
{description} + {description} +
@@ -64,9 +65,9 @@ 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/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/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..c11c426df 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -9,36 +9,46 @@ import BonusPointsContainer from '../../../containers/BonusPointsContainer'; import DownloadResultArchiveContainer from '../../../containers/DownloadResultArchiveContainer'; import CommentThreadContainer from '../../../containers/CommentThreadContainer'; import SourceCodeViewerContainer from '../../../containers/SourceCodeViewerContainer'; +import SubmissionEvaluations from '../SubmissionEvaluations'; +import ResourceRenderer from '../../helpers/ResourceRenderer'; 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 = '', - evaluationStatus, - submittedAt, - userId, - submittedBy, + solution: { createdAt, userId, files, ...restSolution }, maxPoints, - files, - evaluation, + bonusPoints, accepted, - originalSubmissionId, - runtimeEnvironmentId, - isCorrect + runtimeEnvironmentId }, 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 (
@@ -46,12 +56,12 @@ class SubmissionDetail extends Component { @@ -76,31 +86,52 @@ class SubmissionDetail extends Component { - {evaluation && + {evaluations && - + {evaluation && + } - {isSupervisor && + {evaluation && + isSupervisor && } - + {evaluation && + } - {isSupervisor && + {evaluation && + isSupervisor && - + + + } + {isSupervisor && + + + + {(...evaluations) => + + this.setState({ activeSubmissionId: id })} + />} + } } @@ -119,20 +150,20 @@ class SubmissionDetail extends Component { SubmissionDetail.propTypes = { submission: PropTypes.shape({ id: PropTypes.string.isRequired, - evaluationStatus: PropTypes.string.isRequired, note: PropTypes.string, - submittedAt: PropTypes.number.isRequired, - userId: PropTypes.string.isRequired, - submittedBy: PropTypes.string, - evaluation: PropTypes.object, + lastSubmission: PropTypes.shape({ id: PropTypes.string.isRequired }) + .isRequired, + solution: PropTypes.shape({ + createdAt: PropTypes.number.isRequired, + userId: PropTypes.string.isRequired, + files: PropTypes.array + }).isRequired, maxPoints: PropTypes.number.isRequired, - files: PropTypes.array, - originalSubmissionId: PropTypes.string, - runtimeEnvironmentId: PropTypes.string, - isCorrect: PropTypes.bool + 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..32f13047e --- /dev/null +++ b/src/components/Submissions/SubmissionEvaluations/SubmissionEvaluations.js @@ -0,0 +1,64 @@ +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} + > + + + {id === activeSubmissionId + ? + : } + } + /> + ; + +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/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/components/buttons/ResubmitSolution/ResubmitSolution.js b/src/components/buttons/ResubmitSolution/ResubmitSolution.js index b8ea27ec8..e639a13c2 100644 --- a/src/components/buttons/ResubmitSolution/ResubmitSolution.js +++ b/src/components/buttons/ResubmitSolution/ResubmitSolution.js @@ -3,23 +3,36 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import Icon from 'react-fontawesome'; import Button from '../../widgets/FlatButton'; +import Confirm from '../../forms/Confirm'; -const ResubmitSolution = ({ resubmit, isDebug }) => - ; + 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/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/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/BonusPointsContainer/BonusPointsContainer.js b/src/containers/BonusPointsContainer/BonusPointsContainer.js index 89fe99e4b..cdabd8e27 100644 --- a/src/containers/BonusPointsContainer/BonusPointsContainer.js +++ b/src/containers/BonusPointsContainer/BonusPointsContainer.js @@ -5,18 +5,15 @@ import BonusPointsForm from '../../components/forms/BonusPointsForm'; import { setPoints } from '../../redux/modules/submissions'; -const BonusPointsContainer = ({ evaluation, setPoints }) => ( +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/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/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 }) => ({ 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 ( - + { @@ -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/containers/SubmissionsTableContainer/SubmissionsTableContainer.js b/src/containers/SubmissionsTableContainer/SubmissionsTableContainer.js index 20132f8af..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(); @@ -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; }); } @@ -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 d25c00f06..cf3718d2d 100644 --- a/src/pages/Assignment/Assignment.js +++ b/src/pages/Assignment/Assignment.js @@ -17,19 +17,21 @@ 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'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; import { - isSuperAdmin, isStudentOf, - isSupervisorOf + isSupervisorOf, + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import { runtimeEnvironmentSelector } from '../../redux/selectors/runtimeEnvironments'; @@ -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( @@ -356,16 +380,18 @@ export default withLinks( ), userId, loggedInUserId, - isSuperAdmin: isSuperAdmin(loggedInUserId)(state), + isSuperAdmin: isLoggedAsSuperAdmin(state), 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/pages/Dashboard/Dashboard.js b/src/pages/Dashboard/Dashboard.js index e932bbcbd..0947c6db3 100644 --- a/src/pages/Dashboard/Dashboard.js +++ b/src/pages/Dashboard/Dashboard.js @@ -28,7 +28,7 @@ import { getUser, isStudent, isSupervisor, - isSuperAdmin + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; @@ -74,7 +74,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( @@ -108,10 +108,10 @@ class Dashboard extends Component { studentOf, supervisor, supervisorOf, + superadmin, groupAssignments, statistics, allGroups, - isAdmin, links: { GROUP_URI_FACTORY }, intl: { locale } } = this.props; @@ -274,10 +274,10 @@ class Dashboard extends Component {
} } - {(supervisor || isAdmin) && + {(supervisor || superadmin) && {(...groups) => @@ -357,7 +357,6 @@ Dashboard.propTypes = { groupAssignments: ImmutablePropTypes.map, statistics: ImmutablePropTypes.map, allGroups: ImmutablePropTypes.map, - isAdmin: PropTypes.bool, links: PropTypes.object, intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired }; @@ -370,14 +369,13 @@ export default withLinks( userId, student: isStudent(userId)(state), supervisor: isSupervisor(userId)(state), - superadmin: isSuperAdmin(userId)(state), + superadmin: isLoggedAsSuperAdmin(state), user: getUser(userId)(state), studentOf: loggedInStudentOfSelector(state), supervisorOf: loggedInSupervisorOfSelector(state), groupAssignments: loggedInStudentOfGroupsAssignmentsSelector(state), statistics: statisticsSelector(state), - allGroups: groupsSelector(state), - isAdmin: isSuperAdmin(userId)(state) + allGroups: groupsSelector(state) }; }, (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 10cfaffad..b647febf2 100644 --- a/src/pages/Submission/Submission.js +++ b/src/pages/Submission/Submission.js @@ -12,23 +12,30 @@ 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 { isSupervisorOf, isAdminOf, - isSuperAdmin + isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; +import { + evaluationsForSubmissionSelector, + 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 ( @@ -112,19 +121,23 @@ class Submission extends Component {

} - + + {() => + } +
}
@@ -141,7 +154,9 @@ 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 }; export default connect( @@ -151,7 +166,9 @@ 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) }), (dispatch, { params }) => ({ loadAsync: () => Submission.loadAsync(params, dispatch) diff --git a/src/pages/User/User.js b/src/pages/User/User.js index 93d63ddf7..f8955e3e9 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/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/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..74dbf2628 --- /dev/null +++ b/src/redux/modules/submissionEvaluations.js @@ -0,0 +1,45 @@ +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 fetchManyEndpoint = id => + `/assignment-solutions/${id}/evaluations`; + +export const fetchSubmissionEvaluationsForSolution = solutionId => + actions.fetchMany({ + endpoint: fetchManyEndpoint(solutionId) + }); + +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/modules/submissions.js b/src/redux/modules/submissions.js index 84bea781b..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 => @@ -15,6 +14,7 @@ const needsRefetching = item => const { actions, actionTypes, reduceActions } = factory({ resourceName, + apiEndpointFactory: id => `/assignment-solutions/${id}`, needsRefetching }); @@ -53,7 +53,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 +63,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 +71,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 +79,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 } }); @@ -95,21 +95,13 @@ 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 } }); -export const downloadResultArchive = downloadHelper({ - endpoint: id => `/submissions/${id}/download-result`, - fetch: fetchSubmissionIfNeeded, - actionType: additionalActionTypes.DOWNLOAD_RESULT_ARCHIVE, - fileNameSelector: (id, state) => `${id}.zip`, - contentType: 'application/zip' -}); - /** * Reducer */ @@ -124,7 +116,7 @@ const reducer = handleActions( { meta: { submissionId, bonusPoints } } ) => state.setIn( - ['resources', submissionId, 'data', 'evaluation', 'bonusPoints'], + ['resources', submissionId, 'data', 'bonusPoints'], Number(bonusPoints) ), 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/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)); + } ); diff --git a/src/redux/selectors/submissionEvaluations.js b/src/redux/selectors/submissionEvaluations.js new file mode 100644 index 000000000..7dee2e0a3 --- /dev/null +++ b/src/redux/selectors/submissionEvaluations.js @@ -0,0 +1,38 @@ +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 => + submissionEvaluations.get('resources'); + +export const submissionEvaluationsSelector = createSelector( + getSubmissionEvaluations, + getResources +); + +export const submissionEvaluationSelector = evaluationId => + createSelector(submissionEvaluationsSelector, submissionEvaluations => + submissionEvaluations.get(evaluationId) + ); + +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 => + createSelector(getSubmissionEvaluations, state => + state.getIn(['fetchManyStatus', fetchManyEndpoint(id)]) + ); 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 && 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 } }); });