@@ -57,8 +54,8 @@ const TypeEditor = ({
{' '}
type. This name will be used to identify this type in the
codebook, and in your data exports.
- { entity === 'node' && ' Some examples might be "Person", "Place", or "Organization".' }
- { entity === 'edge' && ' Some examples might be "Friends" or "Works With".' }
+ {entity === 'node' && ' Some examples might be "Person", "Place", or "Organization".'}
+ {entity === 'edge' && ' Some examples might be "Friends" or "Works With".'}
- When selecting an existing variable, changes you make to the input control or
- validation options will also change other uses of this variable.
-
-
+
+
+ When selecting an existing variable, changes you make to the input control or
+ validation options will also change other uses of this variable.
+
+
)}
- { isNewVariable && variableType
- && (
-
-
- The selected input control will cause this variable to be defined as
- type
- {' '}
- {variableType}
- . Once set, this cannot be changed
- (although you may change the input control within this type).
-
- A pre-existing variable is currently selected. You cannot change a variable
- type after it has been created, so only
+ The selected input control will cause this variable to be defined as
+ type
{' '}
{variableType}
- {' '}
- compatible
- input controls can be selected above. If you would like to use a different
- input control type, you will need to create a new variable.
+ . Once set, this cannot be changed
+ (although you may change the input control within this type).
+ A pre-existing variable is currently selected. You cannot change a variable
+ type after it has been created, so only
+ {' '}
+ {variableType}
+ {' '}
+ compatible
+ input controls can be selected above. If you would like to use a different
+ input control type, you will need to create a new variable.
+
+
+
)}
- { variableType
- && (
-
-
Preview
-
-
- )}
+ {variableType
+ && (
+
+
Preview
+
+
+ )}
- { isOrdinalOrCategoricalType(variableType)
+ {isOrdinalOrCategoricalType(variableType)
&& (
-
- The input type you selected indicates that this is a categorical or ordinal variable.
- Next, please create a minimum of two possible values for the participant to choose
- between.
-
- )}
- >
-
-
-
-
+
+ The input type you selected indicates that this is a categorical or ordinal variable.
+ Next, please create a minimum of two possible values for the participant to choose
+ between.
+
+ )}
+ >
+
+
+
+
)}
- { isBooleanWithOptions(component)
+ {isBooleanWithOptions(component)
&& (
@@ -172,22 +172,23 @@ const PromptFields = ({
)}
- { isVariableTypeWithParameters(variableType)
+ {isVariableTypeWithParameters(variableType)
&& (
-
-
-
-
-
+
+
+
+
+
)}
diff --git a/src/components/sections/Form/withFormHandlers.js b/src/components/sections/Form/withFormHandlers.js
index ace2ba101..895d7fbb9 100644
--- a/src/components/sections/Form/withFormHandlers.js
+++ b/src/components/sections/Form/withFormHandlers.js
@@ -4,6 +4,7 @@ import { change, SubmissionError } from 'redux-form';
import { getTypeForComponent } from '@app/config/variables';
import { actionCreators as codebookActions } from '@modules/protocol/codebook';
import { getCodebookProperties } from './helpers';
+import { makeGetVariable } from '../../../selectors/codebook';
const formHandlers = withHandlers({
handleChangeFields: ({
@@ -13,10 +14,12 @@ const formHandlers = withHandlers({
entity,
changeForm,
form,
+ getVariable,
}) => async (values) => {
const {
variable, component, _createNewVariable, ...rest
} = values;
+
const variableType = getTypeForComponent(component);
// prune properties that are not part of the codebook:
const codebookProperties = getCodebookProperties(rest);
@@ -31,9 +34,23 @@ const formHandlers = withHandlers({
changeForm(form, '_modified', new Date().getTime());
if (!_createNewVariable) {
+ const current = getVariable(variable);
+
+ if (!current) {
+ throw new SubmissionError({
+ _error: 'Variable not found',
+ });
+ }
+
+ const baseProps = {
+ component: current.component,
+ type: current.type,
+ name: current.name,
+ };
+
// Merge is set to false below so that properties that were removed, such
// as 'options: []' and 'parameters: {}' get deleted.
- await updateVariable(entity, type, variable, configuration, true);
+ await updateVariable(entity, type, variable, { ...baseProps, ...configuration }, false);
return {
variable,
@@ -63,7 +80,11 @@ const mapDispatchToProps = {
createVariable: codebookActions.createVariable,
};
-const formState = connect(null, mapDispatchToProps);
+const mapStateToProps = (state) => ({
+ getVariable: (uuid) => makeGetVariable(uuid)(state),
+});
+
+const formState = connect(mapStateToProps, mapDispatchToProps);
export {
formState,
diff --git a/src/components/sections/ValidationSection.js b/src/components/sections/ValidationSection.js
index 0891a39d6..631087a50 100644
--- a/src/components/sections/ValidationSection.js
+++ b/src/components/sections/ValidationSection.js
@@ -10,12 +10,16 @@ import { getFieldId } from '../../utils/issues';
const ValidationSection = ({
disabled,
form,
+ entity,
variableType,
existingVariables,
}) => {
const dispatch = useDispatch();
const getFormValue = formValueSelector(form);
- const hasValidation = useSelector((state) => getFormValue(state, 'validation'));
+ const hasValidation = useSelector((state) => {
+ const validation = getFormValue(state, 'validation');
+ return validation && Object.keys(pickBy(validation)).length > 0;
+ });
const handleToggleValidation = (nextState) => {
if (nextState === false) {
@@ -45,6 +49,7 @@ const ValidationSection = ({
form={form}
name="validation"
variableType={variableType}
+ entity={entity}
existingVariables={existingVariablesForType}
/>
@@ -61,6 +66,7 @@ ValidationSection.propTypes = {
name: PropTypes.string.isRequired,
}),
).isRequired,
+ entity: PropTypes.string.isRequired,
};
ValidationSection.defaultProps = {
diff --git a/src/ducks/modules/protocol/codebook.js b/src/ducks/modules/protocol/codebook.js
index 9e52dee54..edd90e4e3 100644
--- a/src/ducks/modules/protocol/codebook.js
+++ b/src/ducks/modules/protocol/codebook.js
@@ -3,7 +3,7 @@ import {
omit, get, has, isEmpty, find,
} from 'lodash';
import prune from '@app/utils/prune';
-import { getAllVariableUUIDs, getVariablesForSubject } from '../../../selectors/codebook';
+import { getAllVariableUUIDsByEntity, getVariablesForSubject } from '../../../selectors/codebook';
import { makeGetUsageForType } from '../../../selectors/usage';
import { makeGetIsUsed } from '../../../selectors/codebook/isUsed';
import { getNextCategoryColor } from './utils/helpers';
@@ -64,15 +64,15 @@ const createVariable = (entity, type, variable, configuration) => prune({
entity,
variable,
},
- configuration,
+ configuration: prune(configuration),
});
-const updateVariable = (variable, configuration, merge = false) => prune({
+const updateVariable = (variable, configuration, merge = false) => ({
type: UPDATE_VARIABLE,
meta: {
variable,
},
- configuration,
+ configuration: prune(configuration),
merge,
});
@@ -301,7 +301,7 @@ export default function reducer(state = initialState, action = {}) {
action.merge,
);
case UPDATE_VARIABLE: {
- const variables = getAllVariableUUIDs(state);
+ const variables = getAllVariableUUIDsByEntity(state);
const { entity, entityType } = find(variables, ['uuid', action.meta.variable]);
return getStateWithUpdatedVariable(
diff --git a/src/protocol-validation b/src/protocol-validation
index d70014442..6f5967ffa 160000
--- a/src/protocol-validation
+++ b/src/protocol-validation
@@ -1 +1 @@
-Subproject commit d700144422fef1a0a1052a88ae207cca8d5ff364
+Subproject commit 6f5967ffa8ce8b5d3fa411f4c9553565403dfd24
diff --git a/src/selectors/codebook/index.js b/src/selectors/codebook/index.js
index 589aba779..dfc5adfb0 100644
--- a/src/selectors/codebook/index.js
+++ b/src/selectors/codebook/index.js
@@ -1,5 +1,3 @@
-/* eslint-disable import/prefer-default-export */
-
import { get, find } from 'lodash';
import { getCodebook } from '../protocol';
import { asOptions } from '../utils';
@@ -25,8 +23,29 @@ const getType = (state, subject) => {
*/
const getVariablesForSubject = (state, subject) => get(getType(state, subject), 'variables', {});
-// Get all variables for all subjects in the codebook
-const getAllVariableUUIDs = ({ node: nodeTypes = {}, edge: edgeTypes = {}, ego = {} }) => {
+const getAllVariablesByUUID = ({ node: nodeTypes = {}, edge: edgeTypes = {}, ego = {} }) => {
+ const flattenedVariables = {};
+
+ const addVariables = (variables) => {
+ Object.keys(variables).forEach((variable) => {
+ flattenedVariables[variable] = variables[variable];
+ });
+ };
+
+ Object.values(nodeTypes).forEach((nodeType) => {
+ addVariables(nodeType.variables);
+ });
+
+ Object.values(edgeTypes).forEach((edgeType) => {
+ addVariables(edgeType.variables);
+ });
+
+ addVariables(ego.variables);
+ return flattenedVariables;
+};
+
+// Get all variables for all subjects in the codebook, adding the entity and type
+const getAllVariableUUIDsByEntity = ({ node: nodeTypes = {}, edge: edgeTypes = {}, ego = {} }) => {
const variables = new Set();
// Nodes
@@ -72,20 +91,27 @@ const getAllVariableUUIDs = ({ node: nodeTypes = {}, edge: edgeTypes = {}, ego =
return [...variables]; // Spread converts Set to Array
};
-export const makeGetVariableFromUUID = (uuid) => (state) => {
+const makeGetVariableWithEntity = (uuid) => (state) => {
const codebook = getCodebook(state);
- const variables = getAllVariableUUIDs(codebook);
+ const variables = getAllVariableUUIDsByEntity(codebook);
const found = find(variables, { uuid });
return found;
};
+const makeGetVariable = (uuid) => (state) => {
+ const codebook = getCodebook(state);
+ const variables = getAllVariablesByUUID(codebook);
+ const found = get(variables, uuid, {});
+ return found;
+};
+
/**
- * Given `subject` return a list of options (`{ label, value, ...}`)
- * for matching entity
- *
- * @param {object} state redux state
- * @param {object} subject subject object in format `{ entity, type }`
- */
+ * Given `subject` return a list of options (`{ label, value, ...}`)
+ * for matching entity
+ *
+ * @param {object} state redux state
+ * @param {object} subject subject object in format `{ entity, type }`
+ */
const getVariableOptionsForSubject = (state, subject, isUsedOptions = {}) => {
const variables = getVariablesForSubject(state, subject);
const options = asOptions(variables);
@@ -115,5 +141,7 @@ export {
getVariablesForSubject,
getVariableOptionsForSubject,
getOptionsForVariable,
- getAllVariableUUIDs,
+ getAllVariableUUIDsByEntity,
+ makeGetVariableWithEntity,
+ makeGetVariable,
};
From 42ab9909f54c6b182200570ec5ac4438eb95f3b0 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Thu, 15 Sep 2022 13:45:37 +0200
Subject: [PATCH 3/3] make getAllVariableUUIDsByEntity more robust
---
.../sections/Form/withFieldsHandlers.js | 3 +-
src/selectors/codebook/index.js | 30 +++++++++++++------
2 files changed, 22 insertions(+), 11 deletions(-)
diff --git a/src/components/sections/Form/withFieldsHandlers.js b/src/components/sections/Form/withFieldsHandlers.js
index 956f63671..16ea20484 100644
--- a/src/components/sections/Form/withFieldsHandlers.js
+++ b/src/components/sections/Form/withFieldsHandlers.js
@@ -21,7 +21,6 @@ const mapStateToProps = (state, { form, entity, type }) => {
const component = formSelector(state, 'component');
const createNewVariable = formSelector(state, '_createNewVariable');
const isNewVariable = !!createNewVariable;
-
const existingVariables = getVariablesForSubject(state, { entity, type });
const variableOptions = getVariableOptionsForSubject(state, { entity, type })
// If not a variable with corresponding component, we can't use it here.
@@ -74,7 +73,7 @@ const fieldsHandlers = withHandlers({
if (variableType !== typeForComponent) {
changeField(form, 'validation', null);
changeField(form, 'options', null);
- // Special case for boolean, where BooleanChoice has options but Toggle doesn't
+ // Special case for boolean, where BooleanChoice has options but Toggle doesn't
} else if (variableType === 'boolean') {
changeField(form, 'options', null);
}
diff --git a/src/selectors/codebook/index.js b/src/selectors/codebook/index.js
index dfc5adfb0..737f3c6f8 100644
--- a/src/selectors/codebook/index.js
+++ b/src/selectors/codebook/index.js
@@ -1,4 +1,4 @@
-import { get, find } from 'lodash';
+import { get, find, isObject } from 'lodash';
import { getCodebook } from '../protocol';
import { asOptions } from '../utils';
import { makeOptionsWithIsUsed } from './isUsed';
@@ -23,24 +23,36 @@ const getType = (state, subject) => {
*/
const getVariablesForSubject = (state, subject) => get(getType(state, subject), 'variables', {});
-const getAllVariablesByUUID = ({ node: nodeTypes = {}, edge: edgeTypes = {}, ego = {} }) => {
+const getAllVariablesByUUID = (codebook) => {
+ if (!codebook) { throw new Error('Codebook not found'); }
+
+ const { node: nodeTypes = {}, edge: edgeTypes = {}, ego = {} } = codebook;
const flattenedVariables = {};
const addVariables = (variables) => {
+ if (!variables) { return; }
+ if (!isObject(variables)) { throw new Error('Variables must be an object'); }
+
Object.keys(variables).forEach((variable) => {
flattenedVariables[variable] = variables[variable];
});
};
- Object.values(nodeTypes).forEach((nodeType) => {
- addVariables(nodeType.variables);
- });
+ if (nodeTypes && nodeTypes.variables) {
+ Object.values(nodeTypes).forEach((nodeType) => {
+ addVariables(nodeType.variables);
+ });
+ }
- Object.values(edgeTypes).forEach((edgeType) => {
- addVariables(edgeType.variables);
- });
+ if (edgeTypes && edgeTypes.variables) {
+ Object.values(edgeTypes).forEach((edgeType) => {
+ addVariables(edgeType.variables);
+ });
+ }
- addVariables(ego.variables);
+ if (ego.variables) {
+ addVariables(ego.variables);
+ }
return flattenedVariables;
};