Manual Reference Source

basic/NodeFilter.js

/**
 * Created by jakubniezgoda on 05/12/2017.
 */

import PropTypes from 'prop-types';
import React from 'react';

import Form from './form/Form';
import StageUtils from '../../utils/stageUtils';

/**
 * NodeFilter  - a component showing dropdowns for filtering blueprints, deployments, nodes and nodes instances.
 * Data (list of blueprints, deployments, nodes and node instances) is dynamically fetched from manager.
 *
 * ## Access
 * `Stage.Basic.NodeFilter`
 *
 * ## Usage
 * ![NodeFilter](manual/asset/NodeFilter_0.png)
 *
 * ```
 * let value = {blueprintId: '', deploymentId: '', nodeId: '', nodeInstance: ''}
 * <NodeFilter name='nodeFilter' value={value} />
 * ```
 *
 */
export default class NodeFilter extends React.Component {
    constructor(props, context) {
        super(props, context);

        this.state = NodeFilter.initialState(props);
        this.toolbox = StageUtils.getToolbox(() => {}, () => {}, null);
    }

    /*
     *
     */
    static EMPTY_VALUE = {
        blueprintId: '',
        deploymentId: '',
        nodeId: '',
        nodeInstanceId: ''
    };

    /**
     * propTypes
     *
     * @property {string} name name of the field
     * @property {string} value value of the field (object containing the following string valued keys: blueprintId, deploymentId, nodeId, nodeInstanceId)
     * @property {func} [onChange=_.noop] function to be called on value change
     * @property {bool} [allowMultiple=false] if set to true, then it will be allowed to select more than one blueprint, deployment, node and node instance
     * @property {bool} [allowMultipleBlueprints=false] if set to true, then it will be allowed to select more than one blueprint
     * @property {bool} [allowMultipleDeployments=false] if set to true, then it will be allowed to select more than one deployment
     * @property {bool} [allowMultipleNodes=false] if set to true, then it will be allowed to select more than one node
     * @property {bool} [allowMultipleNodeInstances=false] if set to true, then it will be allowed to select more than one node instance
     * @property {Array} [allowedBlueprints=null] array specifing allowed blueprints to be selected
     * @property {Array} [allowedDeployments=null] array specifing allowed deployments to be selected
     * @property {Array} [allowedNodes=null] array specifing allowed nodes to be selected
     * @property {Array} [allowedNodeInstances=null] array specifing allowed node instances to be selected
     * @property {bool} [showBlueprints=true] if set to false, then it will be not allowed to select blueprint
     * @property {bool} [showDeployments=true] if set to false, then it will be not allowed to select deployment
     * @property {bool} [showNodes=true] if set to false, then it will be not allowed to select node
     * @property {bool} [showNodeInstances=true] if set to false, then it will be not allowed to select node instance
     */
    static propTypes = {
        name: PropTypes.string.isRequired,
        value: PropTypes.shape({
            blueprintId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
            deploymentId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
            nodeId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
            nodeInstanceId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired
        }).isRequired,
        onChange: PropTypes.func,
        allowMultiple: PropTypes.bool,
        allowMultipleBlueprints: PropTypes.bool,
        allowMultipleDeployments: PropTypes.bool,
        allowMultipleNodes: PropTypes.bool,
        allowMultipleNodeInstances: PropTypes.bool,
        allowedBlueprints: PropTypes.array,
        allowedDeployments: PropTypes.array,
        allowedNodes: PropTypes.array,
        allowedNodeInstances: PropTypes.array,
        showBlueprints: PropTypes.bool,
        showDeployments: PropTypes.bool,
        showNodes: PropTypes.bool,
        showNodeInstances: PropTypes.bool
    };

    static defaultProps = {
        onChange: _.noop,
        allowMultiple: false,
        allowMultipleBlueprints: false,
        allowMultipleDeployments: false,
        allowMultipleNode: false,
        allowMultipleNodes: false,
        allowedBlueprints: null,
        allowedDeployments: null,
        allowedNode: null,
        allowedNodes: null,
        showBlueprints: true,
        showDeployments: true,
        showNodes: true,
        showNodeInstances: true
    };

    static initialState = props => ({
        blueprints: [],
        deployments: [],
        nodes: [],
        nodeInstances: [],
        blueprintId: props.value.blueprintId,
        deploymentId: props.value.deploymentId,
        nodeId: props.value.nodeId,
        nodeInstanceId: props.value.nodeInstanceId,
        errors: {}
    });

    static BASIC_PARAMS = { _include: 'id' };

    shouldComponentUpdate(nextProps, nextState) {
        return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
    }

    componentDidUpdate(prevProps, prevState) {
        if (
            prevState.blueprintId !== this.props.value.blueprintId ||
            prevState.deploymentId !== this.props.value.deploymentId ||
            prevState.nodeId !== this.props.value.nodeId ||
            prevState.nodeInstanceId !== this.props.value.nodeInstanceId
        ) {
            this.setState({ ...NodeFilter.initialState(this.props) });
            this._fetchBlueprints();
            this._fetchDeployments();
            this._fetchNodes();
            this._fetchNodeInstances();
        }
    }

    componentDidMount() {
        this._fetchBlueprints();
        this._fetchDeployments();
        this._fetchNodes();
        this._fetchNodeInstances();
    }

    _fetchData(fetchUrl, params, optionsField) {
        const loading = `${optionsField}Loading`;

        const errors = { ...this.state.errors };
        errors[optionsField] = null;
        this.setState({ [loading]: true, [optionsField]: [], errors });
        this.toolbox
            .getManager()
            .doGet(fetchUrl, params)
            .then(data => {
                let ids = _.chain(data.items || [])
                    .map(item => item.id)
                    .uniqWith(_.isEqual)
                    .value();
                if (this._isFilteringSetFor(optionsField)) {
                    ids = _.intersection(ids, this._getAllowedOptionsFor(optionsField));
                }

                const options = _.map(ids, id => ({ text: id, value: id, key: id }));
                if (!this._isMultipleSetFor(optionsField)) {
                    options.unshift({ text: '', value: '', key: '' });
                }

                this.setState({ [loading]: false, [optionsField]: options });
            })
            .catch(error => {
                errors[optionsField] = `Data fetching error: ${error.message}`;
                this.setState({ [loading]: false, [optionsField]: [], errors });
            });
    }

    _fetchBlueprints() {
        const params = { ...NodeFilter.BASIC_PARAMS };
        this._fetchData('/blueprints', params, 'blueprints');
    }

    _fetchDeployments() {
        const params = { ...NodeFilter.BASIC_PARAMS };
        if (!_.isEmpty(this.state.blueprintId)) {
            params.blueprint_id = this.state.blueprintId;
        }
        this._fetchData('/deployments', params, 'deployments');
    }

    _fetchNodes() {
        const params = { ...NodeFilter.BASIC_PARAMS };
        if (!_.isEmpty(this.state.blueprintId)) {
            params.blueprint_id = this.state.blueprintId;
        }
        if (!_.isEmpty(this.state.deploymentId)) {
            params.deployment_id = this.state.deploymentId;
        }
        this._fetchData('/nodes', params, 'nodes');
    }

    _fetchNodeInstances() {
        const params = { ...NodeFilter.BASIC_PARAMS };
        if (!_.isEmpty(this.state.deploymentId)) {
            params.deployment_id = this.state.deploymentId;
        }
        if (!_.isEmpty(this.state.nodeId)) {
            params.node_id = this.state.nodeId;
        }
        this._fetchData('/node-instances', params, 'nodeInstances');
    }

    _handleInputChange(state, event, field, onStateChange) {
        this.setState({ ...state, [field.name]: field.value }, () => {
            if (_.isFunction(onStateChange)) {
                onStateChange();
            }
            this.props.onChange(event, {
                name: this.props.name,
                value: {
                    blueprintId: this.state.blueprintId,
                    deploymentId: this.state.deploymentId,
                    nodeId: this.state.nodeId,
                    nodeInstanceId: this.state.nodeInstanceId
                }
            });
        });
    }

    _isMultipleSetFor(resourcesName) {
        return this.props.allowMultiple || this.props[`allowMultiple${_.upperFirst(resourcesName)}`];
    }

    _getEmptyValueFor(resourcesName) {
        return this._isMultipleSetFor(resourcesName) ? [] : '';
    }

    _isFilteringSetFor(resourcesName) {
        return !_.isEmpty(this.props[`allowed${_.upperFirst(resourcesName)}`]);
    }

    _getAllowedOptionsFor(resourcesName) {
        return this.props[`allowed${_.upperFirst(resourcesName)}`];
    }

    _selectBlueprint(event, field) {
        this._handleInputChange(
            {
                deploymentId: this._getEmptyValueFor('deployments'),
                nodeId: this._getEmptyValueFor('nodes'),
                nodeInstanceId: this._getEmptyValueFor('nodeInstances')
            },
            event,
            field,
            () => {
                this._fetchDeployments();
                this._fetchNodes();
                this._fetchNodeInstances();
            }
        );
    }

    _selectDeployment(event, field) {
        this._handleInputChange(
            {
                nodeId: this._getEmptyValueFor('nodes'),
                nodeInstanceId: this._getEmptyValueFor('nodeInstances')
            },
            event,
            field,
            () => {
                this._fetchNodes();
                this._fetchNodeInstances();
            }
        );
    }

    _selectNode(event, field) {
        this._handleInputChange(
            {
                nodeInstanceId: this._getEmptyValueFor('nodeInstances')
            },
            event,
            field,
            () => {
                this._fetchNodeInstances();
            }
        );
    }

    _selectNodeInstance(event, field) {
        this._handleInputChange({}, event, field);
    }

    render() {
        const { errors } = this.state;
        const errorMessage = 'Data fetching error';

        return (
            <Form.Group widths="equal">
                {this.props.showBlueprints && (
                    <Form.Field error={this.state.errors.blueprints}>
                        <Form.Dropdown
                            search
                            selection
                            value={errors.blueprints ? this._getEmptyValueFor('blueprints') : this.state.blueprintId}
                            multiple={this._isMultipleSetFor('blueprints')}
                            placeholder={errors.blueprints || 'Blueprint'}
                            options={this.state.blueprints}
                            onChange={this._selectBlueprint.bind(this)}
                            name="blueprintId"
                            loading={this.state.blueprintsLoading}
                        />
                    </Form.Field>
                )}
                {this.props.showDeployments && (
                    <Form.Field error={this.state.errors.deployments}>
                        <Form.Dropdown
                            search
                            selection
                            value={errors.deployments ? this._getEmptyValueFor('deployments') : this.state.deploymentId}
                            multiple={this._isMultipleSetFor('deployments')}
                            placeholder={errors.deployments || 'Deployment'}
                            options={this.state.deployments}
                            onChange={this._selectDeployment.bind(this)}
                            name="deploymentId"
                            loading={this.state.deploymentsLoading}
                        />
                    </Form.Field>
                )}
                {this.props.showNodes && (
                    <Form.Field error={this.state.errors.nodes}>
                        <Form.Dropdown
                            search
                            selection
                            value={errors.nodes ? this._getEmptyValueFor('nodes') : this.state.nodeId}
                            multiple={this._isMultipleSetFor('nodes')}
                            placeholder={errors.nodes || 'Node'}
                            options={this.state.nodes}
                            onChange={this._selectNode.bind(this)}
                            name="nodeId"
                            loading={this.state.nodesLoading}
                        />
                    </Form.Field>
                )}
                {this.props.showNodeInstances && (
                    <Form.Field error={this.state.errors.nodeInstances}>
                        <Form.Dropdown
                            search
                            selection
                            value={
                                errors.nodeInstances
                                    ? this._getEmptyValueFor('nodeInstances')
                                    : this.state.nodeInstanceId
                            }
                            multiple={this._isMultipleSetFor('nodeInstances')}
                            placeholder={errors.nodeInstances || 'Node Instance'}
                            options={this.state.nodeInstances}
                            onChange={this._selectNodeInstance.bind(this)}
                            name="nodeInstanceId"
                            loading={this.state.nodeInstancesLoading}
                        />
                    </Form.Field>
                )}
            </Form.Group>
        );
    }
}