diff --git a/src/components/Exercises/ForkExerciseButton/FailedForkExerciseButton.js b/src/components/Exercises/ForkExerciseButton/FailedForkExerciseButton.js deleted file mode 100644 index f15b476e8..000000000 --- a/src/components/Exercises/ForkExerciseButton/FailedForkExerciseButton.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import Button from '../../widgets/FlatButton'; -import { FailedIcon } from '../../icons'; - -const FailedForkExerciseButton = ({ onClick, ...props }) => ( - -); - -FailedForkExerciseButton.propTypes = { - onClick: PropTypes.func.isRequired -}; - -export default FailedForkExerciseButton; diff --git a/src/components/Exercises/ForkExerciseButton/ForkExerciseButton.js b/src/components/Exercises/ForkExerciseButton/ForkExerciseButton.js deleted file mode 100644 index 8d1522312..000000000 --- a/src/components/Exercises/ForkExerciseButton/ForkExerciseButton.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import Button from '../../widgets/FlatButton'; -import Icon from 'react-fontawesome'; -import Confirm from '../../forms/Confirm'; - -const ForkExerciseButton = ({ forkId, onClick, ...props }) => ( - - } - > - - -); - -ForkExerciseButton.propTypes = { - forkId: PropTypes.string.isRequired, - onClick: PropTypes.func.isRequired -}; - -export default ForkExerciseButton; diff --git a/src/components/Exercises/ForkExerciseButton/PendingForkExerciseButton.js b/src/components/Exercises/ForkExerciseButton/PendingForkExerciseButton.js deleted file mode 100644 index bfd1a5461..000000000 --- a/src/components/Exercises/ForkExerciseButton/PendingForkExerciseButton.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; -import Button from '../../widgets/FlatButton'; -import { LoadingIcon } from '../../icons'; - -const PendingForkExerciseButton = props => ( - -); - -export default PendingForkExerciseButton; diff --git a/src/components/Exercises/ForkExerciseButton/SuccessfulForkExerciseButton.js b/src/components/Exercises/ForkExerciseButton/SuccessfulForkExerciseButton.js deleted file mode 100644 index 82d034f34..000000000 --- a/src/components/Exercises/ForkExerciseButton/SuccessfulForkExerciseButton.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import Button from '../../widgets/FlatButton'; -import { SuccessIcon } from '../../icons'; - -const SuccessfulForkExerciseButton = ({ onClick, ...props }) => ( - -); - -SuccessfulForkExerciseButton.propTypes = { - onClick: PropTypes.func.isRequired -}; - -export default SuccessfulForkExerciseButton; diff --git a/src/components/Exercises/ForkExerciseButton/index.js b/src/components/Exercises/ForkExerciseButton/index.js deleted file mode 100644 index 8ca030344..000000000 --- a/src/components/Exercises/ForkExerciseButton/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export default from './ForkExerciseButton'; -export PendingForkExerciseButton from './PendingForkExerciseButton'; -export FailedForkExerciseButton from './FailedForkExerciseButton'; -export SuccessfulForkExerciseButton from './SuccessfulForkExerciseButton'; diff --git a/src/pages/Group/Group.css b/src/components/Groups/HierarchyLine/HierarchyLine.css similarity index 65% rename from src/pages/Group/Group.css rename to src/components/Groups/HierarchyLine/HierarchyLine.css index e94204ca0..0c6d3c3e3 100644 --- a/src/pages/Group/Group.css +++ b/src/components/Groups/HierarchyLine/HierarchyLine.css @@ -4,3 +4,8 @@ border-color: #f5f5f5; color: grey; } + +.slashStyle { + margin: 0 5px; + color: #aaa; +} diff --git a/src/components/Groups/HierarchyLine/HierarchyLine.js b/src/components/Groups/HierarchyLine/HierarchyLine.js new file mode 100644 index 000000000..53f70b2ef --- /dev/null +++ b/src/components/Groups/HierarchyLine/HierarchyLine.js @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Well } from 'react-bootstrap'; +import GroupsNameContainer from '../../../containers/GroupsNameContainer'; + +import './HierarchyLine.css'; + +const HierarchyLine = ({ groupId, parentGroupsIds }) => + + {parentGroupsIds.map( + (groupId, i) => + i !== 0 && + + {' '} + / + + )} + + ; + +HierarchyLine.propTypes = { + groupId: PropTypes.string.isRequired, + parentGroupsIds: PropTypes.array +}; + +export default HierarchyLine; diff --git a/src/components/Groups/HierarchyLine/index.js b/src/components/Groups/HierarchyLine/index.js new file mode 100644 index 000000000..a8016329c --- /dev/null +++ b/src/components/Groups/HierarchyLine/index.js @@ -0,0 +1 @@ +export default from './HierarchyLine'; diff --git a/src/components/Submissions/TestResults/TestResults.js b/src/components/Submissions/TestResults/TestResults.js index 9e6430073..26fbec757 100644 --- a/src/components/Submissions/TestResults/TestResults.js +++ b/src/components/Submissions/TestResults/TestResults.js @@ -15,6 +15,7 @@ const TestResults = ({ evaluation, runtimeEnvironmentId }) => noPadding={true} collapsable={true} isOpen={true} + unlimitedHeight > this.viewForkedExercise()} + > + {' '} + + + ); + default: + return ( +
+ {hasFailed && + + + } +
+ + {(...groups) => + + a.name.localeCompare(b.name, intl.locale) + ) + .filter((item, pos, arr) => arr.indexOf(item) === pos) + .map(group => ({ + key: group.id, + name: group.name + })) + )} + />} + + + + ), + submitting: ( + + ), + success: ( + + ) + }} + /> + +
+ ); + } + } +} + +ForkExerciseForm.propTypes = { + exerciseId: PropTypes.string.isRequired, + forkId: PropTypes.string.isRequired, + forkStatus: PropTypes.string, + forkedExerciseId: PropTypes.string, + anyTouched: PropTypes.bool, + submitting: PropTypes.bool, + hasFailed: PropTypes.bool, + hasSucceeded: PropTypes.bool, + invalid: PropTypes.bool, + handleSubmit: PropTypes.func.isRequired, + push: PropTypes.func.isRequired, + links: PropTypes.object, + groups: ImmutablePropTypes.map, + intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired +}; + +const mapStateToProps = (state, { exerciseId, forkId }) => { + const fork = getFork(exerciseId, forkId)(state); + return { + forkStatus: fork ? fork.status : null, + forkedExerciseId: + fork && fork.status === forkStatuses.FULFILLED ? fork.exerciseId : null + }; +}; + +const mapDispatchToProps = (dispatch, { exerciseId, forkId }) => ({ + push: url => dispatch(push(url)) +}); + +const validate = () => {}; + +export default withLinks( + connect(mapStateToProps, mapDispatchToProps)( + reduxForm({ + form: 'forkExercise', + validate + })(injectIntl(ForkExerciseForm)) + ) +); diff --git a/src/components/forms/ForkExerciseForm/index.js b/src/components/forms/ForkExerciseForm/index.js new file mode 100644 index 000000000..f523ad8b0 --- /dev/null +++ b/src/components/forms/ForkExerciseForm/index.js @@ -0,0 +1 @@ +export default from './ForkExerciseForm'; diff --git a/src/components/forms/SubmitButton/SubmitButton.js b/src/components/forms/SubmitButton/SubmitButton.js index c5d59baa1..01534e7c3 100644 --- a/src/components/forms/SubmitButton/SubmitButton.js +++ b/src/components/forms/SubmitButton/SubmitButton.js @@ -45,6 +45,7 @@ class SubmitButton extends Component { invalid, asyncValidating = false, noIcons = false, + disabled = false, messages: { submit: submitMsg = ( {!submitting ? hasSucceeded @@ -128,6 +131,7 @@ SubmitButton.propTypes = { asyncValidating: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), noIcons: PropTypes.bool, invalid: PropTypes.bool, + disabled: PropTypes.bool, reset: PropTypes.func, messages: PropTypes.shape({ submit: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), diff --git a/src/containers/ForkExerciseButtonContainer/ForkExerciseButtonContainer.js b/src/containers/ForkExerciseButtonContainer/ForkExerciseButtonContainer.js deleted file mode 100644 index 2b36ea88d..000000000 --- a/src/containers/ForkExerciseButtonContainer/ForkExerciseButtonContainer.js +++ /dev/null @@ -1,82 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { push } from 'react-router-redux'; -import { forkStatuses, forkExercise } from '../../redux/modules/exercises'; -import { getFork } from '../../redux/selectors/exercises'; - -import ForkExerciseButton, { - PendingForkExerciseButton, - FailedForkExerciseButton, - SuccessfulForkExerciseButton -} from '../../components/Exercises/ForkExerciseButton'; - -import withLinks from '../../hoc/withLinks'; - -class ForkExerciseButtonContainer extends Component { - viewForkedExercise() { - const { - forkedExerciseId: id, - push, - links: { EXERCISE_URI_FACTORY } - } = this.props; - - const url = EXERCISE_URI_FACTORY(id); - push(url); - } - - fork() { - const { fork } = this.props; - fork(); - } - - render() { - const { forkStatus, forkId } = this.props; - - switch (forkStatus) { - case forkStatuses.PENDING: - return ; - case forkStatuses.REJECTED: - return ; - case forkStatuses.FULFILLED: - return ( - this.viewForkedExercise()} - /> - ); - default: - return ( - this.fork()} forkId={forkId} /> - ); - } - } -} - -ForkExerciseButtonContainer.propTypes = { - fork: PropTypes.func.isRequired, - exerciseId: PropTypes.string.isRequired, - forkId: PropTypes.string.isRequired, - forkStatus: PropTypes.string, - forkedExerciseId: PropTypes.string, - push: PropTypes.func.isRequired, - links: PropTypes.object -}; - -const mapStateToProps = (state, { exerciseId, forkId }) => { - const fork = getFork(exerciseId, forkId)(state); - return { - forkStatus: fork ? fork.status : null, - forkedExerciseId: fork && fork.status === forkStatuses.FULFILLED - ? fork.exerciseId - : null - }; -}; - -const mapDispatchToProps = (dispatch, { exerciseId, forkId }) => ({ - push: url => dispatch(push(url)), - fork: () => dispatch(forkExercise(exerciseId, forkId)) -}); - -export default withLinks( - connect(mapStateToProps, mapDispatchToProps)(ForkExerciseButtonContainer) -); diff --git a/src/containers/ForkExerciseButtonContainer/index.js b/src/containers/ForkExerciseButtonContainer/index.js deleted file mode 100644 index fe84448c9..000000000 --- a/src/containers/ForkExerciseButtonContainer/index.js +++ /dev/null @@ -1 +0,0 @@ -export default from './ForkExerciseButtonContainer'; diff --git a/src/containers/HierarchyLineContainer/HierarchyLineContainer.js b/src/containers/HierarchyLineContainer/HierarchyLineContainer.js new file mode 100644 index 000000000..1155592bd --- /dev/null +++ b/src/containers/HierarchyLineContainer/HierarchyLineContainer.js @@ -0,0 +1,52 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; + +import { fetchGroupIfNeeded } from '../../redux/modules/groups'; +import { groupSelector } from '../../redux/selectors/groups'; +import ResourceRenderer from '../../components/helpers/ResourceRenderer'; +import HierarchyLine from '../../components/Groups/HierarchyLine'; + +class HierarchyLineContainer extends Component { + componentWillMount() { + HierarchyLineContainer.loadAsync(this.props); + } + + componentWillReceiveProps(newProps) { + if (this.props.groupId !== newProps.groupId) { + HierarchyLineContainer.loadAsync(newProps); + } + } + + static loadAsync = ({ loadGroupIfNeeded }) => { + loadGroupIfNeeded(); + }; + + render() { + const { group } = this.props; + return ( + + {group => + } + + ); + } +} + +HierarchyLineContainer.propTypes = { + groupId: PropTypes.string.isRequired, + group: ImmutablePropTypes.map +}; + +export default connect( + (state, { groupId }) => ({ + group: groupSelector(groupId)(state) + }), + (dispatch, { groupId }) => ({ + loadGroupIfNeeded: () => dispatch(fetchGroupIfNeeded(groupId)) + }) +)(HierarchyLineContainer); diff --git a/src/containers/HierarchyLineContainer/index.js b/src/containers/HierarchyLineContainer/index.js new file mode 100644 index 000000000..1c7843691 --- /dev/null +++ b/src/containers/HierarchyLineContainer/index.js @@ -0,0 +1 @@ +export default from './HierarchyLineContainer'; diff --git a/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js b/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js index 28e17e49d..fbf623dcc 100644 --- a/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js +++ b/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, injectIntl } from 'react-intl'; import Box from '../../components/widgets/Box'; import { Table, Accordion, Panel, Row, Col } from 'react-bootstrap'; import Button from '../../components/widgets/FlatButton'; @@ -26,7 +26,25 @@ import SisBindGroupForm from '../../components/forms/SisBindGroupForm'; import withLinks from '../../hoc/withLinks'; import './SisSupervisorGroupsContainer.css'; -const days = ['Po', 'Út', 'St', 'Čt', 'Pá', 'So', 'Ne']; +const days = { + cs: ['Po', 'Út', 'St', 'Čt', 'Pá', 'So', 'Ne'], + en: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] +}; + +const oddEven = { + cs: ['lichý', 'sudý'], + en: ['odd', 'even'] +}; + +const getLocalizedData = (obj, locale) => { + if (obj && obj[locale]) { + return obj[locale]; + } else if (obj && Object.keys(obj).length > 0) { + return Object.keys(obj)[0]; + } else { + return null; + } +}; class SisSupervisorGroupsContainer extends Component { componentDidMount() { @@ -60,7 +78,8 @@ class SisSupervisorGroupsContainer extends Component { createGroup, bindGroup, sisPossibleParents, - links: { GROUP_URI_FACTORY } + links: { GROUP_URI_FACTORY }, + intl: { locale } } = this.props; return ( } - {course.course.captions.cs} ({course.course.code}){' '} + {getLocalizedData( + course.course.captions, + locale + )}{' '} + ({course.course.code}){' '} - {days[course.course.dayOfWeek]}{' '} + { + getLocalizedData(days, locale)[ + course.course.dayOfWeek + ] + }{' '} {course.course.time}{' '} {course.course.fortnightly - ? course.course.oddWeeks - ? '(lichý)' - : '(sudý)' + ? getLocalizedData( + oddEven, + locale + )[ + course.course.oddWeeks ? 0 : 1 + ] : ''} @@ -272,28 +302,32 @@ SisSupervisorGroupsContainer.propTypes = { createGroup: PropTypes.func.isRequired, bindGroup: PropTypes.func.isRequired, links: PropTypes.object, - sisPossibleParents: PropTypes.func.isRequired + sisPossibleParents: PropTypes.func.isRequired, + intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired }; -export default withLinks( - connect( - state => { - const currentUserId = loggedInUserIdSelector(state); - return { - sisStatus: sisStateSelector(state), - currentUserId, - sisCourses: (year, term) => - sisSupervisedCoursesSelector(currentUserId, year, term)(state), - sisPossibleParents: courseId => - sisPossibleParentsSelector(courseId)(state) - }; - }, - dispatch => ({ - loadData: loggedInUserId => - SisSupervisorGroupsContainer.loadData(dispatch, loggedInUserId), - createGroup: (courseId, data) => dispatch(sisCreateGroup(courseId, data)), - bindGroup: (courseId, data, userId, year, term) => - dispatch(sisBindGroup(courseId, data, userId, year, term)) - }) - )(SisSupervisorGroupsContainer) +export default injectIntl( + withLinks( + connect( + state => { + const currentUserId = loggedInUserIdSelector(state); + return { + sisStatus: sisStateSelector(state), + currentUserId, + sisCourses: (year, term) => + sisSupervisedCoursesSelector(currentUserId, year, term)(state), + sisPossibleParents: courseId => + sisPossibleParentsSelector(courseId)(state) + }; + }, + dispatch => ({ + loadData: loggedInUserId => + SisSupervisorGroupsContainer.loadData(dispatch, loggedInUserId), + createGroup: (courseId, data) => + dispatch(sisCreateGroup(courseId, data)), + bindGroup: (courseId, data, userId, year, term) => + dispatch(sisBindGroup(courseId, data, userId, year, term)) + }) + )(SisSupervisorGroupsContainer) + ) ); diff --git a/src/pages/Assignment/Assignment.js b/src/pages/Assignment/Assignment.js index d2fe7bca9..f1a763b35 100644 --- a/src/pages/Assignment/Assignment.js +++ b/src/pages/Assignment/Assignment.js @@ -32,14 +32,12 @@ import { } from '../../redux/selectors/users'; import { runtimeEnvironmentSelector } from '../../redux/selectors/runtimeEnvironments'; -import PageContent from '../../components/layout/PageContent'; +import Page from '../../components/layout/Page'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; import UsersNameContainer from '../../containers/UsersNameContainer'; import { ResubmitAllSolutionsContainer } from '../../containers/ResubmitSolutionContainer'; -import AssignmentDetails, { - LoadingAssignmentDetails, - FailedAssignmentDetails -} from '../../components/Assignments/Assignment/AssignmentDetails'; +import HierarchyLineContainer from '../../containers/HierarchyLineContainer'; +import AssignmentDetails from '../../components/Assignments/Assignment/AssignmentDetails'; import { EditIcon, ResultsIcon } from '../../components/icons'; import LocalizedTexts from '../../components/helpers/LocalizedTexts'; import SubmitSolutionButton from '../../components/Assignments/SubmitSolutionButton'; @@ -89,15 +87,12 @@ class Assignment extends Component { } = this.props; return ( - - {assignment => - - {getLocalizedName(assignment, locale)} - } - - } + + + {getLocalizedName(assignment, locale)} + } description={ - } - failed={} - resource={assignment} - > - {assignment => -
- - - {loggedInUserId !== userId && -

- -

} - {(isSuperAdmin || isSupervisorOf(assignment.groupId)) && -

- - - - - - - -

} - -
- {(isSuperAdmin || isSupervisorOf(assignment.groupId)) && - (!assignment.exerciseSynchronizationInfo.exerciseConfig - .upToDate || - !assignment.exerciseSynchronizationInfo - .exerciseEnvironmentConfigs.upToDate || - !assignment.exerciseSynchronizationInfo.hardwareGroups - .upToDate || - !assignment.exerciseSynchronizationInfo.localizedTexts - .upToDate || - !assignment.exerciseSynchronizationInfo.limits.upToDate || - !assignment.exerciseSynchronizationInfo.scoreConfig - .upToDate || - !assignment.exerciseSynchronizationInfo.scoreCalculator - .upToDate) && - - - -

+ {assignment => +
+ + + + {loggedInUserId !== userId && +

+ +

} + {(isSuperAdmin || isSupervisorOf(assignment.groupId)) && +

+ +

-
+ + + +
-

- -

-
- -
} + + + +

} + + + {(isSuperAdmin || isSupervisorOf(assignment.groupId)) && + (!assignment.exerciseSynchronizationInfo.exerciseConfig + .upToDate || + !assignment.exerciseSynchronizationInfo + .exerciseEnvironmentConfigs.upToDate || + !assignment.exerciseSynchronizationInfo.hardwareGroups + .upToDate || + !assignment.exerciseSynchronizationInfo.localizedTexts + .upToDate || + !assignment.exerciseSynchronizationInfo.limits.upToDate || + !assignment.exerciseSynchronizationInfo.scoreConfig.upToDate || + !assignment.exerciseSynchronizationInfo.scoreCalculator + .upToDate) && - -
- {assignment.localizedTexts.length > 0 && - } -
- - - {(...runtimes) => - - + +

+ +

+
+ +
    + {!assignment.exerciseSynchronizationInfo.exerciseConfig + .upToDate && +
  • + +
  • } + {!assignment.exerciseSynchronizationInfo + .exerciseEnvironmentConfigs.upToDate && +
  • + +
  • } + {!assignment.exerciseSynchronizationInfo.hardwareGroups + .upToDate && +
  • + +
  • } + {!assignment.exerciseSynchronizationInfo.localizedTexts + .upToDate && +
  • + +
  • } + {!assignment.exerciseSynchronizationInfo.limits + .upToDate && +
  • + +
  • } + {!assignment.exerciseSynchronizationInfo.scoreConfig + .upToDate && +
  • + +
  • } + {!assignment.exerciseSynchronizationInfo.scoreCalculator + .upToDate && +
  • + +
  • } +
+
+

+ +

+
+ +
} + + +
+ {assignment.localizedTexts.length > 0 && + } +
+ + + {(...runtimes) => + + - {isStudentOf(assignment.groupId) && -
-

- } - resource={canSubmit} - > - {canSubmit => - } - -

- -
} - - {(isStudentOf(assignment.groupId) || - isSupervisorOf(assignment.groupId) || - isSuperAdmin) && - +

+ } + resource={canSubmit} + > + {canSubmit => + } + +

+ } - } -
-
-
} -
-
+ id={assignment.id} + onSubmit={submitSolution} + onReset={init} + isOpen={submitting} + runtimeEnvironments={runtimes} + /> + } + + {(isStudentOf(assignment.groupId) || + isSupervisorOf(assignment.groupId) || + isSuperAdmin) && + } + } + + + } + ); } } diff --git a/src/pages/AssignmentStats/AssignmentStats.js b/src/pages/AssignmentStats/AssignmentStats.js index 73ff2ef1b..f0c1bb379 100644 --- a/src/pages/AssignmentStats/AssignmentStats.js +++ b/src/pages/AssignmentStats/AssignmentStats.js @@ -14,6 +14,7 @@ import { fetchGroupIfNeeded } from '../../redux/modules/groups'; import Page from '../../components/layout/Page'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; +import HierarchyLineContainer from '../../containers/HierarchyLineContainer'; class AssignmentStats extends Component { static loadAsync = ({ assignmentId }, dispatch) => @@ -106,6 +107,7 @@ class AssignmentStats extends Component { > {assignment =>
+ {group =>
diff --git a/src/pages/Dashboard/Dashboard.js b/src/pages/Dashboard/Dashboard.js index 8c5758062..56e719d2e 100644 --- a/src/pages/Dashboard/Dashboard.js +++ b/src/pages/Dashboard/Dashboard.js @@ -39,7 +39,7 @@ import { groupsAssignmentsSelector, supervisorOfSelector, studentOfSelector, - groupsSelectors + groupsSelector } from '../../redux/selectors/groups'; import { InfoIcon } from '../../components/icons'; import { getJsData } from '../../redux/helpers/resourceManager'; @@ -362,7 +362,7 @@ export default withLinks( groupStatistics: groupId => createGroupsStatsSelector(groupId)(state), usersStatistics: statistics => statistics.find(stat => stat.userId === userId) || {}, - allGroups: groupsSelectors(state).toArray(), + allGroups: groupsSelector(state).toArray(), isAdmin: isSuperAdmin(userId)(state) }; }, diff --git a/src/pages/EditAssignment/EditAssignment.js b/src/pages/EditAssignment/EditAssignment.js index d9949fcb9..a7c65ca2b 100644 --- a/src/pages/EditAssignment/EditAssignment.js +++ b/src/pages/EditAssignment/EditAssignment.js @@ -6,13 +6,12 @@ import { connect } from 'react-redux'; import { push } from 'react-router-redux'; import { reset, getFormValues } from 'redux-form'; import moment from 'moment'; -import PageContent from '../../components/layout/PageContent'; +import Page from '../../components/layout/Page'; -import ResourceRenderer from '../../components/helpers/ResourceRenderer'; import EditAssignmentForm from '../../components/forms/EditAssignmentForm'; import DeleteAssignmentButtonContainer from '../../containers/DeleteAssignmentButtonContainer'; import Box from '../../components/widgets/Box'; -import { LoadingIcon, WarningIcon } from '../../components/icons'; +import HierarchyLineContainer from '../../containers/HierarchyLineContainer'; import { fetchAssignment, @@ -70,7 +69,8 @@ class EditAssignment extends Component { } = this.props; return ( - -
- - } - > + {assignment => +
+ + + editAssignment(assignment.version, formData)} + formValues={formValues} + /> + +
+ + } + > +

- {' '}

- - } - failed={ - - } - > -

- {' '} - + push(GROUP_URI_FACTORY(this.groupId))} />

-
- } - resource={assignment} - > - {data => -
- editAssignment(data.version, formData)} - formValues={formValues} - /> -
} - -
- - } - > -
-

- -

-

- push(GROUP_URI_FACTORY(this.groupId))} - /> -

-
-
-
- +
+ +
} + ); } } diff --git a/src/pages/Exercise/Exercise.js b/src/pages/Exercise/Exercise.js index b941caa1d..c94696f08 100644 --- a/src/pages/Exercise/Exercise.js +++ b/src/pages/Exercise/Exercise.js @@ -29,12 +29,14 @@ import { } from '../../components/icons'; import Confirm from '../../components/forms/Confirm'; import PipelinesSimpleList from '../../components/Pipelines/PipelinesSimpleList'; - -import ForkExerciseButtonContainer from '../../containers/ForkExerciseButtonContainer'; +// import ForkExerciseForm from '../../components/forms/ForkExerciseForm'; import AssignExerciseButton from '../../components/buttons/AssignExerciseButton'; import { isSubmitting } from '../../redux/selectors/submission'; -import { fetchExerciseIfNeeded } from '../../redux/modules/exercises'; +import { + fetchExerciseIfNeeded, + forkExercise +} from '../../redux/modules/exercises'; import { fetchReferenceSolutionsIfNeeded, deleteReferenceSolution @@ -51,9 +53,13 @@ import { create as createPipeline } from '../../redux/modules/pipelines'; import { exercisePipelinesSelector } from '../../redux/selectors/pipelines'; +import { fetchUsersGroupsIfNeeded } from '../../redux/modules/groups'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; -import { supervisorOfSelector } from '../../redux/selectors/groups'; +import { + supervisorOfSelector, + groupsSelector +} from '../../redux/selectors/groups'; import withLinks from '../../hoc/withLinks'; import { getLocalizedName } from '../../helpers/getLocalizedData'; @@ -76,22 +82,23 @@ const messages = defineMessages({ class Exercise extends Component { state = { forkId: null }; - static loadAsync = ({ exerciseId }, dispatch) => + static loadAsync = ({ exerciseId }, dispatch, userId) => Promise.all([ dispatch(fetchExerciseIfNeeded(exerciseId)), dispatch(fetchReferenceSolutionsIfNeeded(exerciseId)), dispatch(fetchHardwareGroups()), - dispatch(fetchExercisePipelines(exerciseId)) + dispatch(fetchExercisePipelines(exerciseId)), + dispatch(fetchUsersGroupsIfNeeded(userId)) ]); componentWillMount() { - this.props.loadAsync(); + this.props.loadAsync(this.props.userId); this.reset(); } componentWillReceiveProps(newProps) { if (this.props.params.exerciseId !== newProps.params.exerciseId) { - newProps.loadAsync(); + newProps.loadAsync(this.props.userId); this.reset(); } } @@ -133,9 +140,11 @@ class Exercise extends Component { exercisePipelines, deleteReferenceSolution, push + // groups, + // forkExercise } = this.props; - const { forkId } = this.state; + // const { forkId } = this.state; const { links: { @@ -210,11 +219,13 @@ class Exercise extends Component { /> + {/* forkExercise(forkId, formData)} + /> */} -
}

@@ -446,7 +457,9 @@ Exercise.propTypes = { exercisePipelines: ImmutablePropTypes.map, createExercisePipeline: PropTypes.func, links: PropTypes.object, - deleteReferenceSolution: PropTypes.func.isRequired + deleteReferenceSolution: PropTypes.func.isRequired, + forkExercise: PropTypes.func.isRequired, + groups: ImmutablePropTypes.map }; export default withLinks( @@ -463,11 +476,13 @@ export default withLinks( canEditExercise: exerciseId => canEditExercise(userId, exerciseId)(state), referenceSolutions: referenceSolutionsSelector(exerciseId)(state), - exercisePipelines: exercisePipelinesSelector(exerciseId)(state) + exercisePipelines: exercisePipelinesSelector(exerciseId)(state), + groups: groupsSelector(state) }; }, (dispatch, { params: { exerciseId } }) => ({ - loadAsync: () => Exercise.loadAsync({ exerciseId }, dispatch), + loadAsync: userId => + Exercise.loadAsync({ exerciseId }, dispatch, userId), assignExercise: groupId => dispatch(assignExercise(groupId, exerciseId)), push: url => dispatch(push(url)), @@ -476,7 +491,9 @@ export default withLinks( createExercisePipeline: () => dispatch(createPipeline({ exerciseId: exerciseId })), deleteReferenceSolution: (exerciseId, solutionId) => - dispatch(deleteReferenceSolution(exerciseId, solutionId)) + dispatch(deleteReferenceSolution(exerciseId, solutionId)), + forkExercise: (forkId, data) => + dispatch(forkExercise(exerciseId, forkId, data)) }) )(Exercise) ) diff --git a/src/pages/Group/Group.js b/src/pages/Group/Group.js index 45cb90b4d..25da87fda 100644 --- a/src/pages/Group/Group.js +++ b/src/pages/Group/Group.js @@ -7,7 +7,6 @@ import { LinkContainer } from 'react-router-bootstrap'; import Button from '../../components/widgets/FlatButton'; import { FormattedMessage } from 'react-intl'; import { List, Map } from 'immutable'; -import { Well } from 'react-bootstrap'; import Page from '../../components/layout/Page'; import GroupDetail, { @@ -18,8 +17,8 @@ import LeaveJoinGroupButtonContainer from '../../containers/LeaveJoinGroupButton import AdminsView from '../../components/Groups/AdminsView'; import SupervisorsView from '../../components/Groups/SupervisorsView'; import StudentsView from '../../components/Groups/StudentsView'; +import HierarchyLine from '../../components/Groups/HierarchyLine'; import { EditIcon } from '../../components/icons'; -import GroupsNameContainer from '../../containers/GroupsNameContainer'; import { isReady, getJsData } from '../../redux/helpers/resourceManager'; import { @@ -50,7 +49,7 @@ import { import { groupSelector, - groupsSelectors, + groupsSelector, groupsAssignmentsSelector, supervisorsOfGroup, studentsOfGroup @@ -63,7 +62,6 @@ import { fetchInstanceIfNeeded } from '../../redux/modules/instances'; import { instanceSelector } from '../../redux/selectors/instances'; import withLinks from '../../hoc/withLinks'; -import './Group.css'; class Group extends Component { static isAdminOrSupervisorOf = (group, userId) => @@ -203,19 +201,11 @@ class Group extends Component { > {data =>

- - {data.parentGroupsIds.map( - (groupId, i) => - i !== 0 && - - {' '} - / - - )} - - - - {data.parentGroupsIds.length > 1 &&

} + +

{(isAdmin || isSuperAdmin) &&

@@ -327,7 +317,7 @@ const mapStateToProps = (state, { params: { groupId } }) => { instance: isReady(group) ? instanceSelector(state, groupData.instanceId) : null, - groups: groupsSelectors(state), + groups: groupsSelector(state), publicGroups: publicGroupsSelectors(state), publicAssignments: groupsAssignmentsSelector(groupId, 'public')(state), allAssignments: groupsAssignmentsSelector(groupId, 'all')(state), diff --git a/src/pages/Pipeline/Pipeline.js b/src/pages/Pipeline/Pipeline.js index b95245566..97966f5ef 100644 --- a/src/pages/Pipeline/Pipeline.js +++ b/src/pages/Pipeline/Pipeline.js @@ -103,32 +103,30 @@ class Pipeline extends Component { > {pipeline =>

+
+ + {isAuthorOfPipeline(pipeline.id) && + + + } + forkPipeline(forkId, formData)} + /> + +
+

-

- - {isAuthorOfPipeline(pipeline.id) && - - - } - forkPipeline(forkId, formData)} - /> - -
-

diff --git a/src/pages/Submission/Submission.js b/src/pages/Submission/Submission.js index 5f319c3de..5cc2aac8f 100644 --- a/src/pages/Submission/Submission.js +++ b/src/pages/Submission/Submission.js @@ -10,6 +10,7 @@ import SubmissionDetail, { } from '../../components/Submissions/SubmissionDetail'; import AcceptSolutionContainer from '../../containers/AcceptSolutionContainer'; import ResubmitSolutionContainer from '../../containers/ResubmitSolutionContainer'; +import HierarchyLineContainer from '../../containers/HierarchyLineContainer'; import { fetchGroupsStats } from '../../redux/modules/stats'; import { fetchAssignmentIfNeeded } from '../../redux/modules/assignments'; @@ -105,6 +106,7 @@ class Submission extends Component { > {(submission, assignment) =>

+ {isSupervisorOrMore(assignment.groupId) &&

diff --git a/src/redux/modules/exercises.js b/src/redux/modules/exercises.js index 6fc69ccaa..67cbe8857 100644 --- a/src/redux/modules/exercises.js +++ b/src/redux/modules/exercises.js @@ -48,13 +48,18 @@ export const forkStatuses = { FULFILLED: 'FULFILLED' }; -export const forkExercise = (id, forkId) => - createApiAction({ +export const forkExercise = (id, forkId, formData = null) => { + let actionData = { type: additionalActionTypes.FORK_EXERCISE, endpoint: `/exercises/${id}/fork`, method: 'POST', meta: { id, forkId } - }); + }; + if (formData && formData.groupId) { + actionData.body = { groupId: formData.groupId }; + } + return createApiAction(actionData); +}; export const create = actions.addResource; export const editExercise = actions.updateResource; diff --git a/src/redux/modules/pipelines.js b/src/redux/modules/pipelines.js index f5102fc9f..e0e88527b 100644 --- a/src/redux/modules/pipelines.js +++ b/src/redux/modules/pipelines.js @@ -37,19 +37,15 @@ export const forkStatuses = { FULFILLED: 'FULFILLED' }; -export const forkPipeline = (id, forkId, exerciseId = null) => { +export const forkPipeline = (id, forkId, formData = null) => { let actionData = { type: additionalActionTypes.FORK_PIPELINE, endpoint: `/pipelines/${id}/fork`, method: 'POST', meta: { id, forkId } }; - if ( - Object.keys(exerciseId).length !== 0 && - exerciseId.constructor === Object && - exerciseId !== null - ) { - actionData.body = { exerciseId }; + if (formData && formData.exerciseId) { + actionData.body = { exerciseId: formData.exerciseId }; } return createApiAction(actionData); }; diff --git a/src/redux/selectors/groups.js b/src/redux/selectors/groups.js index 369e5007e..3226f8b61 100644 --- a/src/redux/selectors/groups.js +++ b/src/redux/selectors/groups.js @@ -12,27 +12,27 @@ import { isReady, getId, getJsData } from '../helpers/resourceManager'; * Select groups part of the state */ -export const groupsSelectors = state => state.groups.get('resources'); +export const groupsSelector = state => state.groups.get('resources'); const filterGroups = (ids, groups) => groups.filter(isReady).filter(group => ids.contains(getId(group))); export const groupSelector = id => - createSelector(groupsSelectors, groups => groups.get(id)); + createSelector(groupsSelector, groups => groups.get(id)); export const studentOfSelector = userId => createSelector( - [studentOfGroupsIdsSelector(userId), groupsSelectors], + [studentOfGroupsIdsSelector(userId), groupsSelector], filterGroups ); export const supervisorOfSelector = userId => createSelector( - [supervisorOfGroupsIdsSelector(userId), groupsSelectors], + [supervisorOfGroupsIdsSelector(userId), groupsSelector], filterGroups ); export const studentOfSelector2 = userId => - createSelector(groupsSelectors, groups => + createSelector(groupsSelector, groups => groups .filter(isReady) .map(getJsData) @@ -40,7 +40,7 @@ export const studentOfSelector2 = userId => ); export const supervisorOfSelector2 = userId => - createSelector(groupsSelectors, groups => + createSelector(groupsSelector, groups => groups .filter(isReady) .map(getJsData) @@ -48,7 +48,7 @@ export const supervisorOfSelector2 = userId => ); export const adminOfSelector = userId => - createSelector(groupsSelectors, groups => + createSelector(groupsSelector, groups => groups .filter(isReady) .map(getJsData) @@ -97,4 +97,4 @@ const getGroupParentIds = (id, groups) => { }; export const allParentIdsForGroup = id => - createSelector(groupsSelectors, groups => getGroupParentIds(id, groups)); + createSelector(groupsSelector, groups => getGroupParentIds(id, groups));