From 7a9a2d0456c58db59a1480aebe328fd6a007fe74 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Thu, 17 Mar 2022 08:29:35 +0200
Subject: [PATCH 01/10] WIP implement min max name generator nodes
---
network-canvas | 2 +-
src/components/Form/ValidatedField.js | 4 +-
src/components/StageEditor/Interfaces.js | 4 +
src/components/sections/MinMaxAlterLimits.js | 117 +++++++++++++++++++
src/components/sections/index.js | 1 +
src/protocol-validation | 2 +-
src/utils/validations.js | 9 ++
7 files changed, 135 insertions(+), 4 deletions(-)
create mode 100644 src/components/sections/MinMaxAlterLimits.js
diff --git a/network-canvas b/network-canvas
index fe649d2db..e51a7157f 160000
--- a/network-canvas
+++ b/network-canvas
@@ -1 +1 @@
-Subproject commit fe649d2db723e51fccfcd1adf1635900e62c5a17
+Subproject commit e51a7157f5393d64062edcc039228372c68a24a1
diff --git a/src/components/Form/ValidatedField.js b/src/components/Form/ValidatedField.js
index 93e8d364a..6808e9b6a 100644
--- a/src/components/Form/ValidatedField.js
+++ b/src/components/Form/ValidatedField.js
@@ -7,12 +7,12 @@ const ValidatedField = ({
validation,
...rest
}) => {
- const validate = useValidate(validation);
+ const validations = useValidate(validation);
return (
);
};
diff --git a/src/components/StageEditor/Interfaces.js b/src/components/StageEditor/Interfaces.js
index d8a6cb9fa..f18af433f 100644
--- a/src/components/StageEditor/Interfaces.js
+++ b/src/components/StageEditor/Interfaces.js
@@ -18,6 +18,7 @@ import {
NarrativePresets,
NodePanels,
NodeType,
+ MinMaxAlterLimits,
OrdinalBinPrompts,
QuickAdd,
SearchOptionsForExternalData,
@@ -101,6 +102,7 @@ const NameGenerator = {
NameGeneratorPrompts,
NodePanels,
SkipLogic,
+ MinMaxAlterLimits,
InterviewScript,
],
documentation: 'https://documentation.networkcanvas.com/interface-documentation/name-generator-using-forms/',
@@ -116,6 +118,7 @@ const NameGeneratorRoster = {
SearchOptionsForExternalData,
NameGeneratorRosterPrompts,
SkipLogic,
+ MinMaxAlterLimits,
InterviewScript,
],
documentation: 'https://documentation.networkcanvas.com/interface-documentation/name-generator-roster/',
@@ -129,6 +132,7 @@ const NameGeneratorQuickAdd = {
NameGeneratorPrompts,
NodePanels,
SkipLogic,
+ MinMaxAlterLimits,
InterviewScript,
],
name: 'Name Generator (quick add)',
diff --git a/src/components/sections/MinMaxAlterLimits.js b/src/components/sections/MinMaxAlterLimits.js
new file mode 100644
index 000000000..7b7be69aa
--- /dev/null
+++ b/src/components/sections/MinMaxAlterLimits.js
@@ -0,0 +1,117 @@
+import React, { useCallback, useMemo } from 'react';
+import { get, isNull } from 'lodash';
+import { Section } from '@components/EditorLayout';
+import {
+ change, FormSection, formValueSelector,
+} from 'redux-form';
+import { actionCreators as dialogActions } from '@modules/dialogs';
+import { useDispatch, useSelector } from 'react-redux';
+import { Number } from '@codaco/ui/lib/components/Fields';
+import { ValidatedField } from '../Form';
+import IssueAnchor from '../IssueAnchor';
+
+const maxValidation = (value, allValues) => {
+ const minValue = get(allValues, 'behaviours.minNodes', null);
+
+ if (isNull(minValue) || !value) {
+ return undefined;
+ }
+
+ return (
+ value >= minValue ? undefined : 'Maximum number of alters must be greater than or equal to the minimum number'
+ );
+};
+
+const minValidation = (value, allValues) => {
+ const maxValue = get(allValues, 'behaviours.maxNodes');
+
+ if (isNull(maxValue) || !value) {
+ return undefined;
+ }
+
+ return (
+ value <= maxValue ? undefined : 'Minimum number of alters must be less than or equal to the maximum number'
+ );
+};
+
+const MinMaxAlterLimits = () => {
+ const getFormValue = formValueSelector('edit-stage');
+ const currentMinValue = useSelector((state) => getFormValue(state, 'behaviours.minNodes'));
+ const currentMaxValue = useSelector((state) => getFormValue(state, 'behaviours.maxNodes'));
+
+ const dispatch = useDispatch();
+ const openDialog = useCallback(
+ (dialog) => dispatch(dialogActions.openDialog(dialog)),
+ [dispatch],
+ );
+
+ const handleToggleChange = useCallback(
+ async (newState) => {
+ console.log('handle')
+ if ((isNull(currentMinValue) && isNull(currentMaxValue)) || newState === true) {
+ return true;
+ }
+
+ const confirm = await openDialog({
+ type: 'Warning',
+ title: 'This will clear your values',
+ message: 'This will clear the minimum and maximum alter values. Do you want to continue?',
+ confirmLabel: 'Clear values',
+ });
+
+ if (confirm) {
+ dispatch(change('edit-stage', 'behaviours.minNodes', null));
+ dispatch(change('edit-stage', 'behaviours.maxNodes', null));
+ return true;
+ }
+
+ return false;
+ },
+ [dispatch, openDialog, currentMinValue, currentMaxValue],
+ );
+
+ const startExpanded = useMemo(() => !isNull(currentMinValue) || !isNull(currentMaxValue), []);
+
+ return (
+
+ This feature allows you to specify that a minimum or maximum number of alters be named.
+ These limits apply to the stage as a whole, regardless of the number of
+ prompts you have.
+
+ )}
+ toggleable
+ startExpanded={startExpanded}
+ handleToggleChange={handleToggleChange}
+ >
+
+
+
+
+
+
+
+ );
+};
+
+export default MinMaxAlterLimits;
diff --git a/src/components/sections/index.js b/src/components/sections/index.js
index 1dc9324a2..eaf808e92 100644
--- a/src/components/sections/index.js
+++ b/src/components/sections/index.js
@@ -16,6 +16,7 @@ export { default as NarrativeBehaviours } from './NarrativeBehaviours';
export { default as NarrativePresets } from './NarrativePresets';
export { default as NodePanels } from './NodePanels';
export { default as NodeType } from './NodeType';
+export { default as MinMaxAlterLimits } from './MinMaxAlterLimits';
export { default as OrdinalBinPrompts } from './OrdinalBinPrompts';
export { default as QuickAdd } from './QuickAdd';
export { default as SearchOptionsForExternalData } from './SearchOptionsForExternalData';
diff --git a/src/protocol-validation b/src/protocol-validation
index cb76e48d0..af149cb41 160000
--- a/src/protocol-validation
+++ b/src/protocol-validation
@@ -1 +1 @@
-Subproject commit cb76e48d07fe24dced6d78d9d964c05aaff4df77
+Subproject commit af149cb4177142f85ab89b589453bfa555e5ed62
diff --git a/src/utils/validations.js b/src/utils/validations.js
index 30433ea17..12d779434 100644
--- a/src/utils/validations.js
+++ b/src/utils/validations.js
@@ -137,6 +137,15 @@ const validations = {
/**
* Returns the named validation function, if no matching one is found it returns a validation
* which will always fail.
+*
+* Use with a custom function by passing a function as the object value:
+*
+* const customValidation = (value) => {
+* return value === 'custom' ? undefined : 'Custom validation failed';
+* };
+*
+* validations.custom = customValidation;
+*
* @param {object} validationOptions The protocol config for the validations to return.
*/
export const getValidations = (validationOptions = {}) => map(
From 2d8190db3187d2ed610f7d7f5985b377c1ac1538 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Thu, 17 Mar 2022 10:17:48 +0200
Subject: [PATCH 02/10] add validation tests and attempt to reconcile NC and
Architect validation functions
---
.../Fields/VariablePicker/VariablePill.js | 4 +-
src/components/sections/MinMaxAlterLimits.js | 14 +-
src/utils/__tests__/validations.test.js | 196 ++++++++++++++++++
src/utils/validations.js | 54 ++---
4 files changed, 235 insertions(+), 33 deletions(-)
create mode 100644 src/utils/__tests__/validations.test.js
diff --git a/src/components/Form/Fields/VariablePicker/VariablePill.js b/src/components/Form/Fields/VariablePicker/VariablePill.js
index 876e90807..d01d051c3 100644
--- a/src/components/Form/Fields/VariablePicker/VariablePill.js
+++ b/src/components/Form/Fields/VariablePicker/VariablePill.js
@@ -12,7 +12,7 @@ import { get } from 'lodash';
import cx from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import { actionCreators as codebookActions } from '@modules/protocol/codebook';
-import { requiredWithMessage, uniqueByList, allowedVariableName } from '@app/utils/validations';
+import { required as requiredValidation, uniqueByList, allowedVariableName } from '@app/utils/validations';
import TextInput from '@codaco/ui/lib/components/Fields/Text';
import { makeGetVariableFromUUID, getVariablesForSubject } from '../../../../selectors/codebook';
import { getColorForType, getIconForType } from '../../../../config/variables';
@@ -108,7 +108,7 @@ const EditableVariablePill = ({ uuid }) => {
const { target: { value } } = event;
setNewName(value);
- const required = requiredWithMessage('You must enter a variable name', true)(value);
+ const required = requiredValidation(true, 'You must enter a variable name', true)(value);
const unique = uniqueByList(existingVariableNames)(value);
const allowed = allowedVariableName()(value);
diff --git a/src/components/sections/MinMaxAlterLimits.js b/src/components/sections/MinMaxAlterLimits.js
index 7b7be69aa..20cb4c444 100644
--- a/src/components/sections/MinMaxAlterLimits.js
+++ b/src/components/sections/MinMaxAlterLimits.js
@@ -1,5 +1,5 @@
import React, { useCallback, useMemo } from 'react';
-import { get, isNull } from 'lodash';
+import { get, isNull, isUndefined } from 'lodash';
import { Section } from '@components/EditorLayout';
import {
change, FormSection, formValueSelector,
@@ -13,7 +13,7 @@ import IssueAnchor from '../IssueAnchor';
const maxValidation = (value, allValues) => {
const minValue = get(allValues, 'behaviours.minNodes', null);
- if (isNull(minValue) || !value) {
+ if (isUndefined(minValue) || isNull(minValue) || !value) {
return undefined;
}
@@ -25,7 +25,7 @@ const maxValidation = (value, allValues) => {
const minValidation = (value, allValues) => {
const maxValue = get(allValues, 'behaviours.maxNodes');
- if (isNull(maxValue) || !value) {
+ if (isUndefined(maxValue) || isNull(maxValue) || !value) {
return undefined;
}
@@ -47,8 +47,7 @@ const MinMaxAlterLimits = () => {
const handleToggleChange = useCallback(
async (newState) => {
- console.log('handle')
- if ((isNull(currentMinValue) && isNull(currentMaxValue)) || newState === true) {
+ if ((isUndefined(currentMinValue) && isUndefined(currentMaxValue)) || newState === true) {
return true;
}
@@ -70,7 +69,8 @@ const MinMaxAlterLimits = () => {
[dispatch, openDialog, currentMinValue, currentMaxValue],
);
- const startExpanded = useMemo(() => !isNull(currentMinValue) || !isNull(currentMaxValue), []);
+ const startExpanded = useMemo(() => !isUndefined(currentMinValue)
+ || !isUndefined(currentMaxValue), []);
return (
{
placeholder="Infinity"
validation={{
greaterThanMin: maxValidation,
- positiveNumber: true,
+ minValue: 1,
}}
/>
diff --git a/src/utils/__tests__/validations.test.js b/src/utils/__tests__/validations.test.js
new file mode 100644
index 000000000..c05a20da0
--- /dev/null
+++ b/src/utils/__tests__/validations.test.js
@@ -0,0 +1,196 @@
+/* eslint-env jest */
+
+import {
+ required,
+ minLength,
+ maxLength,
+ minValue,
+ maxValue,
+ minSelected,
+ maxSelected,
+} from '../validations';
+
+describe('Validations', () => {
+ describe('required()', () => {
+ const errorMessage = 'You must answer this question before continuing';
+ const subject = required(true, errorMessage);
+
+ it('passes for a string', () => {
+ expect(subject('hello world')).toBe(undefined);
+ });
+
+ it('passes for a numerical value', () => {
+ expect(subject(3)).toBe(undefined);
+ expect(subject(0)).toBe(undefined);
+ });
+
+ it('fails for null or undefined', () => {
+ expect(subject(null)).toEqual(errorMessage);
+ expect(subject(undefined)).toEqual(errorMessage);
+ });
+
+ it('fails for an empty string', () => {
+ expect(subject('')).toEqual(errorMessage);
+ });
+ });
+
+ describe('minLength()', () => {
+ const errorMessage = 'Your answer must be 5 characters or more';
+ const subject = minLength(5, errorMessage);
+
+ it('fails for null or undefined', () => {
+ expect(subject(null)).toBe(errorMessage);
+ expect(subject(undefined)).toBe(errorMessage);
+ });
+
+ it('fails for a smaller string', () => {
+ expect(subject('hi')).toBe(errorMessage);
+ });
+
+ it('passes for an exactly matching string', () => {
+ expect(subject('hello')).toBe(undefined);
+ });
+
+ it('passes for a larger string', () => {
+ expect(subject('hello world')).toBe(undefined);
+ });
+ });
+
+ describe('maxLength()', () => {
+ const errorMessage = 'Your answer must be 5 characters or less';
+ const subject = maxLength(5, errorMessage);
+
+ it('passes for null or undefined', () => {
+ expect(subject(null)).toBe(undefined);
+ expect(subject(undefined)).toBe(undefined);
+ });
+
+ it('passes for a smaller string', () => {
+ expect(subject('hi')).toBe(undefined);
+ });
+
+ it('passes for an exactly matching string', () => {
+ expect(subject('hello')).toBe(undefined);
+ });
+
+ it('fails for a larger string', () => {
+ expect(subject('hello world')).toBe(errorMessage);
+ });
+ });
+
+ describe('minValue()', () => {
+ const errorMessage = 'Your answer must be at least 5';
+ const subject = minValue(5, errorMessage);
+
+ it('passes for null or undefined', () => {
+ expect(subject(null)).toBe(undefined);
+ expect(subject(undefined)).toBe(undefined);
+ });
+
+ it('fails for a negative number', () => {
+ expect(subject(-1)).toBe(errorMessage);
+ });
+
+ it('fails for 0', () => {
+ expect(subject(0)).toBe(errorMessage);
+ });
+
+ it('fails for a smaller value', () => {
+ expect(subject(3)).toBe(errorMessage);
+ });
+
+ it('passes for an exactly matching value', () => {
+ expect(subject(5)).toBe(undefined);
+ });
+
+ it('passes for a larger value', () => {
+ expect(subject(10)).toBe(undefined);
+ });
+ });
+
+ describe('maxValue()', () => {
+ const errorMessage = 'Your answer must be less than 5';
+ const subject = maxValue(5, errorMessage);
+
+ it('passes for null or undefined', () => {
+ expect(subject(null)).toBe(undefined);
+ expect(subject(undefined)).toBe(undefined);
+ });
+
+ it('passes for a negative number', () => {
+ expect(subject(-1)).toBe(undefined);
+ });
+
+ it('passes for 0', () => {
+ expect(subject(0)).toBe(undefined);
+ });
+
+ it('passes for a smaller value', () => {
+ expect(subject(3)).toBe(undefined);
+ });
+
+ it('passes for an exactly matching value', () => {
+ expect(subject(5)).toBe(undefined);
+ });
+
+ it('fails for a larger value', () => {
+ expect(subject(10)).toBe(errorMessage);
+ });
+ });
+
+ describe('minSelected()', () => {
+ const errorMessage = 'You must choose a minimum of 2 option(s)';
+ const subject = minSelected(2, errorMessage);
+
+ it('fails for null or undefined', () => {
+ expect(subject(null)).toBe(errorMessage);
+ expect(subject(undefined)).toBe(errorMessage);
+ });
+
+ it('fails for an empty array', () => {
+ expect(subject([])).toBe(errorMessage);
+ });
+
+ it('fails for a smaller array', () => {
+ expect(subject([1])).toBe(errorMessage);
+ });
+
+ it('passes for an exactly matching array', () => {
+ expect(subject([1, 2])).toBe(undefined);
+ });
+
+ it('passes for a larger array', () => {
+ expect(subject([1, 2, 3])).toBe(undefined);
+ });
+ });
+
+ describe('maxSelected()', () => {
+ const errorMessage = 'You must choose a maximum of 2 option(s)';
+ const subject = maxSelected(2, errorMessage);
+
+ it('passes for null or undefined', () => {
+ expect(subject(null)).toBe(undefined);
+ expect(subject(undefined)).toBe(undefined);
+ });
+
+ it('passes for an empty array', () => {
+ expect(subject([])).toBe(undefined);
+ });
+
+ it('passes for a smaller array', () => {
+ expect(subject([1])).toBe(undefined);
+ });
+
+ it('correctly handles zero values', () => {
+ expect(subject([0, false, -1])).toBe(errorMessage);
+ });
+
+ it('passes for an exactly matching array', () => {
+ expect(subject([1, 2])).toBe(undefined);
+ });
+
+ it('fails for a larger array', () => {
+ expect(subject([1, 2, 3])).toBe(errorMessage);
+ });
+ });
+});
diff --git a/src/utils/validations.js b/src/utils/validations.js
index 12d779434..a16e05427 100644
--- a/src/utils/validations.js
+++ b/src/utils/validations.js
@@ -1,11 +1,22 @@
import {
- keys, get, pickBy, map, toPairs, isEqual, isUndefined, isNil,
+ get, isEmpty, map, toPairs, isEqual, isUndefined, isNil, isNull,
} from 'lodash';
import { DateTime } from 'luxon';
+// Simple function to allow returning a custom message if provided, and
+// or defaulting to the default message.
+const messageWithDefault = (message, defaultMessage) => {
+ if (message) {
+ return message;
+ }
+ return defaultMessage;
+};
+
+// Return an array of values given either a collection, an array,
+// or a single value
const coerceArray = (value) => {
if (value instanceof Object) {
- return keys(pickBy(value));
+ return value.reduce((acc, individual) => ([...acc, individual.value]), []);
}
if (value instanceof Array) {
return value;
@@ -31,35 +42,30 @@ const isRoughlyEqual = (left, right) => {
return isEqual(left, right);
};
-export const required = (isRequired) => (value) => {
+export const required = (isRequired, message) => (value) => {
if (!isRequired) {
return undefined;
}
- return (hasValue(value) ? undefined : 'Required');
+ return (hasValue(value) ? undefined : messageWithDefault(message, 'Required'));
};
-export const requiredWithMessage = (
- message,
- isRequired,
-) => (value) => (required(isRequired)(value) ? message : undefined);
-
-export const requiredAcceptsZero = () => (value) => (!isNil(value) ? undefined : 'Required');
+export const requiredAcceptsZero = (isRequired, message) => (value) => (!isNil(value) && isRequired ? undefined : messageWithDefault(message, 'Required'));
-export const requiredAcceptsNull = () => (value) => (!isUndefined(value) ? undefined : 'Required');
+export const requiredAcceptsNull = (isRequired, message) => (value) => (!isUndefined(value) && isRequired ? undefined : messageWithDefault(message, 'Required'));
-export const positiveNumber = () => (value) => (value && Math.sign(value) === -1 ? 'Number must be positive' : undefined);
+export const positiveNumber = (message) => (value) => (value && Math.sign(value) === -1 ? messageWithDefault(message, 'Number must be positive') : undefined);
-export const maxLength = (max) => (value) => (value && value.length > max ? `Must be ${max} characters or less` : undefined);
-export const minLength = (min) => (value) => (value && value.length < min ? `Must be ${min} characters or more` : undefined);
-export const minValue = (min) => (value) => (value && value < min ? `Must be at least ${min}` : undefined);
-export const maxValue = (max) => (value) => (value && value > max ? `Must be less than ${max}` : undefined);
+export const maxLength = (max, message) => (value) => (!isNull(value) && !isUndefined(value) && value.length > max ? messageWithDefault(message, `Must be ${max} characters or less`) : undefined);
+export const minLength = (min, message) => (value) => ((isNull(value) || isUndefined(value)) || value.length < min ? messageWithDefault(message, `Must be ${min} characters or more`) : undefined);
-export const minSelected = (min) => (value) => (!value || coerceArray(value).length < min ? `Must choose ${min} or more` : undefined);
+export const minValue = (min, message) => (value) => (!isNull(value) && value < min ? messageWithDefault(message, `Must be at least ${min}`) : undefined);
+export const maxValue = (max, message) => (value) => (value && value > max ? messageWithDefault(message, `Must be less than ${max}`) : undefined);
-export const maxSelected = (max) => (value) => (!value || coerceArray(value).length > max ? `Must choose ${max} or less` : undefined);
+export const minSelected = (min, message) => (value) => (!value || coerceArray(value).length < min ? messageWithDefault(message, `You must choose a minimum of ${min} option(s)`) : undefined);
+export const maxSelected = (max, message) => (value) => (!isEmpty(value) && coerceArray(value).length > max ? messageWithDefault(message, `You must choose a maximum of ${max} option(s)`) : undefined);
-export const uniqueArrayAttribute = () => (value, allValues, formProps, name) => {
+export const uniqueArrayAttribute = (message) => (value, allValues, name) => {
if (!value) { return undefined; }
// expects `name` of format: `fieldName[n].attribute`
@@ -76,31 +82,31 @@ export const uniqueArrayAttribute = () => (value, allValues, formProps, name) =>
}, 0);
if (instanceCount >= 2) {
- return `${capitalize(attribute)}s must be unique`;
+ return messageWithDefault(message, `${capitalize(attribute)}s must be unique`);
}
return undefined;
};
-export const uniqueByList = (list = []) => (value) => {
+export const uniqueByList = (list = [], message) => (value) => {
if (!value) { return undefined; }
const existsAlready = list
.some((existingValue) => isRoughlyEqual(existingValue, value));
if (existsAlready) {
- return `"${value}" is already used elsewhere in your protocol`;
+ return messageWithDefault(message, `"${value}" is already used elsewhere in your protocol`);
}
return undefined;
};
-export const ISODate = (dateFormat) => (value) => {
+export const ISODate = (dateFormat, message) => (value) => {
const dt = DateTime.fromISO(value);
if (
(value && dateFormat.length !== value.length)
|| (value && !dt.isValid)
) {
- return `Date is not valid (${dateFormat.toUpperCase()})`;
+ return messageWithDefault(message, `Date is not valid (${dateFormat.toUpperCase()})`);
}
return undefined;
};
From aff5b815dcc5ffe77f80a210663455a0c3166132 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Thu, 17 Mar 2022 10:26:36 +0200
Subject: [PATCH 03/10] resolve missing parameter in uniqueArrayAttribute()
---
.../CategoricalBinPrompts/__tests__/PromptFields.test.js | 2 +-
src/utils/__tests__/validations.test.js | 8 ++++++++
src/utils/validations.js | 2 +-
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/components/sections/CategoricalBinPrompts/__tests__/PromptFields.test.js b/src/components/sections/CategoricalBinPrompts/__tests__/PromptFields.test.js
index b1a92ac5f..e35b0044f 100644
--- a/src/components/sections/CategoricalBinPrompts/__tests__/PromptFields.test.js
+++ b/src/components/sections/CategoricalBinPrompts/__tests__/PromptFields.test.js
@@ -69,7 +69,7 @@ export const testPromptFields = (PromptFieldsComponent, name = '') => {
mockStore = getStore(initialState);
});
- // TODO This seems to test the wrong part of codebook
+ // FIXME This seems to test the wrong part of codebook
describe(name, () => {
describe('PromptFields', () => {
diff --git a/src/utils/__tests__/validations.test.js b/src/utils/__tests__/validations.test.js
index c05a20da0..47ce6d8b3 100644
--- a/src/utils/__tests__/validations.test.js
+++ b/src/utils/__tests__/validations.test.js
@@ -193,4 +193,12 @@ describe('Validations', () => {
expect(subject([1, 2, 3])).toBe(errorMessage);
});
});
+
+ it.todo('uniqueArrayAttributr()');
+
+ it.todo('uniqueByList()');
+
+ it.todo('ISODate()');
+
+ it.todo('allowedVariableName()');
});
diff --git a/src/utils/validations.js b/src/utils/validations.js
index a16e05427..70c2133fa 100644
--- a/src/utils/validations.js
+++ b/src/utils/validations.js
@@ -65,7 +65,7 @@ export const maxValue = (max, message) => (value) => (value && value > max ? mes
export const minSelected = (min, message) => (value) => (!value || coerceArray(value).length < min ? messageWithDefault(message, `You must choose a minimum of ${min} option(s)`) : undefined);
export const maxSelected = (max, message) => (value) => (!isEmpty(value) && coerceArray(value).length > max ? messageWithDefault(message, `You must choose a maximum of ${max} option(s)`) : undefined);
-export const uniqueArrayAttribute = (message) => (value, allValues, name) => {
+export const uniqueArrayAttribute = (message) => (value, allValues, _, name) => {
if (!value) { return undefined; }
// expects `name` of format: `fieldName[n].attribute`
From 59e2cff76245e93888b2fc7ec38684005e0e2a38 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Thu, 17 Mar 2022 10:29:52 +0200
Subject: [PATCH 04/10] final wording change
---
src/components/sections/MinMaxAlterLimits.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/sections/MinMaxAlterLimits.js b/src/components/sections/MinMaxAlterLimits.js
index 20cb4c444..8e1e3cad6 100644
--- a/src/components/sections/MinMaxAlterLimits.js
+++ b/src/components/sections/MinMaxAlterLimits.js
@@ -74,11 +74,11 @@ const MinMaxAlterLimits = () => {
return (
- This feature allows you to specify that a minimum or maximum number of alters be named.
- These limits apply to the stage as a whole, regardless of the number of
+ This feature allows you to specify that a minimum or maximum number of alters that can
+ be named. These limits apply to the stage as a whole, regardless of the number of
prompts you have.
)}
From 1e45ee6ce34e6270d2ec55e6e2865dac409a8001 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Thu, 17 Mar 2022 10:54:22 +0200
Subject: [PATCH 05/10] new interviewer ref
---
network-canvas | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/network-canvas b/network-canvas
index e51a7157f..e5c2adb64 160000
--- a/network-canvas
+++ b/network-canvas
@@ -1 +1 @@
-Subproject commit e51a7157f5393d64062edcc039228372c68a24a1
+Subproject commit e5c2adb640c8ac5ee711fa6198037a8f2f2bcd88
From dd299e9740503a2f40d11d7b7f979d118f3724e2 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Thu, 17 Mar 2022 10:54:49 +0200
Subject: [PATCH 06/10] bump version to 6.4.0
---
package-lock.json | 2 +-
package.json | 2 +-
public/package.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index ea8c4ba92..c261bf1df 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "network-canvas-architect",
- "version": "6.3.1",
+ "version": "6.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
diff --git a/package.json b/package.json
index d27ae52f4..3886f001a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "network-canvas-architect",
- "version": "6.3.1",
+ "version": "6.4.0",
"productName": "Network Canvas Architect",
"description": "A tool for building Network Canvas interviews.",
"author": "Complex Data Collective ",
diff --git a/public/package.json b/public/package.json
index ed53844e1..bdd41d873 100644
--- a/public/package.json
+++ b/public/package.json
@@ -1,6 +1,6 @@
{
"name": "network-canvas-architect",
- "version": "6.3.1",
+ "version": "6.4.0",
"productName": "Network Canvas Architect",
"description": "A tool for building Network Canvas interviews.",
"author": "Complex Data Collective",
From 84a3c66fd734be1bf1fde905d439fd0e36df8b72 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Thu, 17 Mar 2022 12:49:15 +0200
Subject: [PATCH 07/10] update interviewer reference
---
network-canvas | 2 +-
src/__mocks__/config.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/network-canvas b/network-canvas
index e5c2adb64..a6127ae4d 160000
--- a/network-canvas
+++ b/network-canvas
@@ -1 +1 @@
-Subproject commit e5c2adb640c8ac5ee711fa6198037a8f2f2bcd88
+Subproject commit a6127ae4d584153f36500f1d3cd01679fe036496
diff --git a/src/__mocks__/config.js b/src/__mocks__/config.js
index 193187e81..dfb266b91 100644
--- a/src/__mocks__/config.js
+++ b/src/__mocks__/config.js
@@ -1,4 +1,4 @@
/* eslint-env jest */
/* eslint-disable import/prefer-default-export */
-export const APP_SCHEMA_VERSION = 2;
+export const APP_SCHEMA_VERSION = 7;
From ab8a15bd1e9330e167e4620c7e7fb1626abc01ad Mon Sep 17 00:00:00 2001
From: Rebecca Madsen
Date: Fri, 18 Mar 2022 21:11:47 -0400
Subject: [PATCH 08/10] fix params for validations
---
src/utils/validations.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/utils/validations.js b/src/utils/validations.js
index 70c2133fa..ab0de37d8 100644
--- a/src/utils/validations.js
+++ b/src/utils/validations.js
@@ -54,7 +54,7 @@ export const requiredAcceptsZero = (isRequired, message) => (value) => (!isNil(v
export const requiredAcceptsNull = (isRequired, message) => (value) => (!isUndefined(value) && isRequired ? undefined : messageWithDefault(message, 'Required'));
-export const positiveNumber = (message) => (value) => (value && Math.sign(value) === -1 ? messageWithDefault(message, 'Number must be positive') : undefined);
+export const positiveNumber = (_, message) => (value) => (value && Math.sign(value) === -1 ? messageWithDefault(message, 'Number must be positive') : undefined);
export const maxLength = (max, message) => (value) => (!isNull(value) && !isUndefined(value) && value.length > max ? messageWithDefault(message, `Must be ${max} characters or less`) : undefined);
export const minLength = (min, message) => (value) => ((isNull(value) || isUndefined(value)) || value.length < min ? messageWithDefault(message, `Must be ${min} characters or more`) : undefined);
@@ -65,7 +65,7 @@ export const maxValue = (max, message) => (value) => (value && value > max ? mes
export const minSelected = (min, message) => (value) => (!value || coerceArray(value).length < min ? messageWithDefault(message, `You must choose a minimum of ${min} option(s)`) : undefined);
export const maxSelected = (max, message) => (value) => (!isEmpty(value) && coerceArray(value).length > max ? messageWithDefault(message, `You must choose a maximum of ${max} option(s)`) : undefined);
-export const uniqueArrayAttribute = (message) => (value, allValues, _, name) => {
+export const uniqueArrayAttribute = (_, message) => (value, allValues, __, name) => {
if (!value) { return undefined; }
// expects `name` of format: `fieldName[n].attribute`
From 8604e97352333e319d841142f28f41682be37b05 Mon Sep 17 00:00:00 2001
From: Joshua Melville
Date: Tue, 22 Mar 2022 18:52:55 +0200
Subject: [PATCH 09/10] add warning if there are multiple prompts on a stage
---
package-lock.json | 2 +-
src/components/sections/MinMaxAlterLimits.js | 37 +++++++++++++++++---
2 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index c261bf1df..3c0abb338 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "network-canvas-architect",
- "version": "6.3.0",
+ "version": "6.4.0",
"hasInstallScript": true,
"devDependencies": {
"@babel/cli": "^7.12.1",
diff --git a/src/components/sections/MinMaxAlterLimits.js b/src/components/sections/MinMaxAlterLimits.js
index 8e1e3cad6..f9e20b474 100644
--- a/src/components/sections/MinMaxAlterLimits.js
+++ b/src/components/sections/MinMaxAlterLimits.js
@@ -9,6 +9,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { Number } from '@codaco/ui/lib/components/Fields';
import { ValidatedField } from '../Form';
import IssueAnchor from '../IssueAnchor';
+import Tip from '../Tip';
const maxValidation = (value, allValues) => {
const minValue = get(allValues, 'behaviours.minNodes', null);
@@ -38,6 +39,10 @@ const MinMaxAlterLimits = () => {
const getFormValue = formValueSelector('edit-stage');
const currentMinValue = useSelector((state) => getFormValue(state, 'behaviours.minNodes'));
const currentMaxValue = useSelector((state) => getFormValue(state, 'behaviours.maxNodes'));
+ const hasMultiplePrompts = useSelector((state) => {
+ const prompts = getFormValue(state, 'prompts');
+ return !!prompts && prompts.length > 1;
+ });
const dispatch = useDispatch();
const openDialog = useCallback(
@@ -76,16 +81,38 @@ const MinMaxAlterLimits = () => {
- This feature allows you to specify that a minimum or maximum number of alters that can
- be named. These limits apply to the stage as a whole, regardless of the number of
- prompts you have.
-
+ <>
+
+ This feature allows you to specify a minimum or maximum number of alters that can
+ be named on this stage. Please note that these limits apply to the
+ {' '}
+
+ stage as a whole
+
+ , regardless of the number of
+ prompts you have created.
+
+ >
)}
toggleable
startExpanded={startExpanded}
handleToggleChange={handleToggleChange}
>
+ { hasMultiplePrompts && (
+
+
+ You have multiple prompts configured on this stage. Remember that the limits you
+ specify here apply to the
+ {' '}
+
+ stage as a whole
+
+ . Consider splitting your prompts up into multiple stages, or ensure you take extra
+ care in the phrasing of your prompts so that you communicate the alter limits
+ to your participants.
+
+
+ )}
Date: Tue, 22 Mar 2022 19:11:55 +0200
Subject: [PATCH 10/10] update submodule reference
---
network-canvas | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/network-canvas b/network-canvas
index a6127ae4d..987da952d 160000
--- a/network-canvas
+++ b/network-canvas
@@ -1 +1 @@
-Subproject commit a6127ae4d584153f36500f1d3cd01679fe036496
+Subproject commit 987da952d9a3c8145d50b0c83c9f83fb64bd86f5