diff --git a/src/components/Exercises/FilesTable/FilesTable.js b/src/components/Exercises/FilesTable/FilesTable.js index 2a3214a67..64477676c 100644 --- a/src/components/Exercises/FilesTable/FilesTable.js +++ b/src/components/Exercises/FilesTable/FilesTable.js @@ -7,7 +7,7 @@ import Icon from 'react-fontawesome'; import { Table } from 'react-bootstrap'; import Button from '../../widgets/FlatButton'; import Box from '../../widgets/Box'; -import { SendIcon } from '../../icons'; +import { SendIcon, DownloadIcon } from '../../icons'; import UploadContainer from '../../../containers/UploadContainer'; import ResourceRenderer from '../../helpers/ResourceRenderer'; @@ -31,7 +31,8 @@ const FilesTable = ({ RowComponent, intl, isOpen = true, - viewOnly = false + viewOnly = false, + downloadArchive }) =>
@@ -88,6 +89,16 @@ const FilesTable = ({

}
} + {downloadArchive && +

+ +

}
; @@ -113,7 +124,8 @@ FilesTable.propTypes = { RowComponent: PropTypes.func.isRequired, intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired, isOpen: PropTypes.bool, - viewOnly: PropTypes.bool + viewOnly: PropTypes.bool, + downloadArchive: PropTypes.func }; export default injectIntl(FilesTable); diff --git a/src/components/SubmissionFailures/FailuresList/FailuresList.js b/src/components/SubmissionFailures/FailuresList/FailuresList.js index 0075e9b16..cdbc1393d 100644 --- a/src/components/SubmissionFailures/FailuresList/FailuresList.js +++ b/src/components/SubmissionFailures/FailuresList/FailuresList.js @@ -20,6 +20,12 @@ const FailuresList = ({ failures, createActions }) => defaultMessage="Description" /> + + + +const FailuresListItem = ({ + id, + createActions, + failure, + links: { + SUBMISSION_DETAIL_URI_FACTORY, + EXERCISE_REFERENCE_SOLUTION_URI_FACTORY + } +}) => {failure.description} + + {failure.assignmentSolutionId && + + + } + {failure.referenceSolutionId && + + + } + {failure.assignmentSolutionId === null && + failure.referenceSolutionId === null && + } + {', '} @@ -55,7 +94,8 @@ const FailuresListItem = ({ id, createActions, failure }) => FailuresListItem.propTypes = { id: PropTypes.string.isRequired, failure: PropTypes.object.isRequired, - createActions: PropTypes.func + createActions: PropTypes.func, + links: PropTypes.object }; -export default FailuresListItem; +export default withLinks(FailuresListItem); diff --git a/src/components/Submissions/ResultArchiveInfoBox/ResultArchiveInfoBox.js b/src/components/Submissions/ResultArchiveInfoBox/ResultArchiveInfoBox.js index 014d1398e..b51b20142 100644 --- a/src/components/Submissions/ResultArchiveInfoBox/ResultArchiveInfoBox.js +++ b/src/components/Submissions/ResultArchiveInfoBox/ResultArchiveInfoBox.js @@ -14,13 +14,13 @@ const messages = defineMessages({ } }); -const ResultArchiveInfoBox = ({ id, intl: { formatMessage } }) => ( +const ResultArchiveInfoBox = ({ id, intl: { formatMessage } }) => -); + color="green" + />; ResultArchiveInfoBox.propTypes = { id: PropTypes.string.isRequired, diff --git a/src/components/Submissions/SolutionArchiveInfoBox/SolutionArchiveInfoBox.js b/src/components/Submissions/SolutionArchiveInfoBox/SolutionArchiveInfoBox.js new file mode 100644 index 000000000..9e3a29c33 --- /dev/null +++ b/src/components/Submissions/SolutionArchiveInfoBox/SolutionArchiveInfoBox.js @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, defineMessages } from 'react-intl'; +import { SimpleInfoBox } from '../../widgets/InfoBox'; + +const messages = defineMessages({ + title: { + id: 'app.solutionArchiveInfoBox.title', + defaultMessage: 'Solution archive' + }, + description: { + id: 'app.solutionArchiveInfoBox.description', + defaultMessage: 'All submitted source files in one ZIP archive' + } +}); + +const SolutionArchiveInfoBox = ({ id, intl: { formatMessage } }) => + ; + +SolutionArchiveInfoBox.propTypes = { + id: PropTypes.string.isRequired, + intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired }).isRequired +}; + +export default injectIntl(SolutionArchiveInfoBox); diff --git a/src/components/Submissions/SolutionArchiveInfoBox/index.js b/src/components/Submissions/SolutionArchiveInfoBox/index.js new file mode 100644 index 000000000..65e50e086 --- /dev/null +++ b/src/components/Submissions/SolutionArchiveInfoBox/index.js @@ -0,0 +1 @@ +export default from './SolutionArchiveInfoBox'; diff --git a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js index 32096ea49..51ee46649 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -7,6 +7,7 @@ import SourceCodeInfoBox from '../../widgets/SourceCodeInfoBox'; import TestResults from '../TestResults'; import BonusPointsContainer from '../../../containers/BonusPointsContainer'; import DownloadResultArchiveContainer from '../../../containers/DownloadResultArchiveContainer'; +import DownloadSolutionArchiveContainer from '../../../containers/DownloadSolutionArchiveContainer'; import CommentThreadContainer from '../../../containers/CommentThreadContainer'; import SourceCodeViewerContainer from '../../../containers/SourceCodeViewerContainer'; import SubmissionEvaluations from '../SubmissionEvaluations'; @@ -137,6 +138,9 @@ class SubmissionDetail extends Component { + + + } {activeSubmissionId && isSupervisor && diff --git a/src/containers/AttachmentFilesTableContainer/AttachmentFilesTableContainer.js b/src/containers/AttachmentFilesTableContainer/AttachmentFilesTableContainer.js index 558a40c9c..a6cec0d02 100644 --- a/src/containers/AttachmentFilesTableContainer/AttachmentFilesTableContainer.js +++ b/src/containers/AttachmentFilesTableContainer/AttachmentFilesTableContainer.js @@ -13,7 +13,8 @@ import { import { fetchAttachmentFiles, addAttachmentFiles, - removeAttachmentFile + removeAttachmentFile, + downloadAttachmentArchive } from '../../redux/modules/attachmentFiles'; import { createGetAttachmentFiles } from '../../redux/selectors/attachmentFiles'; @@ -23,7 +24,8 @@ const AttachmentFilesTableContainer = ({ attachmentFiles, loadFiles, addFiles, - removeFile + removeFile, + downloadArchive }) => ; AttachmentFilesTableContainer.propTypes = { @@ -55,7 +58,8 @@ AttachmentFilesTableContainer.propTypes = { attachmentFiles: ImmutablePropTypes.map, loadFiles: PropTypes.func.isRequired, addFiles: PropTypes.func.isRequired, - removeFile: PropTypes.func.isRequired + removeFile: PropTypes.func.isRequired, + downloadArchive: PropTypes.func }; export default connect( @@ -70,6 +74,10 @@ export default connect( (dispatch, { exercise }) => ({ loadFiles: () => dispatch(fetchAttachmentFiles(exercise.id)), addFiles: files => dispatch(addAttachmentFiles(exercise.id, files)), - removeFile: id => dispatch(removeAttachmentFile(exercise.id, id)) + removeFile: id => dispatch(removeAttachmentFile(exercise.id, id)), + downloadArchive: e => { + e.preventDefault(); + dispatch(downloadAttachmentArchive(exercise.id)); + } }) )(AttachmentFilesTableContainer); diff --git a/src/containers/DownloadSolutionArchiveContainer/DownloadSolutionArchiveContainer.js b/src/containers/DownloadSolutionArchiveContainer/DownloadSolutionArchiveContainer.js new file mode 100644 index 000000000..e96e5bca1 --- /dev/null +++ b/src/containers/DownloadSolutionArchiveContainer/DownloadSolutionArchiveContainer.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import { downloadSolutionArchive } from '../../redux/modules/submissionEvaluations'; +import SolutionArchiveInfoBox from '../../components/Submissions/SolutionArchiveInfoBox'; + +const DownloadResultArchiveContainer = ({ + solutionId, + downloadSolutionArchive +}) => + + + ; + +DownloadResultArchiveContainer.propTypes = { + solutionId: PropTypes.string.isRequired, + downloadSolutionArchive: PropTypes.func.isRequired +}; + +export default connect( + (state, props) => ({}), + (dispatch, { solutionId }) => ({ + downloadSolutionArchive: e => { + e.preventDefault(); + dispatch(downloadSolutionArchive(solutionId)); + } + }) +)(DownloadResultArchiveContainer); diff --git a/src/containers/DownloadSolutionArchiveContainer/index.js b/src/containers/DownloadSolutionArchiveContainer/index.js new file mode 100644 index 000000000..b6b44f620 --- /dev/null +++ b/src/containers/DownloadSolutionArchiveContainer/index.js @@ -0,0 +1 @@ +export default from './DownloadSolutionArchiveContainer'; diff --git a/src/containers/EvaluationProgressContainer/EvaluationProgressContainer.js b/src/containers/EvaluationProgressContainer/EvaluationProgressContainer.js index 2d25af3da..3b32e3043 100644 --- a/src/containers/EvaluationProgressContainer/EvaluationProgressContainer.js +++ b/src/containers/EvaluationProgressContainer/EvaluationProgressContainer.js @@ -102,10 +102,10 @@ class EvaluationProgressContainer extends Component { task_state = 'OK', // eslint-disable-line camelcase text = null }) => ({ - wasSuccessful: command !== 'TASK' || task_state === 'COMPLETED', // eslint-disable-line camelcase - text: text || this.props.intl.formatMessage(this.getRandomMessage()), - status: task_state // eslint-disable-line camelcase - }); + wasSuccessful: command !== 'TASK' || task_state === 'COMPLETED', // eslint-disable-line camelcase + text: text || this.props.intl.formatMessage(this.getRandomMessage()), + status: task_state // eslint-disable-line camelcase + }); getRandomMessage = () => { if (!this.availableMessages || this.availableMessages.length === 0) { @@ -172,38 +172,38 @@ class EvaluationProgressContainer extends Component { return this.state.realTimeProcessing === true ? + isOpen={isOpen} + messages={displayedMessages} + completed={progress.completed} + skipped={progress.skipped} + failed={progress.failed} + finished={isFinished} + showContinueButton={isFinished || this.isClosed} + finishProcessing={this.finish} + onClose={this.userCloseAction} + /> : - ) - }) - ])} - />; + isOpen={isOpen} + completed={0} + skipped={100} + failed={0} + finished={false} + showContinueButton={true} + finishProcessing={this.finish} + onClose={this.userCloseAction} + messages={List([ + this.formatMessage({ + command: 'TASK', + task_state: 'SKIPPED', + text: ( + + ) + }) + ])} + />; }; } diff --git a/src/containers/ResubmitSolutionContainer/ResubmitSolutionContainer.js b/src/containers/ResubmitSolutionContainer/ResubmitSolutionContainer.js index f69b0806d..9f6423693 100644 --- a/src/containers/ResubmitSolutionContainer/ResubmitSolutionContainer.js +++ b/src/containers/ResubmitSolutionContainer/ResubmitSolutionContainer.js @@ -36,6 +36,7 @@ const ResubmitSolutionContainer = ({ monitor={monitor} link={SUBMISSION_DETAIL_URI_FACTORY(assignmentId, newSubmissionId)} onFinish={() => fetchSubmissions(userId)} + onUserClose={() => fetchSubmissions(userId)} /> ); diff --git a/src/containers/SupplementaryFilesTableContainer/SupplementaryFilesTableContainer.js b/src/containers/SupplementaryFilesTableContainer/SupplementaryFilesTableContainer.js index c88ca3d47..128a980f9 100644 --- a/src/containers/SupplementaryFilesTableContainer/SupplementaryFilesTableContainer.js +++ b/src/containers/SupplementaryFilesTableContainer/SupplementaryFilesTableContainer.js @@ -13,7 +13,8 @@ import { import { fetchSupplementaryFilesForExercise, addSupplementaryFiles, - removeSupplementaryFile + removeSupplementaryFile, + downloadSupplementaryArchive } from '../../redux/modules/supplementaryFiles'; import { downloadSupplementaryFile } from '../../redux/modules/files'; @@ -27,6 +28,7 @@ const SupplementaryFilesTableContainer = ({ removeFile, downloadFile, viewOnly = false, + downloadArchive, ...props }) => ; @@ -64,7 +67,8 @@ SupplementaryFilesTableContainer.propTypes = { addFiles: PropTypes.func.isRequired, removeFile: PropTypes.func.isRequired, downloadFile: PropTypes.func.isRequired, - viewOnly: PropTypes.bool + viewOnly: PropTypes.bool, + downloadArchive: PropTypes.func }; export default connect( @@ -80,6 +84,10 @@ export default connect( downloadFile: (event, id) => { event.preventDefault(); dispatch(downloadSupplementaryFile(id)); + }, + downloadArchive: e => { + e.preventDefault(); + dispatch(downloadSupplementaryArchive(exercise.id)); } }) )(SupplementaryFilesTableContainer); diff --git a/src/pages/Assignment/Assignment.js b/src/pages/Assignment/Assignment.js index 9b16e1db3..a5872b356 100644 --- a/src/pages/Assignment/Assignment.js +++ b/src/pages/Assignment/Assignment.js @@ -31,7 +31,7 @@ import { loggedInUserIdSelector } from '../../redux/selectors/auth'; import { isStudentOf, isSupervisorOf, - isLoggedAsSuperAdmin + isAdminOf } from '../../redux/selectors/users'; import Page from '../../components/layout/Page'; @@ -91,9 +91,9 @@ class Assignment extends Component { userId, loggedInUserId, init, - isSuperAdmin, isStudentOf, isSupervisorOf, + isAdminOf, canSubmit, runtimeEnvironments, exerciseSync, @@ -126,6 +126,23 @@ class Assignment extends Component { GROUP_URI_FACTORY(assignment.groupId) }) }, + { + resource: assignment, + iconName: 'puzzle-piece', + breadcrumb: assignment => ({ + text: ( + + ), + link: ({ EXERCISE_URI_FACTORY }) => + isAdminOf(assignment.groupId) || + isSupervisorOf(assignment.groupId) + ? EXERCISE_URI_FACTORY(assignment.exerciseId) + : '#' + }) + }, { text: ( ), - iconName: 'puzzle-piece' + iconName: 'hourglass-start' } ]} > @@ -146,7 +163,8 @@ class Assignment extends Component {

} - {(isSuperAdmin || isSupervisorOf(assignment.groupId)) && + {(isSupervisorOf(assignment.groupId) || + isAdminOf(assignment.groupId)) && // includes superadmin

} - {(isSuperAdmin || isSupervisorOf(assignment.groupId)) && + {(isSupervisorOf(assignment.groupId) || + isAdminOf(assignment.groupId)) && // includes superadmin isStudentOf(loggedInUserId, groupId)(state), isSupervisorOf: groupId => isSupervisorOf(loggedInUserId, groupId)(state), + isAdminOf: groupId => isAdminOf(loggedInUserId, groupId)(state), canSubmit: canSubmitSolution(assignmentId)(state), submissions: getUserSubmissions(userId, assignmentId)(state) }; diff --git a/src/pages/AssignmentStats/AssignmentStats.js b/src/pages/AssignmentStats/AssignmentStats.js index 5f259c4c1..438e67c59 100644 --- a/src/pages/AssignmentStats/AssignmentStats.js +++ b/src/pages/AssignmentStats/AssignmentStats.js @@ -89,6 +89,20 @@ class AssignmentStats extends Component { { resource: assignment, iconName: 'puzzle-piece', + breadcrumb: assignment => ({ + text: ( + + ), + link: ({ EXERCISE_URI_FACTORY }) => + EXERCISE_URI_FACTORY(assignment.exerciseId) + }) + }, + { + resource: assignment, + iconName: 'hourglass-start', breadcrumb: assignment => ({ text: ( this.props.loadAsync(); @@ -73,13 +71,15 @@ class EditAssignment extends Component { links: { ASSIGNMENT_DETAIL_URI_FACTORY, GROUP_URI_FACTORY, - SUPERVISOR_STATS_URI_FACTORY + SUPERVISOR_STATS_URI_FACTORY, + EXERCISE_URI_FACTORY }, params: { assignmentId }, push, assignment, editAssignment, - isSuperAdmin, + isSupervisorOf, + isAdminOf, formValues, exerciseSync } = this.props; @@ -101,8 +101,21 @@ class EditAssignment extends Component { } breadcrumbs={[ { - text: , + resource: assignment, iconName: 'puzzle-piece', + breadcrumb: assignment => ({ + text: ( + + ), + link: EXERCISE_URI_FACTORY(assignment.exerciseId) + }) + }, + { + text: , + iconName: 'hourglass-start', link: ASSIGNMENT_DETAIL_URI_FACTORY(assignmentId) }, { @@ -116,7 +129,8 @@ class EditAssignment extends Component { - {(isSuperAdmin || isSupervisorOf(assignment.groupId)) && + {(isAdminOf(assignment.groupId) || + isSupervisorOf(assignment.groupId)) &&

} - {(isSuperAdmin || isSupervisorOf(assignment.groupId)) && + {(isAdminOf(assignment.groupId) || + isSupervisorOf(assignment.groupId)) && { + const loggedInUserId = loggedInUserIdSelector(state); return { assignment: getAssignment(state)(assignmentId), runtimeEnvironments: runtimeEnvironmentsSelector(state), submitting: isSubmitting(state), - isSuperAdmin: isLoggedAsSuperAdmin(state), canSubmit: canSubmitSolution(assignmentId)(state), - formValues: getFormValues('editAssignment')(state) + formValues: getFormValues('editAssignment')(state), + isSupervisorOf: groupId => + isSupervisorOf(loggedInUserId, groupId)(state), + isAdminOf: groupId => isAdminOf(loggedInUserId, groupId)(state) }; }, (dispatch, { params: { assignmentId } }) => ({ diff --git a/src/pages/ReferenceSolution/ReferenceSolution.js b/src/pages/ReferenceSolution/ReferenceSolution.js index 55bb64952..a61b5335c 100644 --- a/src/pages/ReferenceSolution/ReferenceSolution.js +++ b/src/pages/ReferenceSolution/ReferenceSolution.js @@ -24,6 +24,8 @@ import SourceCodeInfoBox from '../../components/widgets/SourceCodeInfoBox'; import SourceCodeViewerContainer from '../../containers/SourceCodeViewerContainer'; import { RefreshIcon, SendIcon } from '../../components/icons'; import ReferenceSolutionEvaluationsContainer from '../../containers/ReferenceSolutionEvaluationsContainer'; +import SolutionArchiveInfoBox from '../../components/Submissions/SolutionArchiveInfoBox'; +import { downloadSolutionArchive } from '../../redux/modules/referenceSolutionEvaluations'; const messages = defineMessages({ title: { @@ -60,6 +62,7 @@ class ReferenceSolution extends Component { refreshSolutionEvaluations, evaluateReferenceSolution, evaluateReferenceSolutionInDebugMode, + downloadSolutionArchive, intl: { formatMessage }, links: { EXERCISES_URI, EXERCISE_URI_FACTORY } } = this.props; @@ -135,6 +138,13 @@ class ReferenceSolution extends Component { )} + + + + + + + @@ -221,6 +231,7 @@ ReferenceSolution.propTypes = { evaluateReferenceSolution: PropTypes.func.isRequired, evaluateReferenceSolutionInDebugMode: PropTypes.func.isRequired, referenceSolutions: ImmutablePropTypes.map, + downloadSolutionArchive: PropTypes.func, intl: intlShape.isRequired, links: PropTypes.object.isRequired }; @@ -239,7 +250,11 @@ export default withLinks( evaluateReferenceSolution: () => dispatch(evaluateReferenceSolution(params.referenceSolutionId)), evaluateReferenceSolutionInDebugMode: () => - dispatch(evaluateReferenceSolution(params.referenceSolutionId, true)) + dispatch(evaluateReferenceSolution(params.referenceSolutionId, true)), + downloadSolutionArchive: e => { + e.preventDefault(); + dispatch(downloadSolutionArchive(params.referenceSolutionId)); + } }) )(ReferenceSolution) ) diff --git a/src/pages/Submission/Submission.js b/src/pages/Submission/Submission.js index 395bd5afe..eab4ee5e4 100644 --- a/src/pages/Submission/Submission.js +++ b/src/pages/Submission/Submission.js @@ -93,6 +93,21 @@ class Submission extends Component { GROUP_URI_FACTORY(assignment.groupId) }) }, + { + resource: assignment, + iconName: 'puzzle-piece', + breadcrumb: assignment => ( + { + text: ( + + ), + link: ({ EXERCISE_URI_FACTORY }) => + (isSupervisorOrMore(assignment.groupId)) ? EXERCISE_URI_FACTORY(assignment.exerciseId) : '#' + }) + }, { text: ( ), - iconName: 'puzzle-piece', + iconName: 'hourglass-start', link: ({ ASSIGNMENT_DETAIL_URI_FACTORY }) => ASSIGNMENT_DETAIL_URI_FACTORY(assignmentId) }, diff --git a/src/redux/helpers/api/download.js b/src/redux/helpers/api/download.js index 42da3c396..2541a970c 100644 --- a/src/redux/helpers/api/download.js +++ b/src/redux/helpers/api/download.js @@ -8,8 +8,14 @@ export const downloadHelper = ({ actionType, fileNameSelector, contentType -}) => id => (dispatch, getState) => - dispatch(fetch(id)) +}) => id => (dispatch, getState) => { + let initial; + if (fetch !== null) { + initial = dispatch(fetch(id)); + } else { + initial = Promise.resolve(); + } + return initial .then(() => dispatch( createApiAction({ @@ -40,3 +46,4 @@ export const downloadHelper = ({ return Promise.resolve(); }) .catch(e => dispatch(addNotification(e.message, false))); +}; diff --git a/src/redux/modules/attachmentFiles.js b/src/redux/modules/attachmentFiles.js index 3535c2a37..e394c9936 100644 --- a/src/redux/modules/attachmentFiles.js +++ b/src/redux/modules/attachmentFiles.js @@ -5,6 +5,7 @@ import factory, { resourceStatus } from '../helpers/resourceManager'; import { createApiAction } from '../middleware/apiMiddleware'; +import { downloadHelper } from '../helpers/api/download'; const resourceName = 'attachmentFiles'; const { actions, reduceActions } = factory({ resourceName }); @@ -19,7 +20,9 @@ export const actionTypes = { ADD_FILES_FULFILLED: 'recodex/attachmentFiles/ADD_FILES_FULFILLED', ADD_FILES_FAILED: 'recodex/attachmentFiles/ADD_FILES_REJECTED', REMOVE_FILE: 'recodex/attachmentFiles/REMOVE_FILE', - REMOVE_FILE_FULFILLED: 'recodex/attachmentFiles/REMOVE_FILE_FULFILLED' + REMOVE_FILE_FULFILLED: 'recodex/attachmentFiles/REMOVE_FILE_FULFILLED', + DOWNLOAD_ATTACHMENT_ARCHIVE: + 'recodex/attachmentFiles/DOWNLOAD_ATTACHMENT_ARCHIVE' }; export const fetchAttachmentFiles = exerciseId => @@ -53,6 +56,14 @@ export const removeAttachmentFile = (exerciseId, fileId) => meta: { exerciseId, fileId } }); +export const downloadAttachmentArchive = downloadHelper({ + actionType: actionTypes.DOWNLOAD_ATTACHMENT_ARCHIVE, + fetch: null, + endpoint: id => `/exercises/${id}/attachment-files/download-archive`, + fileNameSelector: (id, state) => `${id}.zip`, + contentType: 'application/zip' +}); + /** * Reducer */ diff --git a/src/redux/modules/referenceSolutionEvaluations.js b/src/redux/modules/referenceSolutionEvaluations.js index 18368f5a1..5607b19a0 100644 --- a/src/redux/modules/referenceSolutionEvaluations.js +++ b/src/redux/modules/referenceSolutionEvaluations.js @@ -15,7 +15,8 @@ const { actions, reduceActions } = factory({ */ export const additionalActionTypes = { - DOWNLOAD_EVALUATION_ARCHIVE: 'recodex/files/DOWNLOAD_EVALUATION_ARCHIVE' + DOWNLOAD_EVALUATION_ARCHIVE: 'recodex/files/DOWNLOAD_EVALUATION_ARCHIVE', + DOWNLOAD_SOLUTION_ARCHIVE: 'recodex/files/DOWNLOAD_SOLUTION_ARCHIVE' }; export const fetchReferenceSolutionEvaluation = actions.fetchResource; @@ -29,12 +30,20 @@ export const fetchReferenceSolutionEvaluationsForSolution = solutionId => export const downloadEvaluationArchive = downloadHelper({ actionType: additionalActionTypes.DOWNLOAD_EVALUATION_ARCHIVE, - fetch: fetchReferenceSolutionEvaluationIfNeeded, + fetch: null, endpoint: id => `/reference-solutions/evaluation/${id}/download-result`, fileNameSelector: (id, state) => `${id}.zip`, contentType: 'application/zip' }); +export const downloadSolutionArchive = downloadHelper({ + actionType: additionalActionTypes.DOWNLOAD_SOLUTION_ARCHIVE, + fetch: null, + endpoint: id => `/reference-solutions/${id}/download-solution`, + fileNameSelector: (id, state) => `${id}.zip`, + contentType: 'application/zip' +}); + /** * Reducer */ diff --git a/src/redux/modules/submissionEvaluations.js b/src/redux/modules/submissionEvaluations.js index 74dbf2628..076292e62 100644 --- a/src/redux/modules/submissionEvaluations.js +++ b/src/redux/modules/submissionEvaluations.js @@ -15,7 +15,8 @@ const { actions, reduceActions } = factory({ */ export const additionalActionTypes = { - DOWNLOAD_EVALUATION_ARCHIVE: 'recodex/files/DOWNLOAD_EVALUATION_ARCHIVE' + DOWNLOAD_EVALUATION_ARCHIVE: 'recodex/files/DOWNLOAD_EVALUATION_ARCHIVE', + DOWNLOAD_SOLUTION_ARCHIVE: 'recodex/files/DOWNLOAD_SOLUTION_ARCHIVE' }; export const fetchSubmissionEvaluation = actions.fetchResource; @@ -31,12 +32,20 @@ export const fetchSubmissionEvaluationsForSolution = solutionId => export const downloadEvaluationArchive = downloadHelper({ actionType: additionalActionTypes.DOWNLOAD_EVALUATION_ARCHIVE, - fetch: fetchSubmissionEvaluationIfNeeded, + fetch: null, endpoint: id => `/assignment-solutions/evaluation/${id}/download-result`, fileNameSelector: (id, state) => `${id}.zip`, contentType: 'application/zip' }); +export const downloadSolutionArchive = downloadHelper({ + actionType: additionalActionTypes.DOWNLOAD_SOLUTION_ARCHIVE, + fetch: null, + endpoint: id => `/assignment-solutions/${id}/download-solution`, + fileNameSelector: (id, state) => `${id}.zip`, + contentType: 'application/zip' +}); + /** * Reducer */ diff --git a/src/redux/modules/supplementaryFiles.js b/src/redux/modules/supplementaryFiles.js index 361a4566d..5f8e2298c 100644 --- a/src/redux/modules/supplementaryFiles.js +++ b/src/redux/modules/supplementaryFiles.js @@ -5,6 +5,7 @@ import factory, { resourceStatus } from '../helpers/resourceManager'; import { createApiAction } from '../middleware/apiMiddleware'; +import { downloadHelper } from '../helpers/api/download'; const resourceName = 'supplementaryFiles'; const { actions, reduceActions } = factory({ resourceName }); @@ -19,7 +20,9 @@ export const actionTypes = { ADD_FILES_FULFILLED: 'recodex/supplementaryFiles/ADD_FILES_FULFILLED', ADD_FILES_FAILED: 'recodex/supplementaryFiles/ADD_FILES_REJECTED', REMOVE_FILE: 'recodex/supplementaryFiles/REMOVE_FILE', - REMOVE_FILE_FULFILLED: 'recodex/supplementaryFiles/REMOVE_FILE_FULFILLED' + REMOVE_FILE_FULFILLED: 'recodex/supplementaryFiles/REMOVE_FILE_FULFILLED', + DOWNLOAD_SUPPLEMENTARY_ARCHIVE: + 'recodex/supplementaryFiles/DOWNLOAD_SUPPLEMENTARY_ARCHIVE' }; export const fetchSupplementaryFilesForExercise = exerciseId => @@ -53,6 +56,14 @@ export const removeSupplementaryFile = (exerciseId, fileId) => meta: { exerciseId, fileId } }); +export const downloadSupplementaryArchive = downloadHelper({ + actionType: actionTypes.DOWNLOAD_SUPPLEMENTARY_ARCHIVE, + fetch: null, + endpoint: id => `/exercises/${id}/supplementary-files/download-archive`, + fileNameSelector: (id, state) => `${id}.zip`, + contentType: 'application/zip' +}); + /** * Reducer */