From fc0e87d60d1f161d18742269cce267de0f831d3b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Kruli=C5=A1?=
Date: Thu, 15 Feb 2018 16:22:26 +0100
Subject: [PATCH] Extra files in Simple Edit Form (#177)
* Adding extra files edit boxes to simple edit form.
* Implementing Smart Fill function for extra files.
* Fixing bug with non-loaded Supplementary and Attached files.
* Fixing bug in Smart fill procedure.
* Finalization of extra files inputs in simple config form.
* One last cleanup.
* Fixing the issue of reloading exercise when config or tests changed on simple edit page.
---
.../Exercises/FilesTable/FilesTable.js | 4 +-
.../EditExerciseSimpleConfigForm.css | 41 ++
.../EditExerciseSimpleConfigForm.js | 55 +-
.../EditExerciseSimpleConfigTest.js | 541 ++++++++++--------
.../forms/Fields/ExpandingInputFilesField.js | 2 +-
.../AttachmentFilesTableContainer.js | 7 +-
src/helpers/exerciseSimpleForm.js | 172 ++++--
src/locales/cs.json | 5 +-
src/locales/en.json | 5 +-
.../EditExerciseSimpleConfig.js | 24 +-
src/redux/modules/exerciseConfigs.js | 30 +-
src/redux/selectors/attachmentFiles.js | 29 +-
src/redux/selectors/supplementaryFiles.js | 26 +-
13 files changed, 578 insertions(+), 363 deletions(-)
diff --git a/src/components/Exercises/FilesTable/FilesTable.js b/src/components/Exercises/FilesTable/FilesTable.js
index 64477676c..0753a384f 100644
--- a/src/components/Exercises/FilesTable/FilesTable.js
+++ b/src/components/Exercises/FilesTable/FilesTable.js
@@ -57,8 +57,8 @@ const FilesTable = ({
}
-
- {(...attachments) =>
+
+ {attachments =>
{attachments.length > 0 &&
diff --git a/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.css b/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.css
index fd40d7680..24b1e74e6 100644
--- a/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.css
+++ b/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.css
@@ -1,8 +1,49 @@
.configRow {
padding-left: 15px;
padding-right: 15px;
+ border-bottom: 2px solid #ddd;
+}
+
+.configRow:nth-child(1) {
+ background-color: #f4fcf4!important;
}
.configRow:nth-child(odd) {
background-color: #F9F9F9;
}
+
+
+.compilation {
+ overflow: auto;
+}
+
+.compilation > table {
+ width: 100%;
+ margin-bottom: 10px;
+}
+
+.compilation > table > tbody > tr > td {
+ padding-right: 10px;
+ padding-left: 10px;
+ border-right: 1px solid #ddd;
+ min-width: 250px;
+}
+
+.compilation > table > tbody > tr > td:first-of-type {
+ padding-left: 0;
+}
+
+.compilation > table > tbody > tr > td:last-of-type {
+ border-right-width: 0;
+ padding-right: 0;
+}
+
+.compilation-open {
+ cursor: pointer;
+ font-size: 85%;
+}
+
+.compilation-close {
+ cursor: pointer;
+ margin-top: 0;
+}
diff --git a/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.js b/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.js
index 7d64449d0..9c9747aad 100644
--- a/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.js
+++ b/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.js
@@ -18,6 +18,15 @@ import { encodeTestId } from '../../../redux/modules/simpleLimits';
import { smartFillExerciseConfigForm } from '../../../redux/modules/exerciseConfigs';
import { exerciseConfigFormErrors } from '../../../redux/selectors/exerciseConfigs';
+const hasCompilationExtraFiles = testData =>
+ testData &&
+ testData.compilation &&
+ Object.keys(testData.compilation).reduce(
+ (res, envId) =>
+ res || testData.compilation[envId]['extra-files'].length > 0,
+ false
+ );
+
class EditExerciseSimpleConfigForm extends Component {
render() {
const {
@@ -31,6 +40,7 @@ class EditExerciseSimpleConfigForm extends Component {
formValues,
formErrors,
supplementaryFiles,
+ exercise,
exerciseTests,
smartFill,
intl: { locale }
@@ -116,20 +126,34 @@ class EditExerciseSimpleConfigForm extends Component {
{exerciseTests
.sort((a, b) => a.name.localeCompare(b.name, locale))
- .map((test, idx) =>
-
- )}
+ .map((test, idx) => {
+ const testData =
+ formValues &&
+ formValues.config &&
+ formValues.config[encodeTestId(test.id)];
+ return (
+
+ );
+ })}
}
@@ -151,6 +175,7 @@ EditExerciseSimpleConfigForm.propTypes = {
formValues: PropTypes.object,
formErrors: PropTypes.object,
supplementaryFiles: ImmutablePropTypes.map,
+ exercise: PropTypes.object,
exerciseTests: PropTypes.array,
smartFill: PropTypes.func.isRequired,
intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired
@@ -205,7 +230,7 @@ const validate = formData => {
};
export default connect(
- (state, { exercise }) => {
+ (state, { exercise, exerciseTests }) => {
return {
supplementaryFiles: getSupplementaryFilesForExercise(exercise.id)(state),
formValues: getFormValues(FORM_NAME)(state),
diff --git a/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigTest.js b/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigTest.js
index ccff65d55..da013375d 100644
--- a/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigTest.js
+++ b/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigTest.js
@@ -1,7 +1,7 @@
-import React from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Field, FieldArray } from 'redux-form';
-import { Row, Col } from 'react-bootstrap';
+import { Well, Row, Col } from 'react-bootstrap';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import Icon from 'react-fontawesome';
@@ -80,279 +80,342 @@ const validateCustomJudge = value =>
/>
: undefined;
-const EditExerciseSimpleConfigTest = ({
- supplementaryFiles,
- formValues,
- testName,
- test,
- testId,
- testKey,
- testIndex,
- testErrors,
- smartFill,
- intl
-}) => {
- const supplementaryFilesOptions = supplementaryFiles
- .sort((a, b) => a.name.localeCompare(b.name, intl.locale))
- .filter((item, pos, arr) => arr.indexOf(item) === pos) // WTF?
- .map(data => ({
- key: data.name,
- name: data.name
- }));
- return (
-
-
-
-
- {testName}
-
-
-
-
-
-
-
-
-
- }
- rightLabel={
+class EditExerciseSimpleConfigTest extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { compilationOpen: null }; // null means the user has not changed the state and
+ }
+
+ compilationOpen = ev => {
+ ev.preventDefault();
+ this.setState({ compilationOpen: true });
+ };
+
+ compilationClose = ev => {
+ ev.preventDefault();
+ this.setState({ compilationOpen: false });
+ };
+
+ render() {
+ const {
+ supplementaryFiles,
+ exercise,
+ hasCompilationExtraFiles,
+ useOutFile,
+ useCustomJudge,
+ testName,
+ test,
+ testErrors,
+ smartFill,
+ intl
+ } = this.props;
+ const supplementaryFilesOptions = supplementaryFiles
+ .sort((a, b) => a.name.localeCompare(b.name, intl.locale))
+ .filter((item, pos, arr) => arr.indexOf(item) === pos) // WTF?
+ .map(data => ({
+ key: data.name,
+ name: data.name
+ }));
+ return (
+
+
+
+
+ {testName}
+
+
+
+ {this.state.compilationOpen === true ||
+ (this.state.compilationOpen === null && hasCompilationExtraFiles)
+ ?
+
+
+
+
+
+
+
+
+ {exercise.runtimeEnvironments
+ .sort((a, b) =>
+ a.name.localeCompare(b.name, intl.locale)
+ )
+ .map(env =>
+
+
+ {env.name}
+
+
+ }
+ rightLabel={
+
+ }
+ />
+ |
+ )}
+
+
+
+
+
+ :
+
- }
- />
-
}
+
+
+
- }
- />
-
-
-
-
+
+ }
+ rightLabel={
+
+ }
+ />
+
+ }
/>
-
-
+
+
- }
- />
-
-
-
-
+
+ }
/>
-
-
+
+
- }
- />
- {formValues &&
- formValues.config &&
- formValues.config[testKey] &&
- (formValues.config[testKey].useOutFile === true ||
- formValues.config[testKey].useOutFile === 'true') &&
+
}
- />}
-
- }
- />
-
-
-
-
-
-
- }
- />
- {formValues &&
- formValues.config &&
- formValues.config[testKey] &&
- (formValues.config[testKey].useCustomJudge === true ||
- formValues.config[testKey].useCustomJudge === 'true')
- ?
- }
- />
- :
}
/>}
- {formValues &&
- formValues.config &&
- formValues.config[testKey] &&
- (formValues.config[testKey].useCustomJudge === true ||
- formValues.config[testKey].useCustomJudge === 'true') &&
-
}
- />}
- {testIndex === 0 &&
-
-
+
+
+
+
+
+
+ }
+ />
+ {useCustomJudge
+ ?
+ }
+ />
+ :
+ }
+ />}
+ {useCustomJudge &&
+
}
- >
-
}
+ {Boolean(smartFill) &&
+
+
+ }
>
- {' '}
-
-
-
-
}
-
-
-
- );
-};
+
+
+
}
+
+
+
+ );
+ }
+}
EditExerciseSimpleConfigTest.propTypes = {
+ exercise: PropTypes.object,
testName: PropTypes.string.isRequired,
test: PropTypes.string.isRequired,
- testId: PropTypes.number.isRequired,
- testKey: PropTypes.string.isRequired,
- testIndex: PropTypes.number.isRequired,
supplementaryFiles: PropTypes.array.isRequired,
exerciseTests: PropTypes.array,
- formValues: PropTypes.object,
+ hasCompilationExtraFiles: PropTypes.bool,
+ useOutFile: PropTypes.bool,
+ useCustomJudge: PropTypes.bool,
testErrors: PropTypes.object,
- smartFill: PropTypes.func.isRequired,
+ smartFill: PropTypes.func,
intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired
};
diff --git a/src/components/forms/Fields/ExpandingInputFilesField.js b/src/components/forms/Fields/ExpandingInputFilesField.js
index 711dd881e..260ed3c03 100644
--- a/src/components/forms/Fields/ExpandingInputFilesField.js
+++ b/src/components/forms/Fields/ExpandingInputFilesField.js
@@ -93,7 +93,7 @@ const ExpandingInputFilesField = ({
)}
}
-
+
{fields.length === 0 &&
{
- const getAttachmentFiles = createGetAttachmentFiles(
- exercise.attachmentFilesIds
- );
return {
- attachmentFiles: getAttachmentFiles(state)
+ attachmentFiles: getAttachmentFilesForExercise(exercise.id)(state)
};
},
(dispatch, { exercise }) => ({
diff --git a/src/helpers/exerciseSimpleForm.js b/src/helpers/exerciseSimpleForm.js
index 7f256cd31..98a9a692c 100644
--- a/src/helpers/exerciseSimpleForm.js
+++ b/src/helpers/exerciseSimpleForm.js
@@ -7,6 +7,14 @@ import {
encodeEnvironmentId
} from '../redux/modules/simpleLimits';
+// safe getter to traverse compex object/array structures
+const _safeGet = (obj, path) => {
+ path.forEach(step => {
+ obj = obj && (typeof step === 'function' ? obj.find(step) : obj[step]);
+ });
+ return obj;
+};
+
/*
* Tests and Score
*/
@@ -199,54 +207,96 @@ const getSimpleConfigSimpleVariable = (variables, testObj, variableName) => {
}
};
-// Prepare the initial form data for configuration form ...
-export const getSimpleConfigInitValues = defaultMemoize((config, tests) => {
- const confTests =
- tests && config[0] && config[0].tests ? config[0].tests : [];
+const postprocInputFiles = (files, names) =>
+ files.map((value, i) => ({
+ file: value,
+ name: names && names[i] ? names[i].trim() : ''
+ }));
- let res = {};
- for (let test of tests) {
- const testConf = confTests.find(t => t.name === test.id);
- let testObj = {
- name: test.id
+const getSimpleConfigCompilationVars = (testObj, config, environments) => {
+ const compilation = {};
+ for (const environment of environments) {
+ const pipelines =
+ _safeGet(config, [
+ c => c.name === environment.runtimeEnvironmentId,
+ 'tests',
+ t => t.name === testObj.name,
+ 'pipelines'
+ ]) || [];
+
+ const data = {
+ 'extra-files': [],
+ 'extra-file-names': []
};
+ pipelines.forEach(
+ pipeline =>
+ pipeline.variables &&
+ pipeline.variables.forEach(variable => {
+ if (data[variable.name] !== undefined) {
+ data[variable.name] = variable.value;
+ }
+ })
+ );
- const variables =
- testConf && testConf.pipelines
- ? testConf.pipelines.reduce(
- (acc, pipeline) => acc.concat(pipeline.variables),
- []
- )
- : [];
+ compilation[environment.runtimeEnvironmentId] = {
+ 'extra-files': postprocInputFiles(
+ data['extra-files'],
+ data['extra-file-names']
+ )
+ };
+ }
+ testObj.compilation = compilation;
+};
- for (const varName in EXEC_PIPELINE_VARS) {
- getSimpleConfigSimpleVariable(variables, testObj, varName);
- }
+// Prepare the initial form data for configuration form ...
+export const getSimpleConfigInitValues = defaultMemoize(
+ (config, tests, environments) => {
+ const confTests =
+ tests && config[0] && config[0].tests ? config[0].tests : [];
- // Postprocess input files and their names ...
- testObj.inputFiles = testObj.inputFiles.map((value, i) => ({
- file: value,
- name:
- testObj.actualInputs && testObj.actualInputs[i]
- ? testObj.actualInputs[i].trim()
- : ''
- }));
- delete testObj.actualInputs;
-
- // Additional updates after simple variables were set
- testObj.useOutFile = Boolean(testObj.outputFile);
- testObj.useCustomJudge = Boolean(testObj.customJudgeBinary);
- if (testObj.useCustomJudge) {
- testObj.judgeBinary = '';
+ let res = {};
+ for (let test of tests) {
+ const testConf = confTests.find(t => t.name === test.id);
+ let testObj = {
+ name: test.id
+ };
+
+ const variables =
+ testConf && testConf.pipelines
+ ? testConf.pipelines.reduce(
+ (acc, pipeline) => acc.concat(pipeline.variables),
+ []
+ )
+ : [];
+
+ for (const varName in EXEC_PIPELINE_VARS) {
+ getSimpleConfigSimpleVariable(variables, testObj, varName);
+ }
+
+ // Postprocess input files and their names ...
+ testObj.inputFiles = postprocInputFiles(
+ testObj.inputFiles,
+ testObj.actualInputs
+ );
+ delete testObj.actualInputs;
+
+ // Additional updates after simple variables were set
+ testObj.useOutFile = Boolean(testObj.outputFile);
+ testObj.useCustomJudge = Boolean(testObj.customJudgeBinary);
+ if (testObj.useCustomJudge) {
+ testObj.judgeBinary = '';
+ }
+
+ getSimpleConfigCompilationVars(testObj, config, environments);
+
+ res[encodeTestId(test.id)] = testObj;
}
- res[encodeTestId(test.id)] = testObj;
+ return {
+ config: res
+ };
}
-
- return {
- config: res
- };
-});
+);
// Prepare one variable to be sent in to the API
const transformConfigSimpleVariable = (variables, name, value) => {
@@ -261,11 +311,10 @@ const transformConfigSimpleVariable = (variables, name, value) => {
}
};
-const transformConfigInputFiles = test => {
+const transformConfigInputFiles = files => {
let inputFiles = [];
let actualInputs = [];
- const inFilesArr =
- test.inputFiles && Array.isArray(test.inputFiles) ? test.inputFiles : [];
+ const inFilesArr = files && Array.isArray(files) ? files : [];
for (const item of inFilesArr) {
inputFiles.push(item.file);
@@ -281,7 +330,7 @@ const transformConfigInputFiles = test => {
// Prepare variables for execution pipeline of one test in one environment
const transformConfigTestExecutionVariables = test => {
// Final updates ...
- let overrides = transformConfigInputFiles(test);
+ let overrides = transformConfigInputFiles(test.inputFiles);
if (!test.useCustomJudge) {
overrides.customJudgeBinary = '';
}
@@ -312,12 +361,24 @@ const mergeOriginalVariables = (newVars, origVars) => {
});
};
-// safe getter to traverse compex object/array structures
-const _safeGet = (obj, path) => {
- path.forEach(step => {
- obj = obj && (typeof step === 'function' ? obj.find(step) : obj[step]);
- });
- return obj;
+const mergeCompilationVariables = (origVars, testObj, envId) => {
+ const extraFiles = _safeGet(testObj, ['compilation', envId, 'extra-files']);
+ if (!extraFiles) {
+ return origVars;
+ }
+
+ const transformed = transformConfigInputFiles(extraFiles);
+ const vars = JSON.parse(JSON.stringify(EMPTY_COMPILATION_PIPELINE_VARS));
+ vars.find(v => v.name === 'extra-files').value = transformed.inputFiles;
+ vars.find(v => v.name === 'extra-file-names').value =
+ transformed.actualInputs;
+
+ return [
+ ...origVars.filter(
+ v => v.name !== 'extra-files' && v.name !== 'extra-file-names'
+ ),
+ ...vars
+ ];
};
export const transformConfigValues = (
@@ -362,16 +423,23 @@ export const transformConfigValues = (
const originalPipelines = _safeGet(originalConfig, [
config => config.name === envId,
'tests',
- test => test.name === testName,
+ t => t.name === testName,
'pipelines'
]);
+ // Prepare variables for compilation pipeline ...
const origCompilationVars =
_safeGet(originalPipelines, [
p => p.name === compilationPipeline.id,
'variables'
]) || EMPTY_COMPILATION_PIPELINE_VARS; // if pipeline variables are not present, prepare an empty set
+ const compilationVars = mergeCompilationVariables(
+ origCompilationVars,
+ test,
+ envId
+ );
+
// Prepare variables for execution pipeline ...
const testVars = transformConfigTestExecutionVariables(test);
const origExecutionVars = _safeGet(originalPipelines, [
@@ -387,7 +455,7 @@ export const transformConfigValues = (
pipelines: [
{
name: compilationPipeline.id,
- variables: origCompilationVars // copied from original config
+ variables: compilationVars
},
{
name: executionPipeline.id,
diff --git a/src/locales/cs.json b/src/locales/cs.json
index 4db9ca115..96426f384 100644
--- a/src/locales/cs.json
+++ b/src/locales/cs.json
@@ -312,10 +312,13 @@
"app.editExerciseSimpleConfigForm.validation.customJudge": "Prosím, zvolte spustitelný soubor sudího, nebo si vyberte z předpřipravené množiny vestavěných sudí.",
"app.editExerciseSimpleConfigForm.validation.expectedOutput": "Prosím zvolte soubor s očekávaným výstupem.",
"app.editExerciseSimpleConfigForm.validation.outputFile": "Prosím, vyberte jméno výstupního souboru.",
+ "app.editExerciseSimpleConfigTests.compilationTitle": "Compilation",
"app.editExerciseSimpleConfigTests.customJudgeBinary": "Spustitelný soubor sudího:",
"app.editExerciseSimpleConfigTests.executionArguments": "Spouštěcí argumenty:",
"app.editExerciseSimpleConfigTests.executionTitle": "Spuštění",
"app.editExerciseSimpleConfigTests.expectedOutput": "Očekávaný výstup:",
+ "app.editExerciseSimpleConfigTests.extraFilesActual": "Extra file:",
+ "app.editExerciseSimpleConfigTests.extraFilesRename": "Rename as:",
"app.editExerciseSimpleConfigTests.inputFilesActual": "Vstupní soubor:",
"app.editExerciseSimpleConfigTests.inputFilesRename": "Přejmenování:",
"app.editExerciseSimpleConfigTests.inputStdin": "Std. vstup:",
@@ -687,7 +690,7 @@
"app.filesTable.downloadArchive": "Download all",
"app.filesTable.empty": "There are no uploaded files yet.",
"app.filesTable.title": "Attached files",
- "app.footer.copyright": "Copyright © 2016-2017 ReCodEx. Všechna práva vyhrazena.",
+ "app.footer.copyright": "Copyright © 2016-2018 ReCodEx. Všechna práva vyhrazena.",
"app.footer.version": "Verze {version}",
"app.forkPipelineButton.success": "Show the forked pipeline",
"app.forkPipelineForm.failed": "Saving failed. Please try again later.",
diff --git a/src/locales/en.json b/src/locales/en.json
index e7078f95c..7387e4096 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -312,10 +312,13 @@
"app.editExerciseSimpleConfigForm.validation.customJudge": "Please, select the custom judge binary for this test or use one of the standard judges instead.",
"app.editExerciseSimpleConfigForm.validation.expectedOutput": "Please, fill in the expected output file.",
"app.editExerciseSimpleConfigForm.validation.outputFile": "Please, fill in the name of the output file.",
+ "app.editExerciseSimpleConfigTests.compilationTitle": "Compilation",
"app.editExerciseSimpleConfigTests.customJudgeBinary": "Custom judge executable:",
"app.editExerciseSimpleConfigTests.executionArguments": "Execution arguments:",
"app.editExerciseSimpleConfigTests.executionTitle": "Execution",
"app.editExerciseSimpleConfigTests.expectedOutput": "Expected output:",
+ "app.editExerciseSimpleConfigTests.extraFilesActual": "Extra file:",
+ "app.editExerciseSimpleConfigTests.extraFilesRename": "Rename as:",
"app.editExerciseSimpleConfigTests.inputFilesActual": "Input file:",
"app.editExerciseSimpleConfigTests.inputFilesRename": "Rename as:",
"app.editExerciseSimpleConfigTests.inputStdin": "Stdin:",
@@ -687,7 +690,7 @@
"app.filesTable.downloadArchive": "Download all",
"app.filesTable.empty": "There are no uploaded files yet.",
"app.filesTable.title": "Attached files",
- "app.footer.copyright": "Copyright © 2016-2017 ReCodEx. All rights reserved.",
+ "app.footer.copyright": "Copyright © 2016-2018 ReCodEx. All rights reserved.",
"app.footer.version": "Version {version}",
"app.forkPipelineButton.success": "Show the forked pipeline",
"app.forkPipelineForm.failed": "Saving failed. Please try again later.",
diff --git a/src/pages/EditExerciseSimpleConfig/EditExerciseSimpleConfig.js b/src/pages/EditExerciseSimpleConfig/EditExerciseSimpleConfig.js
index fae91255b..61b5b7ef4 100644
--- a/src/pages/EditExerciseSimpleConfig/EditExerciseSimpleConfig.js
+++ b/src/pages/EditExerciseSimpleConfig/EditExerciseSimpleConfig.js
@@ -105,28 +105,21 @@ class EditExerciseSimpleConfig extends Component {
]);
transformAndSendTestsValues = data => {
- const {
- editTests,
- editScoreConfig,
- fetchConfig,
- fetchEnvironmentSimpleLimits
- } = this.props;
-
+ const { editTests, editScoreConfig, reloadConfigAndLimits } = this.props;
const { tests, scoreConfig } = transformTestsValues(data);
-
return Promise.all([
editTests({ tests }),
editScoreConfig({ scoreConfig })
- ]).then(() => Promise.all([fetchConfig(), fetchEnvironmentSimpleLimits()]));
+ ]).then(reloadConfigAndLimits);
};
transformAndSendConfigValuesCreator = defaultMemoize(
(pipelines, environments, tests, config) => {
- const { setConfig } = this.props;
+ const { setConfig, reloadExercise } = this.props;
return data =>
setConfig(
transformConfigValues(data, pipelines, environments, tests, config)
- );
+ ).then(reloadExercise);
}
);
@@ -141,7 +134,7 @@ class EditExerciseSimpleConfig extends Component {
return data => {
const newEnvironments = transformEnvValues(data, environments);
const configData = transformConfigValues(
- getSimpleConfigInitValues(config, tests),
+ getSimpleConfigInitValues(config, tests, environments),
pipelines,
newEnvironments,
tests,
@@ -316,7 +309,7 @@ class EditExerciseSimpleConfig extends Component {
-
+
@@ -325,7 +318,8 @@ class EditExerciseSimpleConfig extends Component {
?
-
+
diff --git a/src/redux/modules/exerciseConfigs.js b/src/redux/modules/exerciseConfigs.js
index fd76e4f39..3756d12e8 100644
--- a/src/redux/modules/exerciseConfigs.js
+++ b/src/redux/modules/exerciseConfigs.js
@@ -233,6 +233,33 @@ const prepareTransformations = (template, firstTestId, tests, files) => {
.filter(({ file }) => file); // remove records which do not have apropriate file
}
+ // Compilation extra files ...
+ const compilation = {}; // matches for all environments
+ for (const envId in template.compilation) {
+ compilation[envId] = template.compilation[envId][
+ 'extra-files'
+ ].map(({ name, file }) => ({
+ name,
+ file,
+ matches: bestMatchFileNames(file, firstTestId, tests, files)
+ }));
+ }
+
+ transformations.compilation = testId => {
+ const transformed = {};
+ for (const envId in compilation) {
+ transformed[envId] = {
+ 'extra-files': compilation[envId]
+ .map(({ name, file, matches }) => ({
+ name,
+ file: (matches && matches.testFiles[testId]) || file
+ }))
+ .filter(({ file }) => file) // remove records which do not have apropriate file
+ };
+ }
+ return transformed;
+ };
+
return transformations;
};
@@ -274,7 +301,8 @@ export const smartFillExerciseConfigForm = (
'useCustomJudge',
'judgeBinary',
'customJudgeBinary',
- 'judgeArgs'
+ 'judgeArgs',
+ 'compilation'
]
.map(field => {
const value = transformations[field]
diff --git a/src/redux/selectors/attachmentFiles.js b/src/redux/selectors/attachmentFiles.js
index ac3e8652c..12c9a4d18 100644
--- a/src/redux/selectors/attachmentFiles.js
+++ b/src/redux/selectors/attachmentFiles.js
@@ -1,18 +1,23 @@
-import { createSelector } from 'reselect';
+import { createSelector, defaultMemoize } from 'reselect';
+import { Map } from 'immutable';
import { isReady } from '../helpers/resourceManager';
+import { getExercise } from './exercises';
-const getAttachmentFiles = state => state.attachmentFiles;
-export const getAttachmentFile = id =>
- createSelector(getAttachmentFiles, attachmentFiles =>
- attachmentFiles.get(id)
- );
+const EMPTY_MAP = Map();
export const attachmentFilesSelector = state =>
state.attachmentFiles.get('resources');
-export const createGetAttachmentFiles = ids =>
- createSelector(attachmentFilesSelector, attachmentFiles =>
- attachmentFiles
- .filter(isReady)
- .filter(file => ids.indexOf(file.getIn(['data', 'id'])) >= 0)
- );
+export const getAttachmentFilesForExercise = defaultMemoize(exerciseId =>
+ createSelector(
+ [getExercise(exerciseId), attachmentFilesSelector],
+ (exercise, attachmentFiles) => {
+ const ids = exercise && exercise.getIn(['data', 'attachmentFilesIds']);
+ return ids && attachmentFiles
+ ? attachmentFiles
+ .filter(isReady)
+ .filter(file => ids.indexOf(file.getIn(['data', 'id'])) >= 0)
+ : EMPTY_MAP;
+ }
+ )
+);
diff --git a/src/redux/selectors/supplementaryFiles.js b/src/redux/selectors/supplementaryFiles.js
index 7d84b8ba1..e52713ea6 100644
--- a/src/redux/selectors/supplementaryFiles.js
+++ b/src/redux/selectors/supplementaryFiles.js
@@ -1,35 +1,23 @@
import { createSelector, defaultMemoize } from 'reselect';
+import { Map } from 'immutable';
import { isReady } from '../helpers/resourceManager';
import { getExercise } from './exercises';
-const getSupplementaryFiles = state => state.supplementaryFiles;
-export const getSupplementaryFile = id =>
- createSelector(getSupplementaryFiles, supplementaryFiles =>
- supplementaryFiles.get(id)
- );
+const EMPTY_MAP = Map();
export const supplementaryFilesSelector = state =>
state.supplementaryFiles.get('resources');
-export const createGetSupplementaryFiles = ids =>
- createSelector(supplementaryFilesSelector, supplementaryFiles =>
- supplementaryFiles
- .filter(isReady)
- .filter(file => ids.indexOf(file.getIn(['data', 'id'])) >= 0)
- );
-
export const getSupplementaryFilesForExercise = defaultMemoize(exerciseId =>
createSelector(
[getExercise(exerciseId), supplementaryFilesSelector],
(exercise, supplementaryFiles) => {
const ids = exercise && exercise.getIn(['data', 'supplementaryFilesIds']);
- return (
- ids &&
- supplementaryFiles &&
- supplementaryFiles
- .filter(isReady)
- .filter(file => ids.indexOf(file.getIn(['data', 'id'])) >= 0)
- );
+ return ids && supplementaryFiles
+ ? supplementaryFiles
+ .filter(isReady)
+ .filter(file => ids.indexOf(file.getIn(['data', 'id'])) >= 0)
+ : EMPTY_MAP;
}
)
);