diff --git a/client/app/scripts/components/topology-options.js b/client/app/scripts/components/topology-options.js index c4276c28e..bc7a153c0 100644 --- a/client/app/scripts/components/topology-options.js +++ b/client/app/scripts/components/topology-options.js @@ -1,5 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; +import { Map as makeMap } from 'immutable'; +import includes from 'lodash/includes'; import { getCurrentTopologyOptions } from '../utils/topology-utils'; import { activeTopologyOptionsSelector } from '../selectors/topology'; @@ -11,19 +13,37 @@ class TopologyOptions extends React.Component { super(props, context); this.handleOptionClick = this.handleOptionClick.bind(this); + this.handleNoneClick = this.handleNoneClick.bind(this); } handleOptionClick(optionId, value, topologyId) { + let nextOptions = [value]; const { activeOptions, options } = this.props; const selectedOption = options.find(o => o.get('id') === optionId); if (selectedOption.get('selectType') === 'union') { - const isSelectedAlready = activeOptions.get(selectedOption.get('id')).includes(value); - const addOrRemove = isSelectedAlready ? 'remove' : 'add'; - this.props.changeTopologyOption(optionId, value, topologyId, addOrRemove); - } else { - this.props.changeTopologyOption(optionId, value, topologyId); + // Multi-select topology options (such as k8s namespaces) are handled here. + // Users can select one, many, or none of these options. + // The component builds an array of the next selected values that are sent to the action. + const opts = activeOptions.toJS(); + const selected = selectedOption.get('id'); + const isSelectedAlready = includes(opts[selected], value); + + if (isSelectedAlready) { + // Remove the option if it is already selected + nextOptions = opts[selected].filter(o => o !== value); + } else { + // Add it to the array if it's not selected + nextOptions = opts[selected].concat(value); + } + // Since the user is clicking an option, remove the highlighting from the 'none' option. + nextOptions = nextOptions.filter(o => o !== 'none'); } + this.props.changeTopologyOption(optionId, nextOptions, topologyId); + } + + handleNoneClick(optionId, value, topologyId) { + this.props.changeTopologyOption(optionId, ['none'], topologyId); } renderOption(option) { @@ -32,7 +52,10 @@ class TopologyOptions extends React.Component { const activeValue = activeOptions && activeOptions.has(optionId) ? activeOptions.get(optionId) : option.get('defaultValue'); - + const noneItem = makeMap({ + value: 'none', + label: option.get('noneLabel') + }); return (
@@ -46,6 +69,15 @@ class TopologyOptions extends React.Component { item={item} /> ))} + {option.get('selectType') === 'union' && + + }
); diff --git a/client/app/scripts/reducers/__tests__/root-test.js b/client/app/scripts/reducers/__tests__/root-test.js index ec7993be7..45f46daee 100644 --- a/client/app/scripts/reducers/__tests__/root-test.js +++ b/client/app/scripts/reducers/__tests__/root-test.js @@ -167,14 +167,14 @@ describe('RootReducer', () => { type: ActionTypes.CHANGE_TOPOLOGY_OPTION, topologyId: 'topo1', option: 'option1', - value: 'on' + value: ['on'] }; const ChangeTopologyOptionAction2 = { type: ActionTypes.CHANGE_TOPOLOGY_OPTION, topologyId: 'topo1', option: 'option1', - value: 'off' + value: ['off'] }; const ClickNodeAction = { @@ -371,15 +371,13 @@ describe('RootReducer', () => { type: ActionTypes.CHANGE_TOPOLOGY_OPTION, topologyId: 'services', option: 'namespace', - value: 'scope', - addOrRemove: 'add' + value: ['default', 'scope'], }; const removeAction = { type: ActionTypes.CHANGE_TOPOLOGY_OPTION, topologyId: 'services', option: 'namespace', - value: 'scope', - addOrRemove: 'remove' + value: ['default'] }; let nextState = initialState; nextState = reducer(nextState, { type: ActionTypes.RECEIVE_TOPOLOGIES, topologies}); diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index 3e673e837..199645ea4 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -194,7 +194,6 @@ export function rootReducer(state = initialState, action) { // set option on parent topology const topology = findTopologyById(state.get('topologies'), action.topologyId); if (topology) { - let values = [action.value]; const topologyId = topology.get('parentId') || topology.get('id'); const optionKey = ['topologyOptions', topologyId, action.option]; const currentOption = state.getIn(['topologyOptions', topologyId, action.option]); @@ -203,12 +202,7 @@ export function rootReducer(state = initialState, action) { state = clearNodes(state); } - if (action.addOrRemove === 'add') { - values = state.getIn(optionKey).concat(values); - } else if (action.addOrRemove === 'remove') { - values = state.getIn(optionKey).filter(o => !values.includes(o)); - } - state = state.setIn(optionKey, values); + state = state.setIn(optionKey, action.value); } return state; }