From 39da2657c1b418c102f130ec9f2b5b2c582983af Mon Sep 17 00:00:00 2001
From: Petr Stefan
Date: Thu, 22 Feb 2018 15:53:21 +0100
Subject: [PATCH 1/4] Only superadmin can edit group external identifier
---
.../forms/EditGroupForm/EditGroupForm.js | 31 ++++++++++---------
src/pages/EditGroup/EditGroup.js | 13 ++++++--
2 files changed, 27 insertions(+), 17 deletions(-)
diff --git a/src/components/forms/EditGroupForm/EditGroupForm.js b/src/components/forms/EditGroupForm/EditGroupForm.js
index 29abfbf04..2495d6248 100644
--- a/src/components/forms/EditGroupForm/EditGroupForm.js
+++ b/src/components/forms/EditGroupForm/EditGroupForm.js
@@ -21,7 +21,8 @@ const EditGroupForm = ({
hasThreshold,
collapsable = false,
isOpen = true,
- reset
+ reset,
+ isSuperAdmin
}) =>
-
- }
- />
+ {isSuperAdmin &&
+
+ }
+ />}
{
diff --git a/src/pages/EditGroup/EditGroup.js b/src/pages/EditGroup/EditGroup.js
index 0e50df632..86e187330 100644
--- a/src/pages/EditGroup/EditGroup.js
+++ b/src/pages/EditGroup/EditGroup.js
@@ -16,7 +16,10 @@ import { LocalizedGroupName } from '../../components/helpers/LocalizedNames';
import { fetchGroupIfNeeded, editGroup } from '../../redux/modules/groups';
import { groupSelector } from '../../redux/selectors/groups';
import { loggedInUserIdSelector } from '../../redux/selectors/auth';
-import { isSupervisorOf } from '../../redux/selectors/users';
+import {
+ isSupervisorOf,
+ isLoggedAsSuperAdmin
+} from '../../redux/selectors/users';
import { getLocalizedTextsLocales } from '../../helpers/getLocalizedData';
import withLinks from '../../hoc/withLinks';
@@ -50,6 +53,7 @@ class EditGroup extends Component {
const {
params: { groupId },
group,
+ isSuperAdmin,
links: { GROUP_URI_FACTORY },
editGroup,
hasThreshold,
@@ -91,6 +95,7 @@ class EditGroup extends Component {
onSubmit={editGroup}
hasThreshold={hasThreshold}
localizedTextsLocales={getLocalizedTextsLocales(localizedTexts)}
+ isSuperAdmin={isSuperAdmin}
/>
isSupervisorOf(userId, groupId)(state),
hasThreshold: editGroupFormSelector(state, 'hasThreshold'),
- localizedTexts: editGroupFormSelector(state, 'localizedTexts')
+ localizedTexts: editGroupFormSelector(state, 'localizedTexts'),
+ isSuperAdmin: isLoggedAsSuperAdmin(state)
};
},
(dispatch, { params: { groupId } }) => ({
From 819362a50a1d74eaafe0b135d2a0f714320418e1 Mon Sep 17 00:00:00 2001
From: Petr Stefan
Date: Thu, 22 Feb 2018 16:49:48 +0100
Subject: [PATCH 2/4] Forking of exercises for superadmins
---
.../ForkExerciseForm/ForkExerciseForm.css | 4 +-
.../ForkExerciseForm/ForkExerciseForm.js | 15 ++++--
src/pages/Exercise/Exercise.js | 53 ++++++++++++++-----
src/redux/modules/users.js | 1 +
4 files changed, 52 insertions(+), 21 deletions(-)
diff --git a/src/components/forms/ForkExerciseForm/ForkExerciseForm.css b/src/components/forms/ForkExerciseForm/ForkExerciseForm.css
index ca4e570d0..824f8b236 100644
--- a/src/components/forms/ForkExerciseForm/ForkExerciseForm.css
+++ b/src/components/forms/ForkExerciseForm/ForkExerciseForm.css
@@ -1,4 +1,4 @@
-.formSpace {
- padding-left: 10px;
+.forkForm {
+ padding-left: 0px;
display: flex;
}
diff --git a/src/components/forms/ForkExerciseForm/ForkExerciseForm.js b/src/components/forms/ForkExerciseForm/ForkExerciseForm.js
index 20119434d..672fd4634 100644
--- a/src/components/forms/ForkExerciseForm/ForkExerciseForm.js
+++ b/src/components/forms/ForkExerciseForm/ForkExerciseForm.js
@@ -13,7 +13,7 @@ import { SuccessIcon } from '../../../components/icons';
import { forkStatuses } from '../../../redux/modules/exercises';
import { getFork } from '../../../redux/selectors/exercises';
import ResourceRenderer from '../../helpers/ResourceRenderer';
-import { getLocalizedName } from '../../../helpers/getLocalizedData';
+import { getGroupCanonicalLocalizedName } from '../../../helpers/getLocalizedData';
import withLinks from '../../../hoc/withLinks';
@@ -41,6 +41,7 @@ class ForkExerciseForm extends Component {
hasSucceeded = false,
invalid,
groups,
+ groupsAccessor,
intl: { locale }
} = this.props;
@@ -70,7 +71,7 @@ class ForkExerciseForm extends Component {
defaultMessage="Saving failed. Please try again later."
/>
}
-
}
- noPadding
+ unlimitedHeight
>
-
- {() =>
-
-
- this.assignExercise(groupId)}
- />}
+
+ {visibleGroups =>
+ }
}
@@ -497,6 +519,7 @@ Exercise.propTypes = {
}).isRequired,
loadAsync: PropTypes.func.isRequired,
assignExercise: PropTypes.func.isRequired,
+ editAssignment: PropTypes.func.isRequired,
push: PropTypes.func.isRequired,
exercise: ImmutablePropTypes.map,
supervisedGroups: PropTypes.object,
@@ -512,9 +535,13 @@ Exercise.propTypes = {
forkExercise: PropTypes.func.isRequired,
groups: ImmutablePropTypes.map,
isSuperAdmin: PropTypes.bool,
- groupsAccessor: PropTypes.func.isRequired
+ groupsAccessor: PropTypes.func.isRequired,
+ firstDeadline: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
+ allowSecondDeadline: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
};
+const editMultiAssignFormSelector = formValueSelector('multiAssign');
+
export default withLinks(
injectIntl(
connect(
@@ -525,14 +552,18 @@ export default withLinks(
userId,
exercise: exerciseSelector(exerciseId)(state),
submitting: isSubmitting(state),
- supervisedGroups: supervisorOfSelector(userId)(state),
canEditExercise: exerciseId =>
canEditExercise(userId, exerciseId)(state),
referenceSolutions: referenceSolutionsSelector(exerciseId)(state),
exercisePipelines: exercisePipelinesSelector(exerciseId)(state),
groups: groupsSelector(state),
groupsAccessor: groupDataAccessorSelector(state),
- isSuperAdmin: isLoggedAsSuperAdmin(state)
+ isSuperAdmin: isLoggedAsSuperAdmin(state),
+ firstDeadline: editMultiAssignFormSelector(state, 'firstDeadline'),
+ allowSecondDeadline: editMultiAssignFormSelector(
+ state,
+ 'allowSecondDeadline'
+ )
};
},
(dispatch, { params: { exerciseId } }) => ({
@@ -540,6 +571,7 @@ export default withLinks(
Exercise.loadAsync({ exerciseId }, dispatch, userId),
assignExercise: groupId =>
dispatch(assignExercise(groupId, exerciseId)),
+ editAssignment: (id, body) => dispatch(editAssignment(id, body)),
push: url => dispatch(push(url)),
initCreateReferenceSolution: userId =>
dispatch(init(userId, exerciseId)),
From 5b2adef09c4ccde5faeeb5e0a924c230a9f632e7 Mon Sep 17 00:00:00 2001
From: Petr Stefan
Date: Sun, 25 Feb 2018 00:01:51 +0100
Subject: [PATCH 4/4] Fix merge
---
src/pages/Exercise/Exercise.js | 42 ++++++++++++++++++++++++++++------
1 file changed, 35 insertions(+), 7 deletions(-)
diff --git a/src/pages/Exercise/Exercise.js b/src/pages/Exercise/Exercise.js
index ce717f162..586583aea 100644
--- a/src/pages/Exercise/Exercise.js
+++ b/src/pages/Exercise/Exercise.js
@@ -13,6 +13,7 @@ import { Row, Col } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';
import Icon from 'react-fontawesome';
import { formValueSelector } from 'redux-form';
+import moment from 'moment';
import SupplementaryFilesTableContainer from '../../containers/SupplementaryFilesTableContainer/SupplementaryFilesTableContainer';
import Button from '../../components/widgets/FlatButton';
@@ -46,7 +47,10 @@ import {
} 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';
+import {
+ create as assignExercise,
+ editAssignment
+} from '../../redux/modules/assignments';
import { exerciseSelector } from '../../redux/selectors/exercises';
import { referenceSolutionsSelector } from '../../redux/selectors/referenceSolutions';
import {
@@ -126,13 +130,35 @@ class Exercise extends Component {
this.setState({ forkId: Math.random().toString() });
}
- assignExercise = groupId => {
- const { assignExercise, push } = this.props;
- const { links: { ASSIGNMENT_EDIT_URI_FACTORY } } = this.context;
+ assignExercise = formData => {
+ const { assignExercise, editAssignment } = this.props;
- assignExercise(groupId).then(({ value: assigment }) =>
- push(ASSIGNMENT_EDIT_URI_FACTORY(assigment.id))
- );
+ const groups =
+ formData && formData.groups
+ ? Object.keys(formData.groups).filter(key => formData.groups[key])
+ : [];
+
+ let actions = [];
+
+ for (const groupId of groups) {
+ assignExercise(groupId).then(({ value: assigment }) => {
+ let assignmentData = Object.assign({}, assigment, formData, {
+ firstDeadline: moment(formData.firstDeadline).unix(),
+ secondDeadline: moment(formData.secondDeadline).unix(),
+ submissionsCountLimit: Number(formData.submissionsCountLimit),
+ isPublic: true
+ });
+ if (!assignmentData.allowSecondDeadline) {
+ delete assignmentData.secondDeadline;
+ delete assignmentData.maxPointsBeforeSecondDeadline;
+ }
+ delete assignmentData.groups;
+
+ return editAssignment(assigment.id, assignmentData);
+ });
+ }
+
+ return Promise.all(actions);
};
createExercisePipeline = () => {
@@ -464,6 +490,7 @@ Exercise.propTypes = {
}).isRequired,
loadAsync: PropTypes.func.isRequired,
assignExercise: PropTypes.func.isRequired,
+ editAssignment: PropTypes.func.isRequired,
push: PropTypes.func.isRequired,
exercise: ImmutablePropTypes.map,
supervisedGroups: PropTypes.object,
@@ -512,6 +539,7 @@ export default withLinks(
(dispatch, { params: { exerciseId } }) => ({
loadAsync: userId => Exercise.loadAsync({ exerciseId }, dispatch, userId),
assignExercise: groupId => dispatch(assignExercise(groupId, exerciseId)),
+ editAssignment: (id, body) => dispatch(editAssignment(id, body)),
push: url => dispatch(push(url)),
initCreateReferenceSolution: userId => dispatch(init(userId, exerciseId)),
createExercisePipeline: () =>