diff --git a/README.md b/README.md index d7aa9f1cb..4d430206e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ NODE_ENV=development API_BASE=http://localhost:4000/v1 PORT=8080 WEBPACK_DEV_SERVER_PORT=8081 +TITLE=ReCodEx +ALLOW_NORMAL_REGISTRATION=true +ALLOW_LDAP_REGISTRATION=false +ALLOW_CAS_REGISTRATION=true ``` ## Run Dev @@ -37,7 +41,7 @@ yarn build yarn start ``` -Consider using [PM2](http://pm2.keymetrics.io/) or similar tool to run the `yarn start` command to prevent crashes of the web service. +Consider using [PM2](http://pm2.keymetrics.io/) or similar tool to run the `yarn start` command to prevent crashes of the web service. It is wise to use watch mode of PM2 in `prod/` subdirectory and deploy the app using `yarn deploy` command. ## License diff --git a/src/components/forms/EditAssignmentForm/EditAssignmentForm.js b/src/components/forms/EditAssignmentForm/EditAssignmentForm.js index 4182a261f..044870249 100644 --- a/src/components/forms/EditAssignmentForm/EditAssignmentForm.js +++ b/src/components/forms/EditAssignmentForm/EditAssignmentForm.js @@ -1,27 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { canUseDOM } from 'exenv'; import { reduxForm, Field, FieldArray, touch } from 'redux-form'; -import { FormattedHTMLMessage, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import { Alert, HelpBlock } from 'react-bootstrap'; import isNumeric from 'validator/lib/isNumeric'; import FormBox from '../../widgets/FormBox'; -import { - DatetimeField, - TextField, - CheckboxField, - SourceCodeField -} from '../Fields'; +import { DatetimeField, TextField, CheckboxField } from '../Fields'; import LocalizedTextsFormField from '../LocalizedTextsFormField'; import SubmitButton from '../SubmitButton'; import { validateAssignment } from '../../../redux/modules/assignments'; -if (canUseDOM) { - require('codemirror/mode/yaml/yaml'); -} - const EditAssignmentForm = ({ initialValues: assignment, anyTouched, @@ -117,24 +107,6 @@ const EditAssignmentForm = ({ component={LocalizedTextsFormField} /> - - } - /> - - - - +
+ {hasFailed && + + + } + + + } + /> + +
+ + ), + submitting: ( + + ), + success: ( + + ) + }} + /> +
+
; + +EditScoreConfigForm.propTypes = { + handleSubmit: PropTypes.func.isRequired, + anyTouched: PropTypes.bool, + submitting: PropTypes.bool, + hasFailed: PropTypes.bool, + hasSucceeded: PropTypes.bool, + invalid: PropTypes.bool +}; + +const validate = () => { + const errors = {}; + return errors; +}; + +export default reduxForm({ + form: 'editScoreConfig', + validate +})(EditScoreConfigForm); diff --git a/src/components/forms/EditScoreConfigForm/index.js b/src/components/forms/EditScoreConfigForm/index.js new file mode 100644 index 000000000..6373871de --- /dev/null +++ b/src/components/forms/EditScoreConfigForm/index.js @@ -0,0 +1 @@ +export default from './EditScoreConfigForm'; diff --git a/src/pages/Assignment/Assignment.js b/src/pages/Assignment/Assignment.js index 02e0f5d89..28a10d660 100644 --- a/src/pages/Assignment/Assignment.js +++ b/src/pages/Assignment/Assignment.js @@ -181,7 +181,11 @@ class Assignment extends Component { .upToDate || !assignment.exerciseSynchronizationInfo.localizedTexts .upToDate || - !assignment.exerciseSynchronizationInfo.limits.upToDate) && + !assignment.exerciseSynchronizationInfo.limits.upToDate || + !assignment.exerciseSynchronizationInfo.scoreConfig + .upToDate || + !assignment.exerciseSynchronizationInfo.scoreCalculator + .upToDate) && @@ -237,6 +241,22 @@ class Assignment extends Component { defaultMessage="Limits" /> } + {!assignment.exerciseSynchronizationInfo.scoreConfig + .upToDate && +
  • + +
  • } + {!assignment.exerciseSynchronizationInfo + .scoreCalculator.upToDate && +
  • + +
  • }

    diff --git a/src/pages/EditExerciseConfig/EditExerciseConfig.js b/src/pages/EditExerciseConfig/EditExerciseConfig.js index 261186d96..8ee77fd4e 100644 --- a/src/pages/EditExerciseConfig/EditExerciseConfig.js +++ b/src/pages/EditExerciseConfig/EditExerciseConfig.js @@ -12,6 +12,7 @@ import ResourceRenderer from '../../components/helpers/ResourceRenderer'; import EditExerciseConfigForm from '../../components/forms/EditExerciseConfigForm/EditExerciseConfigForm'; import EditEnvironmentConfigForm from '../../components/forms/EditEnvironmentConfigForm'; +import EditScoreConfigForm from '../../components/forms/EditScoreConfigForm'; import EditSimpleLimitsBox from '../../components/Exercises/EditSimpleLimitsBox'; import SupplementaryFilesTableContainer from '../../containers/SupplementaryFilesTableContainer'; @@ -29,6 +30,10 @@ import { fetchExerciseConfigIfNeeded, setExerciseConfig } from '../../redux/modules/exerciseConfigs'; +import { + fetchScoreConfigIfNeeded, + setScoreConfig +} from '../../redux/modules/exerciseScoreConfig'; import { fetchExerciseEnvironmentConfigIfNeeded, setExerciseEnvironmentConfig @@ -37,6 +42,7 @@ import { getExercise } from '../../redux/selectors/exercises'; import { pipelinesSelector } from '../../redux/selectors/pipelines'; import { exerciseConfigSelector } from '../../redux/selectors/exerciseConfigs'; import { exerciseEnvironmentConfigSelector } from '../../redux/selectors/exerciseEnvironmentConfigs'; +import { exerciseScoreConfigSelector } from '../../redux/selectors/exerciseScoreConfig'; import { loggedInUserIdSelector } from '../../redux/selectors/auth'; import { fetchRuntimeEnvironments } from '../../redux/modules/runtimeEnvironments'; import { runtimeEnvironmentsSelector } from '../../redux/selectors/runtimeEnvironments'; @@ -69,7 +75,8 @@ class EditExerciseConfig extends Component { dispatch(fetchExerciseConfigIfNeeded(exerciseId)), dispatch(fetchRuntimeEnvironments()), dispatch(fetchExerciseEnvironmentConfigIfNeeded(exerciseId)), - dispatch(fetchPipelines()) + dispatch(fetchPipelines()), + dispatch(fetchScoreConfigIfNeeded(exerciseId)) ]); render() { @@ -83,12 +90,14 @@ class EditExerciseConfig extends Component { environmentFormValues, exerciseConfig, exerciseEnvironmentConfig, + exerciseScoreConfig, editEnvironmentSimpleLimits, pipelines, limits, setHorizontally, setVertically, - setAll + setAll, + editScoreConfig } = this.props; return ( @@ -157,6 +166,27 @@ class EditExerciseConfig extends Component { + + + + } + unlimitedHeight + > + + {config => + } + + + +
    @@ -203,13 +233,15 @@ EditExerciseConfig.propTypes = { environmentFormValues: PropTypes.object, exerciseConfig: PropTypes.object, exerciseEnvironmentConfig: PropTypes.object, + exerciseScoreConfig: PropTypes.object, editEnvironmentSimpleLimits: PropTypes.func.isRequired, pipelines: ImmutablePropTypes.map, links: PropTypes.object.isRequired, limits: PropTypes.func.isRequired, setHorizontally: PropTypes.func.isRequired, setVertically: PropTypes.func.isRequired, - setAll: PropTypes.func.isRequired + setAll: PropTypes.func.isRequired, + editScoreConfig: PropTypes.func.isRequired }; export default withLinks( @@ -224,6 +256,7 @@ export default withLinks( exerciseEnvironmentConfig: exerciseEnvironmentConfigSelector( exerciseId )(state), + exerciseScoreConfig: exerciseScoreConfigSelector(exerciseId)(state), pipelines: pipelinesSelector(state), limits: runtimeEnvironmentId => simpleLimitsSelector(exerciseId, runtimeEnvironmentId)(state) @@ -247,7 +280,8 @@ export default withLinks( setVertically(formName, exerciseId, runtimeEnvironmentId, testName) ), setAll: (formName, runtimeEnvironmentId) => testName => () => - dispatch(setAll(formName, exerciseId, runtimeEnvironmentId, testName)) + dispatch(setAll(formName, exerciseId, runtimeEnvironmentId, testName)), + editScoreConfig: data => dispatch(setScoreConfig(exerciseId, data)) }) )(EditExerciseConfig) ); diff --git a/src/redux/modules/exerciseScoreConfig.js b/src/redux/modules/exerciseScoreConfig.js new file mode 100644 index 000000000..204230209 --- /dev/null +++ b/src/redux/modules/exerciseScoreConfig.js @@ -0,0 +1,19 @@ +import { handleActions } from 'redux-actions'; +import factory, { initialState } from '../helpers/resourceManager'; + +/** + * Create actions & reducer + */ + +const resourceName = 'exerciseScoreConfig'; +const { actions, reduceActions } = factory({ + resourceName, + apiEndpointFactory: id => `/exercises/${id}/score-config` +}); + +export const fetchScoreConfig = actions.fetchResource; +export const fetchScoreConfigIfNeeded = actions.fetchOneIfNeeded; +export const setScoreConfig = actions.updateResource; + +const reducer = handleActions(reduceActions, initialState); +export default reducer; diff --git a/src/redux/reducer.js b/src/redux/reducer.js index 8f83dd6c5..e5966d26b 100644 --- a/src/redux/reducer.js +++ b/src/redux/reducer.js @@ -11,6 +11,7 @@ import emailVerification from './modules/emailVerification'; import evaluationProgress from './modules/evaluationProgress'; import exerciseConfigs from './modules/exerciseConfigs'; import exerciseEnvironmentConfigs from './modules/exerciseEnvironmentConfigs'; +import exerciseScoreConfig from './modules/exerciseScoreConfig'; import exercises from './modules/exercises'; import pipelines from './modules/pipelines'; import files from './modules/files'; @@ -56,6 +57,7 @@ const createRecodexReducers = token => ({ evaluationProgress, exerciseConfigs, exerciseEnvironmentConfigs, + exerciseScoreConfig, exercises, pipelines, files, diff --git a/src/redux/selectors/exerciseScoreConfig.js b/src/redux/selectors/exerciseScoreConfig.js new file mode 100644 index 000000000..6a3881eaa --- /dev/null +++ b/src/redux/selectors/exerciseScoreConfig.js @@ -0,0 +1,14 @@ +import { createSelector } from 'reselect'; + +const getExerciseScoreConfig = state => state.exerciseScoreConfig; +const getResources = exerciseScoreConfig => + exerciseScoreConfig.get('resources'); + +export const exerciseScoreConfigsSelector = createSelector( + getExerciseScoreConfig, + getResources +); +export const exerciseScoreConfigSelector = exerciseId => + createSelector(exerciseScoreConfigsSelector, configs => + configs.get(exerciseId) + );