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 && + } - > - -
-
} - - -

- ); -}; + + + } + + + + ); + } +} 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; } ) );