Skip to content

Commit

Permalink
Merge pull request #122 from ReCodEx/score_config
Browse files Browse the repository at this point in the history
WIP: Score config
  • Loading branch information
Neloop committed Oct 30, 2017
2 parents 66da76a + 49e7548 commit 91aefbc
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 36 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
32 changes: 2 additions & 30 deletions src/components/forms/EditAssignmentForm/EditAssignmentForm.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -117,24 +107,6 @@ const EditAssignmentForm = ({
component={LocalizedTextsFormField}
/>

<Field
name="scoreConfig"
component={SourceCodeField}
mode="yaml"
label={
<FormattedMessage
id="app.editAssignmentForm.scoreConfig"
defaultMessage="Score configuration:"
/>
}
/>
<HelpBlock>
<FormattedHTMLMessage
id="app.editAssignmentForm.moreAboutScoreConfig"
defaultMessage="Read more about <a href='https://github.com/ReCodEx/wiki/wiki/Assignments#scoring'>score configuration</a> syntax."
/>
</HelpBlock>

<Field
name="firstDeadline"
component={DatetimeField}
Expand Down
94 changes: 94 additions & 0 deletions src/components/forms/EditScoreConfigForm/EditScoreConfigForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from 'react';
import PropTypes from 'prop-types';
import { canUseDOM } from 'exenv';
import { reduxForm, Field } from 'redux-form';
import { FormattedMessage } from 'react-intl';
import { Alert } from 'react-bootstrap';

import { SourceCodeField } from '../Fields';
import SubmitButton from '../SubmitButton';

if (canUseDOM) {
require('codemirror/mode/yaml/yaml');
}

const EditScoreConfigForm = ({
anyTouched,
submitting,
handleSubmit,
hasFailed = false,
hasSucceeded = false,
invalid
}) =>
<div>
{hasFailed &&
<Alert bsStyle="danger">
<FormattedMessage
id="app.editScoreConfigForm.failed"
defaultMessage="Saving failed. Please try again later."
/>
</Alert>}

<Field
name="scoreConfig"
component={SourceCodeField}
mode="yaml"
label={
<FormattedMessage
id="app.editScoreConfigForm.scoreConfig"
defaultMessage="Score configuration:"
/>
}
/>

<div className="text-center">
<SubmitButton
id="editEnvironmentConfig"
invalid={invalid}
submitting={submitting}
hasSucceeded={hasSucceeded}
dirty={anyTouched}
hasFailed={hasFailed}
handleSubmit={handleSubmit}
messages={{
submit: (
<FormattedMessage
id="app.editScoreConfigForm.submit"
defaultMessage="Change configuration"
/>
),
submitting: (
<FormattedMessage
id="app.editScoreConfigForm.submitting"
defaultMessage="Saving configuration ..."
/>
),
success: (
<FormattedMessage
id="app.editScoreConfigForm.success"
defaultMessage="Configuration was changed."
/>
)
}}
/>
</div>
</div>;

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);
1 change: 1 addition & 0 deletions src/components/forms/EditScoreConfigForm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default from './EditScoreConfigForm';
22 changes: 21 additions & 1 deletion src/pages/Assignment/Assignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) &&
<Row>
<Col sm={12}>
<Alert bsStyle="warning">
Expand Down Expand Up @@ -237,6 +241,22 @@ class Assignment extends Component {
defaultMessage="Limits"
/>
</li>}
{!assignment.exerciseSynchronizationInfo.scoreConfig
.upToDate &&
<li>
<FormattedMessage
id="app.assignment.syncScoreConfig"
defaultMessage="Score configuration"
/>
</li>}
{!assignment.exerciseSynchronizationInfo
.scoreCalculator.upToDate &&
<li>
<FormattedMessage
id="app.assignment.syncScoreCalculator"
defaultMessage="Score calculator"
/>
</li>}
</ul>
</div>
<p>
Expand Down
42 changes: 38 additions & 4 deletions src/pages/EditExerciseConfig/EditExerciseConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,6 +30,10 @@ import {
fetchExerciseConfigIfNeeded,
setExerciseConfig
} from '../../redux/modules/exerciseConfigs';
import {
fetchScoreConfigIfNeeded,
setScoreConfig
} from '../../redux/modules/exerciseScoreConfig';
import {
fetchExerciseEnvironmentConfigIfNeeded,
setExerciseEnvironmentConfig
Expand All @@ -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';
Expand Down Expand Up @@ -69,7 +75,8 @@ class EditExerciseConfig extends Component {
dispatch(fetchExerciseConfigIfNeeded(exerciseId)),
dispatch(fetchRuntimeEnvironments()),
dispatch(fetchExerciseEnvironmentConfigIfNeeded(exerciseId)),
dispatch(fetchPipelines())
dispatch(fetchPipelines()),
dispatch(fetchScoreConfigIfNeeded(exerciseId))
]);

render() {
Expand All @@ -83,12 +90,14 @@ class EditExerciseConfig extends Component {
environmentFormValues,
exerciseConfig,
exerciseEnvironmentConfig,
exerciseScoreConfig,
editEnvironmentSimpleLimits,
pipelines,
limits,
setHorizontally,
setVertically,
setAll
setAll,
editScoreConfig
} = this.props;

return (
Expand Down Expand Up @@ -157,6 +166,27 @@ class EditExerciseConfig extends Component {
</Box>
</Col>
</Row>
<Row>
<Col lg={6}>
<Box
title={
<FormattedMessage
id="app.editExercise.editScoreConfig"
defaultMessage="Edit score configurations"
/>
}
unlimitedHeight
>
<ResourceRenderer resource={exerciseScoreConfig}>
{config =>
<EditScoreConfigForm
onSubmit={editScoreConfig}
initialValues={{ scoreConfig: config }}
/>}
</ResourceRenderer>
</Box>
</Col>
</Row>
<br />
<Row>
<Col lg={12}>
Expand Down Expand Up @@ -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(
Expand All @@ -224,6 +256,7 @@ export default withLinks(
exerciseEnvironmentConfig: exerciseEnvironmentConfigSelector(
exerciseId
)(state),
exerciseScoreConfig: exerciseScoreConfigSelector(exerciseId)(state),
pipelines: pipelinesSelector(state),
limits: runtimeEnvironmentId =>
simpleLimitsSelector(exerciseId, runtimeEnvironmentId)(state)
Expand All @@ -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)
);
19 changes: 19 additions & 0 deletions src/redux/modules/exerciseScoreConfig.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions src/redux/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -56,6 +57,7 @@ const createRecodexReducers = token => ({
evaluationProgress,
exerciseConfigs,
exerciseEnvironmentConfigs,
exerciseScoreConfig,
exercises,
pipelines,
files,
Expand Down
14 changes: 14 additions & 0 deletions src/redux/selectors/exerciseScoreConfig.js
Original file line number Diff line number Diff line change
@@ -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)
);

0 comments on commit 91aefbc

Please sign in to comment.