+
diff --git a/package.json b/package.json
index 77222324ba..6d4d9d7d1d 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,7 @@
"build-docs": "cross-env NODE_ENV=production jsdoc src -r -d docs-build -c ./jsdoc.conf.json --verbose",
"electron": "electron ./www",
"electron:dev": "cross-env NODE_ENV=development NC_DEVSERVER_FILE=\".devserver\" electron public/",
- "ios:dev": "node scripts/check-dev-server.js && cross-env LIVE_RELOAD=1 cordova run ios --buildFlag='-UseModernBuildSystem=0' --developmentTeam=85EZ69PQHJ --device",
+ "ios:dev": "node scripts/check-dev-server.js && cross-env LIVE_RELOAD=1 cordova run ios --buildFlag='-UseModernBuildSystem=0' --developmentTeam=85EZ69PQHJ",
"android:dev": "node scripts/check-dev-server.js && cross-env LIVE_RELOAD=1 cordova run android",
"generate-icons": "node scripts/generate-app-icons.js",
"dist:android": "npm run build:android && cordova build android --release",
@@ -243,4 +243,4 @@
"cordova-plugin-network-canvas-client": {}
}
}
-}
+}
\ No newline at end of file
diff --git a/public/protocols/development.netcanvas/protocol.json b/public/protocols/development.netcanvas/protocol.json
index 2f89444684..9b719854b2 100644
--- a/public/protocols/development.netcanvas/protocol.json
+++ b/public/protocols/development.netcanvas/protocol.json
@@ -1,6 +1,6 @@
{
"name": "Development Protocol",
- "description": "",
+ "description": "The Network Canvas development protocol is designed for our team to test new features. It is not intended for general use.",
"lastModified": "2018-10-01T00:00:00.000Z",
"networkCanvasVersion": "~4.0.0",
"variableRegistry": {
@@ -705,10 +705,8 @@
"type": "eda5e3bb-8e1c-4216-9e06-adc0ff6b7f73",
"attributes": {
"8a35cd77-7bc4-4c7e-b98a-673b6a21321f": "About My Health",
- "8ee3a187-d4be-458e-8abb-71efcc071949": "",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "About My Health",
"66646d68-cd33-407b-a349-56707021df72": "About My Health",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "55 E Washington St, Chicago, Illinois, 60602",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.882945,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.62572,
@@ -722,7 +720,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "Access Anixter Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Anixter",
"66646d68-cd33-407b-a349-56707021df72": "Access - Anixter",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "2020 N Clybourn Ave, Chicago, Illinois, 60614",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.91837,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.660281,
@@ -736,7 +733,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Ashland Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Ashland Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Ashland Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "5159 S Ashland Ave, Chicago, IL 60609",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.799756,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.66443,
@@ -750,7 +746,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Auburn-Gresham Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Auburn-Gresham Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Auburn-Gresham Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "8234 S Ashland Ave, Chicago, IL 60620",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.743832,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.663648,
@@ -764,7 +759,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Booker Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Booker Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Booker Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "654 E 47th St, Chicago, IL 60653",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.809683,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.609344,
@@ -778,7 +772,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Brandon Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Brandon Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Brandon Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "8300 S Brandon Ave, Chicago, IL 60617",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 40.74453,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.547014,
@@ -792,7 +785,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Cabrini Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Cabrini Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Cabrini Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "3450 S Archer Ave, Chicago, IL 60608",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.831331,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.676624,
@@ -806,7 +798,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Centro Medico",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Centro Medico",
"66646d68-cd33-407b-a349-56707021df72": "Access - Centro Medico",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "3700 W 26th St, Chicago, IL 60623",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.844509,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.717409,
@@ -820,7 +811,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Centro Medico San Rafael",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Centro Medico San Rafael",
"66646d68-cd33-407b-a349-56707021df72": "Access - Centro Medico San Rafael",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "3204 W 26th St, Chicago, IL 60623",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.844713,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.705448,
@@ -834,7 +824,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Doctors Medical Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Doctors Medical Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Doctors Medical Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "6240 W 55th St, Chicago, IL 60638",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.792657,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.778601,
@@ -848,7 +837,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Evanston-Rogers Park Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Evanston-Rogers Park Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Evanston-Rogers Park Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "1555 Howard St, Chicago, IL 60626",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 42.019113,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.670241,
@@ -862,7 +850,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS at Gary Comer Youth Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Gary Comer Youth Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Gary Comer Youth Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "7200 S Ingleside Ave, Chicago, IL 60619",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.764069,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.602436,
@@ -890,7 +877,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Humboldt Park Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Humboldt Park Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Humboldt Park Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "3202 W North Ave, Chicago, IL 60647",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.910444,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.707085,
@@ -904,7 +890,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS at the Illinois Eye Institute",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Illinois Eye Institute",
"66646d68-cd33-407b-a349-56707021df72": "Access - Illinois Eye Institute",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "3241 S Michigan Ave, Chicago, IL 60616",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.835265,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.622279,
@@ -918,7 +903,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Kedzie Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Kedzie Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Kedzie Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "3213 W 47th Pl, Chicago, IL 60632",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.806249,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.704915,
@@ -932,7 +916,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "Servicios Medicos La Villita",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - La Villita",
"66646d68-cd33-407b-a349-56707021df72": "Access - La Villita",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "3303 W 26th St, Chicago, Illinois, 60623",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.843835,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.707797,
@@ -946,7 +929,6 @@
"8ee3a187-d4be-458e-8abb-71efcc071949": "ACCESS Madison Family Health Center",
"1e9ce62c-44c5-484d-9c26-0d20cc7d7238": "Access - Madison Family Health Center",
"66646d68-cd33-407b-a349-56707021df72": "Access - Madison Family Health Center",
- "18fbc928-d027-42de-bc96-ff5c09bf4944": "",
"67132d2b-c371-4c57-a5eb-6520083f9d22": "3800 W Madison St, Chicago, IL 60624",
"bdc60147-fe7a-4c3c-a164-e5b370f6a281": 41.881126,
"931a7b23-e433-4e7e-8e13-48b72e5f0549": -87.721142,
@@ -1134,10 +1116,11 @@
},
{
"id": "2we",
- "text": "Within the past 2 weeks, who has provided advice?",
- "additionalAttributes": {
- "03b03617-46ae-41cb-9462-9acd8a17edd6": true
- }
+ "text": "Prompt with no additional attributes"
+ },
+ {
+ "id": "2wj",
+ "text": "Second prompt with no additional attributes"
}
]
},
diff --git a/src/behaviours/DragAndDrop/reducer.js b/src/behaviours/DragAndDrop/reducer.js
index a60c5d03c5..d2dff6588e 100644
--- a/src/behaviours/DragAndDrop/reducer.js
+++ b/src/behaviours/DragAndDrop/reducer.js
@@ -27,7 +27,7 @@ const willAccept = (accepts, source) => {
...source,
});
} catch (e) {
- console.log('Error in accept() function', e, source); // eslint-disable-line no-console
+ console.warn('Error in accept() function', e, source); // eslint-disable-line no-console
return false;
}
};
diff --git a/src/components/Canvas/EdgeLayout.js b/src/components/Canvas/EdgeLayout.js
index 26fea49db0..71be8c8558 100644
--- a/src/components/Canvas/EdgeLayout.js
+++ b/src/components/Canvas/EdgeLayout.js
@@ -13,9 +13,17 @@ export class EdgeLayout extends PureComponent {
edges: [],
};
- renderEdge = ({ key, from, to, type }) => (
-
- );
+ renderEdge = (edge) => {
+ if (!['key', 'from', 'to', 'type'].every(prop => prop in edge)) {
+ return null;
+ }
+
+ const { key, from, to, type } = edge;
+
+ return (
+
+ );
+ };
render() {
const { edges } = this.props;
diff --git a/src/components/Canvas/NodeLayout.js b/src/components/Canvas/NodeLayout.js
index 3c2dbff12f..93ae6953a2 100644
--- a/src/components/Canvas/NodeLayout.js
+++ b/src/components/Canvas/NodeLayout.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { isEmpty, isEqual, pick, has } from 'lodash';
+import { isEmpty, isEqual, pick, has, isNil } from 'lodash';
import LayoutNode from '../../containers/Canvas/LayoutNode';
import { nodePrimaryKeyProperty, getNodeAttributes, nodeAttributesProperty } from '../../ducks/modules/network';
@@ -73,7 +73,9 @@ class NodeLayout extends Component {
{ nodes.map((node) => {
const nodeAttributes = getNodeAttributes(node);
- if (!has(nodeAttributes, layoutVariable)) { return null; }
+ if (!has(nodeAttributes, layoutVariable) || isNil(nodeAttributes[layoutVariable])) {
+ return null;
+ }
return (
,
@@ -79,7 +80,7 @@ ShallowWrapper {
"y": 100,
}
}
- type={undefined}
+ type="type"
viewBoxScale={100}
/>,
],
@@ -102,7 +103,7 @@ ShallowWrapper {
"x": 100,
"y": 100,
},
- "type": undefined,
+ "type": "type",
"viewBoxScale": 100,
},
"ref": null,
@@ -138,7 +139,7 @@ ShallowWrapper {
"y": 100,
}
}
- type={undefined}
+ type="type"
viewBoxScale={100}
/>
,
@@ -164,7 +165,7 @@ ShallowWrapper {
"y": 100,
}
}
- type={undefined}
+ type="type"
viewBoxScale={100}
/>,
],
@@ -187,7 +188,7 @@ ShallowWrapper {
"x": 100,
"y": 100,
},
- "type": undefined,
+ "type": "type",
"viewBoxScale": 100,
},
"ref": null,
diff --git a/src/containers/Canvas/LayoutNode.js b/src/containers/Canvas/LayoutNode.js
index 3ce73557ba..b42d17640d 100644
--- a/src/containers/Canvas/LayoutNode.js
+++ b/src/containers/Canvas/LayoutNode.js
@@ -24,7 +24,6 @@ class LayoutNode extends PureComponent {
selected,
selectedColor,
} = this.props;
-
const nodeAttributes = getNodeAttributes(node);
const { x, y } = nodeAttributes[layoutVariable];
diff --git a/src/containers/Canvas/NodeLayout.js b/src/containers/Canvas/NodeLayout.js
index a34c40581c..f8d6a1ecfa 100644
--- a/src/containers/Canvas/NodeLayout.js
+++ b/src/containers/Canvas/NodeLayout.js
@@ -1,7 +1,7 @@
import { bindActionCreators } from 'redux';
+import { isNil } from 'lodash';
import { connect } from 'react-redux';
import { compose, withHandlers, withState } from 'recompose';
-import { has } from 'lodash';
import { withBounds } from '../../behaviours';
import { actionCreators as sessionsActions } from '../../ducks/modules/sessions';
import { nodePrimaryKeyProperty, nodeAttributesProperty } from '../../ducks/modules/network';
@@ -21,7 +21,8 @@ const withDropHandlers = withHandlers({
onDrop: ({ updateNode, layoutVariable, setRerenderCount, rerenderCount, width, height, x, y }) =>
(item) => {
updateNode(
- item.meta,
+ item.meta[nodePrimaryKeyProperty],
+ {},
{
[layoutVariable]: relativeCoords({ width, height, x, y }, item),
},
@@ -30,19 +31,18 @@ const withDropHandlers = withHandlers({
// Horrible hack for performance (only re-render nodes on drop, not on drag)
setRerenderCount(rerenderCount + 1);
},
- onDrag: ({ layoutVariable, updateNode, width, height, x, y }) => (item) => {
- if (!has(item.meta[nodeAttributesProperty], layoutVariable)) { return; }
-
- updateNode(
- item.meta,
- {
- [layoutVariable]: relativeCoords({ width, height, x, y }, item),
- },
- );
- },
- onDragEnd: ({ layoutVariable, setRerenderCount, rerenderCount }) => (item) => {
- if (!has(item.meta[nodeAttributesProperty], layoutVariable)) { return; }
-
+ onDrag: ({ layoutVariable, updateNode, width, height, x, y }) =>
+ (item) => {
+ if (isNil(item.meta[nodeAttributesProperty][layoutVariable])) { return; }
+ updateNode(
+ item.meta[nodePrimaryKeyProperty],
+ {},
+ {
+ [layoutVariable]: relativeCoords({ width, height, x, y }, item),
+ },
+ );
+ },
+ onDragEnd: ({ setRerenderCount, rerenderCount }) => () => {
// make sure to also re-render nodes that were updated on drag end
setRerenderCount(rerenderCount + 1);
},
diff --git a/src/containers/CategoricalList.js b/src/containers/CategoricalList.js
index b87cd200c5..c6c2105854 100644
--- a/src/containers/CategoricalList.js
+++ b/src/containers/CategoricalList.js
@@ -139,8 +139,11 @@ class CategoricalList extends Component {
return;
}
- this.props.toggleNodeAttributes(meta[nodePrimaryKeyProperty],
- { [this.props.activePromptVariable]: [binValue] });
+ this.props.updateNode(
+ meta[nodePrimaryKeyProperty],
+ {},
+ { [this.props.activePromptVariable]: [binValue] },
+ );
};
renderCategoricalBin = (bin, index, sizes) => {
@@ -219,7 +222,7 @@ CategoricalList.propTypes = {
displayVariable: PropTypes.string.isRequired,
prompt: PropTypes.object.isRequired,
stage: PropTypes.object.isRequired,
- toggleNodeAttributes: PropTypes.func.isRequired,
+ updateNode: PropTypes.func.isRequired,
};
CategoricalList.defaultProps = {
@@ -259,7 +262,7 @@ function makeMapStateToProps() {
function mapDispatchToProps(dispatch) {
return {
- toggleNodeAttributes: bindActionCreators(sessionsActions.toggleNodeAttributes, dispatch),
+ updateNode: bindActionCreators(sessionsActions.updateNode, dispatch),
};
}
diff --git a/src/containers/Interfaces/NameGenerator.js b/src/containers/Interfaces/NameGenerator.js
index b5a8606b82..bb5d2c15c2 100644
--- a/src/containers/Interfaces/NameGenerator.js
+++ b/src/containers/Interfaces/NameGenerator.js
@@ -2,14 +2,15 @@ import React, { Component } from 'react';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
-import { get, has } from 'lodash';
+import { get, has, omit } from 'lodash';
import withPrompt from '../../behaviours/withPrompt';
import { actionCreators as sessionsActions } from '../../ducks/modules/sessions';
-import { makeNetworkNodesForPrompt } from '../../selectors/interface';
-import { makeGetPromptNodeAttributes, makeGetNodeIconName } from '../../selectors/name-generator';
+import { makeNetworkNodesForPrompt, makeGetAdditionalAttributes } from '../../selectors/interface';
+import { makeGetPromptNodeModelData, makeGetNodeIconName } from '../../selectors/name-generator';
import { PromptSwiper, NodePanels, NodeForm } from '../';
import { NodeList, NodeBin } from '../../components/';
import { Icon } from '../../ui/components';
+import { nodeAttributesProperty, nodePrimaryKeyProperty } from '../../ducks/modules/network';
/**
* Name Generator Interface
@@ -31,9 +32,19 @@ class NameGenerator extends Component {
handleSubmitForm = ({ form, addAnotherNode } = { addAnotherNode: false }) => {
if (form) {
if (!this.state.selectedNode) {
- this.props.addNodes({ attributes: { ...form } }, this.props.newNodeAttributes);
+ /**
+ * addNode(modelData, attributeData);
+ */
+ this.props.addNode(
+ this.props.newNodeModelData,
+ { ...this.props.newNodeAttributes, ...form },
+ );
} else {
- this.props.updateNode({ ...this.state.selectedNode }, form);
+ /**
+ * updateNode(nodeId, newModelData, newAttributeData)
+ */
+ const selectedUID = this.state.selectedNode[nodePrimaryKeyProperty];
+ this.props.updateNode(selectedUID, {}, form);
}
}
@@ -43,15 +54,25 @@ class NameGenerator extends Component {
/**
* Drop node handler
* Adds prompt attributes to existing nodes, or adds new nodes to the network.
- * @param {object} node - key/value object containing node object from the network store
+ * @param {object} item - key/value object containing node object from the network store
*/
handleDropNode = (item) => {
const node = { ...item.meta };
// Test if we are updating an existing network node, or adding it to the network
- if (has(node, 'promptId') || has(node, 'stageId')) {
- this.props.updateNode(node, { ...this.props.activePromptAttributes });
+ if (has(node, 'promptIDs')) {
+ this.props.updateNode(
+ node[nodePrimaryKeyProperty],
+ { ...this.props.newNodeModelData },
+ { ...this.props.activePromptAttributes },
+ );
} else {
- this.props.addNodes(node, this.props.newNodeAttributes);
+ const droppedAttributeData = node[nodeAttributesProperty];
+ const droppedModelData = omit(node, nodeAttributesProperty);
+
+ this.props.addNode(
+ { ...this.props.newNodeModelData, ...droppedModelData },
+ { ...droppedAttributeData, ...this.props.newNodeAttributes },
+ );
}
}
@@ -155,9 +176,10 @@ NameGenerator.defaultProps = {
NameGenerator.propTypes = {
activePromptAttributes: PropTypes.object,
- addNodes: PropTypes.func.isRequired,
+ addNode: PropTypes.func.isRequired,
form: PropTypes.object,
newNodeAttributes: PropTypes.object.isRequired,
+ newNodeModelData: PropTypes.object.isRequired,
nodesForPrompt: PropTypes.array.isRequired,
nodeIconName: PropTypes.string.isRequired,
prompt: PropTypes.object.isRequired,
@@ -169,13 +191,15 @@ NameGenerator.propTypes = {
function makeMapStateToProps() {
const networkNodesForPrompt = makeNetworkNodesForPrompt();
- const getPromptNodeAttributes = makeGetPromptNodeAttributes();
+ const getPromptNodeAttributes = makeGetAdditionalAttributes();
+ const getPromptNodeModelData = makeGetPromptNodeModelData();
const getNodeIconName = makeGetNodeIconName();
return function mapStateToProps(state, props) {
return {
activePromptAttributes: props.prompt.additionalAttributes,
newNodeAttributes: getPromptNodeAttributes(state, props),
+ newNodeModelData: getPromptNodeModelData(state, props),
nodesForPrompt: networkNodesForPrompt(state, props),
nodeIconName: getNodeIconName(state, props),
};
@@ -184,7 +208,7 @@ function makeMapStateToProps() {
function mapDispatchToProps(dispatch) {
return {
- addNodes: bindActionCreators(sessionsActions.addNodes, dispatch),
+ addNode: bindActionCreators(sessionsActions.addNode, dispatch),
updateNode: bindActionCreators(sessionsActions.updateNode, dispatch),
};
}
diff --git a/src/containers/Interfaces/NameGeneratorAutoComplete.js b/src/containers/Interfaces/NameGeneratorAutoComplete.js
index ffcdf23e6f..fe71967f59 100644
--- a/src/containers/Interfaces/NameGeneratorAutoComplete.js
+++ b/src/containers/Interfaces/NameGeneratorAutoComplete.js
@@ -1,4 +1,5 @@
import React, { Component } from 'react';
+import { map } from 'lodash';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
@@ -10,23 +11,23 @@ import Search from '../../containers/Search';
import { actionCreators as sessionsActions } from '../../ducks/modules/sessions';
import { actionCreators as searchActions } from '../../ducks/modules/search';
import { nodeAttributesProperty } from '../../ducks/modules/network';
-import { getNodeLabelFunction, makeGetSubjectType, makeNetworkNodesForPrompt, networkNodes } from '../../selectors/interface';
-import { getCardDisplayLabel, getCardAdditionalProperties, makeGetNodeIconName, makeGetPromptNodeAttributes } from '../../selectors/name-generator';
+import { getNodeLabelFunction, makeGetSubjectType, makeNetworkNodesForPrompt, networkNodes, makeGetAdditionalAttributes } from '../../selectors/interface';
+import { getCardDisplayLabel, getCardAdditionalProperties, makeGetNodeIconName, makeGetPromptNodeModelData } from '../../selectors/name-generator';
import { PromptSwiper } from '../';
import { NodeBin, NodeList } from '../../components/';
-const networkNodesForPrompt = makeNetworkNodesForPrompt();
-const getPromptNodeAttributes = makeGetPromptNodeAttributes();
-const getNodeType = makeGetSubjectType();
-const getNodeIconName = makeGetNodeIconName();
-
/**
* NameGeneratorAutoComplete Interface
* @extends Component
*/
class NameGeneratorAutoComplete extends Component {
onSearchComplete(selectedResults) {
- this.props.addNodes(selectedResults, this.props.newNodeAttributes);
+ const withNewModelData = map(selectedResults, result => ({
+ ...this.props.newNodeModelData,
+ ...result,
+ }));
+
+ this.props.batchAddNodes(withNewModelData, this.props.newNodeAttributes);
this.props.closeSearch();
}
@@ -113,12 +114,13 @@ class NameGeneratorAutoComplete extends Component {
}
NameGeneratorAutoComplete.propTypes = {
- addNodes: PropTypes.func.isRequired,
+ batchAddNodes: PropTypes.func.isRequired,
closeSearch: PropTypes.func.isRequired,
excludedNodes: PropTypes.array.isRequired,
getLabel: PropTypes.func.isRequired,
labelKey: PropTypes.string.isRequired,
newNodeAttributes: PropTypes.object.isRequired,
+ newNodeModelData: PropTypes.object.isRequired,
nodesForPrompt: PropTypes.array.isRequired,
nodeIconName: PropTypes.string.isRequired,
nodeType: PropTypes.string.isRequired,
@@ -133,19 +135,26 @@ NameGeneratorAutoComplete.propTypes = {
function mapDispatchToProps(dispatch) {
return {
- addNodes: bindActionCreators(sessionsActions.addNodes, dispatch),
+ batchAddNodes: bindActionCreators(sessionsActions.batchAddNodes, dispatch),
closeSearch: bindActionCreators(searchActions.closeSearch, dispatch),
toggleSearch: bindActionCreators(searchActions.toggleSearch, dispatch),
};
}
function makeMapStateToProps() {
+ const networkNodesForPrompt = makeNetworkNodesForPrompt();
+ const getPromptNodeAttributes = makeGetAdditionalAttributes();
+ const getPromptNodeModelData = makeGetPromptNodeModelData();
+ const getNodeType = makeGetSubjectType();
+ const getNodeIconName = makeGetNodeIconName();
+
return function mapStateToProps(state, props) {
return {
excludedNodes: networkNodes(state, props),
getLabel: getNodeLabelFunction(state),
labelKey: getCardDisplayLabel(state, props),
newNodeAttributes: getPromptNodeAttributes(state, props),
+ newNodeModelData: getPromptNodeModelData(state, props),
nodeIconName: getNodeIconName(state, props),
nodesForPrompt: networkNodesForPrompt(state, props),
nodeType: getNodeType(state, props),
diff --git a/src/containers/Interfaces/NameGeneratorList.js b/src/containers/Interfaces/NameGeneratorList.js
index 2c8ea50251..d4a1702cca 100644
--- a/src/containers/Interfaces/NameGeneratorList.js
+++ b/src/containers/Interfaces/NameGeneratorList.js
@@ -2,19 +2,19 @@ import React, { Component } from 'react';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
-import { differenceBy } from 'lodash';
+import { differenceBy, omit } from 'lodash';
import withPrompt from '../../behaviours/withPrompt';
import { actionCreators as sessionsActions } from '../../ducks/modules/sessions';
-import { nodePrimaryKeyProperty, getNodeAttributes } from '../../ducks/modules/network';
-import { makeNetworkNodesForOtherPrompts, networkNodes } from '../../selectors/interface';
+import { nodePrimaryKeyProperty, getNodeAttributes, nodeAttributesProperty } from '../../ducks/modules/network';
+import { makeNetworkNodesForOtherPrompts, networkNodes, makeGetAdditionalAttributes } from '../../selectors/interface';
import {
getDataByPrompt,
getCardDisplayLabel,
getCardAdditionalProperties,
getSortableFields,
- makeGetPromptNodeAttributes,
getInitialSortOrder,
+ makeGetPromptNodeModelData,
} from '../../selectors/name-generator';
import { PromptSwiper } from '../../containers';
import { ListSelect } from '../../components';
@@ -37,7 +37,15 @@ class NameGeneratorList extends Component {
* Select node submit handler
*/
onSubmitNewNode = (node) => {
- this.props.addNode({ ...node }, { ...this.props.newNodeAttributes });
+ const attributeData = {
+ ...this.props.newNodeAttributes,
+ ...node[nodeAttributesProperty],
+ };
+ const modelData = {
+ ...this.props.newNodeModelData,
+ ...omit(node, nodeAttributesProperty),
+ };
+ this.props.addNode(modelData, attributeData);
}
onRemoveNode = (node) => {
@@ -101,6 +109,7 @@ NameGeneratorList.propTypes = {
initialSortOrder: PropTypes.array.isRequired,
labelKey: PropTypes.string.isRequired,
newNodeAttributes: PropTypes.object.isRequired,
+ newNodeModelData: PropTypes.object.isRequired,
nodesForList: PropTypes.array.isRequired,
prompt: PropTypes.object.isRequired,
promptForward: PropTypes.func.isRequired,
@@ -112,15 +121,9 @@ NameGeneratorList.propTypes = {
visibleSupplementaryFields: PropTypes.array.isRequired,
};
-// NameGeneratorList.defaultProps = {
-// initialSortOrder: [{
-// property: '',
-// direction: 'asc',
-// }],
-// };
-
function makeMapStateToProps() {
- const getPromptNodeAttributes = makeGetPromptNodeAttributes();
+ const getPromptNodeAttributes = makeGetAdditionalAttributes();
+ const getPromptNodeModelData = makeGetPromptNodeModelData();
const networkNodesForOtherPrompts = makeNetworkNodesForOtherPrompts();
return function mapStateToProps(state, props) {
@@ -135,6 +138,7 @@ function makeMapStateToProps() {
return {
labelKey: getCardDisplayLabel(state, props),
newNodeAttributes: getPromptNodeAttributes(state, props),
+ newNodeModelData: getPromptNodeModelData(state, props),
nodesForList,
initialSortOrder: getInitialSortOrder(state, props),
selectedNodes: networkNodes(state),
@@ -146,7 +150,7 @@ function makeMapStateToProps() {
function mapDispatchToProps(dispatch) {
return {
- addNode: bindActionCreators(sessionsActions.addNodes, dispatch),
+ addNode: bindActionCreators(sessionsActions.addNode, dispatch),
removeNode: bindActionCreators(sessionsActions.removeNode, dispatch),
};
}
diff --git a/src/containers/Interfaces/OrdinalBin.js b/src/containers/Interfaces/OrdinalBin.js
index 6c50ab2c6e..f136d6a0f7 100644
--- a/src/containers/Interfaces/OrdinalBin.js
+++ b/src/containers/Interfaces/OrdinalBin.js
@@ -1,12 +1,11 @@
import React from 'react';
-import { bindActionCreators, compose } from 'redux';
+import { compose } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import withPrompt from '../../behaviours/withPrompt';
import { PromptSwiper, OrdinalBins } from '../';
import { makeGetPromptVariable, makeNetworkNodesForType } from '../../selectors/interface';
import { MultiNodeBucket } from '../../components';
-import { actionCreators as sessionsActions } from '../../ducks/modules/sessions';
import { nodeAttributesProperty } from '../../ducks/modules/network';
/**
@@ -72,13 +71,7 @@ function makeMapStateToProps() {
};
}
-function mapDispatchToProps(dispatch) {
- return {
- updateNode: bindActionCreators(sessionsActions.updateNode, dispatch),
- };
-}
-
export default compose(
withPrompt,
- connect(makeMapStateToProps, mapDispatchToProps),
+ connect(makeMapStateToProps),
)(OrdinalBin);
diff --git a/src/containers/Interfaces/__tests__/NameGenerator.test.js b/src/containers/Interfaces/__tests__/NameGenerator.test.js
index 3b5cdf477e..01075b8ee0 100644
--- a/src/containers/Interfaces/__tests__/NameGenerator.test.js
+++ b/src/containers/Interfaces/__tests__/NameGenerator.test.js
@@ -5,7 +5,7 @@ import { shallow } from 'enzyme';
import { UnconnectedNameGenerator as NameGenerator } from '../NameGenerator';
const requiredProps = {
- addNodes: jest.fn(),
+ addNode: jest.fn(),
getLabel: jest.fn(),
newNodeAttributes: {},
nodesForPrompt: [],
diff --git a/src/containers/MainMenu/__tests__/MainMenu.test.js b/src/containers/MainMenu/__tests__/MainMenu.test.js
index 584dbebdb9..5ca65f765e 100644
--- a/src/containers/MainMenu/__tests__/MainMenu.test.js
+++ b/src/containers/MainMenu/__tests__/MainMenu.test.js
@@ -130,8 +130,7 @@ describe('', () => {
it('Mock data button', () => {
subject.find('Button[children="Add mock nodes"]').at(0).simulate('click');
- expect(actions.filter(({ type }) => type === 'ADD_NODES')).toHaveLength(1);
- expect(actions.filter(({ type }) => type === 'ADD_NODES')[0].nodes).toHaveLength(20);
+ expect(actions.filter(({ type }) => type === 'ADD_NODE')).toHaveLength(20);
expect(isMenuOpen(subject)).toBe(false);
});
});
diff --git a/src/containers/NodePanels.js b/src/containers/NodePanels.js
index c3492b2090..52f9ea1a12 100644
--- a/src/containers/NodePanels.js
+++ b/src/containers/NodePanels.js
@@ -3,11 +3,11 @@ import { compose, bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { includes, map, differenceBy } from 'lodash';
-import { networkNodes, makeNetworkNodesForOtherPrompts } from '../selectors/interface';
+import { networkNodes, makeNetworkNodesForOtherPrompts, makeGetAdditionalAttributes } from '../selectors/interface';
import { getExternalData } from '../selectors/externalData';
import { actionCreators as sessionsActions } from '../ducks/modules/sessions';
import { nodePrimaryKeyProperty } from '../ducks/modules/network';
-import { makeGetPromptNodeAttributes, makeGetPanelConfiguration } from '../selectors/name-generator';
+import { makeGetPanelConfiguration } from '../selectors/name-generator';
import { Panel, Panels, NodeList } from '../components/';
import { getCSSVariableAsString } from '../ui/utils/CSSVariables';
import { MonitorDragSource } from '../behaviours/DragAndDrop';
@@ -17,7 +17,6 @@ import { MonitorDragSource } from '../behaviours/DragAndDrop';
*/
class NodePanels extends PureComponent {
static propTypes = {
- activePromptAttributes: PropTypes.object,
isDragging: PropTypes.bool,
meta: PropTypes.object,
panels: PropTypes.array,
@@ -25,11 +24,10 @@ class NodePanels extends PureComponent {
newNodeAttributes: PropTypes.object.isRequired,
removeNode: PropTypes.func.isRequired,
stage: PropTypes.object,
- toggleNodeAttributes: PropTypes.func.isRequired,
+ removeNodeFromPrompt: PropTypes.func.isRequired,
};
static defaultProps = {
- activePromptAttributes: {},
isDragging: false,
meta: {},
panels: [],
@@ -40,13 +38,14 @@ class NodePanels extends PureComponent {
onDrop = ({ meta }, dataSource) => {
/**
* Handle a node being dropped into a panel
- *
- * If
+ * If this panel is showing the interview network, remove the node from the current prompt.
+ * If it is an external data panel, remove the node form the interview network.
*/
if (dataSource === 'existing') {
- this.props.toggleNodeAttributes(
+ this.props.removeNodeFromPrompt(
meta[nodePrimaryKeyProperty],
- { ...this.props.activePromptAttributes },
+ this.props.prompt.id,
+ this.props.newNodeAttributes,
);
} else {
this.props.removeNode(meta[nodePrimaryKeyProperty]);
@@ -116,12 +115,17 @@ class NodePanels extends PureComponent {
}
}
-const getNodesForDataSource = ({ nodes, existingNodes, externalData, dataSource }) => (
+/**
+ *
+ * @param {array} nodes - all network nodes
+ *
+ */
+const getNodesForDataSource = ({ sessionNodes, otherPromptNodes, externalData, dataSource }) => (
dataSource === 'existing' ?
- existingNodes :
+ otherPromptNodes :
differenceBy(
- externalData[dataSource] && externalData[dataSource].nodes,
- nodes,
+ externalData[dataSource].nodes,
+ sessionNodes,
nodePrimaryKeyProperty,
)
);
@@ -133,13 +137,13 @@ const getOriginNodeIds = ({ existingNodes, externalData, dataSource }) => (
);
function makeMapStateToProps() {
- const getPromptNodeAttributes = makeGetPromptNodeAttributes();
- const networkNodesForOtherPrompts = makeNetworkNodesForOtherPrompts();
+ const getPromptNodeAttributes = makeGetAdditionalAttributes();
const getPanelConfiguration = makeGetPanelConfiguration();
+ const getNetworkNodesForOtherPrompts = makeNetworkNodesForOtherPrompts();
return function mapStateToProps(state, props) {
const allNodes = networkNodes(state);
- const existingNodes = networkNodesForOtherPrompts(state, props);
+ const existingNodes = getNetworkNodesForOtherPrompts(state, props);
const externalData = getExternalData(state);
const newNodeAttributes = getPromptNodeAttributes(state, props);
@@ -152,18 +156,18 @@ function makeMapStateToProps() {
});
const nodes = getNodesForDataSource({
- nodes: allNodes,
- existingNodes,
+ sessionNodes: allNodes,
+ otherPromptNodes: existingNodes,
externalData,
dataSource: panel.dataSource,
});
const accepts = (panel.dataSource === 'existing') ?
({ meta }) => (
- meta.itemType === 'EXISTING_NODE' &&
- (meta.stageId !== newNodeAttributes.stageId ||
- meta.promptId !== newNodeAttributes.promptId)
+ // existing network node
+ meta.itemType === 'EXISTING_NODE'
) : ({ meta }) => (
+ // external data
meta.itemType === 'EXISTING_NODE' &&
includes(originNodeIds, meta[nodePrimaryKeyProperty])
);
@@ -177,7 +181,7 @@ function makeMapStateToProps() {
});
return {
- activePromptAttributes: props.prompt.additionalAttributes,
+ activePromptId: props.prompt.id,
newNodeAttributes,
panels,
};
@@ -186,7 +190,7 @@ function makeMapStateToProps() {
function mapDispatchToProps(dispatch) {
return {
- toggleNodeAttributes: bindActionCreators(sessionsActions.toggleNodeAttributes, dispatch),
+ removeNodeFromPrompt: bindActionCreators(sessionsActions.removeNodeFromPrompt, dispatch),
removeNode: bindActionCreators(sessionsActions.removeNode, dispatch),
};
}
diff --git a/src/containers/OrdinalBins.js b/src/containers/OrdinalBins.js
index 9167688c49..067b4a4df4 100644
--- a/src/containers/OrdinalBins.js
+++ b/src/containers/OrdinalBins.js
@@ -16,7 +16,7 @@ class OrdinalBins extends PureComponent {
bins: PropTypes.array.isRequired,
prompt: PropTypes.object.isRequired,
stage: PropTypes.object.isRequired,
- toggleNodeAttributes: PropTypes.func.isRequired,
+ updateNode: PropTypes.func.isRequired,
};
static defaultProps = {
@@ -61,9 +61,11 @@ class OrdinalBins extends PureComponent {
return;
}
- const newValue = {};
- newValue[this.props.activePromptVariable] = bin.value;
- this.props.toggleNodeAttributes(meta[nodePrimaryKeyProperty], newValue);
+ this.props.updateNode(
+ meta[nodePrimaryKeyProperty],
+ {},
+ { [this.props.activePromptVariable]: bin.value },
+ );
};
const accentColor = this.calculateAccentColor(index, missingValue);
@@ -128,7 +130,7 @@ function makeMapStateToProps() {
function mapDispatchToProps(dispatch) {
return {
- toggleNodeAttributes: bindActionCreators(sessionsActions.toggleNodeAttributes, dispatch),
+ updateNode: bindActionCreators(sessionsActions.updateNode, dispatch),
};
}
diff --git a/src/containers/Search/Search.js b/src/containers/Search/Search.js
index 6a4dd71033..b07a7d5470 100644
--- a/src/containers/Search/Search.js
+++ b/src/containers/Search/Search.js
@@ -170,7 +170,7 @@ class Search extends Component {
className={searchClasses}
in={!collapsed}
>
-