From ef208bf7bb47eb6ebe7ee85cf3d5494a3ff22517 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Thu, 19 Oct 2017 19:58:08 +0200 Subject: [PATCH 1/4] Explicit assign of group admin --- .../Groups/GroupDetail/GroupDetail.js | 26 +++++++++---------- .../Users/SupervisorsList/SupervisorsList.js | 19 +++++++++----- .../SupervisorsListItem.js | 19 ++++++-------- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/components/Groups/GroupDetail/GroupDetail.js b/src/components/Groups/GroupDetail/GroupDetail.js index 8f989eaf1..8a702c145 100644 --- a/src/components/Groups/GroupDetail/GroupDetail.js +++ b/src/components/Groups/GroupDetail/GroupDetail.js @@ -20,13 +20,14 @@ const GroupDetail = ({ parentGroupId, isPublic = false, childGroups, + adminId, ...group }, groups, publicGroups, supervisors, isAdmin -}) => ( +}) =>
@@ -47,7 +48,7 @@ const GroupDetail = ({ > - {externalId && ( + {externalId && - - )} + } - {threshold !== null && ( + {threshold !== null && - )} + }
- {externalId} + + {externalId} +
-
- {childGroups.all.length > 0 && ( + {childGroups.all.length > 0 && - - )} + } -
-); + ; GroupDetail.propTypes = { group: PropTypes.shape({ diff --git a/src/components/Users/SupervisorsList/SupervisorsList.js b/src/components/Users/SupervisorsList/SupervisorsList.js index 8db998a3a..5d6631998 100644 --- a/src/components/Users/SupervisorsList/SupervisorsList.js +++ b/src/components/Users/SupervisorsList/SupervisorsList.js @@ -6,17 +6,24 @@ import SupervisorsListItem, { LoadingSupervisorsListItem } from '../SupervisorsListItem'; -const SupervisorsList = ({ groupId, users, isLoaded = true, isAdmin }) => ( +const SupervisorsList = ({ + groupId, + users, + isLoaded = true, + isAdmin, + mainAdminId +}) => - {users.map(user => ( + {users.map(user => - ))} + )} {users.length === 0 && isLoaded && @@ -31,14 +38,14 @@ const SupervisorsList = ({ groupId, users, isLoaded = true, isAdmin }) => ( {!isLoaded && } -
-); + ; SupervisorsList.propTypes = { users: PropTypes.array.isRequired, groupId: PropTypes.string.isRequired, isLoaded: PropTypes.bool, - isAdmin: PropTypes.bool + isAdmin: PropTypes.bool, + mainAdminId: PropTypes.string }; export default SupervisorsList; diff --git a/src/components/Users/SupervisorsListItem/SupervisorsListItem.js b/src/components/Users/SupervisorsListItem/SupervisorsListItem.js index c135f8f6a..d206e0c32 100644 --- a/src/components/Users/SupervisorsListItem/SupervisorsListItem.js +++ b/src/components/Users/SupervisorsListItem/SupervisorsListItem.js @@ -1,9 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; -import MakeRemoveSupervisorButtonContainer - from '../../../containers/MakeRemoveSupervisorButtonContainer'; +import MakeRemoveSupervisorButtonContainer from '../../../containers/MakeRemoveSupervisorButtonContainer'; import MakeGroupAdminButton from '../../Groups/MakeGroupAdminButton'; import { makeAdmin } from '../../../redux/modules/groups'; import { adminsOfGroup } from '../../../redux/selectors/groups'; @@ -15,9 +13,9 @@ const SupervisorsListItem = ({ fullName, avatarUrl, groupId, - groupAdmins, - makeAdmin -}) => ( + makeAdmin, + mainAdminId +}) => @@ -25,14 +23,13 @@ const SupervisorsListItem = ({ {isAdmin && - {groupAdmins.indexOf(id) < 0 && + {id !== mainAdminId && makeAdmin(groupId, id)} bsSize="xs" />} } - -); + ; SupervisorsListItem.propTypes = { id: PropTypes.string.isRequired, @@ -40,8 +37,8 @@ SupervisorsListItem.propTypes = { groupId: PropTypes.string.isRequired, fullName: PropTypes.string.isRequired, avatarUrl: PropTypes.string.isRequired, - groupAdmins: ImmutablePropTypes.list.isRequired, - makeAdmin: PropTypes.func.isRequired + makeAdmin: PropTypes.func.isRequired, + mainAdminId: PropTypes.string.isRequired }; const mapStateToProps = (state, { groupId }) => ({ From d63169a88862f8e7598d181c5d12900c3374f612 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Thu, 19 Oct 2017 20:48:12 +0200 Subject: [PATCH 2/4] Fix assignment page permissions for different user --- src/pages/Assignment/Assignment.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/pages/Assignment/Assignment.js b/src/pages/Assignment/Assignment.js index 7032d2a83..02e0f5d89 100644 --- a/src/pages/Assignment/Assignment.js +++ b/src/pages/Assignment/Assignment.js @@ -294,12 +294,15 @@ class Assignment extends Component { isOpen={submitting} runtimeEnvironments={runtimes} /> - - } + + {(isStudentOf(assignment.groupId) || + isSupervisorOf(assignment.groupId) || + isSuperAdmin) && + } } @@ -336,7 +339,8 @@ export default withLinks( const environments = runtimeEnvironmentsSelector(assignmentId)( state ).toJS(); - userId = userId || loggedInUserIdSelector(state); + const loggedInUserId = loggedInUserIdSelector(state); + userId = userId || loggedInUserId; return { assignment: assignmentSelector(state), submitting: isSubmitting(state), @@ -344,10 +348,11 @@ export default withLinks( runtimeEnvironmentSelector(i)(state) ), userId, - loggeInUserId: loggedInUserIdSelector(state), - isSuperAdmin: isSuperAdmin(userId)(state), - isStudentOf: groupId => isStudentOf(userId, groupId)(state), - isSupervisorOf: groupId => isSupervisorOf(userId, groupId)(state), + loggedInUserId, + isSuperAdmin: isSuperAdmin(loggedInUserId)(state), + isStudentOf: groupId => isStudentOf(loggedInUserId, groupId)(state), + isSupervisorOf: groupId => + isSupervisorOf(loggedInUserId, groupId)(state), canSubmit: canSubmitSolution(assignmentId)(state) }; }, From 331cc2c5fc3df36d1ae35d586737120dd736d242 Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Thu, 19 Oct 2017 22:18:14 +0200 Subject: [PATCH 3/4] Reference solutions can be deleted --- src/pages/Exercise/Exercise.js | 73 ++++++++++++++++++------- src/redux/modules/referenceSolutions.js | 24 +++++++- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/pages/Exercise/Exercise.js b/src/pages/Exercise/Exercise.js index aa7a160e6..c7b594917 100644 --- a/src/pages/Exercise/Exercise.js +++ b/src/pages/Exercise/Exercise.js @@ -35,7 +35,10 @@ import AssignExerciseButton from '../../components/buttons/AssignExerciseButton' import { isSubmitting } from '../../redux/selectors/submission'; import { fetchExerciseIfNeeded } from '../../redux/modules/exercises'; -import { fetchReferenceSolutionsIfNeeded } from '../../redux/modules/referenceSolutions'; +import { + fetchReferenceSolutionsIfNeeded, + deleteReferenceSolution +} from '../../redux/modules/referenceSolutions'; import { createReferenceSolution, init } from '../../redux/modules/submission'; import { fetchHardwareGroups } from '../../redux/modules/hwGroups'; import { create as assignExercise } from '../../redux/modules/assignments'; @@ -132,6 +135,7 @@ class Exercise extends Component { intl: { formatMessage }, initCreateReferenceSolution, exercisePipelines, + deleteReferenceSolution, push } = this.props; @@ -349,23 +353,51 @@ class Exercise extends Component { referenceSolutions.length > 0 ? - + + deleteReferenceSolution( exercise.id, - evaluationId - ) - )} - > - {' '} - - } + solutionId + )} + question={ + + } + > + + + } /> :

dispatch(init(userId, exerciseId)), createExercisePipeline: () => - dispatch(createPipeline({ exerciseId: exerciseId })) + dispatch(createPipeline({ exerciseId: exerciseId })), + deleteReferenceSolution: (exerciseId, solutionId) => + dispatch(deleteReferenceSolution(exerciseId, solutionId)) }) )(Exercise) ) diff --git a/src/redux/modules/referenceSolutions.js b/src/redux/modules/referenceSolutions.js index d9057321b..99d5d0911 100644 --- a/src/redux/modules/referenceSolutions.js +++ b/src/redux/modules/referenceSolutions.js @@ -17,6 +17,13 @@ const { actions, reduceActions } = factory({ * Actions */ +export const additionalActionTypes = { + REMOVE: 'recodex/referenceSolutions/REMOVE', + REMOVE_PENDING: 'recodex/referenceSolutions/REMOVE_PENDING', + REMOVE_FULFILLED: 'recodex/referenceSolutions/REMOVE_FULFILLED', + REMOVE_REJECTED: 'recodex/referenceSolutions/REMOVE_REJECTED' +}; + export const fetchReferenceSolutions = actions.fetchResource; export const fetchReferenceSolutionsIfNeeded = actions.fetchOneIfNeeded; // fetch solutions for one exercise @@ -29,6 +36,14 @@ export const evaluateReferenceSolution = (solutionId, isDebug = false) => meta: { solutionId } }); +export const deleteReferenceSolution = (exerciseId, solutionId) => + createApiAction({ + type: additionalActionTypes.REMOVE, + endpoint: `/reference-solutions/${solutionId}`, + method: 'DELETE', + meta: { exerciseId, solutionId } + }); + /** * Reducer */ @@ -46,7 +61,14 @@ const reducer = handleActions( } return data.push(fromJS(payload)); }) - : state + : state, + [additionalActionTypes.REMOVE_FULFILLED]: ( + state, + { payload, meta: { exerciseId, solutionId } } + ) => + state.updateIn(['resources', exerciseId, 'data'], solutions => + solutions.filter(solution => solution.toJS().id !== solutionId) + ) }), initialState ); From 3f447f3eac43e33de9f6bf8a62b64252c04f309a Mon Sep 17 00:00:00 2001 From: Petr Stefan Date: Thu, 19 Oct 2017 23:33:02 +0200 Subject: [PATCH 4/4] Allow removing supplementary files May be altered after API implementation, but hopefully it is ok. --- .../SupplementaryFilesTableHeaderRow.js | 1 + .../SupplementaryFilesTableRow.js | 36 ++++++++++++++++--- src/components/forms/Confirm/Confirm.js | 24 ++++++++----- src/redux/modules/supplementaryFiles.js | 18 ++++++++-- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/components/Exercises/AttachedFilesTable/SupplementaryFilesTableHeaderRow.js b/src/components/Exercises/AttachedFilesTable/SupplementaryFilesTableHeaderRow.js index 8423aa24a..ffb94f210 100644 --- a/src/components/Exercises/AttachedFilesTable/SupplementaryFilesTableHeaderRow.js +++ b/src/components/Exercises/AttachedFilesTable/SupplementaryFilesTableHeaderRow.js @@ -21,6 +21,7 @@ const SupplementaryFilesTableHeaderRow = () => defaultMessage="Uploaded at" /> + ; export default SupplementaryFilesTableHeaderRow; diff --git a/src/components/Exercises/AttachedFilesTable/SupplementaryFilesTableRow.js b/src/components/Exercises/AttachedFilesTable/SupplementaryFilesTableRow.js index fd7807a24..9ebe711a7 100644 --- a/src/components/Exercises/AttachedFilesTable/SupplementaryFilesTableRow.js +++ b/src/components/Exercises/AttachedFilesTable/SupplementaryFilesTableRow.js @@ -2,8 +2,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import prettyBytes from 'pretty-bytes'; -import { FormattedDate, FormattedTime } from 'react-intl'; +import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'; +import { Button } from 'react-bootstrap'; +import Confirm from '../../../components/forms/Confirm'; +import { DeleteIcon } from '../../../components/icons'; import { downloadSupplementaryFile } from '../../../redux/modules/files'; +import { removeSupplementaryFile } from '../../../redux/modules/supplementaryFiles'; const SupplementaryFilesTableRow = ({ id, @@ -11,7 +15,8 @@ const SupplementaryFilesTableRow = ({ hashName, size, uploadedAt, - downloadFile + downloadFile, + removeSupplementaryFile }) => @@ -27,6 +32,27 @@ const SupplementaryFilesTableRow = ({   + + removeSupplementaryFile(id)} + question={ + + } + className="pull-right" + > + + + ; SupplementaryFilesTableRow.propTypes = { @@ -35,7 +61,8 @@ SupplementaryFilesTableRow.propTypes = { hashName: PropTypes.string.isRequired, size: PropTypes.number.isRequired, uploadedAt: PropTypes.number.isRequired, - downloadFile: PropTypes.func.isRequired + downloadFile: PropTypes.func.isRequired, + removeSupplementaryFile: PropTypes.func.isRequired }; export default connect( @@ -44,6 +71,7 @@ export default connect( downloadFile: e => { e.preventDefault(); dispatch(downloadSupplementaryFile(id)); - } + }, + removeSupplementaryFile: fileId => dispatch(removeSupplementaryFile(fileId)) }) )(SupplementaryFilesTableRow); diff --git a/src/components/forms/Confirm/Confirm.js b/src/components/forms/Confirm/Confirm.js index 8547929d0..7e34cad02 100644 --- a/src/components/forms/Confirm/Confirm.js +++ b/src/components/forms/Confirm/Confirm.js @@ -32,15 +32,13 @@ class Confirm extends Component { id, yes = ( - - {' '} + {' '} ), no = ( - - {' '} + {' '} ) @@ -52,8 +50,12 @@ class Confirm extends Component {

- - + +
@@ -62,9 +64,12 @@ class Confirm extends Component { } render() { - const { children } = this.props; + const { children, className = '' } = this.props; return ( - + {React.cloneElement(children, { onClick: e => this.askForConfirmation(e) })} @@ -87,7 +92,8 @@ Confirm.propTypes = { question: stringOrFormattedMessage.isRequired, yes: stringOrFormattedMessage, no: stringOrFormattedMessage, - children: PropTypes.element.isRequired + children: PropTypes.element.isRequired, + className: PropTypes.string }; export default Confirm; diff --git a/src/redux/modules/supplementaryFiles.js b/src/redux/modules/supplementaryFiles.js index bc5e28a83..3ad0feb22 100644 --- a/src/redux/modules/supplementaryFiles.js +++ b/src/redux/modules/supplementaryFiles.js @@ -17,7 +17,9 @@ export const actionTypes = { ADD_FILES: 'recodex/supplementaryFiles/ADD_FILES', ADD_FILES_PENDING: 'recodex/supplementaryFiles/ADD_FILES_PENDING', ADD_FILES_FULFILLED: 'recodex/supplementaryFiles/ADD_FILES_FULFILLED', - ADD_FILES_FAILED: 'recodex/supplementaryFiles/ADD_FILES_REJECTED' + ADD_FILES_FAILED: 'recodex/supplementaryFiles/ADD_FILES_REJECTED', + REMOVE_FILE: 'recodex/supplementaryFiles/REMOVE_FILE', + REMOVE_FILE_FULFILLED: 'recodex/supplementaryFiles/REMOVE_FILE_FULFILLED' }; export const fetchSupplementaryFilesForExercise = exerciseId => @@ -42,6 +44,14 @@ export const addSupplementaryFiles = (exerciseId, files) => } }); +export const removeSupplementaryFile = fileId => + createApiAction({ + type: actionTypes.REMOVE_FILE, + endpoint: `/uploaded-files/${fileId}`, + method: 'DELETE', + meta: { fileId } + }); + /** * Reducer */ @@ -56,7 +66,11 @@ const reducer = handleActions( createRecord({ data, state: resourceStatus.FULFILLED }) ), state - ) + ), + [actionTypes.REMOVE_FILE_FULFILLED]: ( + state, + { payload, meta: { fileId } } + ) => state.deleteIn(['resources', fileId]) }), initialState );