From 470e04f4cb45b66a9a10a774ea524f7d7d015dd0 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Fri, 4 Jan 2019 15:41:15 +0000 Subject: [PATCH 01/22] update UI ref --- src/ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui b/src/ui index 23ed556e65..42cef8284f 160000 --- a/src/ui +++ b/src/ui @@ -1 +1 @@ -Subproject commit 23ed556e65b2ef0d5f29c949dfd4c689641fbce2 +Subproject commit 42cef8284fb2644b77c2ace66e5ea69111cfb37a From b7261390e62eb18c1b9dc47bd2f69d0f5dfbe5a1 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Fri, 4 Jan 2019 20:16:00 +0000 Subject: [PATCH 02/22] fix prompt selectors for name generator --- src/selectors/interface.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/selectors/interface.js b/src/selectors/interface.js index 4d0dfee18d..9f847416bd 100644 --- a/src/selectors/interface.js +++ b/src/selectors/interface.js @@ -166,8 +166,9 @@ export const makeNetworkNodesForType = () => /** * makeNetworkNodesForPrompt - * Take the "additional attributes" specified by the current prompt, and filter nodes of the current - * prompt type + * + * Return a filtered node list containing only nodes that have both the additional attributes + * specified for this prompt, AND the current promptId. */ export const makeNetworkNodesForPrompt = () => { @@ -175,15 +176,17 @@ export const makeNetworkNodesForPrompt = () => { const networkNodesForSubject = makeNetworkNodesForType(); return createSelector( - networkNodesForSubject, getAttributes, - (nodes, attributes) => - filter(nodes, { [nodeAttributesProperty]: attributes }), + networkNodesForSubject, getAttributes, propPromptId, + (nodes, attributes, promptId) => + filter(nodes, { [nodeAttributesProperty]: attributes, promptId }), ); }; /** * makeNetworkNodesForOtherPrompts() - * Same as above, except returns a filtered node list that **excludes** nodes that match. + * + * Same as above, except returns a filtered node list that **excludes** nodes that match the current + * prompt's additional attributes or promptId. */ export const makeNetworkNodesForOtherPrompts = () => { @@ -192,8 +195,9 @@ export const makeNetworkNodesForOtherPrompts = () => { const networkNodesForSubject = makeNetworkNodesForType(); return createSelector( - networkNodesForSubject, getAttributes, - (nodes, attributes) => - reject(nodes, node => isMatch(getNodeAttributes(node), attributes)), + networkNodesForSubject, getAttributes, propPromptId, + (nodes, attributes, promptId) => + reject(nodes, node => + isMatch(getNodeAttributes(node), attributes) || node.promptId === promptId), ); }; From 84c9afd3f86d6f0fc15c82f30ad7224ce56f5a14 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Wed, 9 Jan 2019 13:24:08 +0000 Subject: [PATCH 03/22] fix linting on UI --- src/ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui b/src/ui index 42cef8284f..ca8243cd6f 160000 --- a/src/ui +++ b/src/ui @@ -1 +1 @@ -Subproject commit 42cef8284fb2644b77c2ace66e5ea69111cfb37a +Subproject commit ca8243cd6f612ed479ed82f988925722f1c363eb From 0b281f8f9aad0d184d82ebb33546cfe98d6c0340 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Thu, 10 Jan 2019 00:39:15 +0000 Subject: [PATCH 04/22] refactor add node --- .../development.netcanvas/protocol.json | 9 ++-- src/containers/Interfaces/NameGenerator.js | 33 ++++++++++---- .../Interfaces/NameGeneratorAutoComplete.js | 14 +++--- .../Interfaces/NameGeneratorList.js | 5 +-- src/containers/NodePanels.js | 6 +-- src/ducks/modules/network.js | 45 ++++++++++++++++++- src/ducks/modules/sessions.js | 16 ++++++- .../__tests__/name-generator.test.js | 2 +- src/selectors/interface.js | 14 +++--- src/selectors/name-generator.js | 16 ++----- 10 files changed, 114 insertions(+), 46 deletions(-) diff --git a/public/protocols/development.netcanvas/protocol.json b/public/protocols/development.netcanvas/protocol.json index 2f89444684..40ae9e526d 100644 --- a/public/protocols/development.netcanvas/protocol.json +++ b/public/protocols/development.netcanvas/protocol.json @@ -1134,10 +1134,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": "2we", + "text": "Second prompt with no additional attributes" } ] }, diff --git a/src/containers/Interfaces/NameGenerator.js b/src/containers/Interfaces/NameGenerator.js index b5a8606b82..1769f94c76 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 } from '../../ducks/modules/network'; /** * Name Generator Interface @@ -31,7 +32,14 @@ class NameGenerator extends Component { handleSubmitForm = ({ form, addAnotherNode } = { addAnotherNode: false }) => { if (form) { if (!this.state.selectedNode) { - this.props.addNodes({ attributes: { ...form } }, this.props.newNodeAttributes); + /** + * Desired interface for addNode(): + * addNode(modelData: {promptID, stageID}, attributes); + */ + + this.props.addNode( + this.props.newNodeModelData, { ...this.props.newNodeAttributes, ...form }); + // this.props.addNodes({ attributes: { ...form } }, this.props.newNodeAttributes); } else { this.props.updateNode({ ...this.state.selectedNode }, form); } @@ -51,7 +59,13 @@ class NameGenerator extends Component { if (has(node, 'promptId') || has(node, 'stageId')) { this.props.updateNode(node, { ...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 +169,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 +184,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 +201,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..3267c22edf 100644 --- a/src/containers/Interfaces/NameGeneratorAutoComplete.js +++ b/src/containers/Interfaces/NameGeneratorAutoComplete.js @@ -10,16 +10,11 @@ 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 } 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 @@ -140,6 +135,11 @@ function mapDispatchToProps(dispatch) { } function makeMapStateToProps() { + const networkNodesForPrompt = makeNetworkNodesForPrompt(); + const getPromptNodeAttributes = makeGetAdditionalAttributes(); + const getNodeType = makeGetSubjectType(); + const getNodeIconName = makeGetNodeIconName(); + return function mapStateToProps(state, props) { return { excludedNodes: networkNodes(state, props), diff --git a/src/containers/Interfaces/NameGeneratorList.js b/src/containers/Interfaces/NameGeneratorList.js index 2c8ea50251..41f86c3044 100644 --- a/src/containers/Interfaces/NameGeneratorList.js +++ b/src/containers/Interfaces/NameGeneratorList.js @@ -7,13 +7,12 @@ import { differenceBy } 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 { makeNetworkNodesForOtherPrompts, networkNodes, makeGetAdditionalAttributes } from '../../selectors/interface'; import { getDataByPrompt, getCardDisplayLabel, getCardAdditionalProperties, getSortableFields, - makeGetPromptNodeAttributes, getInitialSortOrder, } from '../../selectors/name-generator'; import { PromptSwiper } from '../../containers'; @@ -120,7 +119,7 @@ NameGeneratorList.propTypes = { // }; function makeMapStateToProps() { - const getPromptNodeAttributes = makeGetPromptNodeAttributes(); + const getPromptNodeAttributes = makeGetAdditionalAttributes(); const networkNodesForOtherPrompts = makeNetworkNodesForOtherPrompts(); return function mapStateToProps(state, props) { diff --git a/src/containers/NodePanels.js b/src/containers/NodePanels.js index c3492b2090..070025b0cc 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'; @@ -133,7 +133,7 @@ const getOriginNodeIds = ({ existingNodes, externalData, dataSource }) => ( ); function makeMapStateToProps() { - const getPromptNodeAttributes = makeGetPromptNodeAttributes(); + const getPromptNodeAttributes = makeGetAdditionalAttributes(); const networkNodesForOtherPrompts = makeNetworkNodesForOtherPrompts(); const getPanelConfiguration = makeGetPanelConfiguration(); diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index afb219152d..b6dff5e261 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -12,6 +12,7 @@ export const nodeAttributesProperty = 'attributes'; export const primaryKeyPropertyForWorker = 'networkCanvasId'; export const nodeTypePropertyForWorker = 'networkCanvasType'; +export const ADD_NODE = 'ADD_NODE'; export const ADD_NODES = 'ADD_NODES'; export const REMOVE_NODE = 'REMOVE_NODE'; export const UPDATE_NODE = 'UPDATE_NODE'; @@ -47,7 +48,7 @@ export const getNodeAttributes = node => node[nodeAttributesProperty] || {}; /** * existingNodes - Existing network.nodes - * netNodes - nodes to be added to the network + * newNodes - nodes to be added to the network * additionalProperties - static props shared to add to each member of newNodes */ function getNodesWithBatchAdd(existingNodes, newNodes, additionalProperties = {}) { @@ -65,6 +66,39 @@ function getNodesWithBatchAdd(existingNodes, newNodes, additionalProperties = {} return existingNodes.concat(newNodes.map(withModelandAttributeData)); } + +/** + * existingNodes - Existing network.nodes + * newNodes - nodes to be added to the network + * additionalProperties - static props shared to add to each member of newNodes +*/ +function getNewNodeList(existingNodes, modelData, attributeData) { + console.log(existingNodes); + console.log(modelData); + console.log(attributeData); + + const { + itemType, + type, + promptId, + stageId, + } = modelData; + + const withModelandAttributeData = { + [nodePrimaryKeyProperty]: uuidv4(), + [nodeAttributesProperty]: attributeData, + promptIDs: [promptId], + stageId, + type, + itemType, + }; + + console.log(withModelandAttributeData); + + return existingNodes.concat(withModelandAttributeData); +} + + /** * @param {Array} nodes - the current state.nodes * @param {Object} updatingNode - the node to be updated. Will match on _uid. @@ -98,6 +132,15 @@ function getUpdatedNodes(nodes, updatingNode, nodeAttributeData = null) { export default function reducer(state = initialState, action = {}) { switch (action.type) { + /** + * Add single node using new syntax (modelData, attributeData) + */ + case ADD_NODE: { + return { + ...state, + nodes: getNewNodeList(state.nodes, action.modelData, action.attributeData), + }; + } case ADD_NODES: { return { ...state, diff --git a/src/ducks/modules/sessions.js b/src/ducks/modules/sessions.js index 6011ba01ca..52e9ece3ca 100644 --- a/src/ducks/modules/sessions.js +++ b/src/ducks/modules/sessions.js @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { combineEpics } from 'redux-observable'; import uuidv4 from '../../utils/uuid'; -import network, { nodePrimaryKeyProperty, ADD_NODES, REMOVE_NODE, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, ADD_EDGE, TOGGLE_EDGE, REMOVE_EDGE, SET_EGO, UNSET_EGO } from './network'; +import network, { nodePrimaryKeyProperty, ADD_NODE, ADD_NODES, REMOVE_NODE, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, ADD_EDGE, TOGGLE_EDGE, REMOVE_EDGE, SET_EGO, UNSET_EGO } from './network'; import ApiClient from '../../utils/ApiClient'; import { protocolIdFromSessionPath } from '../../utils/matchSessionPath'; @@ -24,6 +24,7 @@ const withTimestamp = session => ({ export default function reducer(state = initialState, action = {}) { switch (action.type) { + case ADD_NODE: case ADD_NODES: case REMOVE_NODE: case UPDATE_NODE: @@ -108,6 +109,17 @@ const addNodes = (nodes, additionalProperties) => (dispatch, getState) => { }); }; +const addNode = (modelData, attributeData) => (dispatch, getState) => { + const { session } = getState(); + + dispatch({ + type: ADD_NODE, + sessionId: session, + modelData, + attributeData, + }); +}; + const updateNode = (node, additionalProperties = null) => (dispatch, getState) => { const { session } = getState(); @@ -245,6 +257,7 @@ const exportSessionEpic = (action$, store) => ( ); const actionCreators = { + addNode, addNodes, updateNode, removeNode, @@ -261,6 +274,7 @@ const actionCreators = { }; const actionTypes = { + ADD_NODE, ADD_NODES, REMOVE_NODE, UPDATE_NODE, diff --git a/src/selectors/__tests__/name-generator.test.js b/src/selectors/__tests__/name-generator.test.js index 8e661d5ad3..95af98728b 100644 --- a/src/selectors/__tests__/name-generator.test.js +++ b/src/selectors/__tests__/name-generator.test.js @@ -116,7 +116,7 @@ describe('name generator selector', () => { }); describe('memoed selectors', () => { it('should get node attributes for the prompt', () => { - const selected = NameGen.makeGetPromptNodeAttributes(); + const selected = NameGen.makeGetAdditionalAttributes(); expect(selected(mockState, mockProps)).toEqual({ attributes: { close_friend: true }, type: 'person', diff --git a/src/selectors/interface.js b/src/selectors/interface.js index 9f847416bd..73bee8f9f9 100644 --- a/src/selectors/interface.js +++ b/src/selectors/interface.js @@ -1,14 +1,13 @@ /* eslint-disable import/prefer-default-export */ import { createSelector } from 'reselect'; -import { findKey, filter, isMatch, reject } from 'lodash'; +import { findKey, filter, isMatch, reject, includes } from 'lodash'; import { assert, createDeepEqualSelector } from './utils'; import { protocolRegistry } from './protocol'; import { getAdditionalAttributes, getSubject } from '../utils/protocol/accessors'; import { getCurrentSession } from './session'; import { getNodeAttributes, - nodeAttributesProperty, } from '../ducks/modules/network'; import { asExportableNetwork, @@ -60,6 +59,7 @@ export const getWorkerNetwork = createDeepEqualSelector( (network, registry) => asWorkerAgentNetwork(network, registry), ); +// Returns current stage and prompt ID export const makeGetIds = () => createSelector( propStageId, propPromptId, @@ -172,13 +172,15 @@ export const makeNetworkNodesForType = () => */ export const makeNetworkNodesForPrompt = () => { - const getAttributes = makeGetAdditionalAttributes(); const networkNodesForSubject = makeNetworkNodesForType(); return createSelector( - networkNodesForSubject, getAttributes, propPromptId, - (nodes, attributes, promptId) => - filter(nodes, { [nodeAttributesProperty]: attributes, promptId }), + networkNodesForSubject, propPromptId, + (nodes, promptId) => + filter(nodes, (node) => { + console.log(includes(node.promptIDs, promptId)); + return includes(node.promptIDs, promptId); + }), ); }; diff --git a/src/selectors/name-generator.js b/src/selectors/name-generator.js index 2e0bebabfa..baff51fa29 100644 --- a/src/selectors/name-generator.js +++ b/src/selectors/name-generator.js @@ -2,8 +2,7 @@ import { createSelector } from 'reselect'; import { has } from 'lodash'; -import { makeGetSubject, makeGetIds, makeGetSubjectType, makeGetAdditionalAttributes } from './interface'; -import { nodeAttributesProperty } from '../ducks/modules/network'; +import { makeGetSubject, makeGetIds, makeGetSubjectType } from './interface'; import { protocolRegistry } from './protocol'; import { getExternalData } from './externalData'; @@ -28,24 +27,17 @@ const propCardOptions = (_, props) => props.prompt.cardOptions; const propSortOptions = (_, props) => props.prompt.sortOptions; const propPanels = (_, props) => props.stage.panels; -// Static props that will be added to any created/edited node on the prompt -// Any protocol-specific props will exist in the [nodeAttributesProperty] object -export const makeGetPromptNodeAttributes = () => { +// blah! +export const makeGetPromptNodeModelData = () => { const getSubject = makeGetSubject(); const getIds = makeGetIds(); - const getAdditionalAttributes = makeGetAdditionalAttributes(); return createSelector( getSubject, getIds, - getAdditionalAttributes - , - ({ type }, ids, additionalAttributes) => ({ + ({ type }, ids) => ({ type, ...ids, - [nodeAttributesProperty]: { - ...additionalAttributes, - }, }), ); }; From ad75068fd10cb469be9c1879b4fa399a622c3368 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Thu, 10 Jan 2019 02:05:34 +0000 Subject: [PATCH 05/22] refactor update node --- src/containers/Interfaces/NameGenerator.js | 23 ++++++---- src/ducks/modules/network.js | 50 +++++++++++----------- src/ducks/modules/sessions.js | 7 +-- src/selectors/interface.js | 6 +-- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/containers/Interfaces/NameGenerator.js b/src/containers/Interfaces/NameGenerator.js index 1769f94c76..f6e43c7ef9 100644 --- a/src/containers/Interfaces/NameGenerator.js +++ b/src/containers/Interfaces/NameGenerator.js @@ -10,7 +10,7 @@ import { makeGetPromptNodeModelData, makeGetNodeIconName } from '../../selectors import { PromptSwiper, NodePanels, NodeForm } from '../'; import { NodeList, NodeBin } from '../../components/'; import { Icon } from '../../ui/components'; -import { nodeAttributesProperty } from '../../ducks/modules/network'; +import { nodeAttributesProperty, nodePrimaryKeyProperty } from '../../ducks/modules/network'; /** * Name Generator Interface @@ -33,15 +33,18 @@ class NameGenerator extends Component { if (form) { if (!this.state.selectedNode) { /** - * Desired interface for addNode(): - * addNode(modelData: {promptID, stageID}, attributes); + * addNode(modelData, attributeData); */ - this.props.addNode( - this.props.newNodeModelData, { ...this.props.newNodeAttributes, ...form }); - // this.props.addNodes({ attributes: { ...form } }, this.props.newNodeAttributes); + 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); } } @@ -57,7 +60,11 @@ class NameGenerator extends Component { 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 }); + this.props.updateNode( + node[nodePrimaryKeyProperty], + { ...this.props.newNodeModelData }, + { ...this.props.activePromptAttributes }, + ); } else { const droppedAttributeData = node[nodeAttributesProperty]; const droppedModelData = omit(node, nodeAttributesProperty); diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index b6dff5e261..e9300ee76e 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -1,4 +1,4 @@ -import { reject, findIndex, isMatch, omit } from 'lodash'; +import { reject, findIndex, isMatch, omit, merge, has, concat } from 'lodash'; import uuidv4 from '../../utils/uuid'; @@ -69,14 +69,10 @@ function getNodesWithBatchAdd(existingNodes, newNodes, additionalProperties = {} /** * existingNodes - Existing network.nodes - * newNodes - nodes to be added to the network - * additionalProperties - static props shared to add to each member of newNodes + * modelData + * attributeData */ function getNewNodeList(existingNodes, modelData, attributeData) { - console.log(existingNodes); - console.log(modelData); - console.log(attributeData); - const { itemType, type, @@ -93,39 +89,38 @@ function getNewNodeList(existingNodes, modelData, attributeData) { itemType, }; - console.log(withModelandAttributeData); - return existingNodes.concat(withModelandAttributeData); } /** - * @param {Array} nodes - the current state.nodes - * @param {Object} updatingNode - the node to be updated. Will match on _uid. - * @param {Object} nodeAttributeData - additional attributes to update the node with. + * @param {Array} existingNodes - the current state.nodes + * @param {Object} nodeID - the node to be updated. Will match on _uid. + * @param {Object} newModelData - + * @param {Object} newAttributeData - additional attributes to update the node with. * If null, then the updatingNode's `attributes` property * will overwrite the original node's. Use this to perform * a 'full' update, but ensure the entire updated node is * passed as `updatingNode`. */ -function getUpdatedNodes(nodes, updatingNode, nodeAttributeData = null) { - return nodes.map((node) => { - if (node[nodePrimaryKeyProperty] !== updatingNode[nodePrimaryKeyProperty]) { return node; } +function getUpdatedNodes(existingNodes, nodeID, newModelData, newAttributeData) { + return existingNodes.map((node) => { + if (node[nodePrimaryKeyProperty] !== nodeID) { return node; } - const updatedNode = { + let updatedNode = { ...node, - ...updatingNode, - [nodePrimaryKeyProperty]: node[nodePrimaryKeyProperty], }; - if (nodeAttributeData) { - updatedNode[nodeAttributesProperty] = { - ...node[nodeAttributesProperty], - ...updatingNode[nodeAttributesProperty], - ...nodeAttributeData, - }; + if (has(newModelData, 'promptId')) { + updatedNode.promptIDs = concat(node.promptIDs, newModelData.promptId); } + // Merge new model data + updatedNode = merge(updatedNode, omit(newModelData, 'promptId')); + + // Merge mew attribute data + updatedNode[nodeAttributesProperty] = merge(node[nodeAttributesProperty], newAttributeData); + console.log(updatedNode); return updatedNode; }); } @@ -181,7 +176,12 @@ export default function reducer(state = initialState, action = {}) { case UPDATE_NODE: { return { ...state, - nodes: getUpdatedNodes(state.nodes, action.node, action.additionalProperties), + nodes: getUpdatedNodes( + state.nodes, + action.nodeID, + action.newModelData, + action.newAttributeData, + ), }; } case REMOVE_NODE: { diff --git a/src/ducks/modules/sessions.js b/src/ducks/modules/sessions.js index 52e9ece3ca..a98ecc8598 100644 --- a/src/ducks/modules/sessions.js +++ b/src/ducks/modules/sessions.js @@ -120,14 +120,15 @@ const addNode = (modelData, attributeData) => (dispatch, getState) => { }); }; -const updateNode = (node, additionalProperties = null) => (dispatch, getState) => { +const updateNode = (nodeID, newModelData, newAttributeData = null) => (dispatch, getState) => { const { session } = getState(); dispatch({ type: UPDATE_NODE, sessionId: session, - node, - additionalProperties, + nodeID, + newModelData, + newAttributeData, }); }; diff --git a/src/selectors/interface.js b/src/selectors/interface.js index 73bee8f9f9..3dced53a59 100644 --- a/src/selectors/interface.js +++ b/src/selectors/interface.js @@ -176,11 +176,7 @@ export const makeNetworkNodesForPrompt = () => { return createSelector( networkNodesForSubject, propPromptId, - (nodes, promptId) => - filter(nodes, (node) => { - console.log(includes(node.promptIDs, promptId)); - return includes(node.promptIDs, promptId); - }), + (nodes, promptId) => filter(nodes, node => includes(node.promptIDs, promptId)), ); }; From 90334bda1d8c08e76c482edf0f613b7f930fcdc2 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Thu, 10 Jan 2019 04:59:27 +0000 Subject: [PATCH 06/22] ensure UID from external data is preseved --- .../development.netcanvas/protocol.json | 2 +- src/containers/Interfaces/NameGenerator.js | 2 +- src/containers/NodePanels.js | 30 +++++++++++-------- src/ducks/modules/network.js | 5 ++-- src/selectors/interface.js | 12 +++----- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/public/protocols/development.netcanvas/protocol.json b/public/protocols/development.netcanvas/protocol.json index 40ae9e526d..9453575caf 100644 --- a/public/protocols/development.netcanvas/protocol.json +++ b/public/protocols/development.netcanvas/protocol.json @@ -1137,7 +1137,7 @@ "text": "Prompt with no additional attributes" }, { - "id": "2we", + "id": "2wj", "text": "Second prompt with no additional attributes" } ] diff --git a/src/containers/Interfaces/NameGenerator.js b/src/containers/Interfaces/NameGenerator.js index f6e43c7ef9..8ac9dcad69 100644 --- a/src/containers/Interfaces/NameGenerator.js +++ b/src/containers/Interfaces/NameGenerator.js @@ -54,7 +54,7 @@ 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 }; diff --git a/src/containers/NodePanels.js b/src/containers/NodePanels.js index 070025b0cc..8cd5836464 100644 --- a/src/containers/NodePanels.js +++ b/src/containers/NodePanels.js @@ -7,7 +7,7 @@ import { networkNodes, makeNetworkNodesForOtherPrompts, makeGetAdditionalAttribu import { getExternalData } from '../selectors/externalData'; import { actionCreators as sessionsActions } from '../ducks/modules/sessions'; import { nodePrimaryKeyProperty } from '../ducks/modules/network'; -import { makeGetPanelConfiguration } from '../selectors/name-generator'; +import { makeGetPanelConfiguration, makeGetPromptNodeModelData } from '../selectors/name-generator'; import { Panel, Panels, NodeList } from '../components/'; import { getCSSVariableAsString } from '../ui/utils/CSSVariables'; import { MonitorDragSource } from '../behaviours/DragAndDrop'; @@ -116,12 +116,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, ) ); @@ -134,14 +139,15 @@ const getOriginNodeIds = ({ existingNodes, externalData, dataSource }) => ( function makeMapStateToProps() { const getPromptNodeAttributes = makeGetAdditionalAttributes(); - const networkNodesForOtherPrompts = makeNetworkNodesForOtherPrompts(); 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); + const newNodeModelData = makeGetPromptNodeModelData(state, props); const panels = getPanelConfiguration(state, props) .map((panel) => { @@ -152,8 +158,8 @@ function makeMapStateToProps() { }); const nodes = getNodesForDataSource({ - nodes: allNodes, - existingNodes, + sessionNodes: allNodes, + otherPromptNodes: existingNodes, externalData, dataSource: panel.dataSource, }); @@ -161,8 +167,8 @@ function makeMapStateToProps() { const accepts = (panel.dataSource === 'existing') ? ({ meta }) => ( meta.itemType === 'EXISTING_NODE' && - (meta.stageId !== newNodeAttributes.stageId || - meta.promptId !== newNodeAttributes.promptId) + (meta.stageId !== newNodeModelData.stageId || + meta.promptIDs !== newNodeModelData.promptId) ) : ({ meta }) => ( meta.itemType === 'EXISTING_NODE' && includes(originNodeIds, meta[nodePrimaryKeyProperty]) @@ -177,7 +183,7 @@ function makeMapStateToProps() { }); return { - activePromptAttributes: props.prompt.additionalAttributes, + activePromptId: props.prompt.id, newNodeAttributes, panels, }; diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index e9300ee76e..f664ea3852 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -81,7 +81,8 @@ function getNewNodeList(existingNodes, modelData, attributeData) { } = modelData; const withModelandAttributeData = { - [nodePrimaryKeyProperty]: uuidv4(), + [nodePrimaryKeyProperty]: + modelData[nodePrimaryKeyProperty] ? modelData[nodePrimaryKeyProperty] : uuidv4(), [nodeAttributesProperty]: attributeData, promptIDs: [promptId], stageId, @@ -120,7 +121,7 @@ function getUpdatedNodes(existingNodes, nodeID, newModelData, newAttributeData) // Merge mew attribute data updatedNode[nodeAttributesProperty] = merge(node[nodeAttributesProperty], newAttributeData); - console.log(updatedNode); + return updatedNode; }); } diff --git a/src/selectors/interface.js b/src/selectors/interface.js index 3dced53a59..99e11e37cc 100644 --- a/src/selectors/interface.js +++ b/src/selectors/interface.js @@ -1,7 +1,7 @@ /* eslint-disable import/prefer-default-export */ import { createSelector } from 'reselect'; -import { findKey, filter, isMatch, reject, includes } from 'lodash'; +import { findKey, filter, includes } from 'lodash'; import { assert, createDeepEqualSelector } from './utils'; import { protocolRegistry } from './protocol'; import { getAdditionalAttributes, getSubject } from '../utils/protocol/accessors'; @@ -184,18 +184,14 @@ export const makeNetworkNodesForPrompt = () => { * makeNetworkNodesForOtherPrompts() * * Same as above, except returns a filtered node list that **excludes** nodes that match the current - * prompt's additional attributes or promptId. + * prompt's promptId. */ export const makeNetworkNodesForOtherPrompts = () => { - // used to check prompt ids - const getAttributes = makeGetAdditionalAttributes(); const networkNodesForSubject = makeNetworkNodesForType(); return createSelector( - networkNodesForSubject, getAttributes, propPromptId, - (nodes, attributes, promptId) => - reject(nodes, node => - isMatch(getNodeAttributes(node), attributes) || node.promptId === promptId), + networkNodesForSubject, propPromptId, + (nodes, promptId) => filter(nodes, node => !includes(node.promptIDs, promptId)), ); }; From 20f4135e6499ac48e29951b37cfa5e4221e01565 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Thu, 10 Jan 2019 08:52:26 +0000 Subject: [PATCH 07/22] implement remove node from prompt action --- src/containers/NodePanels.js | 12 +++++------- src/ducks/modules/network.js | 37 ++++++++++++++++++++++++++++++++++- src/ducks/modules/sessions.js | 17 +++++++++++++++- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/containers/NodePanels.js b/src/containers/NodePanels.js index 8cd5836464..37d3e9d4ca 100644 --- a/src/containers/NodePanels.js +++ b/src/containers/NodePanels.js @@ -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: [], @@ -41,12 +39,12 @@ class NodePanels extends PureComponent { /** * Handle a node being dropped into a panel * - * If */ 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]); @@ -192,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/ducks/modules/network.js b/src/ducks/modules/network.js index f664ea3852..aa38ce3cff 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -1,4 +1,4 @@ -import { reject, findIndex, isMatch, omit, merge, has, concat } from 'lodash'; +import { reject, findIndex, isMatch, omit, merge, has, concat, pull, keys } from 'lodash'; import uuidv4 from '../../utils/uuid'; @@ -15,6 +15,7 @@ export const nodeTypePropertyForWorker = 'networkCanvasType'; export const ADD_NODE = 'ADD_NODE'; export const ADD_NODES = 'ADD_NODES'; export const REMOVE_NODE = 'REMOVE_NODE'; +export const REMOVE_NODE_FROM_PROMPT = 'REMOVE_NODE_FROM_PROMPT'; export const UPDATE_NODE = 'UPDATE_NODE'; export const TOGGLE_NODE_ATTRIBUTES = 'TOGGLE_NODE_ATTRIBUTES'; export const ADD_EDGE = 'ADD_EDGE'; @@ -195,6 +196,40 @@ export default function reducer(state = initialState, action = {}) { edge.from === removenodePrimaryKeyProperty || edge.to === removenodePrimaryKeyProperty), }; } + case REMOVE_NODE_FROM_PROMPT: { + const getActionedNodeList = (existingNodes, nodeId, promptId, promptAttributes) => + existingNodes.map( + (node) => { + if (node[nodePrimaryKeyProperty] !== nodeId) { + return node; + } + + const updatedNode = { + ...node, + }; + + // Remove prompt attributes + updatedNode[nodeAttributesProperty] = omit( + node[nodeAttributesProperty], + keys(promptAttributes), + ); + + // Remove prompt ID from collection + updatedNode.promptIDs = pull(node.promptIDs, promptId); + + return updatedNode; + }); + + return { + ...state, + nodes: getActionedNodeList( + state.nodes, + action.nodeId, + action.promptId, + action.promptAttributes, + ), + }; + } case ADD_EDGE: if (edgeExists(state.edges, action.edge)) { return state; } return { diff --git a/src/ducks/modules/sessions.js b/src/ducks/modules/sessions.js index a98ecc8598..fd1fb6bc19 100644 --- a/src/ducks/modules/sessions.js +++ b/src/ducks/modules/sessions.js @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { combineEpics } from 'redux-observable'; import uuidv4 from '../../utils/uuid'; -import network, { nodePrimaryKeyProperty, ADD_NODE, ADD_NODES, REMOVE_NODE, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, ADD_EDGE, TOGGLE_EDGE, REMOVE_EDGE, SET_EGO, UNSET_EGO } from './network'; +import network, { nodePrimaryKeyProperty, ADD_NODE, ADD_NODES, REMOVE_NODE, REMOVE_NODE_FROM_PROMPT, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, ADD_EDGE, TOGGLE_EDGE, REMOVE_EDGE, SET_EGO, UNSET_EGO } from './network'; import ApiClient from '../../utils/ApiClient'; import { protocolIdFromSessionPath } from '../../utils/matchSessionPath'; @@ -27,6 +27,7 @@ export default function reducer(state = initialState, action = {}) { case ADD_NODE: case ADD_NODES: case REMOVE_NODE: + case REMOVE_NODE_FROM_PROMPT: case UPDATE_NODE: case TOGGLE_NODE_ATTRIBUTES: case ADD_EDGE: @@ -153,6 +154,18 @@ const removeNode = uid => (dispatch, getState) => { }); }; +const removeNodeFromPrompt = (nodeId, promptId, promptAttributes) => (dispatch, getState) => { + const { session } = getState(); + + dispatch({ + type: REMOVE_NODE_FROM_PROMPT, + sessionId: session, + nodeId, + promptId, + promptAttributes, + }); +}; + const addEdge = edge => (dispatch, getState) => { const { session } = getState(); @@ -262,6 +275,7 @@ const actionCreators = { addNodes, updateNode, removeNode, + removeNodeFromPrompt, addEdge, toggleEdge, removeEdge, @@ -278,6 +292,7 @@ const actionTypes = { ADD_NODE, ADD_NODES, REMOVE_NODE, + REMOVE_NODE_FROM_PROMPT, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, ADD_EDGE, From dea885af1cc1a97873d8c77ec13e4d2342ebc70e Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Thu, 10 Jan 2019 10:45:53 +0000 Subject: [PATCH 08/22] investigate node list not re-rendering --- src/containers/NodePanels.js | 4 ++-- src/selectors/interface.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/containers/NodePanels.js b/src/containers/NodePanels.js index 37d3e9d4ca..866e3c0b4b 100644 --- a/src/containers/NodePanels.js +++ b/src/containers/NodePanels.js @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { compose, bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { includes, map, differenceBy } from 'lodash'; +import { includes, map, differenceBy, has } from 'lodash'; import { networkNodes, makeNetworkNodesForOtherPrompts, makeGetAdditionalAttributes } from '../selectors/interface'; import { getExternalData } from '../selectors/externalData'; import { actionCreators as sessionsActions } from '../ducks/modules/sessions'; @@ -166,7 +166,7 @@ function makeMapStateToProps() { ({ meta }) => ( meta.itemType === 'EXISTING_NODE' && (meta.stageId !== newNodeModelData.stageId || - meta.promptIDs !== newNodeModelData.promptId) + !has(meta.promptIDs, newNodeModelData.promptId)) ) : ({ meta }) => ( meta.itemType === 'EXISTING_NODE' && includes(originNodeIds, meta[nodePrimaryKeyProperty]) diff --git a/src/selectors/interface.js b/src/selectors/interface.js index 99e11e37cc..7a4e0fa42d 100644 --- a/src/selectors/interface.js +++ b/src/selectors/interface.js @@ -173,7 +173,6 @@ export const makeNetworkNodesForType = () => export const makeNetworkNodesForPrompt = () => { const networkNodesForSubject = makeNetworkNodesForType(); - return createSelector( networkNodesForSubject, propPromptId, (nodes, promptId) => filter(nodes, node => includes(node.promptIDs, promptId)), From 1b66f87ebfad2cefba79b529229986eac262343e Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Fri, 11 Jan 2019 11:53:59 +0000 Subject: [PATCH 09/22] fix node add not triggering re-render --- src/ducks/modules/network.js | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index aa38ce3cff..083ef6c18c 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -1,4 +1,4 @@ -import { reject, findIndex, isMatch, omit, merge, has, concat, pull, keys } from 'lodash'; +import { reject, findIndex, isMatch, omit, merge, concat, pull, keys } from 'lodash'; import uuidv4 from '../../utils/uuid'; @@ -108,22 +108,12 @@ function getNewNodeList(existingNodes, modelData, attributeData) { function getUpdatedNodes(existingNodes, nodeID, newModelData, newAttributeData) { return existingNodes.map((node) => { if (node[nodePrimaryKeyProperty] !== nodeID) { return node; } - - let updatedNode = { + return { ...node, + ...omit(newModelData, 'promptId'), + promptIDs: concat(node.promptIDs, newModelData.promptId), + [nodeAttributesProperty]: merge(node[nodeAttributesProperty], newAttributeData), }; - - if (has(newModelData, 'promptId')) { - updatedNode.promptIDs = concat(node.promptIDs, newModelData.promptId); - } - - // Merge new model data - updatedNode = merge(updatedNode, omit(newModelData, 'promptId')); - - // Merge mew attribute data - updatedNode[nodeAttributesProperty] = merge(node[nodeAttributesProperty], newAttributeData); - - return updatedNode; }); } @@ -204,20 +194,11 @@ export default function reducer(state = initialState, action = {}) { return node; } - const updatedNode = { + return { ...node, + [nodeAttributesProperty]: omit(node[nodeAttributesProperty], keys(promptAttributes)), + promptIDs: pull(node.promptIDs, promptId), }; - - // Remove prompt attributes - updatedNode[nodeAttributesProperty] = omit( - node[nodeAttributesProperty], - keys(promptAttributes), - ); - - // Remove prompt ID from collection - updatedNode.promptIDs = pull(node.promptIDs, promptId); - - return updatedNode; }); return { From 77c62edeb49f544658e82fd339bbe281e433ad55 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Fri, 11 Jan 2019 13:10:10 +0000 Subject: [PATCH 10/22] fix panel interaction not causing re-render --- src/containers/Interfaces/NameGenerator.js | 2 +- src/containers/NodePanels.js | 14 ++++---- src/ducks/modules/network.js | 41 ++++++++-------------- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/containers/Interfaces/NameGenerator.js b/src/containers/Interfaces/NameGenerator.js index 8ac9dcad69..2c71531ffa 100644 --- a/src/containers/Interfaces/NameGenerator.js +++ b/src/containers/Interfaces/NameGenerator.js @@ -59,7 +59,7 @@ class NameGenerator extends Component { 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')) { + if (has(node, 'promptIDs')) { this.props.updateNode( node[nodePrimaryKeyProperty], { ...this.props.newNodeModelData }, diff --git a/src/containers/NodePanels.js b/src/containers/NodePanels.js index 866e3c0b4b..52f9ea1a12 100644 --- a/src/containers/NodePanels.js +++ b/src/containers/NodePanels.js @@ -2,12 +2,12 @@ import React, { PureComponent } from 'react'; import { compose, bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { includes, map, differenceBy, has } from 'lodash'; +import { includes, map, differenceBy } from 'lodash'; 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 { makeGetPanelConfiguration, makeGetPromptNodeModelData } 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'; @@ -38,7 +38,8 @@ class NodePanels extends PureComponent { onDrop = ({ meta }, dataSource) => { /** * Handle a node being dropped into a panel - * + * 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.removeNodeFromPrompt( @@ -145,7 +146,6 @@ function makeMapStateToProps() { const existingNodes = getNetworkNodesForOtherPrompts(state, props); const externalData = getExternalData(state); const newNodeAttributes = getPromptNodeAttributes(state, props); - const newNodeModelData = makeGetPromptNodeModelData(state, props); const panels = getPanelConfiguration(state, props) .map((panel) => { @@ -164,10 +164,10 @@ function makeMapStateToProps() { const accepts = (panel.dataSource === 'existing') ? ({ meta }) => ( - meta.itemType === 'EXISTING_NODE' && - (meta.stageId !== newNodeModelData.stageId || - !has(meta.promptIDs, newNodeModelData.promptId)) + // existing network node + meta.itemType === 'EXISTING_NODE' ) : ({ meta }) => ( + // external data meta.itemType === 'EXISTING_NODE' && includes(originNodeIds, meta[nodePrimaryKeyProperty]) ); diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index 083ef6c18c..a0c35604b5 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -1,4 +1,4 @@ -import { reject, findIndex, isMatch, omit, merge, concat, pull, keys } from 'lodash'; +import { reject, findIndex, isMatch, omit, merge, concat, keys } from 'lodash'; import uuidv4 from '../../utils/uuid'; @@ -94,20 +94,21 @@ function getNewNodeList(existingNodes, modelData, attributeData) { return existingNodes.concat(withModelandAttributeData); } +function getActionedNodeList(existingNodes, nodeId, promptId, promptAttributes) { + return existingNodes.map( + (node) => { + if (node[nodePrimaryKeyProperty] !== nodeId) { return node; } + return { + ...node, + [nodeAttributesProperty]: omit(node[nodeAttributesProperty], keys(promptAttributes)), + promptIDs: node.promptIDs.filter(id => id !== promptId), + }; + }); +} -/** - * @param {Array} existingNodes - the current state.nodes - * @param {Object} nodeID - the node to be updated. Will match on _uid. - * @param {Object} newModelData - - * @param {Object} newAttributeData - additional attributes to update the node with. - * If null, then the updatingNode's `attributes` property - * will overwrite the original node's. Use this to perform - * a 'full' update, but ensure the entire updated node is - * passed as `updatingNode`. - */ -function getUpdatedNodes(existingNodes, nodeID, newModelData, newAttributeData) { +function getUpdatedNodes(existingNodes, nodeId, newModelData, newAttributeData) { return existingNodes.map((node) => { - if (node[nodePrimaryKeyProperty] !== nodeID) { return node; } + if (node[nodePrimaryKeyProperty] !== nodeId) { return node; } return { ...node, ...omit(newModelData, 'promptId'), @@ -187,20 +188,6 @@ export default function reducer(state = initialState, action = {}) { }; } case REMOVE_NODE_FROM_PROMPT: { - const getActionedNodeList = (existingNodes, nodeId, promptId, promptAttributes) => - existingNodes.map( - (node) => { - if (node[nodePrimaryKeyProperty] !== nodeId) { - return node; - } - - return { - ...node, - [nodeAttributesProperty]: omit(node[nodeAttributesProperty], keys(promptAttributes)), - promptIDs: pull(node.promptIDs, promptId), - }; - }); - return { ...state, nodes: getActionedNodeList( From 203125b9c373bc64da69b96cf13a1f85dae4c594 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Mon, 14 Jan 2019 18:10:14 +0000 Subject: [PATCH 11/22] tidy up selectors --- src/containers/Interfaces/NameGenerator.js | 2 +- src/ducks/modules/network.js | 195 +++++++++------------ src/ducks/modules/sessions.js | 4 +- src/selectors/interface.js | 3 +- 4 files changed, 82 insertions(+), 122 deletions(-) diff --git a/src/containers/Interfaces/NameGenerator.js b/src/containers/Interfaces/NameGenerator.js index 2c71531ffa..bb5d2c15c2 100644 --- a/src/containers/Interfaces/NameGenerator.js +++ b/src/containers/Interfaces/NameGenerator.js @@ -41,7 +41,7 @@ class NameGenerator extends Component { ); } else { /** - * updateNode(nodeID, newModelData, newAttributeData) + * updateNode(nodeId, newModelData, newAttributeData) */ const selectedUID = this.state.selectedNode[nodePrimaryKeyProperty]; this.props.updateNode(selectedUID, {}, form); diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index a0c35604b5..757a3bfee4 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -42,139 +42,95 @@ function edgeExists(edges, edge) { ); } -/** - * All generated data is stored inside an 'attributes' property on the node - */ export const getNodeAttributes = node => node[nodeAttributesProperty] || {}; -/** - * existingNodes - Existing network.nodes - * newNodes - nodes to be added to the network - * additionalProperties - static props shared to add to each member of newNodes -*/ -function getNodesWithBatchAdd(existingNodes, newNodes, additionalProperties = {}) { - // Create a function to create a UUID and merge node attributes - const withModelandAttributeData = newNode => ({ - ...additionalProperties, - [nodePrimaryKeyProperty]: uuidv4(), - ...newNode, // second to prevent overwriting existing node UUID (e.g., assigned to externalData) - [nodeAttributesProperty]: { - ...additionalProperties[nodeAttributesProperty], - ...newNode[nodeAttributesProperty], - }, - }); - - return existingNodes.concat(newNodes.map(withModelandAttributeData)); -} - - -/** - * existingNodes - Existing network.nodes - * modelData - * attributeData -*/ -function getNewNodeList(existingNodes, modelData, attributeData) { - const { - itemType, - type, - promptId, - stageId, - } = modelData; - - const withModelandAttributeData = { - [nodePrimaryKeyProperty]: - modelData[nodePrimaryKeyProperty] ? modelData[nodePrimaryKeyProperty] : uuidv4(), - [nodeAttributesProperty]: attributeData, - promptIDs: [promptId], - stageId, - type, - itemType, - }; - - return existingNodes.concat(withModelandAttributeData); -} - -function getActionedNodeList(existingNodes, nodeId, promptId, promptAttributes) { - return existingNodes.map( - (node) => { - if (node[nodePrimaryKeyProperty] !== nodeId) { return node; } - return { - ...node, - [nodeAttributesProperty]: omit(node[nodeAttributesProperty], keys(promptAttributes)), - promptIDs: node.promptIDs.filter(id => id !== promptId), - }; - }); -} - -function getUpdatedNodes(existingNodes, nodeId, newModelData, newAttributeData) { - return existingNodes.map((node) => { - if (node[nodePrimaryKeyProperty] !== nodeId) { return node; } - return { - ...node, - ...omit(newModelData, 'promptId'), - promptIDs: concat(node.promptIDs, newModelData.promptId), - [nodeAttributesProperty]: merge(node[nodeAttributesProperty], newAttributeData), - }; - }); -} - export default function reducer(state = initialState, action = {}) { switch (action.type) { - /** - * Add single node using new syntax (modelData, attributeData) - */ case ADD_NODE: { return { ...state, - nodes: getNewNodeList(state.nodes, action.modelData, action.attributeData), + nodes: (() => { + const { + itemType, + type, + promptId, + stageId, + } = action.modelData; + + const withModelandAttributeData = { + [nodePrimaryKeyProperty]: + action.modelData[nodePrimaryKeyProperty] ? + action.modelData[nodePrimaryKeyProperty] : uuidv4(), + [nodeAttributesProperty]: action.attributeData, + promptIDs: [promptId], + stageId, + type, + itemType, + }; + + return state.nodes.concat(withModelandAttributeData); + })(), }; } case ADD_NODES: { return { ...state, - nodes: getNodesWithBatchAdd(state.nodes, action.nodes, action.additionalProperties), + nodes: (() => { + const withModelandAttributeData = newNode => ({ + ...action.additionalProperties, + [nodePrimaryKeyProperty]: uuidv4(), + ...newNode, // second to prevent overwriting existing node UUID (e.g. externalData) + [nodeAttributesProperty]: { + ...action.additionalProperties[nodeAttributesProperty], + ...newNode[nodeAttributesProperty], + }, + }); + + return state.nodes.concat(action.nodes.map(withModelandAttributeData)); + })(), }; } - /** - * TOGGLE_NODE_ATTRIBUTES - */ case TOGGLE_NODE_ATTRIBUTES: { - const updatedNodes = state.nodes.map((node) => { - if (node[nodePrimaryKeyProperty] !== action[nodePrimaryKeyProperty]) { - return node; - } - - // If the node's attrs contain the same key/vals, remove them - if (isMatch(node[nodeAttributesProperty], action.attributes)) { - const omittedKeys = Object.keys(action.attributes); - const nestedProps = omittedKeys.map(key => `${nodeAttributesProperty}.${key}`); - return omit(node, nestedProps); - } - - // Otherwise, add/update - return { - ...node, - [nodeAttributesProperty]: { - ...node[nodeAttributesProperty], - ...action.attributes, - }, - }; - }); - return { ...state, - nodes: updatedNodes, + nodes: ( + () => state.nodes.map( + (node) => { + if (node[nodePrimaryKeyProperty] !== action[nodePrimaryKeyProperty]) { return node; } + + // If the node's attrs contain the same key/vals, remove them + if (isMatch(node[nodeAttributesProperty], action.attributes)) { + const omittedKeys = Object.keys(action.attributes); + const nestedProps = omittedKeys.map(key => `${nodeAttributesProperty}.${key}`); + return omit(node, nestedProps); + } + + // Otherwise, add/update + return { + ...node, + [nodeAttributesProperty]: { + ...node[nodeAttributesProperty], + ...action.attributes, + }, + }; + }, // end node map function + ) + )(), }; } case UPDATE_NODE: { return { ...state, - nodes: getUpdatedNodes( - state.nodes, - action.nodeID, - action.newModelData, - action.newAttributeData, - ), + nodes: (() => state.nodes.map((node) => { + if (node[nodePrimaryKeyProperty] !== action.nodeId) { return node; } + return { + ...node, + ...omit(action.newModelData, 'promptId'), + promptIDs: concat(node.promptIDs, action.newModelData.promptId), + [nodeAttributesProperty]: merge(node[nodeAttributesProperty], action.newAttributeData), + }; + }) + )(), }; } case REMOVE_NODE: { @@ -190,12 +146,17 @@ export default function reducer(state = initialState, action = {}) { case REMOVE_NODE_FROM_PROMPT: { return { ...state, - nodes: getActionedNodeList( - state.nodes, - action.nodeId, - action.promptId, - action.promptAttributes, - ), + nodes: (() => state.nodes.map( + (node) => { + if (node[nodePrimaryKeyProperty] !== action.nodeId) { return node; } + return { + ...node, + [nodeAttributesProperty]: + omit(node[nodeAttributesProperty], keys(action.promptAttributes)), + promptIDs: node.promptIDs.filter(id => id !== action.promptId), + }; + }) + )(), }; } case ADD_EDGE: diff --git a/src/ducks/modules/sessions.js b/src/ducks/modules/sessions.js index fd1fb6bc19..d02ecb38b1 100644 --- a/src/ducks/modules/sessions.js +++ b/src/ducks/modules/sessions.js @@ -121,13 +121,13 @@ const addNode = (modelData, attributeData) => (dispatch, getState) => { }); }; -const updateNode = (nodeID, newModelData, newAttributeData = null) => (dispatch, getState) => { +const updateNode = (nodeId, newModelData, newAttributeData = null) => (dispatch, getState) => { const { session } = getState(); dispatch({ type: UPDATE_NODE, sessionId: session, - nodeID, + nodeId, newModelData, newAttributeData, }); diff --git a/src/selectors/interface.js b/src/selectors/interface.js index 7a4e0fa42d..80fd1ab002 100644 --- a/src/selectors/interface.js +++ b/src/selectors/interface.js @@ -167,8 +167,7 @@ export const makeNetworkNodesForType = () => /** * makeNetworkNodesForPrompt * - * Return a filtered node list containing only nodes that have both the additional attributes - * specified for this prompt, AND the current promptId. + * Return a filtered node list containing only nodes where node IDs contains the current promptId. */ export const makeNetworkNodesForPrompt = () => { From 1d71a4cd1efd4a928f17e81ef2d9f52c501275d9 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 00:01:01 +0000 Subject: [PATCH 12/22] implement batch add nodes action --- .../Interfaces/NameGeneratorAutoComplete.js | 19 +++- src/containers/Search/Search.js | 2 +- src/ducks/modules/network.js | 91 +++++++++++-------- src/ducks/modules/sessions.js | 42 +++++---- 4 files changed, 95 insertions(+), 59 deletions(-) diff --git a/src/containers/Interfaces/NameGeneratorAutoComplete.js b/src/containers/Interfaces/NameGeneratorAutoComplete.js index 3267c22edf..82611b7be9 100644 --- a/src/containers/Interfaces/NameGeneratorAutoComplete.js +++ b/src/containers/Interfaces/NameGeneratorAutoComplete.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import { map, merge } from 'lodash'; import { bindActionCreators, compose } from 'redux'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; @@ -11,7 +12,7 @@ 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, makeGetAdditionalAttributes } from '../../selectors/interface'; -import { getCardDisplayLabel, getCardAdditionalProperties, makeGetNodeIconName } from '../../selectors/name-generator'; +import { getCardDisplayLabel, getCardAdditionalProperties, makeGetNodeIconName, makeGetPromptNodeModelData } from '../../selectors/name-generator'; import { PromptSwiper } from '../'; import { NodeBin, NodeList } from '../../components/'; @@ -21,7 +22,14 @@ import { NodeBin, NodeList } from '../../components/'; */ class NameGeneratorAutoComplete extends Component { onSearchComplete(selectedResults) { - this.props.addNodes(selectedResults, this.props.newNodeAttributes); + console.log(this.props.newNodeModelData); + + const withNewModelData = map(selectedResults, result => ({ + ...this.props.newNodeModelData, + ...result, + })); + console.log(withNewModelData); + this.props.batchAddNodes(withNewModelData, this.props.newNodeAttributes); this.props.closeSearch(); } @@ -108,12 +116,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, @@ -128,7 +137,7 @@ 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), }; @@ -137,6 +146,7 @@ function mapDispatchToProps(dispatch) { function makeMapStateToProps() { const networkNodesForPrompt = makeNetworkNodesForPrompt(); const getPromptNodeAttributes = makeGetAdditionalAttributes(); + const getPromptNodeModelData = makeGetPromptNodeModelData(); const getNodeType = makeGetSubjectType(); const getNodeIconName = makeGetNodeIconName(); @@ -146,6 +156,7 @@ function makeMapStateToProps() { 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/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} > -
+ { e.preventDefault(); }}> this.onClose(evt)} /> {Headers} diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index 757a3bfee4..82adeaa47e 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -13,7 +13,7 @@ export const primaryKeyPropertyForWorker = 'networkCanvasId'; export const nodeTypePropertyForWorker = 'networkCanvasType'; export const ADD_NODE = 'ADD_NODE'; -export const ADD_NODES = 'ADD_NODES'; +export const BATCH_ADD_NODES = 'BATCH_ADD_NODES'; export const REMOVE_NODE = 'REMOVE_NODE'; export const REMOVE_NODE_FROM_PROMPT = 'REMOVE_NODE_FROM_PROMPT'; export const UPDATE_NODE = 'UPDATE_NODE'; @@ -44,50 +44,67 @@ function edgeExists(edges, edge) { export const getNodeAttributes = node => node[nodeAttributesProperty] || {}; +/** + * This function generates default values for all variables in the variable registry for this node + * type. + * + * @param {object} registryForType - An object containing the variable registry entry for this + * node type. + */ + +const getDefaultAttributesForNodeType = (registryForType) => { + const defaultAttributesObject = {}; + + // Boolean variables are initialised as `false`, and everything else as `null` + Object.keys(registryForType).forEach((key) => { + defaultAttributesObject[key] = registryForType[key].type === 'boolean' ? false : null; + }); + + return defaultAttributesObject; +}; + + +const nodeWithModelandAttributeData = (modelData, attributeData, defaultAttributeData = {}) => ({ + ...modelData, + [nodePrimaryKeyProperty]: + modelData[nodePrimaryKeyProperty] ? modelData[nodePrimaryKeyProperty] : uuidv4(), + [nodeAttributesProperty]: { + ...getDefaultAttributesForNodeType(defaultAttributeData), + ...modelData[nodeAttributesProperty], + ...attributeData, + }, + promptIDs: [modelData.promptId], + stageId: modelData.stageId, + type: modelData.type, + itemType: modelData.itemType, +}); + export default function reducer(state = initialState, action = {}) { switch (action.type) { case ADD_NODE: { return { ...state, - nodes: (() => { - const { - itemType, - type, - promptId, - stageId, - } = action.modelData; - - const withModelandAttributeData = { - [nodePrimaryKeyProperty]: - action.modelData[nodePrimaryKeyProperty] ? - action.modelData[nodePrimaryKeyProperty] : uuidv4(), - [nodeAttributesProperty]: action.attributeData, - promptIDs: [promptId], - stageId, - type, - itemType, - }; - - return state.nodes.concat(withModelandAttributeData); - })(), + nodes: ( + () => state.nodes.concat( + nodeWithModelandAttributeData( + action.modelData, + action.attributeData, + action.registryForType, + ), + ) + )(), }; } - case ADD_NODES: { + case BATCH_ADD_NODES: { return { ...state, - nodes: (() => { - const withModelandAttributeData = newNode => ({ - ...action.additionalProperties, - [nodePrimaryKeyProperty]: uuidv4(), - ...newNode, // second to prevent overwriting existing node UUID (e.g. externalData) - [nodeAttributesProperty]: { - ...action.additionalProperties[nodeAttributesProperty], - ...newNode[nodeAttributesProperty], - }, - }); - - return state.nodes.concat(action.nodes.map(withModelandAttributeData)); - })(), + nodes: (() => + state.nodes.concat(action.nodeList.map(node => nodeWithModelandAttributeData( + node, + action.attributeData, + action.registryForTypes[node.type], + ))) + )(), }; } case TOGGLE_NODE_ATTRIBUTES: { @@ -196,7 +213,7 @@ export default function reducer(state = initialState, action = {}) { const actionCreators = {}; const actionTypes = { - ADD_NODES, + BATCH_ADD_NODES, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, REMOVE_NODE, diff --git a/src/ducks/modules/sessions.js b/src/ducks/modules/sessions.js index d02ecb38b1..ea2aa99ee7 100644 --- a/src/ducks/modules/sessions.js +++ b/src/ducks/modules/sessions.js @@ -1,9 +1,9 @@ -import { isArray, omit } from 'lodash'; +import { omit, each, map, uniq } from 'lodash'; import { Observable } from 'rxjs'; import { combineEpics } from 'redux-observable'; import uuidv4 from '../../utils/uuid'; -import network, { nodePrimaryKeyProperty, ADD_NODE, ADD_NODES, REMOVE_NODE, REMOVE_NODE_FROM_PROMPT, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, ADD_EDGE, TOGGLE_EDGE, REMOVE_EDGE, SET_EGO, UNSET_EGO } from './network'; +import network, { nodePrimaryKeyProperty, ADD_NODE, BATCH_ADD_NODES, REMOVE_NODE, REMOVE_NODE_FROM_PROMPT, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, ADD_EDGE, TOGGLE_EDGE, REMOVE_EDGE, SET_EGO, UNSET_EGO } from './network'; import ApiClient from '../../utils/ApiClient'; import { protocolIdFromSessionPath } from '../../utils/matchSessionPath'; @@ -25,7 +25,7 @@ const withTimestamp = session => ({ export default function reducer(state = initialState, action = {}) { switch (action.type) { case ADD_NODE: - case ADD_NODES: + case BATCH_ADD_NODES: case REMOVE_NODE: case REMOVE_NODE_FROM_PROMPT: case UPDATE_NODE: @@ -95,29 +95,37 @@ export default function reducer(state = initialState, action = {}) { * * @memberof! NetworkActionCreators */ -const addNodes = (nodes, additionalProperties) => (dispatch, getState) => { - const { session } = getState(); +const batchAddNodes = (nodeList, attributeData) => (dispatch, getState) => { + const { session: sessionId, protocol: { variableRegistry: { node: nodeRegistry } } } = getState(); + const one = map(nodeList, 'type'); + console.log(one); + const two = uniq(one); + console.log(two); + + const registryForTypes = {}; + each(two, (nodeType) => { + registryForTypes[nodeType] = nodeRegistry[nodeType].variables; + }); - let nodeOrNodes = nodes; - if (!isArray(nodeOrNodes)) { - nodeOrNodes = [nodeOrNodes]; - } dispatch({ - type: ADD_NODES, - sessionId: session, - nodes: nodeOrNodes, - additionalProperties, + type: BATCH_ADD_NODES, + sessionId, + nodeList, + attributeData, + registryForTypes, }); }; const addNode = (modelData, attributeData) => (dispatch, getState) => { - const { session } = getState(); + const { session: sessionId, protocol: { variableRegistry: { node: nodeRegistry } } } = getState(); + const registryForType = nodeRegistry[modelData.type].variables; dispatch({ type: ADD_NODE, - sessionId: session, + sessionId, modelData, attributeData, + registryForType, }); }; @@ -272,7 +280,7 @@ const exportSessionEpic = (action$, store) => ( const actionCreators = { addNode, - addNodes, + batchAddNodes, updateNode, removeNode, removeNodeFromPrompt, @@ -290,7 +298,7 @@ const actionCreators = { const actionTypes = { ADD_NODE, - ADD_NODES, + BATCH_ADD_NODES, REMOVE_NODE, REMOVE_NODE_FROM_PROMPT, UPDATE_NODE, From 1e65f29e6eb53f1e7d0af2f5ff3c65f9d2dfc09f Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 00:06:42 +0000 Subject: [PATCH 13/22] remove console statements --- .../Interfaces/NameGeneratorAutoComplete.js | 6 ++---- src/ducks/modules/sessions.js | 18 ++++++------------ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/containers/Interfaces/NameGeneratorAutoComplete.js b/src/containers/Interfaces/NameGeneratorAutoComplete.js index 82611b7be9..fe71967f59 100644 --- a/src/containers/Interfaces/NameGeneratorAutoComplete.js +++ b/src/containers/Interfaces/NameGeneratorAutoComplete.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { map, merge } from 'lodash'; +import { map } from 'lodash'; import { bindActionCreators, compose } from 'redux'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; @@ -22,13 +22,11 @@ import { NodeBin, NodeList } from '../../components/'; */ class NameGeneratorAutoComplete extends Component { onSearchComplete(selectedResults) { - console.log(this.props.newNodeModelData); - const withNewModelData = map(selectedResults, result => ({ ...this.props.newNodeModelData, ...result, })); - console.log(withNewModelData); + this.props.batchAddNodes(withNewModelData, this.props.newNodeAttributes); this.props.closeSearch(); } diff --git a/src/ducks/modules/sessions.js b/src/ducks/modules/sessions.js index ea2aa99ee7..efd271b1e3 100644 --- a/src/ducks/modules/sessions.js +++ b/src/ducks/modules/sessions.js @@ -1,4 +1,4 @@ -import { omit, each, map, uniq } from 'lodash'; +import { omit, each, map } from 'lodash'; import { Observable } from 'rxjs'; import { combineEpics } from 'redux-observable'; @@ -85,25 +85,19 @@ export default function reducer(state = initialState, action = {}) { } /** - * Add a node or nodes to the state. + * Add a batch of nodes to the state. * - * @param {Array|Object} nodes - one or more nodes to add - * @param {Object} [additionalProperties] shared properties to apply to every new node. Note that - * user data (e.g., the "additionalAttributes" defined in - * a protocol) should exist under a child property named - * 'attributes'. + * @param {Collection} [nodeList] An array of objects representing nodes to add. + * @param {Object} [attributeData] Attribute data that will be merged with each node * * @memberof! NetworkActionCreators */ const batchAddNodes = (nodeList, attributeData) => (dispatch, getState) => { const { session: sessionId, protocol: { variableRegistry: { node: nodeRegistry } } } = getState(); - const one = map(nodeList, 'type'); - console.log(one); - const two = uniq(one); - console.log(two); + const nodeTypes = map(nodeList, 'type'); const registryForTypes = {}; - each(two, (nodeType) => { + each(nodeTypes, (nodeType) => { registryForTypes[nodeType] = nodeRegistry[nodeType].variables; }); From bc4806383e735c9e8803ffe73de4871a6086283c Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 13:30:38 +0000 Subject: [PATCH 14/22] update sociogram to use new updateNode --- src/components/Canvas/NodeLayout.js | 6 ++++-- src/containers/Canvas/LayoutNode.js | 1 - src/containers/Canvas/NodeLayout.js | 13 +++++-------- src/selectors/canvas.js | 10 +++++----- src/selectors/interface.js | 3 +-- 5 files changed, 15 insertions(+), 18 deletions(-) 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 ( (item) => { updateNode( - item.meta, + item.meta[nodePrimaryKeyProperty], + {}, { [layoutVariable]: relativeCoords({ width, height, x, y }, item), }, @@ -31,18 +31,15 @@ const withDropHandlers = withHandlers({ setRerenderCount(rerenderCount + 1); }, onDrag: ({ layoutVariable, updateNode, width, height, x, y }) => (item) => { - if (!has(item.meta[nodeAttributesProperty], layoutVariable)) { return; } - updateNode( - item.meta, + item.meta[nodePrimaryKeyProperty], + {}, { [layoutVariable]: relativeCoords({ width, height, x, y }, item), }, ); }, - onDragEnd: ({ layoutVariable, setRerenderCount, rerenderCount }) => (item) => { - if (!has(item.meta[nodeAttributesProperty], layoutVariable)) { return; } - + onDragEnd: ({ setRerenderCount, rerenderCount }) => () => { // make sure to also re-render nodes that were updated on drag end setRerenderCount(rerenderCount + 1); }, diff --git a/src/selectors/canvas.js b/src/selectors/canvas.js index 69f51af634..d74d6d48a9 100644 --- a/src/selectors/canvas.js +++ b/src/selectors/canvas.js @@ -1,4 +1,4 @@ -import { first, has } from 'lodash'; +import { first, has, isNil } from 'lodash'; import { networkNodes, networkEdges } from './interface'; import { createDeepEqualSelector } from './utils'; import sortOrder from '../utils/sortOrder'; @@ -26,14 +26,14 @@ export const makeGetNextUnplacedNode = () => getSubject, getLayout, getSortOptions, - (nodes, subject, layout, sortOptions) => { + (nodes, subject, layoutVariable, sortOptions) => { const type = subject && subject.type; const unplacedNodes = nodes.filter((node) => { const attributes = getNodeAttributes(node); return ( node.type === type && - !has(attributes, layout) + (has(attributes, layoutVariable) && isNil(attributes[layoutVariable])) ); }); @@ -54,13 +54,13 @@ export const makeGetPlacedNodes = () => networkNodes, getSubject, getLayout, - (nodes, subject, layout) => { + (nodes, subject, layoutVariable) => { const type = subject && subject.type; return nodes.filter((node) => { const attributes = getNodeAttributes(node); return ( node.type === type && - has(attributes, layout) + (has(attributes, layoutVariable) && !isNil(attributes[layoutVariable])) ); }); }, diff --git a/src/selectors/interface.js b/src/selectors/interface.js index 80fd1ab002..1651091c8b 100644 --- a/src/selectors/interface.js +++ b/src/selectors/interface.js @@ -79,8 +79,7 @@ export const makeGetSubject = () => ); const nodeTypeIsDefined = (variableRegistry, nodeType) => - variableRegistry.node && - !!variableRegistry.node[nodeType]; + variableRegistry.node && !!variableRegistry.node[nodeType]; // TODO: Once schema validation is in place, we don't need these asserts. export const makeGetSubjectType = () => (createSelector( From b8960f1c0f4741e9fb5442e1b874ec59e3fec803 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 13:42:34 +0000 Subject: [PATCH 15/22] update ordinal bin to use updateNode --- src/containers/Interfaces/OrdinalBin.js | 11 ++--------- src/containers/OrdinalBins.js | 12 +++++++----- 2 files changed, 9 insertions(+), 14 deletions(-) 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/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), }; } From 9267e7ba71d2bf299fe7a1d26f08e37c68fe243f Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 14:43:25 +0000 Subject: [PATCH 16/22] fix sociogram reset --- src/components/Canvas/EdgeLayout.js | 14 +++++++-- src/containers/CategoricalList.js | 11 ++++--- src/containers/__tests__/NodePanels.test.js | 1 - src/ducks/modules/network.js | 10 +++++-- src/ducks/modules/reset.js | 33 +++++++++++++++++---- 5 files changed, 52 insertions(+), 17 deletions(-) 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/containers/CategoricalList.js b/src/containers/CategoricalList.js index df7d51e7dc..5522542676 100644 --- a/src/containers/CategoricalList.js +++ b/src/containers/CategoricalList.js @@ -57,8 +57,11 @@ class CategoricalList extends Component { return; } - this.props.toggleNodeAttributes(meta[nodePrimaryKeyProperty], - { [this.props.activePromptVariable]: [bin.value] }); + this.props.updateNode( + meta[nodePrimaryKeyProperty], + {}, + { [this.props.activePromptVariable]: [bin.value] }, + ); }; const binDetails = this.getDetails(bin.nodes); @@ -140,7 +143,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 = { @@ -180,7 +183,7 @@ function makeMapStateToProps() { function mapDispatchToProps(dispatch) { return { - toggleNodeAttributes: bindActionCreators(sessionsActions.toggleNodeAttributes, dispatch), + updateNode: bindActionCreators(sessionsActions.updateNode, dispatch), }; } diff --git a/src/containers/__tests__/NodePanels.test.js b/src/containers/__tests__/NodePanels.test.js index 390a32c5dd..dfcccb0cda 100644 --- a/src/containers/__tests__/NodePanels.test.js +++ b/src/containers/__tests__/NodePanels.test.js @@ -7,7 +7,6 @@ import { NodePanels } from '../NodePanels'; jest.mock('../../ui/utils/CSSVariables'); const mockProps = { - toggleNodeAttributes: () => {}, removeNode: () => {}, activePromptAttributes: {}, newNodeAttributes: {}, diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index 82adeaa47e..df17ad9328 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -1,4 +1,4 @@ -import { reject, findIndex, isMatch, omit, merge, concat, keys } from 'lodash'; +import { reject, findIndex, isMatch, omit, concat, keys } from 'lodash'; import uuidv4 from '../../utils/uuid'; @@ -143,8 +143,12 @@ export default function reducer(state = initialState, action = {}) { return { ...node, ...omit(action.newModelData, 'promptId'), - promptIDs: concat(node.promptIDs, action.newModelData.promptId), - [nodeAttributesProperty]: merge(node[nodeAttributesProperty], action.newAttributeData), + promptIDs: action.newModelData.promptId ? + node.promptIDs.push(action.newModelData.promptId) : node.promptIDs, + [nodeAttributesProperty]: { + ...node[nodeAttributesProperty], + ...action.newAttributeData, + }, }; }) )(), diff --git a/src/ducks/modules/reset.js b/src/ducks/modules/reset.js index 3adb13d7da..724ba37008 100644 --- a/src/ducks/modules/reset.js +++ b/src/ducks/modules/reset.js @@ -1,6 +1,5 @@ -import { omit } from 'lodash'; import { actionCreators as sessionsActions } from './sessions'; -import { nodeAttributesProperty } from './network'; +import { nodePrimaryKeyProperty } from './network'; import { actionCreators as deviceActions } from './deviceSettings'; const RESET_STATE = 'RESET_STATE'; @@ -9,11 +8,33 @@ const RESET_PROPERTY_FOR_ALL_NODES = 'RESET/PROPERTY_FOR_ALL_NODES'; const resetPropertyForAllNodes = property => (dispatch, getState) => { - const { session } = getState(); - const { sessions: { [session]: { network: { nodes } } } } = getState(); + const { session: sessionId } = getState(); + const { + sessions: { + [sessionId]: { + network: { nodes }, + }, + }, + protocol: { + variableRegistry: { + node: nodeRegistry, + }, + }, + } = getState(); - nodes.forEach(node => dispatch( - sessionsActions.updateNode(omit(node, `${nodeAttributesProperty}.${property}`)))); + nodes.forEach((node) => { + const registryForType = nodeRegistry[node.type].variables; + const variableType = registryForType[property].type; + dispatch( + sessionsActions.updateNode( + node[nodePrimaryKeyProperty], + {}, + { + [property]: variableType === 'boolean' ? false : null, + }, + ), + ); + }); }; const resetEdgesOfType = edgeType => From ca39348f8b3879c7d7a050bf93011a34d6486b9a Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 16:20:28 +0000 Subject: [PATCH 17/22] fix transition to node layout when dragging unpositioned node --- config.xml | 3 ++- package.json | 4 ++-- src/behaviours/DragAndDrop/reducer.js | 1 + src/containers/Canvas/NodeLayout.js | 21 ++++++++++++--------- src/ducks/modules/network.js | 4 ++-- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/config.xml b/config.xml index 53713ac6d3..e36c227365 100644 --- a/config.xml +++ b/config.xml @@ -10,7 +10,7 @@ - + @@ -29,6 +29,7 @@ + diff --git a/package.json b/package.json index e5377560a1..c473c14312 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/src/behaviours/DragAndDrop/reducer.js b/src/behaviours/DragAndDrop/reducer.js index a60c5d03c5..3917c8fa9b 100644 --- a/src/behaviours/DragAndDrop/reducer.js +++ b/src/behaviours/DragAndDrop/reducer.js @@ -104,6 +104,7 @@ const triggerDrag = (state, source) => { source.setValidMove(true); if (some(hits.obstacles, { isOver: true }) || hits.source.isOutOfBounds) { + console.log('here'); source.setValidMove(false); return; } diff --git a/src/containers/Canvas/NodeLayout.js b/src/containers/Canvas/NodeLayout.js index b18126faad..f8d6a1ecfa 100644 --- a/src/containers/Canvas/NodeLayout.js +++ b/src/containers/Canvas/NodeLayout.js @@ -1,4 +1,5 @@ import { bindActionCreators } from 'redux'; +import { isNil } from 'lodash'; import { connect } from 'react-redux'; import { compose, withHandlers, withState } from 'recompose'; import { withBounds } from '../../behaviours'; @@ -30,15 +31,17 @@ 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) => { - updateNode( - item.meta[nodePrimaryKeyProperty], - {}, - { - [layoutVariable]: relativeCoords({ width, height, x, y }, item), - }, - ); - }, + 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/ducks/modules/network.js b/src/ducks/modules/network.js index df17ad9328..0da29740f0 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -1,4 +1,4 @@ -import { reject, findIndex, isMatch, omit, concat, keys } from 'lodash'; +import { reject, findIndex, isMatch, omit, keys } from 'lodash'; import uuidv4 from '../../utils/uuid'; @@ -65,7 +65,7 @@ const getDefaultAttributesForNodeType = (registryForType) => { const nodeWithModelandAttributeData = (modelData, attributeData, defaultAttributeData = {}) => ({ - ...modelData, + ...omit(modelData, 'promptId'), [nodePrimaryKeyProperty]: modelData[nodePrimaryKeyProperty] ? modelData[nodePrimaryKeyProperty] : uuidv4(), [nodeAttributesProperty]: { From d551dc84e45a92c891e32651d78f9197d8100958 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 17:31:34 +0000 Subject: [PATCH 18/22] fix mock node generation --- src/behaviours/DragAndDrop/reducer.js | 3 +- src/ducks/modules/mock.js | 39 ++++++++----------- src/ducks/modules/network.js | 2 +- src/selectors/__tests__/canvas.test.js | 12 +++--- .../__tests__/name-generator.test.js | 10 ----- 5 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/behaviours/DragAndDrop/reducer.js b/src/behaviours/DragAndDrop/reducer.js index 3917c8fa9b..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; } }; @@ -104,7 +104,6 @@ const triggerDrag = (state, source) => { source.setValidMove(true); if (some(hits.obstacles, { isOver: true }) || hits.source.isOutOfBounds) { - console.log('here'); source.setValidMove(false); return; } diff --git a/src/ducks/modules/mock.js b/src/ducks/modules/mock.js index 2b31ed721f..6ffe164611 100644 --- a/src/ducks/modules/mock.js +++ b/src/ducks/modules/mock.js @@ -3,7 +3,6 @@ import faker from 'faker'; import { has, times } from 'lodash'; import { actionCreators as sessionsActions } from './sessions'; -import { nodeAttributesProperty } from './network'; const MOCK_GENERATE_NODES = 'MOCK/GENERATE_NODES'; @@ -29,29 +28,25 @@ const mockValue = (nodeVariable) => { }; const generateNodes = (variableDefs, typeKey, howMany = 0, additionalAttributes = {}) => - (dispatch) => { - const mockNodes = times(howMany, () => { - const mockAttrs = Object.entries(variableDefs).reduce((acc, [variableId, variable]) => { - if (!has(additionalAttributes, variableId)) { - acc[variableId] = mockValue(variable); - } - return acc; - }, {}); - - return { - [nodeAttributesProperty]: mockAttrs, + dispatch => + times(howMany, () => { + const mockAttributes = Object.entries(variableDefs).reduce( + (acc, [variableId, variable]) => { + if (!has(additionalAttributes, variableId)) { + acc[variableId] = mockValue(variable); + } + return acc; + }, {}, + ); + + const modelData = { + promptIDs: ['mock'], + stageId: 'mock', + type: typeKey, }; - }); - const additionalProperties = { - promptId: 'mock', - stageId: 'mock', - type: typeKey, - [nodeAttributesProperty]: additionalAttributes, - }; - - return dispatch(sessionsActions.addNodes(mockNodes, { ...additionalProperties })); - }; + dispatch(sessionsActions.addNode(modelData, mockAttributes)); + }); const actionCreators = { generateNodes, diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index 0da29740f0..e249bb004e 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -144,7 +144,7 @@ export default function reducer(state = initialState, action = {}) { ...node, ...omit(action.newModelData, 'promptId'), promptIDs: action.newModelData.promptId ? - node.promptIDs.push(action.newModelData.promptId) : node.promptIDs, + [...node.promptIDs, action.newModelData.promptId] : node.promptIDs, [nodeAttributesProperty]: { ...node[nodeAttributesProperty], ...action.newAttributeData, diff --git a/src/selectors/__tests__/canvas.test.js b/src/selectors/__tests__/canvas.test.js index 3cf3a27b31..f4c0692d1a 100644 --- a/src/selectors/__tests__/canvas.test.js +++ b/src/selectors/__tests__/canvas.test.js @@ -6,12 +6,14 @@ import { makeGetPlacedNodes, makeGetDisplayEdges, } from '../canvas'; +import { nodeAttributesProperty } from '../../ducks/modules/network'; + +const node1 = { _uid: 1, type: 'person', [nodeAttributesProperty]: { role: ['a'], name: 'alpha', closeness: [1, 1] } }; +const node2 = { _uid: 2, type: 'person', [nodeAttributesProperty]: { role: ['a'], name: 'foxtrot', closeness: null } }; +const node3 = { _uid: 3, type: 'person', [nodeAttributesProperty]: { role: ['a'], name: 'bravo', closeness: null } }; +const node4 = { _uid: 4, type: 'person', [nodeAttributesProperty]: { role: ['a'], name: 'echo', closeness: [1, 1] } }; +const node5 = { _uid: 5, type: 'person', [nodeAttributesProperty]: { role: ['b'], name: 'charlie', closeness: [1, 1] } }; -const node1 = { _uid: 1, type: 'person', attributes: { role: ['a'], name: 'alpha', closeness: [1, 1] } }; -const node2 = { _uid: 2, type: 'person', attributes: { role: ['a'], name: 'foxtrot' } }; -const node3 = { _uid: 3, type: 'person', attributes: { role: ['a'], name: 'bravo' } }; -const node4 = { _uid: 4, type: 'person', attributes: { role: ['a'], name: 'echo', closeness: [1, 1] } }; -const node5 = { _uid: 5, type: 'person', attributes: { role: ['b'], name: 'charlie', closeness: [1, 1] } }; const mockState = { session: 'testSession', diff --git a/src/selectors/__tests__/name-generator.test.js b/src/selectors/__tests__/name-generator.test.js index 95af98728b..85317acea5 100644 --- a/src/selectors/__tests__/name-generator.test.js +++ b/src/selectors/__tests__/name-generator.test.js @@ -115,16 +115,6 @@ describe('name generator selector', () => { }); }); describe('memoed selectors', () => { - it('should get node attributes for the prompt', () => { - const selected = NameGen.makeGetAdditionalAttributes(); - expect(selected(mockState, mockProps)).toEqual({ - attributes: { close_friend: true }, - type: 'person', - promptId: 'promptId123', - stageId: 'stageId123', - }); - }); - it('should get card display label', () => { expect(NameGen.getCardDisplayLabel(mockState, mockProps)).toEqual('card label'); expect(NameGen.getCardDisplayLabel(null, emptyProps)).toEqual(undefined); From 548efef8e3e10456b8c03bb9ec84ea2de7979e84 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 21:11:39 +0000 Subject: [PATCH 19/22] fix tests --- .../Canvas/__tests__/EdgeLayout.test.js | 1 + .../__snapshots__/EdgeLayout.test.js.snap | 13 ++-- .../MainMenu/__tests__/MainMenu.test.js | 3 +- .../__snapshots__/NodePanels.test.js.snap | 1 - src/ducks/modules/__tests__/network.test.js | 75 +++++-------------- src/ducks/modules/__tests__/sessions.test.js | 53 ++++++------- src/ducks/modules/network.js | 1 + src/selectors/__tests__/interface.test.js | 10 ++- 8 files changed, 65 insertions(+), 92 deletions(-) diff --git a/src/components/Canvas/__tests__/EdgeLayout.test.js b/src/components/Canvas/__tests__/EdgeLayout.test.js index 8c09d09bdf..b85cd34b2b 100644 --- a/src/components/Canvas/__tests__/EdgeLayout.test.js +++ b/src/components/Canvas/__tests__/EdgeLayout.test.js @@ -7,6 +7,7 @@ import { EdgeLayout } from '../EdgeLayout'; const mockEdgeCoords = [ { key: 'foo_bar_baz', + type: 'type', from: { x: 100, y: 100, diff --git a/src/components/Canvas/__tests__/__snapshots__/EdgeLayout.test.js.snap b/src/components/Canvas/__tests__/__snapshots__/EdgeLayout.test.js.snap index f245a31c1d..b385cc4144 100644 --- a/src/components/Canvas/__tests__/__snapshots__/EdgeLayout.test.js.snap +++ b/src/components/Canvas/__tests__/__snapshots__/EdgeLayout.test.js.snap @@ -17,6 +17,7 @@ ShallowWrapper { "x": 100, "y": 100, }, + "type": "type", }, ] } @@ -53,7 +54,7 @@ ShallowWrapper { "y": 100, } } - type={undefined} + type="type" viewBoxScale={100} /> , @@ -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/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/__tests__/__snapshots__/NodePanels.test.js.snap b/src/containers/__tests__/__snapshots__/NodePanels.test.js.snap index e71c5b28fc..5a980ab0e5 100644 --- a/src/containers/__tests__/__snapshots__/NodePanels.test.js.snap +++ b/src/containers/__tests__/__snapshots__/NodePanels.test.js.snap @@ -21,7 +21,6 @@ ShallowWrapper { "id": null, } } - toggleNodeAttributes={[Function]} />, Symbol(enzyme.__renderer__): Object { "batchedUpdates": [Function], diff --git a/src/ducks/modules/__tests__/network.test.js b/src/ducks/modules/__tests__/network.test.js index 82c6e22417..d8c39af319 100644 --- a/src/ducks/modules/__tests__/network.test.js +++ b/src/ducks/modules/__tests__/network.test.js @@ -3,6 +3,7 @@ import reducer, { actionTypes, nodePrimaryKeyProperty as PK, + nodeAttributesProperty, } from '../network'; const mockState = { @@ -18,88 +19,50 @@ describe('network reducer', () => { expect(reducer(undefined, {})).toEqual(mockState); }); - it('should handle ADD_NODES with a single node', () => { + it('should handle ADD_NODE', () => { const newState = reducer( { ...mockState, - nodes: [{ id: 1, attributes: { name: 'baz' } }], }, { - type: actionTypes.ADD_NODES, - nodes: [{ attributes: { name: 'foo' } }], + type: actionTypes.ADD_NODE, + modelData: { [PK]: '383a6119e94aa2a1b2e1a5e84b2936b753437a11' }, + attributeData: { name: 'foo' }, }, ); + expect(newState.nodes.length).toBe(1); + expect(newState.nodes[0]).toEqual({ [PK]: '383a6119e94aa2a1b2e1a5e84b2936b753437a11', [nodeAttributesProperty]: { name: 'foo' }, itemType: undefined, promptIDs: [undefined], stageId: undefined, type: undefined }); - expect(newState.nodes.length).toBe(2); - expect(newState.nodes[0]).toEqual({ id: 1, attributes: { name: 'baz' } }); - - const newNode = newState.nodes[1]; + const newNode = newState.nodes[0]; expect(newNode.attributes.name).toEqual('foo'); - expect(newNode[PK]).toMatch(UIDPattern); - }); - - it('should handle ADD_NODES', () => { - const newState = reducer( - { - ...mockState, - nodes: [{ [PK]: 1, attributes: { name: 'baz' } }], - }, - { - type: actionTypes.ADD_NODES, - nodes: [{ attributes: { name: 'foo' } }, { attributes: { name: 'bar' } }], - }, - ); - - expect(newState.nodes.length).toBe(3); - expect(newState.nodes[0]).toEqual({ [PK]: 1, attributes: { name: 'baz' } }); - expect(newState.nodes[1]).toMatchObject({ attributes: { name: 'foo' }, [PK]: expect.stringMatching(UIDPattern) }); - expect(newState.nodes[2]).toMatchObject({ attributes: { name: 'bar' }, [PK]: expect.stringMatching(UIDPattern) }); }); it('preserves UID when adding a node', () => { const newState = reducer( mockState, { - type: actionTypes.ADD_NODES, - nodes: [{ attributes: { name: 'foo' }, [PK]: '22' }], + type: actionTypes.ADD_NODE, + modelData: { [PK]: '22' }, + attributeData: { name: 'foo' }, }, ); expect(newState.nodes[0][PK]).toEqual('22'); }); - it('should support additionalProperties for ADD_NODES', () => { + it('should support additionalProperties for ADD_NODE', () => { const newState = reducer( { ...mockState, nodes: [], }, { - type: actionTypes.ADD_NODES, - nodes: [{ attributes: { name: 'foo' } }, { attributes: { name: 'bar' } }], - additionalProperties: { stageId: '2', attributes: { isFriend: true } }, + type: actionTypes.ADD_NODE, + modelData: {}, + attributeData: { name: 'foo', isFriend: true }, }, ); - expect(newState.nodes[0].stageId).toBe('2'); - expect(newState.nodes[1].stageId).toBe('2'); expect(newState.nodes[0].attributes.isFriend).toBe(true); - expect(newState.nodes[1].attributes.isFriend).toBe(true); - expect(newState.nodes[0].attributes.name).toEqual('foo'); - expect(newState.nodes[1].attributes.name).toEqual('bar'); - }); - - it('should prefer node.attributes to additionalAttributes.attributes ', () => { - const newState = reducer( - { - ...mockState, - nodes: [], - }, - { - type: actionTypes.ADD_NODES, - nodes: [{ attributes: { name: 'foo' } }], - additionalAttributes: { attributes: { name: 'defaultName' } }, - }, - ); expect(newState.nodes[0].attributes.name).toEqual('foo'); }); @@ -136,14 +99,16 @@ describe('network reducer', () => { const newState = reducer( { ...mockState, - nodes: [{ [PK]: 1, id: 1, name: 'baz' }], + nodes: [{ [PK]: 1, id: 1, [nodeAttributesProperty]: { name: 'baz' } }], }, { type: actionTypes.UPDATE_NODE, - node: { [PK]: 1, name: 'foo' }, + nodeId: 1, + newModelData: {}, + newAttributeData: { name: 'foo' }, }, ); - expect(newState.nodes[0]).toEqual({ [PK]: 1, id: 1, name: 'foo' }); + expect(newState.nodes[0]).toEqual({ [PK]: 1, id: 1, [nodeAttributesProperty]: { name: 'foo' } }); }); it('toggles node attributes on', () => { diff --git a/src/ducks/modules/__tests__/sessions.test.js b/src/ducks/modules/__tests__/sessions.test.js index 56436ef27c..c4d0518ec0 100644 --- a/src/ducks/modules/__tests__/sessions.test.js +++ b/src/ducks/modules/__tests__/sessions.test.js @@ -92,12 +92,13 @@ describe('sessions reducer', () => { expect(newState[mockSessionId]).toEqual(undefined); }); - it('should handle ADD_NODES', () => { + it('should handle ADD_NODE', () => { const newState = reducer(mockStateWithSession, { - type: actionTypes.ADD_NODES, + type: actionTypes.ADD_NODE, sessionId: mockSessionId, - nodes: [{}], + modelData: {}, + attributeData: {}, }, ); expect(newState[mockSessionId].network.nodes).toHaveLength(1); @@ -106,7 +107,7 @@ describe('sessions reducer', () => { it('should throw if ADD_NODES called without an active session', () => { expect(() => reducer(mockState, { - type: actionTypes.ADD_NODES, + type: actionTypes.ADD_NODE, sessionId: 'a', nodes: [{}], }, @@ -115,30 +116,31 @@ describe('sessions reducer', () => { }); describe('sessions actions', () => { - it('should create an ADD_NODES action with a single node', () => { - const store = mockStore({ sessions: { a: {} }, session: 'a' }); - - const expectedAction = { - type: actionTypes.ADD_NODES, - sessionId: 'a', - nodes: [{ name: 'foo' }], - }; - - store.dispatch(actionCreators.addNodes({ name: 'foo' })); - expect(store.getActions()).toEqual([expectedAction]); - }); - - it('should create an ADD_NODES action for batch adding', () => { - const store = mockStore({ sessions: { a: {} }, session: 'a' }); + it('should create an BATCH_ADD_NODES action for batch adding', () => { + const store = mockStore({ + sessions: { a: {} }, + session: 'a', + protocol: { + variableRegistry: { + node: { + nodeType: { + variables: {}, + }, + }, + }, + }, + }); const expectedAction = { - type: actionTypes.ADD_NODES, + type: actionTypes.BATCH_ADD_NODES, sessionId: 'a', - nodes: [{ name: 'foo' }, { name: 'bar' }], + nodeList: [], + attributeData: {}, + registryForTypes: {}, }; - store.dispatch(actionCreators.addNodes([{ name: 'foo' }, { name: 'bar' }])); + store.dispatch(actionCreators.batchAddNodes([], {})); expect(store.getActions()).toEqual([expectedAction]); }); @@ -148,11 +150,12 @@ describe('sessions actions', () => { const expectedAction = { type: actionTypes.UPDATE_NODE, sessionId: 'a', - node: {}, - additionalProperties: null, + nodeId: {}, + newAttributeData: null, + newModelData: {}, }; - store.dispatch(actionCreators.updateNode({})); + store.dispatch(actionCreators.updateNode({}, {})); expect(store.getActions()).toEqual([expectedAction]); }); diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index e249bb004e..2c4a8fadc4 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -217,6 +217,7 @@ export default function reducer(state = initialState, action = {}) { const actionCreators = {}; const actionTypes = { + ADD_NODE, BATCH_ADD_NODES, UPDATE_NODE, TOGGLE_NODE_ATTRIBUTES, diff --git a/src/selectors/__tests__/interface.test.js b/src/selectors/__tests__/interface.test.js index c3ff9a6f87..21f67ac9f5 100644 --- a/src/selectors/__tests__/interface.test.js +++ b/src/selectors/__tests__/interface.test.js @@ -24,6 +24,7 @@ const mockStage = { const externalNode1 = { uid: 'person_1', type: 'person', + promptIDs: ['promptId123'], attributes: { name: 'F. Anita', nickname: 'Annie', @@ -35,6 +36,7 @@ const externalNode1 = { const externalNode2 = { uid: 'person_2', type: 'person', + promptIDs: ['promptId123'], attributes: { name: 'H. Barry', nickname: 'Baz', @@ -45,6 +47,7 @@ const externalNode2 = { const externalNode3 = { uid: 'person_3', type: 'person', + promptIDs: ['promptId123'], attributes: { nickname: 'Carl', age: 25, @@ -54,6 +57,7 @@ const externalNode3 = { const externalNode4 = { id: 4, uid: 'person_3', + promptIDs: ['promptId123'], type: 'person', attributes: { age: 25, @@ -93,12 +97,12 @@ const emptyProps = { stage: {}, }; -const personNode = { uid: 1, type: 'person', attributes: { name: 'foo' } }; -const closeFriendNode = { uid: 2, type: 'person', attributes: { name: 'bar', close_friend: true } }; +const personNode = { uid: 1, promptIDs: ['promptIdxxx'], type: 'person', attributes: { name: 'foo' } }; +const closeFriendNode = { uid: 2, promptIDs: ['promptId123'], type: 'person', attributes: { name: 'bar', close_friend: true } }; const nodes = [ personNode, closeFriendNode, - { uid: 3, attributes: { name: 'baz' }, type: 'venue' }, + { uid: 3, promptIDs: ['promptId456'], attributes: { name: 'baz' }, type: 'venue' }, ]; const edges = [{ to: 'bar', from: 'foo' }, { to: 'asdf', from: 'qwerty' }]; From 21e5f0a70fa52c9490d404fcb1b305a700e81c22 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 21:45:07 +0000 Subject: [PATCH 20/22] fix name generator list --- .../development.netcanvas/protocol.json | 20 +------------- src/containers/CategoricalList.js | 2 +- .../Interfaces/NameGeneratorList.js | 27 +++++++++++-------- .../__tests__/NameGenerator.test.js | 2 +- 4 files changed, 19 insertions(+), 32 deletions(-) diff --git a/public/protocols/development.netcanvas/protocol.json b/public/protocols/development.netcanvas/protocol.json index 9453575caf..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, diff --git a/src/containers/CategoricalList.js b/src/containers/CategoricalList.js index f8fa95665d..c6c2105854 100644 --- a/src/containers/CategoricalList.js +++ b/src/containers/CategoricalList.js @@ -142,7 +142,7 @@ class CategoricalList extends Component { this.props.updateNode( meta[nodePrimaryKeyProperty], {}, - { [this.props.activePromptVariable]: [bin.value] }, + { [this.props.activePromptVariable]: [binValue] }, ); }; diff --git a/src/containers/Interfaces/NameGeneratorList.js b/src/containers/Interfaces/NameGeneratorList.js index 41f86c3044..d4a1702cca 100644 --- a/src/containers/Interfaces/NameGeneratorList.js +++ b/src/containers/Interfaces/NameGeneratorList.js @@ -2,11 +2,11 @@ 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 { nodePrimaryKeyProperty, getNodeAttributes, nodeAttributesProperty } from '../../ducks/modules/network'; import { makeNetworkNodesForOtherPrompts, networkNodes, makeGetAdditionalAttributes } from '../../selectors/interface'; import { getDataByPrompt, @@ -14,6 +14,7 @@ import { getCardAdditionalProperties, getSortableFields, getInitialSortOrder, + makeGetPromptNodeModelData, } from '../../selectors/name-generator'; import { PromptSwiper } from '../../containers'; import { ListSelect } from '../../components'; @@ -36,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) => { @@ -100,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, @@ -111,15 +121,9 @@ NameGeneratorList.propTypes = { visibleSupplementaryFields: PropTypes.array.isRequired, }; -// NameGeneratorList.defaultProps = { -// initialSortOrder: [{ -// property: '', -// direction: 'asc', -// }], -// }; - function makeMapStateToProps() { const getPromptNodeAttributes = makeGetAdditionalAttributes(); + const getPromptNodeModelData = makeGetPromptNodeModelData(); const networkNodesForOtherPrompts = makeNetworkNodesForOtherPrompts(); return function mapStateToProps(state, props) { @@ -134,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), @@ -145,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/__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: [], From e1f22430c21574d5291b7a8e4a66a3d58c8b6190 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 15 Jan 2019 22:00:09 +0000 Subject: [PATCH 21/22] fix linting errors in tests --- src/ducks/modules/__tests__/network.test.js | 2 -- src/ducks/modules/__tests__/sessions.test.js | 1 - 2 files changed, 3 deletions(-) diff --git a/src/ducks/modules/__tests__/network.test.js b/src/ducks/modules/__tests__/network.test.js index d8c39af319..c360283133 100644 --- a/src/ducks/modules/__tests__/network.test.js +++ b/src/ducks/modules/__tests__/network.test.js @@ -12,8 +12,6 @@ const mockState = { edges: [], }; -const UIDPattern = /[a-f\d]+-/; - describe('network reducer', () => { it('should return the initial state', () => { expect(reducer(undefined, {})).toEqual(mockState); diff --git a/src/ducks/modules/__tests__/sessions.test.js b/src/ducks/modules/__tests__/sessions.test.js index c4d0518ec0..811d036f88 100644 --- a/src/ducks/modules/__tests__/sessions.test.js +++ b/src/ducks/modules/__tests__/sessions.test.js @@ -116,7 +116,6 @@ describe('sessions reducer', () => { }); describe('sessions actions', () => { - it('should create an BATCH_ADD_NODES action for batch adding', () => { const store = mockStore({ sessions: { a: {} }, From e29dda243cac2c64a67a01ec1b00b1a8cf88df3d Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Thu, 17 Jan 2019 12:30:58 +0000 Subject: [PATCH 22/22] relocate default attribute generation to sessions.js --- src/ducks/modules/network.js | 24 +----------------------- src/ducks/modules/sessions.js | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/ducks/modules/network.js b/src/ducks/modules/network.js index 2c4a8fadc4..5c6792bb76 100644 --- a/src/ducks/modules/network.js +++ b/src/ducks/modules/network.js @@ -44,32 +44,11 @@ function edgeExists(edges, edge) { export const getNodeAttributes = node => node[nodeAttributesProperty] || {}; -/** - * This function generates default values for all variables in the variable registry for this node - * type. - * - * @param {object} registryForType - An object containing the variable registry entry for this - * node type. - */ - -const getDefaultAttributesForNodeType = (registryForType) => { - const defaultAttributesObject = {}; - - // Boolean variables are initialised as `false`, and everything else as `null` - Object.keys(registryForType).forEach((key) => { - defaultAttributesObject[key] = registryForType[key].type === 'boolean' ? false : null; - }); - - return defaultAttributesObject; -}; - - -const nodeWithModelandAttributeData = (modelData, attributeData, defaultAttributeData = {}) => ({ +const nodeWithModelandAttributeData = (modelData, attributeData) => ({ ...omit(modelData, 'promptId'), [nodePrimaryKeyProperty]: modelData[nodePrimaryKeyProperty] ? modelData[nodePrimaryKeyProperty] : uuidv4(), [nodeAttributesProperty]: { - ...getDefaultAttributesForNodeType(defaultAttributeData), ...modelData[nodeAttributesProperty], ...attributeData, }, @@ -89,7 +68,6 @@ export default function reducer(state = initialState, action = {}) { nodeWithModelandAttributeData( action.modelData, action.attributeData, - action.registryForType, ), ) )(), diff --git a/src/ducks/modules/sessions.js b/src/ducks/modules/sessions.js index efd271b1e3..ea25a6509b 100644 --- a/src/ducks/modules/sessions.js +++ b/src/ducks/modules/sessions.js @@ -110,6 +110,25 @@ const batchAddNodes = (nodeList, attributeData) => (dispatch, getState) => { }); }; +/** + * This function generates default values for all variables in the variable registry for this node + * type. + * + * @param {object} registryForType - An object containing the variable registry entry for this + * node type. + */ + +const getDefaultAttributesForNodeType = (registryForType = {}) => { + const defaultAttributesObject = {}; + + // Boolean variables are initialised as `false`, and everything else as `null` + Object.keys(registryForType).forEach((key) => { + defaultAttributesObject[key] = registryForType[key].type === 'boolean' ? false : null; + }); + + return defaultAttributesObject; +}; + const addNode = (modelData, attributeData) => (dispatch, getState) => { const { session: sessionId, protocol: { variableRegistry: { node: nodeRegistry } } } = getState(); const registryForType = nodeRegistry[modelData.type].variables; @@ -118,8 +137,10 @@ const addNode = (modelData, attributeData) => (dispatch, getState) => { type: ADD_NODE, sessionId, modelData, - attributeData, - registryForType, + attributeData: { + ...getDefaultAttributesForNodeType(registryForType), + ...attributeData, + }, }); };