From e806161facbe0d45e7a068ea2b00612aea137a15 Mon Sep 17 00:00:00 2001 From: Martin Krulis Date: Sun, 26 Nov 2017 18:49:12 +0100 Subject: [PATCH 1/5] Redux Dev Server is started only if specified correctly in .env. Forgotten translation of exercise name in AssignmentStats. --- .env-sample | 1 + bin/dev.js | 22 +++++++++++++----- src/pages/AssignmentStats/AssignmentStats.js | 6 ++--- src/redux/store.js | 24 +++++++++++++------- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/.env-sample b/.env-sample index 15d00dade..ebbf2820f 100644 --- a/.env-sample +++ b/.env-sample @@ -2,6 +2,7 @@ NODE_ENV=production API_BASE=https://recodex.projekty.ms.mff.cuni.cz:4000/v1 PORT=443 # WEBPACK_DEV_SERVER_PORT=8081 # might be usefull for dev environment, if port 8081 is necessary for something +# If the REDUX_DEV_SERVER_PORT is not defined, the server will not be started. # REDUX_DEV_SERVER_PORT=8082 TITLE=ReCodEx diff --git a/bin/dev.js b/bin/dev.js index 5569cd9c4..a9b224d64 100644 --- a/bin/dev.js +++ b/bin/dev.js @@ -1,4 +1,3 @@ -import React from 'react'; import Express from 'express'; import webpack from 'webpack'; import WebpackDevServer from 'webpack-dev-server'; @@ -9,7 +8,7 @@ import remotedev from 'remotedev-server'; const PORT = process.env.PORT || 8080; const WEBPACK_DEV_SERVER_PORT = process.env.WEBPACK_DEV_SERVER_PORT || 8081; -const REDUX_DEV_SERVER_PORT = process.env.REDUX_DEV_SERVER_PORT || 8082; +const REDUX_DEV_SERVER_PORT = process.env.REDUX_DEV_SERVER_PORT || null; let app = new Express(); app.set('view engine', 'ejs'); @@ -39,10 +38,21 @@ var server = new WebpackDevServer(webpack(config), { stats: { colors: true } }); -remotedev({ - hostname: '127.0.0.1', - port: REDUX_DEV_SERVER_PORT -}); +if (REDUX_DEV_SERVER_PORT) { + console.log( + `${colors.yellow('ReduxDevServer')} is running on ${colors.underline( + `http://localhost:${REDUX_DEV_SERVER_PORT}` + )}` + ); + remotedev({ + hostname: '127.0.0.1', + port: REDUX_DEV_SERVER_PORT + }); +} else { + console.log( + 'No ReduxDevServer port defined, using embeded redux dev tools instead.' + ); +} server.listen(WEBPACK_DEV_SERVER_PORT, 'localhost', () => { console.log( diff --git a/src/pages/AssignmentStats/AssignmentStats.js b/src/pages/AssignmentStats/AssignmentStats.js index f0c1bb379..3ee4098a2 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 { LocalizedExerciseName } from '../../components/helpers/LocalizedNames'; import HierarchyLineContainer from '../../containers/HierarchyLineContainer'; class AssignmentStats extends Component { @@ -53,10 +54,7 @@ class AssignmentStats extends Component { resource={assignment} title={ - {assignment => - - {assignment.name} - } + {assignment => } } description={ diff --git a/src/redux/store.js b/src/redux/store.js index 705e1a870..1e0fbcdfa 100644 --- a/src/redux/store.js +++ b/src/redux/store.js @@ -15,7 +15,10 @@ import filter from 'redux-storage-decorator-filter'; import { actionTypes as authActionTypes } from './modules/auth'; import { actionTypes as switchingActionTypes } from './modules/userSwitching'; -const REDUX_DEV_SERVER_PORT = process.env.REDUX_DEV_SERVER_PORT !== 'undefined' ? process.env.REDUX_DEV_SERVER_PORT : 8082; +const REDUX_DEV_SERVER_PORT = + process.env.REDUX_DEV_SERVER_PORT !== 'undefined' + ? process.env.REDUX_DEV_SERVER_PORT + : null; const engine = filter(createEngine('recodex/store'), ['userSwitching']); @@ -36,17 +39,22 @@ const getMiddleware = history => [ ) ]; -const composeEnhancers = composeWithDevTools({ - realtime: true, - name: 'ReCodEx', - host: '127.0.0.1', - port: REDUX_DEV_SERVER_PORT -}); +const composeEnhancers = REDUX_DEV_SERVER_PORT + ? composeWithDevTools({ + realtime: true, + name: 'ReCodEx', + host: '127.0.0.1', + port: REDUX_DEV_SERVER_PORT + }) + : f => f; const dev = history => composeEnhancers( compose( - applyMiddleware(...getMiddleware(history), loggerMiddleware(!canUseDOM)) + applyMiddleware(...getMiddleware(history), loggerMiddleware(!canUseDOM)), + !REDUX_DEV_SERVER_PORT && canUseDOM && window.devToolsExtension // if no server is in place and dev tools are available, use them + ? window.devToolsExtension() + : f => f ) ); From 1929e2869c222c9ac477659cffb4953feb0cb0f7 Mon Sep 17 00:00:00 2001 From: Martin Krulis Date: Sun, 26 Nov 2017 22:04:04 +0100 Subject: [PATCH 2/5] Fix dashboard SIS panel to correctly fetch public groups instead of groups when accessing group names. --- .../SisSupervisorGroupsContainer.js | 13 +++++++++---- src/pages/Dashboard/Dashboard.js | 9 ++++++++- src/redux/modules/publicGroups.js | 1 + src/redux/selectors/publicGroups.js | 7 +++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js b/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js index 10d51c14a..748f8a1d9 100644 --- a/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js +++ b/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js @@ -9,7 +9,7 @@ import Button from '../../components/widgets/FlatButton'; import { LinkContainer } from 'react-router-bootstrap'; import Icon from 'react-fontawesome'; -import { fetchGroupsIfNeeded } from '../../redux/modules/groups'; +import { fetchPublicGroupsIfNeeded } from '../../redux/modules/publicGroups'; import { fetchSisStatusIfNeeded } from '../../redux/modules/sisStatus'; import { fetchSisSupervisedCourses, @@ -21,7 +21,7 @@ import { sisPossibleParentsSelector } from '../../redux/selectors/sisPossiblePar import { sisStateSelector } from '../../redux/selectors/sisStatus'; import { sisSupervisedCoursesSelector } from '../../redux/selectors/sisSupervisedCourses'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; -import { groupDataAccessorSelector } from '../../redux/selectors/groups'; +import { publicGroupDataAccessorSelector } from '../../redux/selectors/publicGroups'; import UsersNameContainer from '../UsersNameContainer'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; @@ -72,7 +72,12 @@ class SisSupervisorGroupsContainer extends Component { .then(res => res.value) .then(parents => parents.map(parent => - dispatch(fetchGroupsIfNeeded(...parent.parentGroupsIds)) + dispatch( + fetchPublicGroupsIfNeeded( + parent.id, + ...parent.parentGroupsIds + ) + ) ) ) ) @@ -341,7 +346,7 @@ export default injectIntl( currentUserId, sisCourses: sisSupervisedCoursesSelector(state), sisPossibleParents: sisPossibleParentsSelector(state), - groupsAccessor: groupDataAccessorSelector(state) + groupsAccessor: publicGroupDataAccessorSelector(state) }; }, dispatch => ({ diff --git a/src/pages/Dashboard/Dashboard.js b/src/pages/Dashboard/Dashboard.js index 0947c6db3..0f8585d0f 100644 --- a/src/pages/Dashboard/Dashboard.js +++ b/src/pages/Dashboard/Dashboard.js @@ -23,6 +23,7 @@ import { fetchGroupsIfNeeded, fetchInstanceGroupsIfNeeded } from '../../redux/modules/groups'; +import { fetchPublicGroupsIfNeeded } from '../../redux/modules/publicGroups'; import { getUser, @@ -86,7 +87,13 @@ class Dashboard extends Component { groups.map(({ value: group }) => Promise.all([ dispatch(fetchAssignmentsForGroup(group.id)), - dispatch(fetchGroupsStatsIfNeeded(group.id)) + dispatch(fetchGroupsStatsIfNeeded(group.id)), + dispatch( + fetchPublicGroupsIfNeeded( + group.id, + ...group.parentGroupsIds + ) + ) ]) ) ) diff --git a/src/redux/modules/publicGroups.js b/src/redux/modules/publicGroups.js index 9160c3409..843ebee92 100644 --- a/src/redux/modules/publicGroups.js +++ b/src/redux/modules/publicGroups.js @@ -11,6 +11,7 @@ const { actions, reduceActions } = factory({ * Actions */ +export const fetchPublicGroupsIfNeeded = actions.fetchIfNeeded; export const fetchPublicGroupIfNeeded = actions.fetchOneIfNeeded; export const fetchInstancePublicGroups = instanceId => diff --git a/src/redux/selectors/publicGroups.js b/src/redux/selectors/publicGroups.js index 40e4d617e..e83094ebd 100644 --- a/src/redux/selectors/publicGroups.js +++ b/src/redux/selectors/publicGroups.js @@ -1,11 +1,18 @@ import { createSelector } from 'reselect'; +import { Map } from 'immutable'; /** * Select groups part of the state */ +const EMPTY_MAP = Map(); export const publicGroupsSelectors = state => state.publicGroups.get('resources'); export const publicGroupSelector = id => createSelector(publicGroupsSelectors, groups => groups.get(id)); + +export const publicGroupDataAccessorSelector = createSelector( + publicGroupsSelectors, + groups => groupId => groups.getIn([groupId, 'data'], EMPTY_MAP) +); From ed64bc123e98f413c6cec8066d91f0ddf0bfdcd7 Mon Sep 17 00:00:00 2001 From: Martin Krulis Date: Mon, 27 Nov 2017 19:21:05 +0100 Subject: [PATCH 3/5] Fixing problem with submission details page (when solution has no submissions). --- .../AssignmentStatusIcon.js | 14 ++++++++++ .../NotEvaluatedSubmissionTableRow.js | 15 +++++++---- .../SubmissionsTable/SubmissionsTable.js | 6 ++++- .../SubmissionDetail/SubmissionDetail.js | 26 +++++++++++-------- .../SubmissionStatus/SubmissionStatus.js | 9 +++++-- src/locales/cs.json | 14 +++++----- src/locales/en.json | 14 +++++----- src/pages/Assignment/Assignment.js | 9 ++++--- 8 files changed, 72 insertions(+), 35 deletions(-) diff --git a/src/components/Assignments/Assignment/AssignmentStatusIcon/AssignmentStatusIcon.js b/src/components/Assignments/Assignment/AssignmentStatusIcon/AssignmentStatusIcon.js index d7352054b..e7af4606a 100644 --- a/src/components/Assignments/Assignment/AssignmentStatusIcon/AssignmentStatusIcon.js +++ b/src/components/Assignments/Assignment/AssignmentStatusIcon/AssignmentStatusIcon.js @@ -64,6 +64,20 @@ const AssignmentStatusIcon = ({ id, status, accepted = false }) => { /> ); + case 'missing-submission': + return ( + } + message={ + + } + /> + ); + default: return ( - + @@ -26,8 +30,8 @@ const NotEvaluatedSubmissionTableRow = ({ @@ -38,7 +42,8 @@ NotEvaluatedSubmissionTableRow.propTypes = { note: PropTypes.string.isRequired, solution: PropTypes.shape({ createdAt: PropTypes.number.isRequired - }).isRequired + }).isRequired, + lastSubmission: PropTypes.object }; export default NotEvaluatedSubmissionTableRow; diff --git a/src/components/Assignments/SubmissionsTable/SubmissionsTable.js b/src/components/Assignments/SubmissionsTable/SubmissionsTable.js index dce54fba7..5088a09d5 100644 --- a/src/components/Assignments/SubmissionsTable/SubmissionsTable.js +++ b/src/components/Assignments/SubmissionsTable/SubmissionsTable.js @@ -66,7 +66,9 @@ const SubmissionsTable = ({ const id = data.id; const link = SUBMISSION_DETAIL_URI_FACTORY(assignmentId, id); - switch (data.lastSubmission.evaluationStatus) { + switch (data.lastSubmission + ? data.lastSubmission.evaluationStatus + : null) { case 'done': return ( ); + case null: case 'work-in-progress': return ( ); case 'evaluation-failed': diff --git a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js index c11c426df..ff320f350 100644 --- a/src/components/Submissions/SubmissionDetail/SubmissionDetail.js +++ b/src/components/Submissions/SubmissionDetail/SubmissionDetail.js @@ -22,7 +22,9 @@ class SubmissionDetail extends Component { componentWillMount() { this.setState({ - activeSubmissionId: this.props.submission.lastSubmission.id + activeSubmissionId: this.props.submission.lastSubmission + ? this.props.submission.lastSubmission.id + : null }); } @@ -42,14 +44,16 @@ class SubmissionDetail extends Component { evaluations } = this.props; const { openFileId, activeSubmissionId } = this.state; - const { - submittedBy, - evaluation, - isCorrect, - evaluationStatus, - ...restSub - } = evaluations.toJS()[activeSubmissionId].data; + if (activeSubmissionId) { + var { + submittedBy, + evaluation, + isCorrect, + evaluationStatus, + ...restSub + } = evaluations.toJS()[activeSubmissionId].data; + } else evaluationStatus = 'missing-submission'; return (
@@ -118,7 +122,8 @@ class SubmissionDetail extends Component { } - {isSupervisor && + {activeSubmissionId && + isSupervisor && @@ -151,8 +156,7 @@ SubmissionDetail.propTypes = { submission: PropTypes.shape({ id: PropTypes.string.isRequired, note: PropTypes.string, - lastSubmission: PropTypes.shape({ id: PropTypes.string.isRequired }) - .isRequired, + lastSubmission: PropTypes.shape({ id: PropTypes.string.isRequired }), solution: PropTypes.shape({ createdAt: PropTypes.number.isRequired, userId: PropTypes.string.isRequired, diff --git a/src/components/Submissions/SubmissionStatus/SubmissionStatus.js b/src/components/Submissions/SubmissionStatus/SubmissionStatus.js index 7ab66a299..33dd9b136 100644 --- a/src/components/Submissions/SubmissionStatus/SubmissionStatus.js +++ b/src/components/Submissions/SubmissionStatus/SubmissionStatus.js @@ -102,8 +102,8 @@ const SubmissionStatus = ({ @@ -128,6 +128,11 @@ const SubmissionStatus = ({ id="app.submission.evaluation.status.systemFailiure" defaultMessage="Evaluation process had failed and your submission could not have been evaluated. Please submit the solution once more. If you keep receiving errors please contact the administrator of this project." />} + {evaluationStatus === 'missing-submission' && + } diff --git a/src/locales/cs.json b/src/locales/cs.json index 6018932df..5c4b12367 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -33,6 +33,8 @@ "app.assignemntStatusIcon.inProgress": "Řešení úlohy je právě vyhodnocováno.", "app.assignemntStatusIcon.none": "Zatím nebyla odevzdána žádná řešení.", "app.assignemntStatusIcon.ok": "Úloha byla úspěšně splněna.", + "app.assignemntStatusIcon.solutionMissingSubmission": "The solution was not submitted for evaluation probably due to an error. You may need to resubmit it.", + "app.assignment.alreadySubmitted": "Already submitted:", "app.assignment.canSubmit": "Můžete odevzdat více řešení:", "app.assignment.deadline": "Termín odevzdání", "app.assignment.editSettings": "Upravit nastavení zadané úlohy", @@ -276,7 +278,6 @@ "app.editGroupForm.successNew": "Create group", "app.editGroupForm.titleEdit": "Edit group", "app.editGroupForm.titleNew": "Create new group", - "app.editGroupForm.validation.description": "Please fill the description of the group.", "app.editGroupForm.validation.emptyName": "Please fill the name of the group.", "app.editGroupForm.validation.localizedText": "Please fill localized information.", "app.editGroupForm.validation.localizedText.locale": "Please select the language.", @@ -777,6 +778,7 @@ "app.resourceDependendBreadcrumbItem.loading": "Načítání ...", "app.resourceRenderer.loading": "Načítání ...", "app.resourceRenderer.loadingFailed": "Načítání se nezdařilo.", + "app.resubmitSolution.confirm": "Are you sure you want to resubmit this solution?", "app.resubmitSolution.resubmitAll": "Znovu vyhodnotit všechna řešení této zadané úlohy", "app.resubmitSolution.resubmitAllConfirm": "Opravdu chcete vyhodnotit všechna řešení všech studentů tohoto zadání úlohy? Tato akce může trvat velmi dlouho.", "app.resubmitSolution.resubmitDebug": "Resubmit (debug mode)", @@ -836,18 +838,18 @@ "app.submission.evaluation.bonusPoints": "Bonusové body:", "app.submission.evaluation.status.failed": "Řešení nesplňuje zadaná kritéria.", "app.submission.evaluation.status.isCorrect": "Řešení splňuje zadaná kritérii.", + "app.submission.evaluation.status.solutionMissingSubmission": "Solution was not submitted for evaluation. This was probably caused by an error in the assignment configuration.", "app.submission.evaluation.status.systemFailiure": "Vyhodnocení selhalo a Řešení nebylo ohodnoceno. Prosíme odešlete Řešení ještě jednou. Pokud problém přetrvává, obraťte se na správce systému.", "app.submission.evaluation.status.workInProgress": "Vyhodnocování řešení ještě nebylo dokončeno.", "app.submission.evaluation.title": "Vyhodnocení řešení", "app.submission.evaluation.title.testResults": "Výsledky testů", - "app.submission.isCorrect": "Řešení je správné:", + "app.submission.evaluationStatus": "Evaluation Status:", "app.submission.note": "Poznámka:", - "app.submission.originalSubmission": "Původní řešení:", + "app.submission.reevaluatedBy": "Reevaluated by:", "app.submission.submittedAt": "Odevzdáno:", - "app.submission.submittedBy": "Odevzdáno cvičícím:", "app.submission.title": "Řešení", - "app.submissionEvaluation.active": "Active", "app.submissionEvaluation.select": "Select", + "app.submissionEvaluation.selected": "Selected", "app.submissionEvaluation.title": "Other submissions of this solution", "app.submissionStatus.accepted": "Toto řešení bylo připnuto cvičícím.", "app.submissions.testResultsTable.exitCode": "Exit code", @@ -860,7 +862,6 @@ "app.submissions.testResultsTable.testName": "Název testu", "app.submissions.testResultsTable.timeExceeded": "Časový limit", "app.submissionsTable.failedLoading": "Nebylo možné načíst odevzdaná řešení.", - "app.submissionsTable.findOutResult": "Zjistit výsledky vyhodnocení", "app.submissionsTable.loading": "Načítají se odezvdaná řešení ...", "app.submissionsTable.noSolutionsFound": "Zatím nebyla odevzdána žádná řešení.", "app.submissionsTable.note": "Poznámka", @@ -869,6 +870,7 @@ "app.submissionsTable.solutionValidity": "Správnost řešení", "app.submissionsTable.submissionDate": "Datum odevzdání", "app.submissionsTable.submitNewSolution": "Odevzdat nové řešení", + "app.submissionsTable.title": "Submitted solutions", "app.submissionsTableContainer.title": "Odevzdaná řešení", "app.submistSolution.instructions": "Musíte připojit alespoň jeden soubor se zdrojovým kódem a počkat, než jsou všechny soubory nahrány na server. Pokud nastane problém při uploadu některých soborů, nahrajte je znovu nebo soubory odeberte. Jméno souboru NESMÍ OBSAHOVAT žádné nestandardní znaky (například v kódování UTF-8).", "app.submistSolution.submitFailed": "Odevzdané řešení bylo aktivně odmítnuto. Toto je obvykle způsobeno nahráním souborů se jménem obsahujícím nestandardní znaky nebo špatnou koncovkou. Pokud nemůžete odevzdat řešení bez zjevného důvodu, kontaktujte prosím svého cvičícího.", diff --git a/src/locales/en.json b/src/locales/en.json index a6612c58d..bdd77c3b5 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -33,6 +33,8 @@ "app.assignemntStatusIcon.inProgress": "Assignment solution is being evaluated.", "app.assignemntStatusIcon.none": "No solutions were submmitted so far.", "app.assignemntStatusIcon.ok": "Assignment is successfully completed.", + "app.assignemntStatusIcon.solutionMissingSubmission": "The solution was not submitted for evaluation probably due to an error. You may need to resubmit it.", + "app.assignment.alreadySubmitted": "Already submitted:", "app.assignment.canSubmit": "You can submit more solutions:", "app.assignment.deadline": "Deadline", "app.assignment.editSettings": "Edit assignment settings", @@ -276,7 +278,6 @@ "app.editGroupForm.successNew": "Create group", "app.editGroupForm.titleEdit": "Edit group", "app.editGroupForm.titleNew": "Create new group", - "app.editGroupForm.validation.description": "Please fill the description of the group.", "app.editGroupForm.validation.emptyName": "Please fill the name of the group.", "app.editGroupForm.validation.localizedText": "Please fill localized information.", "app.editGroupForm.validation.localizedText.locale": "Please select the language.", @@ -777,6 +778,7 @@ "app.resourceDependendBreadcrumbItem.loading": "Loading ...", "app.resourceRenderer.loading": "Loading ...", "app.resourceRenderer.loadingFailed": "Loading failed.", + "app.resubmitSolution.confirm": "Are you sure you want to resubmit this solution?", "app.resubmitSolution.resubmitAll": "Resubmit all solution for this assignment", "app.resubmitSolution.resubmitAllConfirm": "Are you sure you want to resubmit all solutions of all students for this assignment? This can take serious amount of time.", "app.resubmitSolution.resubmitDebug": "Resubmit (debug mode)", @@ -836,18 +838,18 @@ "app.submission.evaluation.bonusPoints": "Bonus points:", "app.submission.evaluation.status.failed": "The solution does not meet the defined criteria.", "app.submission.evaluation.status.isCorrect": "The solution is correct and meets all criteria.", + "app.submission.evaluation.status.solutionMissingSubmission": "Solution was not submitted for evaluation. This was probably caused by an error in the assignment configuration.", "app.submission.evaluation.status.systemFailiure": "Evaluation process failed and your submission could not have been evaluated. Please submit the solution once more. If you keep receiving errors please contact the administrator of this project.", "app.submission.evaluation.status.workInProgress": "The solution has not been evaluated yet.", "app.submission.evaluation.title": "The solution evaluation", "app.submission.evaluation.title.testResults": "Test results", - "app.submission.isCorrect": "The solution is correct:", + "app.submission.evaluationStatus": "Evaluation Status:", "app.submission.note": "Note:", - "app.submission.originalSubmission": "Resubmit of:", + "app.submission.reevaluatedBy": "Reevaluated by:", "app.submission.submittedAt": "Submitted at:", - "app.submission.submittedBy": "Submitted by:", "app.submission.title": "The solution", - "app.submissionEvaluation.active": "Active", "app.submissionEvaluation.select": "Select", + "app.submissionEvaluation.selected": "Selected", "app.submissionEvaluation.title": "Other submissions of this solution", "app.submissionStatus.accepted": "This solution was marked by one of the supervisors as accepted.", "app.submissions.testResultsTable.exitCode": "Exit code", @@ -860,7 +862,6 @@ "app.submissions.testResultsTable.testName": "Test name", "app.submissions.testResultsTable.timeExceeded": "Time limit", "app.submissionsTable.failedLoading": "Could not load this submission.", - "app.submissionsTable.findOutResult": "Find out results of evaluation", "app.submissionsTable.loading": "Loading submitted solutions ...", "app.submissionsTable.noSolutionsFound": "No solutions were submitted yet.", "app.submissionsTable.note": "Note", @@ -869,6 +870,7 @@ "app.submissionsTable.solutionValidity": "Solution validity", "app.submissionsTable.submissionDate": "Date of submission", "app.submissionsTable.submitNewSolution": "Submit new solution", + "app.submissionsTable.title": "Submitted solutions", "app.submissionsTableContainer.title": "Submitted solutions", "app.submistSolution.instructions": "You must attach at least one file with source code and wait, until all your files are uploaded to the server. If there is a problem uploading any of the files, check the name of the file. The name MUST NOT contain non-standard characters (like UTF-8 ones). Then try to upload it again.", "app.submistSolution.submitFailed": "Submission was rejected by the server. This usually means you have uploaded incorrect files - do your files have name with ASCII characters only and proper file type extensions? If you cannot submit the solution and there is no obvious reason, contact your supervisor to sort things out.", diff --git a/src/pages/Assignment/Assignment.js b/src/pages/Assignment/Assignment.js index cf3718d2d..c7280c19b 100644 --- a/src/pages/Assignment/Assignment.js +++ b/src/pages/Assignment/Assignment.js @@ -273,12 +273,13 @@ class Assignment extends Component { } + -
- {assignment.localizedTexts.length > 0 && - } -
+ {assignment.localizedTexts.length > 0 && +
+ +
} {(...runtimes) => From 920caadaeefb20c68d8ce34859d7592b8a2bc4ba Mon Sep 17 00:00:00 2001 From: Martin Krulis Date: Tue, 28 Nov 2017 12:42:20 +0100 Subject: [PATCH 4/5] Fixing bug on users overview page. Details (assignments) of groups that user cannot see (due to permissions) are no longer requested from API. --- src/pages/Pipeline/Pipeline.js | 14 +++++++++++--- src/pages/User/User.js | 23 ++++++++++++++--------- src/server.js | 9 ++++++++- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/pages/Pipeline/Pipeline.js b/src/pages/Pipeline/Pipeline.js index 97966f5ef..ada43e8f1 100644 --- a/src/pages/Pipeline/Pipeline.js +++ b/src/pages/Pipeline/Pipeline.js @@ -48,13 +48,21 @@ class Pipeline extends Component { this.setState({ forkId: Math.random().toString() }); } - static loadAsync = ({ pipelineId }, dispatch, setState) => + static loadAsync = ( + { pipelineId }, + dispatch, + userId, + isSuperadmin, + setState = null + ) => Promise.all([ dispatch(fetchPipelineIfNeeded(pipelineId)) .then(res => res.value) .then(pipeline => { const graph = createGraphFromNodes(pipeline.pipeline.boxes); - setState({ graph }); + if (setState) { + setState({ graph }); + } }), dispatch(fetchExercises()) ]); @@ -180,7 +188,7 @@ export default withLinks( }, (dispatch, { params: { pipelineId } }) => ({ loadAsync: setState => - Pipeline.loadAsync({ pipelineId }, dispatch, setState), + Pipeline.loadAsync({ pipelineId }, dispatch, null, false, setState), forkPipeline: (forkId, data) => dispatch(forkPipeline(pipelineId, forkId, data)) }) diff --git a/src/pages/User/User.js b/src/pages/User/User.js index 5c62d5e7b..d077f360b 100644 --- a/src/pages/User/User.js +++ b/src/pages/User/User.js @@ -43,13 +43,16 @@ import { getJsData } from '../../redux/helpers/resourceManager'; import withLinks from '../../hoc/withLinks'; class User extends Component { - componentWillMount = () => this.props.loadAsync(this.props.loggedInUserId); + componentWillMount = () => + this.props.loadAsync(this.props.loggedInUserId, this.props.isAdmin); componentWillReceiveProps = newProps => { if ( this.props.params.userId !== newProps.params.userId || - this.props.commonGroups.length > newProps.commonGroups.length + this.props.commonGroups.length > newProps.commonGroups.length || + this.props.loggedInUserId !== newProps.loggedInUserId || + this.props.isAdmin !== newProps.isAdmin ) { - newProps.loadAsync(newProps.loggedInUserId); + newProps.loadAsync(newProps.loggedInUserId, newProps.isAdmin); } }; @@ -58,7 +61,7 @@ class User extends Component { * to load the groups and necessary data for the intersection * of user's groups of which the current user is a supervisor. */ - static loadAsync = ({ userId }, dispatch, loggedInUserId) => + static loadAsync = ({ userId }, dispatch, loggedInUserId, isAdmin) => dispatch((dispatch, getState) => dispatch(fetchProfileIfNeeded(userId)) .then(() => dispatch(fetchUserIfNeeded(loggedInUserId))) @@ -73,9 +76,11 @@ class User extends Component { Promise.all( groups.value.map(group => { if ( - group.students.indexOf(userId) >= 0 || - group.supervisors.indexOf(loggedInUserId) >= 0 || - group.admins.indexOf(loggedInUserId) >= 0 + group.students.indexOf(userId) >= 0 && + (isAdmin || + userId === loggedInUserId || + group.supervisors.indexOf(loggedInUserId) >= 0 || + group.admins.indexOf(loggedInUserId) >= 0) ) { return Promise.all([ dispatch(fetchAssignmentsForGroup(group.id)), @@ -320,8 +325,8 @@ export default withLinks( }; }, (dispatch, { params }) => ({ - loadAsync: loggedInUserId => - User.loadAsync(params, dispatch, loggedInUserId) + loadAsync: (loggedInUserId, isAdmin) => + User.loadAsync(params, dispatch, loggedInUserId, isAdmin) }) )(User) ); diff --git a/src/server.js b/src/server.js index 521d6c2af..80ac1db42 100644 --- a/src/server.js +++ b/src/server.js @@ -20,6 +20,7 @@ import { syncHistoryWithStore } from 'react-router-redux'; import createHistory from 'react-router/lib/createMemoryHistory'; import { configureStore } from './redux/store'; import { loggedInUserIdSelector } from './redux/selectors/auth'; +import { isLoggedAsSuperAdmin } from './redux/selectors/users'; import createRoutes from './pages/routes'; addLocaleData([...cs]); @@ -97,6 +98,7 @@ app.get('*', (req, res) => { res.status(404).send('Not found'); } else { const userId = loggedInUserIdSelector(store.getState()); // try to get the user ID from the token (if any) + const isSuperadmin = isLoggedAsSuperAdmin(store.getState()); const loadAsync = renderProps.components .filter(component => component) .map(component => { @@ -108,7 +110,12 @@ app.get('*', (req, res) => { }) .filter(component => component.loadAsync) .map(component => - component.loadAsync(renderProps.params, store.dispatch, userId) + component.loadAsync( + renderProps.params, + store.dispatch, + userId, + isSuperadmin + ) ); Promise.all(loadAsync) From f33c5deef5c4e1eb69a01a486ab733cba0056503 Mon Sep 17 00:00:00 2001 From: Martin Krulis Date: Tue, 28 Nov 2017 15:46:02 +0100 Subject: [PATCH 5/5] Fixed problem with deleting exercises on exercise overview page. --- src/components/buttons/DeleteButton/DeleteButton.js | 6 +----- .../buttons/DeleteButton/DeletingFailedButton.js | 10 ++++------ .../DeleteExerciseButtonContainer.js | 12 +++++++----- src/locales/cs.json | 3 ++- src/locales/en.json | 3 ++- src/pages/Exercises/Exercises.js | 11 +++++++++-- 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/components/buttons/DeleteButton/DeleteButton.js b/src/components/buttons/DeleteButton/DeleteButton.js index fe0b74c2a..eefbb89a0 100644 --- a/src/components/buttons/DeleteButton/DeleteButton.js +++ b/src/components/buttons/DeleteButton/DeleteButton.js @@ -13,11 +13,7 @@ import { } from '../../../redux/helpers/resourceManager'; const DeleteButton = ({ resource, deleteResource, disabled, ...props }) => { - if (!resource) { - return null; - } - - if (isDeleted(resource)) { + if (!resource || isDeleted(resource)) { return ; } diff --git a/src/components/buttons/DeleteButton/DeletingFailedButton.js b/src/components/buttons/DeleteButton/DeletingFailedButton.js index b4a270dcc..4ceaac315 100644 --- a/src/components/buttons/DeleteButton/DeletingFailedButton.js +++ b/src/components/buttons/DeleteButton/DeletingFailedButton.js @@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl'; import Button from '../../widgets/FlatButton'; import { FailedIcon } from '../../icons'; -const DeletingFailedButton = ({ onClick, ...props }) => ( +const DeletingFailedButton = ({ onClick, ...props }) => -); + ; DeletingFailedButton.propTypes = { onClick: PropTypes.func diff --git a/src/containers/DeleteExerciseButtonContainer/DeleteExerciseButtonContainer.js b/src/containers/DeleteExerciseButtonContainer/DeleteExerciseButtonContainer.js index 4c3ae4f82..5a4c22caf 100644 --- a/src/containers/DeleteExerciseButtonContainer/DeleteExerciseButtonContainer.js +++ b/src/containers/DeleteExerciseButtonContainer/DeleteExerciseButtonContainer.js @@ -11,13 +11,12 @@ const DeleteExerciseButtonContainer = ({ deleteExercise, onDeleted, ...props -}) => ( +}) => -); + />; DeleteExerciseButtonContainer.propTypes = { id: PropTypes.string.isRequired, @@ -32,8 +31,11 @@ export default connect( }), (dispatch, { id, onDeleted }) => ({ deleteExercise: () => { - onDeleted && onDeleted(); - return dispatch(deleteExercise(id)); + const promise = dispatch(deleteExercise(id)); + if (onDeleted) { + promise.then(onDeleted); + } + return promise; } }) )(DeleteExerciseButtonContainer); diff --git a/src/locales/cs.json b/src/locales/cs.json index 5c4b12367..c5e9ed9fe 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -143,7 +143,8 @@ "app.deleteButton.confirm": "Opravdu toto chcete smazat? Tato operace nemůže být vrácena.", "app.deleteButton.delete": "Smazat", "app.deleteButton.deleted": "Smazáno.", - "app.deleteButton.deleting": "Mazání selhalo", + "app.deleteButton.deleting": "Mazání ...", + "app.deleteButton.deletingFailed": "Mazání selhalo", "app.editAssignment.deleteAssignment": "Smazat zadání úlohy", "app.editAssignment.deleteAssignmentWarning": "Smazání zadané úlohy odstraní všechna studentská řešní. Pro případné obnovení těchto dat prosím kontaktujte správce ReCodExu.", "app.editAssignment.description": "Změnit nastavení zadání úlohy včetně jejích limitů", diff --git a/src/locales/en.json b/src/locales/en.json index bdd77c3b5..4ae6aacfe 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -143,7 +143,8 @@ "app.deleteButton.confirm": "Are you sure you want to delete the resource? This cannot be undone.", "app.deleteButton.delete": "Delete", "app.deleteButton.deleted": "Deleted.", - "app.deleteButton.deleting": "Deleting failed", + "app.deleteButton.deleting": "Deleting ...", + "app.deleteButton.deletingFailed": "Deleting failed", "app.editAssignment.deleteAssignment": "Delete the assignment", "app.editAssignment.deleteAssignmentWarning": "Deleting an assignment will remove all the students submissions and you will have to contact the administrator of ReCodEx if you wanted to restore the assignment in the future.", "app.editAssignment.description": "Change assignment settings and limits", diff --git a/src/pages/Exercises/Exercises.js b/src/pages/Exercises/Exercises.js index 7f53e45fd..4eea247de 100644 --- a/src/pages/Exercises/Exercises.js +++ b/src/pages/Exercises/Exercises.js @@ -23,8 +23,8 @@ import { create as createExercise } from '../../redux/modules/exercises'; import { searchExercises } from '../../redux/modules/search'; +import { getSearchQuery } from '../../redux/selectors/search'; import ExercisesList from '../../components/Exercises/ExercisesList'; - import FetchManyResourceRenderer from '../../components/helpers/FetchManyResourceRenderer'; import withLinks from '../../hoc/withLinks'; @@ -48,6 +48,7 @@ class Exercises extends Component { render() { const { + query, isSuperAdmin, isAuthorOfExercise, fetchStatus, @@ -176,7 +177,11 @@ class Exercises extends Component { /> - + search(query)} + /> } />} /> @@ -189,6 +194,7 @@ class Exercises extends Component { Exercises.propTypes = { loadAsync: PropTypes.func.isRequired, + query: PropTypes.string, createExercise: PropTypes.func.isRequired, isSuperAdmin: PropTypes.bool.isRequired, isAuthorOfExercise: PropTypes.func.isRequired, @@ -203,6 +209,7 @@ export default withLinks( state => { const userId = loggedInUserIdSelector(state); return { + query: getSearchQuery('exercises-page')(state), fetchStatus: fetchManyStatus(state), isSuperAdmin: isLoggedAsSuperAdmin(state), isAuthorOfExercise: exerciseId =>