123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- import React from 'react';
- import { connect } from 'react-redux';
- import { Set as makeSet, Map as makeMap } from 'immutable';
- import includes from 'lodash/includes';
- import { trackAnalyticsEvent } from '../utils/tracking-utils';
- import { getCurrentTopologyOptions } from '../utils/topology-utils';
- import { activeTopologyOptionsSelector } from '../selectors/topology';
- import TopologyOptionAction from './topology-option-action';
- import { changeTopologyOption } from '../actions/request-actions';
- class TopologyOptions extends React.Component {
- constructor(props, context) {
- super(props, context);
- this.trackOptionClick = this.trackOptionClick.bind(this);
- this.handleOptionClick = this.handleOptionClick.bind(this);
- this.handleNoneClick = this.handleNoneClick.bind(this);
- }
- trackOptionClick(optionId, nextOptions) {
- trackAnalyticsEvent('scope.topology.option.click', {
- layout: this.props.topologyViewMode,
- optionId,
- parentTopologyId: this.props.currentTopology.get('parentId'),
- topologyId: this.props.currentTopology.get('id'),
- value: nextOptions,
- });
- }
- 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') {
- // 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 selectedActiveOptions = opts[selected] || [];
- const isSelectedAlready = includes(selectedActiveOptions, value);
- if (isSelectedAlready) {
- // Remove the option if it is already selected
- nextOptions = selectedActiveOptions.filter(o => o !== value);
- } else {
- // Add it to the array if it's not selected
- nextOptions = selectedActiveOptions.concat(value);
- }
- // Since the user is clicking an option, remove the highlighting from the none option,
- // unless they are removing the last option. In that case, default to the none label.
- // Note that since the other ids are potentially user-controlled (eg. k8s namespaces),
- // the only string we can use for the none option is the empty string '',
- // since that can't collide.
- if (nextOptions.length === 0) {
- nextOptions = [''];
- } else {
- nextOptions = nextOptions.filter(o => o !== '');
- }
- }
- this.trackOptionClick(optionId, nextOptions);
- this.props.changeTopologyOption(optionId, nextOptions, topologyId);
- }
- handleNoneClick(optionId, value, topologyId) {
- const nextOptions = [''];
- this.trackOptionClick(optionId, nextOptions);
- this.props.changeTopologyOption(optionId, nextOptions, topologyId);
- }
- renderOption(option) {
- const { activeOptions, currentTopologyId } = this.props;
- const optionId = option.get('id');
- // Make the active value be the intersection of the available options
- // and the active selection and use the default value if there is no
- // overlap. It seems intuitive that active selection would always be a
- // subset of available option, but the exception can happen when going
- // back in time (making available options change, while not touching
- // the selection).
- // TODO: This logic should probably be made consistent with how topology
- // selection is handled when time travelling, especially when the name-
- // spaces are brought under category selection.
- // TODO: Consider extracting this into a global selector.
- let activeValue = option.get('defaultValue');
- if (activeOptions && activeOptions.has(optionId)) {
- const activeSelection = makeSet(activeOptions.get(optionId));
- const availableOptions = makeSet(option.get('options').map(o => o.get('value')));
- const intersection = activeSelection.intersect(availableOptions);
- if (!intersection.isEmpty()) {
- activeValue = intersection.toJS();
- }
- }
- const noneItem = makeMap({
- label: option.get('noneLabel'),
- value: ''
- });
- return (
- <div className="topology-option" key={optionId}>
- <div className="topology-option-wrapper">
- {option.get('selectType') === 'union'
- && (
- <TopologyOptionAction
- onClick={this.handleNoneClick}
- optionId={optionId}
- item={noneItem}
- topologyId={currentTopologyId}
- activeValue={activeValue}
- />
- )
- }
-
- {option.get('options').map(item => (
- <TopologyOptionAction
- onClick={this.handleOptionClick}
- optionId={optionId}
- topologyId={currentTopologyId}
- key={item.get('value')}
- activeValue={activeValue}
- item={item}
- />
- ))}
- </div>
- </div>
- );
- }
- render() {
- const { options } = this.props;
- return (
- <div className="topology-options">
- {options && options.toIndexedSeq().map(option => this.renderOption(option))}
- </div>
- );
- }
- }
- function mapStateToProps(state) {
- return {
- activeOptions: activeTopologyOptionsSelector(state),
- currentTopology: state.get('currentTopology'),
- currentTopologyId: state.get('currentTopologyId'),
- options: getCurrentTopologyOptions(state),
- topologyViewMode: state.get('topologyViewMode')
- };
- }
- export default connect(
- mapStateToProps,
- { changeTopologyOption }
- )(TopologyOptions);
|