Manual Reference Source

basic/NodeFilter.js

  1. /**
  2. * Created by jakubniezgoda on 05/12/2017.
  3. */
  4.  
  5. import PropTypes from 'prop-types';
  6. import React from 'react';
  7.  
  8. import Form from './form/Form';
  9. import StageUtils from '../../utils/stageUtils';
  10.  
  11. /**
  12. * NodeFilter - a component showing dropdowns for filtering blueprints, deployments, nodes and nodes instances.
  13. * Data (list of blueprints, deployments, nodes and node instances) is dynamically fetched from manager.
  14. *
  15. * ## Access
  16. * `Stage.Basic.NodeFilter`
  17. *
  18. * ## Usage
  19. * ![NodeFilter](manual/asset/NodeFilter_0.png)
  20. *
  21. * ```
  22. * let value = {blueprintId: '', deploymentId: '', nodeId: '', nodeInstance: ''}
  23. * <NodeFilter name='nodeFilter' value={value} />
  24. * ```
  25. *
  26. */
  27. export default class NodeFilter extends React.Component {
  28. constructor(props, context) {
  29. super(props, context);
  30.  
  31. this.state = NodeFilter.initialState(props);
  32. this.toolbox = StageUtils.getToolbox(() => {}, () => {}, null);
  33. }
  34.  
  35. /*
  36. *
  37. */
  38. static EMPTY_VALUE = {
  39. blueprintId: '',
  40. deploymentId: '',
  41. nodeId: '',
  42. nodeInstanceId: ''
  43. };
  44.  
  45. /**
  46. * propTypes
  47. *
  48. * @property {string} name name of the field
  49. * @property {string} value value of the field (object containing the following string valued keys: blueprintId, deploymentId, nodeId, nodeInstanceId)
  50. * @property {func} [onChange=_.noop] function to be called on value change
  51. * @property {bool} [allowMultiple=false] if set to true, then it will be allowed to select more than one blueprint, deployment, node and node instance
  52. * @property {bool} [allowMultipleBlueprints=false] if set to true, then it will be allowed to select more than one blueprint
  53. * @property {bool} [allowMultipleDeployments=false] if set to true, then it will be allowed to select more than one deployment
  54. * @property {bool} [allowMultipleNodes=false] if set to true, then it will be allowed to select more than one node
  55. * @property {bool} [allowMultipleNodeInstances=false] if set to true, then it will be allowed to select more than one node instance
  56. * @property {Array} [allowedBlueprints=null] array specifing allowed blueprints to be selected
  57. * @property {Array} [allowedDeployments=null] array specifing allowed deployments to be selected
  58. * @property {Array} [allowedNodes=null] array specifing allowed nodes to be selected
  59. * @property {Array} [allowedNodeInstances=null] array specifing allowed node instances to be selected
  60. * @property {bool} [showBlueprints=true] if set to false, then it will be not allowed to select blueprint
  61. * @property {bool} [showDeployments=true] if set to false, then it will be not allowed to select deployment
  62. * @property {bool} [showNodes=true] if set to false, then it will be not allowed to select node
  63. * @property {bool} [showNodeInstances=true] if set to false, then it will be not allowed to select node instance
  64. */
  65. static propTypes = {
  66. name: PropTypes.string.isRequired,
  67. value: PropTypes.shape({
  68. blueprintId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
  69. deploymentId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
  70. nodeId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
  71. nodeInstanceId: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired
  72. }).isRequired,
  73. onChange: PropTypes.func,
  74. allowMultiple: PropTypes.bool,
  75. allowMultipleBlueprints: PropTypes.bool,
  76. allowMultipleDeployments: PropTypes.bool,
  77. allowMultipleNodes: PropTypes.bool,
  78. allowMultipleNodeInstances: PropTypes.bool,
  79. allowedBlueprints: PropTypes.array,
  80. allowedDeployments: PropTypes.array,
  81. allowedNodes: PropTypes.array,
  82. allowedNodeInstances: PropTypes.array,
  83. showBlueprints: PropTypes.bool,
  84. showDeployments: PropTypes.bool,
  85. showNodes: PropTypes.bool,
  86. showNodeInstances: PropTypes.bool
  87. };
  88.  
  89. static defaultProps = {
  90. onChange: _.noop,
  91. allowMultiple: false,
  92. allowMultipleBlueprints: false,
  93. allowMultipleDeployments: false,
  94. allowMultipleNode: false,
  95. allowMultipleNodes: false,
  96. allowedBlueprints: null,
  97. allowedDeployments: null,
  98. allowedNode: null,
  99. allowedNodes: null,
  100. showBlueprints: true,
  101. showDeployments: true,
  102. showNodes: true,
  103. showNodeInstances: true
  104. };
  105.  
  106. static initialState = props => ({
  107. blueprints: [],
  108. deployments: [],
  109. nodes: [],
  110. nodeInstances: [],
  111. blueprintId: props.value.blueprintId,
  112. deploymentId: props.value.deploymentId,
  113. nodeId: props.value.nodeId,
  114. nodeInstanceId: props.value.nodeInstanceId,
  115. errors: {}
  116. });
  117.  
  118. static BASIC_PARAMS = { _include: 'id' };
  119.  
  120. shouldComponentUpdate(nextProps, nextState) {
  121. return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
  122. }
  123.  
  124. componentDidUpdate(prevProps, prevState) {
  125. if (
  126. prevState.blueprintId !== this.props.value.blueprintId ||
  127. prevState.deploymentId !== this.props.value.deploymentId ||
  128. prevState.nodeId !== this.props.value.nodeId ||
  129. prevState.nodeInstanceId !== this.props.value.nodeInstanceId
  130. ) {
  131. this.setState({ ...NodeFilter.initialState(this.props) });
  132. this._fetchBlueprints();
  133. this._fetchDeployments();
  134. this._fetchNodes();
  135. this._fetchNodeInstances();
  136. }
  137. }
  138.  
  139. componentDidMount() {
  140. this._fetchBlueprints();
  141. this._fetchDeployments();
  142. this._fetchNodes();
  143. this._fetchNodeInstances();
  144. }
  145.  
  146. _fetchData(fetchUrl, params, optionsField) {
  147. const loading = `${optionsField}Loading`;
  148.  
  149. const errors = { ...this.state.errors };
  150. errors[optionsField] = null;
  151. this.setState({ [loading]: true, [optionsField]: [], errors });
  152. this.toolbox
  153. .getManager()
  154. .doGet(fetchUrl, params)
  155. .then(data => {
  156. let ids = _.chain(data.items || [])
  157. .map(item => item.id)
  158. .uniqWith(_.isEqual)
  159. .value();
  160. if (this._isFilteringSetFor(optionsField)) {
  161. ids = _.intersection(ids, this._getAllowedOptionsFor(optionsField));
  162. }
  163.  
  164. const options = _.map(ids, id => ({ text: id, value: id, key: id }));
  165. if (!this._isMultipleSetFor(optionsField)) {
  166. options.unshift({ text: '', value: '', key: '' });
  167. }
  168.  
  169. this.setState({ [loading]: false, [optionsField]: options });
  170. })
  171. .catch(error => {
  172. errors[optionsField] = `Data fetching error: ${error.message}`;
  173. this.setState({ [loading]: false, [optionsField]: [], errors });
  174. });
  175. }
  176.  
  177. _fetchBlueprints() {
  178. const params = { ...NodeFilter.BASIC_PARAMS };
  179. this._fetchData('/blueprints', params, 'blueprints');
  180. }
  181.  
  182. _fetchDeployments() {
  183. const params = { ...NodeFilter.BASIC_PARAMS };
  184. if (!_.isEmpty(this.state.blueprintId)) {
  185. params.blueprint_id = this.state.blueprintId;
  186. }
  187. this._fetchData('/deployments', params, 'deployments');
  188. }
  189.  
  190. _fetchNodes() {
  191. const params = { ...NodeFilter.BASIC_PARAMS };
  192. if (!_.isEmpty(this.state.blueprintId)) {
  193. params.blueprint_id = this.state.blueprintId;
  194. }
  195. if (!_.isEmpty(this.state.deploymentId)) {
  196. params.deployment_id = this.state.deploymentId;
  197. }
  198. this._fetchData('/nodes', params, 'nodes');
  199. }
  200.  
  201. _fetchNodeInstances() {
  202. const params = { ...NodeFilter.BASIC_PARAMS };
  203. if (!_.isEmpty(this.state.deploymentId)) {
  204. params.deployment_id = this.state.deploymentId;
  205. }
  206. if (!_.isEmpty(this.state.nodeId)) {
  207. params.node_id = this.state.nodeId;
  208. }
  209. this._fetchData('/node-instances', params, 'nodeInstances');
  210. }
  211.  
  212. _handleInputChange(state, event, field, onStateChange) {
  213. this.setState({ ...state, [field.name]: field.value }, () => {
  214. if (_.isFunction(onStateChange)) {
  215. onStateChange();
  216. }
  217. this.props.onChange(event, {
  218. name: this.props.name,
  219. value: {
  220. blueprintId: this.state.blueprintId,
  221. deploymentId: this.state.deploymentId,
  222. nodeId: this.state.nodeId,
  223. nodeInstanceId: this.state.nodeInstanceId
  224. }
  225. });
  226. });
  227. }
  228.  
  229. _isMultipleSetFor(resourcesName) {
  230. return this.props.allowMultiple || this.props[`allowMultiple${_.upperFirst(resourcesName)}`];
  231. }
  232.  
  233. _getEmptyValueFor(resourcesName) {
  234. return this._isMultipleSetFor(resourcesName) ? [] : '';
  235. }
  236.  
  237. _isFilteringSetFor(resourcesName) {
  238. return !_.isEmpty(this.props[`allowed${_.upperFirst(resourcesName)}`]);
  239. }
  240.  
  241. _getAllowedOptionsFor(resourcesName) {
  242. return this.props[`allowed${_.upperFirst(resourcesName)}`];
  243. }
  244.  
  245. _selectBlueprint(event, field) {
  246. this._handleInputChange(
  247. {
  248. deploymentId: this._getEmptyValueFor('deployments'),
  249. nodeId: this._getEmptyValueFor('nodes'),
  250. nodeInstanceId: this._getEmptyValueFor('nodeInstances')
  251. },
  252. event,
  253. field,
  254. () => {
  255. this._fetchDeployments();
  256. this._fetchNodes();
  257. this._fetchNodeInstances();
  258. }
  259. );
  260. }
  261.  
  262. _selectDeployment(event, field) {
  263. this._handleInputChange(
  264. {
  265. nodeId: this._getEmptyValueFor('nodes'),
  266. nodeInstanceId: this._getEmptyValueFor('nodeInstances')
  267. },
  268. event,
  269. field,
  270. () => {
  271. this._fetchNodes();
  272. this._fetchNodeInstances();
  273. }
  274. );
  275. }
  276.  
  277. _selectNode(event, field) {
  278. this._handleInputChange(
  279. {
  280. nodeInstanceId: this._getEmptyValueFor('nodeInstances')
  281. },
  282. event,
  283. field,
  284. () => {
  285. this._fetchNodeInstances();
  286. }
  287. );
  288. }
  289.  
  290. _selectNodeInstance(event, field) {
  291. this._handleInputChange({}, event, field);
  292. }
  293.  
  294. render() {
  295. const { errors } = this.state;
  296. const errorMessage = 'Data fetching error';
  297.  
  298. return (
  299. <Form.Group widths="equal">
  300. {this.props.showBlueprints && (
  301. <Form.Field error={this.state.errors.blueprints}>
  302. <Form.Dropdown
  303. search
  304. selection
  305. value={errors.blueprints ? this._getEmptyValueFor('blueprints') : this.state.blueprintId}
  306. multiple={this._isMultipleSetFor('blueprints')}
  307. placeholder={errors.blueprints || 'Blueprint'}
  308. options={this.state.blueprints}
  309. onChange={this._selectBlueprint.bind(this)}
  310. name="blueprintId"
  311. loading={this.state.blueprintsLoading}
  312. />
  313. </Form.Field>
  314. )}
  315. {this.props.showDeployments && (
  316. <Form.Field error={this.state.errors.deployments}>
  317. <Form.Dropdown
  318. search
  319. selection
  320. value={errors.deployments ? this._getEmptyValueFor('deployments') : this.state.deploymentId}
  321. multiple={this._isMultipleSetFor('deployments')}
  322. placeholder={errors.deployments || 'Deployment'}
  323. options={this.state.deployments}
  324. onChange={this._selectDeployment.bind(this)}
  325. name="deploymentId"
  326. loading={this.state.deploymentsLoading}
  327. />
  328. </Form.Field>
  329. )}
  330. {this.props.showNodes && (
  331. <Form.Field error={this.state.errors.nodes}>
  332. <Form.Dropdown
  333. search
  334. selection
  335. value={errors.nodes ? this._getEmptyValueFor('nodes') : this.state.nodeId}
  336. multiple={this._isMultipleSetFor('nodes')}
  337. placeholder={errors.nodes || 'Node'}
  338. options={this.state.nodes}
  339. onChange={this._selectNode.bind(this)}
  340. name="nodeId"
  341. loading={this.state.nodesLoading}
  342. />
  343. </Form.Field>
  344. )}
  345. {this.props.showNodeInstances && (
  346. <Form.Field error={this.state.errors.nodeInstances}>
  347. <Form.Dropdown
  348. search
  349. selection
  350. value={
  351. errors.nodeInstances
  352. ? this._getEmptyValueFor('nodeInstances')
  353. : this.state.nodeInstanceId
  354. }
  355. multiple={this._isMultipleSetFor('nodeInstances')}
  356. placeholder={errors.nodeInstances || 'Node Instance'}
  357. options={this.state.nodeInstances}
  358. onChange={this._selectNodeInstance.bind(this)}
  359. name="nodeInstanceId"
  360. loading={this.state.nodeInstancesLoading}
  361. />
  362. </Form.Field>
  363. )}
  364. </Form.Group>
  365. );
  366. }
  367. }