LENS-864: LENS - 864
Project: http://git-wip-us.apache.org/repos/asf/lens/repo Commit: http://git-wip-us.apache.org/repos/asf/lens/commit/2870be7c Tree: http://git-wip-us.apache.org/repos/asf/lens/tree/2870be7c Diff: http://git-wip-us.apache.org/repos/asf/lens/diff/2870be7c Branch: refs/heads/LENS-581 Commit: 2870be7c8c2dbef92c237878c5faba048d3a139d Parents: 7b5f4a0 Author: Ankeet Maini <[email protected]> Authored: Wed Nov 18 17:58:45 2015 +0530 Committer: Rajat Khandelwal <[email protected]> Committed: Wed Nov 18 17:58:45 2015 +0530 ---------------------------------------------------------------------- lens-ui/app/components/QueryBoxComponent.js | 122 +++++++------ .../app/components/QueryParamRowComponent.js | 176 +++++++++---------- lens-ui/app/components/QueryParamsComponent.js | 72 +++++--- .../components/SavedQueryPreviewComponent.js | 26 +-- 4 files changed, 219 insertions(+), 177 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lens/blob/2870be7c/lens-ui/app/components/QueryBoxComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/QueryBoxComponent.js b/lens-ui/app/components/QueryBoxComponent.js index 6f4eeb7..ec6a06b 100644 --- a/lens-ui/app/components/QueryBoxComponent.js +++ b/lens-ui/app/components/QueryBoxComponent.js @@ -21,6 +21,7 @@ import React from 'react'; import ClassNames from 'classnames'; import CodeMirror from 'codemirror'; import assign from 'object-assign'; +import _ from 'lodash'; import 'codemirror/lib/codemirror.css'; import 'codemirror/addon/edit/matchbrackets.js'; import 'codemirror/addon/hint/sql-hint.js'; @@ -74,6 +75,20 @@ function setCode (code) { } } +function getEmptyState () { + return { + clientMessage: null, // to give user instant ack + isRunQueryDisabled: true, + serverMessage: null, // type (success or error), text as keys + isCollapsed: false, + params: null, + isModeEdit: false, + savedQueryId: null, + runImmediately: false, + description: '' + }; +} + // used to populate the query box when user wants to edit a query // TODO improve this. // this takes in the query handle and writes the query @@ -114,16 +129,7 @@ class QueryBox extends React.Component { this.saveOrUpdate = this.saveOrUpdate.bind(this); this.runSavedQuery = this.runSavedQuery.bind(this); - this.state = { - clientMessage: null, // to give user instant ack - isRunQueryDisabled: true, - serverMessage: null, // type (success or error), text as keys - isCollapsed: false, - params: null, - isModeEdit: false, - savedQueryId: null, - runImmediately: false - }; + this.state = getEmptyState(); } componentDidMount () { @@ -184,6 +190,7 @@ class QueryBox extends React.Component { this.setState({ params: savedQuery.parameters, savedQueryId: savedQuery.id, + description: savedQuery.description, isModeEdit: true }); } @@ -257,7 +264,7 @@ class QueryBox extends React.Component { { this.state.params && !!this.state.params.length && <QueryParams params={this.state.params} close={this.closeParamBox} - saveParams={this.saveParams}/> + saveParams={this.saveParams} description={this.state.description}/> } { this.state.serverMessage && @@ -304,17 +311,40 @@ class QueryBox extends React.Component { updateQuery (params) { let query = this._getSavedQueryDetails(params); if (!query) return; + + var options = { + parameters: query.parameters, + description: query.description, + name: query.name + }; + AdhocQueryActions - .updateSavedQuery(query.secretToken, query.user, query.query, query.params, this.state.savedQueryId); - this.setState({ clientMessage: clientMessages.updateQuery }); + .updateSavedQuery(query.secretToken, query.user, query.query, + options, this.state.savedQueryId); + + this.setState({ + clientMessage: clientMessages.updateQuery, + runImmediately: params && params.runImmediately + }); } saveQuery (params) { let query = this._getSavedQueryDetails(params); if (!query) return; + + var options = { + parameters: query.parameters, + description: query.description, + name: query.name + }; + AdhocQueryActions - .saveQuery(query.secretToken, query.user, query.query, query.params); - this.setState({ clientMessage: clientMessages.saveQuery }); + .saveQuery(query.secretToken, query.user, query.query, options); + + this.setState({ + clientMessage: clientMessages.saveQuery, + runImmediately: params && params.runImmediately + }); } // internal which is called during save saved query & edit saved query @@ -329,14 +359,13 @@ class QueryBox extends React.Component { let user = UserStore.getUserDetails().email; let query = codeMirror.getValue(); - params = assign({}, params); - params.name = queryName; - return { secretToken: secretToken, user: user, query: query, - params: params + parameters: params && params.parameters, + description: params && params.description, + name: queryName }; } @@ -419,42 +448,47 @@ class QueryBox extends React.Component { _onChangeSavedQueryStore (hash) { if (!hash) return; + var newState = _.assign({}, this.state); + switch (hash.type) { case 'failure': - this.state.clientMessage = null; - this.state.serverMessage = hash.message; + newState.clientMessage = null; + newState.serverMessage = hash.message; break; case 'success': - this.state.clientMessage = null; - this.state.serverMessage = hash.message; - // make the mode of QueryBox back to normal, if it's in Edit - if (this.state.isModeEdit) { - this.state.isModeEdit = false; - } - // trigger to fetch the edited from server again let token = UserStore.getUserDetails().secretToken; if (hash.id) AdhocQueryActions.getSavedQueryById(token, hash.id); // means the query was saved successfully. // run immediately? - if (this.state.runImmediately && hash.id) { + if (newState.runImmediately && hash.id) { this.runSavedQuery(hash.id); - this.state.runImmediately = false; + newState.runImmediately = false; } - // make params null - this.state.params = null; - + // empty the state, clean the slate + setCode(''); + this.refs.queryName.getDOMNode().value = ''; + newState = getEmptyState(); + newState.serverMessage = hash.message; break; case 'params': - this.state.params = hash.params; + newState.params = hash.params.map(param => { + return { + name: param.name, + dataType: param.dataType || 'STRING', + collectionType: param.collectionType || 'SINGLE', + defaultValue: param.defaultValue || null, + displayName: param.displayName || param.name + }; + }); break; } - this.setState(this.state); + this.setState(newState); } runSavedQuery (id) { @@ -472,29 +506,17 @@ class QueryBox extends React.Component { } closeParamBox () { - this.setState({params: null, clientMessage: null}); + this.cancel(); } - saveParams (params) { // contains parameters, description et all - this.state.params = assign(this.state.params, params.parameters); - this.state.runImmediately = params.runImmediately; - - // edit or save new, only state variable will tell + saveParams (params) { !this.state.isModeEdit ? this.saveQuery(params) : this.updateQuery(params); } cancel () { setCode(''); this.refs.queryName.getDOMNode().value = ''; - this.setState({ - clientMessage: null, // to give user instant ack - isRunQueryDisabled: true, - serverMessage: null, // type (success or error), text as keys - isCollapsed: false, - params: null, - isModeEdit: false, - savedQueryId: null - }); + this.setState(getEmptyState()); } } http://git-wip-us.apache.org/repos/asf/lens/blob/2870be7c/lens-ui/app/components/QueryParamRowComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/QueryParamRowComponent.js b/lens-ui/app/components/QueryParamRowComponent.js index fb5f5da..89c8a8e 100644 --- a/lens-ui/app/components/QueryParamRowComponent.js +++ b/lens-ui/app/components/QueryParamRowComponent.js @@ -20,73 +20,53 @@ import React from 'react'; import { Multiselect } from 'react-widgets'; import assign from 'object-assign'; +import _ from 'lodash'; import 'react-widgets/dist/css/core.css'; import 'react-widgets/dist/css/react-widgets.css'; -// returns true/false if the default value is correct -// and also returns the value -function validate (val, dataType) { - // if (dataType === 'NUMBER' && !window.isNaN(val)) return [true, val]; - // if (dataType === 'BOOLEAN' && (val === 'true' || val === 'false')) { - // return [true, val]; - // } - // if (dataType === 'STRING' && typeof val === 'string') return [true, val]; - - return [true, val]; -} - class QueryParamRow extends React.Component { constructor (props) { super(props); - // state being decided by mode of use of this component - // `entryMode` is used by the SavedQueryPreviewComponent, - // to just add values and run the saved query. - if (props.entryMode) { - this.state = assign({}, props.param); - } else { - this.state = assign({}, props.param, { - dataType: 'STRING', - collectionType: 'SINGLE', - displayName: props.param.name - }); - } - - this.changeDisplayName = this.changeDisplayName.bind(this); - this.changeDataType = this.changeDataType.bind(this); - this.changeCollectionType = this.changeCollectionType.bind(this); - this.changeDefaultValue = this.changeDefaultValue.bind(this); - this.addDefaultValue = this.addDefaultValue.bind(this); - this.preventEnter = this.preventEnter.bind(this); - } + this.state = { + paramChange: assign({}, props.param) + }; - componentWillReceiveProps (props) { - this.setState(assign({}, props.param)); + this._handleChange = this._handleChange.bind(this); + this.getDefaultValueInput = this.getDefaultValueInput.bind(this); } - componentWillUpdate (props, state) { - this.props.updateParam({ - name: props.param.name, - param: state - }); + shouldComponentUpdate (newProps, newState) { + return !_.isEqual(this.state, newState); } render () { let param = this.props.param; + let collectionType = this.state.paramChange.collectionType; + let dataType = this.state.paramChange.dataType; + + let collectionTypeBox = (<select className='form-control' required + defaultValue='SINGLE' onChange={this._handleChange('ChangeCollectionType')}> + <option value='SINGLE'>Single</option> + <option value='MULTIPLE'>Multiple</option> + </select>); + + let valueBox = this.getDefaultValueInput(collectionType, dataType); + return ( <tr> <td>{param.name}</td> <td> { this.props.entryMode ? param.displayName : <input type='text' className='form-control' required defaultValue={param.name} - placeholder='display name' onChange={this.changeDisplayName}/> + placeholder='display name' onChange={this._handleChange('ChangeDisplayName')}/> } </td> <td> { this.props.entryMode ? param.dataType : - <select className='form-control' defaultValue='STRING' - onChange={this.changeDataType}> + <select className='form-control' defaultValue={dataType || 'STRING'} + onChange={this._handleChange('ChangeDataType')}> <option value='STRING'>String</option> <option value='NUMBER'>Number</option> <option value='BOOLEAN'>Boolean</option> @@ -95,78 +75,88 @@ class QueryParamRow extends React.Component { </td> <td> { this.props.entryMode ? param.collectionType : - <select className='form-control' required defaultValue='SINGLE' - onChange={this.changeCollectionType}> - <option value='SINGLE'>Single</option> - <option value='MULTIPLE'>Multiple</option> - </select> + {collectionTypeBox} } </td> <td> - { !this.props.entryMode && (this.state.collectionType === 'SINGLE' ? - <input type='text' className='form-control' required value={this.state.defaultValue} - placeholder='default value' onChange={this.changeDefaultValue}/> : - <Multiselect messages={{createNew: 'Enter to add'}} - onCreate={this.addDefaultValue} - defaultValue={this.state.defaultValue} onKeyDown={this.preventEnter} - /> - )} - - { this.props.entryMode && (param.collectionType === 'SINGLE' ? - <input type='text' className='form-control' required value={this.state.defaultValue} - placeholder='default value' onChange={this.changeDefaultValue}/> : - <Multiselect messages={{createNew: 'Enter to add'}} - onCreate={this.addDefaultValue} - defaultValue={this.state.defaultValue} onKeyDown={this.preventEnter} - /> - )} + {valueBox} </td> </tr> ); } - // these methods change the default values - // called by normal input - changeDefaultValue (e) { - let val = validate(e.target.value, this.state.dataType); - - if (val[0]) this.setState({defaultValue: val[1]}); + _handleChange (elementType) { + return (arg) => { + let paramChange; + let state = _.assign({}, this.state.paramChange); + let val; + switch (elementType) { + case 'ChangeMultiselect': + paramChange = _.assign({}, state, {defaultValue: arg}); + break; + + case 'ChangeDefaultTextValue': + paramChange = _.assign({}, state, {defaultValue: arg.target.value}); + break; + + case 'AddItemInMultiSelect': + this.state.paramChange.defaultValue.push(arg); + paramChange = _.assign({}, this.state.paramChange, { + //defaultValue: [...this.state.paramChange.defaultValue, item] + }); + break; + + case 'ChangeDataType': + val = this.state.paramChange.collectionType === 'SINGLE' ? null : []; + paramChange = _.assign({}, state, { + dataType: arg.target.value, + defaultValue: val, + }); + break; + + case 'ChangeCollectionType': + val = arg.target.value === 'MULTIPLE' ? [] : null; + paramChange = _.assign({}, state, { + collectionType: arg.target.value, + defaultValue: val + }); + break; + + case 'ChangeDisplayName': + paramChange = _.assign({}, state, {displayName: arg.target.value}); + break; + } + + this.setState({paramChange}); + this.props.saveParamChanges(paramChange); + }; } - // called my multiselect - addDefaultValue (item) { - let val = validate(item, this.state.dataType); - - if (val[0]) { - this.state.defaultValue.push(val[1]); - this.setState(this.state); - } - } preventEnter (e) { if (e.keyCode == 13) e.preventDefault(); } - changeDataType (e) { - let val = this.state.collectionType === 'SINGLE' ? null : []; - this.setState({dataType: e.target.value, defaultValue: val}); - } - - changeCollectionType (e) { - let val = e.target.value === 'MULTIPLE' ? [] : null; - this.setState({defaultValue: val}); - this.setState({collectionType: e.target.value}); - } - - changeDisplayName (e) { - this.setState({displayName: e.target.value}); + getDefaultValueInput (collectionType, dataType) { + let valueBox = null; + if (collectionType === 'SINGLE') { + valueBox = <input type='text' className='form-control' required value={this.state.paramChange.defaultValue} + placeholder='default value' onChange={this._handleChange('ChangeDefaultTextValue')}/>; + } else if (collectionType === 'MULTIPLE') { + valueBox = <Multiselect messages={{createNew: 'Enter to add'}} + onCreate={this._handleChange('AddItemInMultiSelect')} data={this.state.paramChange.defaultValue} + onChange={this._handleChange('ChangeMultiselect')} + value={this.state.paramChange.defaultValue} onKeyDown={this.preventEnter} + />; + } + return valueBox; } } QueryParamRow.propTypes = { param: React.PropTypes.object.isRequired, - updateParam: React.PropTypes.func.isRequired, + saveParamChanges: React.PropTypes.func.isRequired, entryMode: React.PropTypes.boolean }; http://git-wip-us.apache.org/repos/asf/lens/blob/2870be7c/lens-ui/app/components/QueryParamsComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/QueryParamsComponent.js b/lens-ui/app/components/QueryParamsComponent.js index a49e338..c3325c8 100644 --- a/lens-ui/app/components/QueryParamsComponent.js +++ b/lens-ui/app/components/QueryParamsComponent.js @@ -26,28 +26,35 @@ import QueryParamRow from './QueryParamRowComponent'; class QueryParams extends React.Component { constructor (props) { super(props); - this.state = {description: '', childrenParams: {}, runImmediately: false}; + this.state = { + paramChanges: [], + runImmediately: false, + description: props.description + }; this.close = this.close.bind(this); this.save = this.save.bind(this); - this.update = this.update.bind(this); this.handleChange = this.handleChange.bind(this); this.handleCheck = this.handleCheck.bind(this); - this._getChildrenParams = this._getChildrenParams.bind(this); + this.saveParamChanges = this.saveParamChanges.bind(this); } componentWillReceiveProps (props) { - if (!_.isEqual(props.params, this.props.params)) { - this.state.childrenParams = {}; - } + this.setState({description: props.description, paramChanges: []}); } render () { - let params = this.props.params && this.props.params.map((param, index) => { - return <QueryParamRow key={param.name} param={param} updateParam={this.update}/>; - }); - - if (!params) return null; + let propParams = this.props.params; + if (!propParams) return null; + + let changedParams = this.state.paramChanges; + let params = this.mergeParamChanges(propParams, changedParams) + .map(param => { + return ( + <QueryParamRow key={param.name} param={param} + saveParamChanges={this.saveParamChanges} /> + ); + }); return ( <form onSubmit={this.save} style={{padding: '10px', boxShadow: '2px 2px 2px 2px grey', @@ -72,7 +79,7 @@ class QueryParams extends React.Component { <div className='form-group'> <label className='sr-only' htmlFor='queryDescription'>Description</label> <input type='text' className='form-control' style={{fontWeight: 'normal'}} - onChange={this.handleChange} id='queryDescription' + onChange={this.handleChange} id='queryDescription' value={this.state.description} placeholder='(Optional description) e.g. This awesome query does magic along with its job.' /> </div> @@ -93,7 +100,8 @@ class QueryParams extends React.Component { save (e) { e.preventDefault(); - var parameters = this._getChildrenParams(); + // merges the initial props and the delta changes done by child components + var parameters = this.mergeParamChanges(this.props.params, this.state.paramChanges); this.props.saveParams({ parameters: parameters, description: this.state.description, @@ -101,23 +109,39 @@ class QueryParams extends React.Component { }); } - _getChildrenParams () { - return Object.keys(this.state.childrenParams).map(name => { - return this.state.childrenParams[name]; - }); - } - handleChange (e) { - this.setState({description: e.target.value}); + this.setState({ description: e.target.value }); } handleCheck (e) { - this.setState({runImmediately: e.target.checked}); + this.setState({ runImmediately: e.target.checked }); + } + + mergeParamChanges (original = [], changes = []) { + return original.map(originalParam => { + let change = changes.filter(changedParam => { + return changedParam.name === originalParam.name; + }); + return _.assign({}, originalParam, change[0]); + }); } - // called by the child component {name, param} - update (param) { - this.state.childrenParams[param.name] = param.param; + saveParamChanges (changedParam) { + // getting the param from the paramChanges state. + var param = this.state.paramChanges.filter(param => { + return param.name === changedParam.name; + })[0]; + + // apply the changedParam over the above param. + var newParam = _.assign({}, param, changedParam); + + // getting all the other changes except the current as + // we want to over-write it + var newChangedParams = this.state.paramChanges.filter(param => { + return param.name !== newParam.name; + }); + + this.setState({paramChanges: [...newChangedParams, newParam]}); } } http://git-wip-us.apache.org/repos/asf/lens/blob/2870be7c/lens-ui/app/components/SavedQueryPreviewComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/SavedQueryPreviewComponent.js b/lens-ui/app/components/SavedQueryPreviewComponent.js index 4f9459a..eea1e47 100644 --- a/lens-ui/app/components/SavedQueryPreviewComponent.js +++ b/lens-ui/app/components/SavedQueryPreviewComponent.js @@ -20,6 +20,7 @@ import React from 'react'; import { Link } from 'react-router'; import CodeMirror from 'codemirror'; +import _ from 'lodash'; import 'codemirror/mode/sql/sql.js'; import 'codemirror/addon/runmode/runmode.js'; @@ -30,13 +31,16 @@ import UserStore from '../stores/UserStore'; class SavedQueryPreview extends React.Component { constructor (props) { super(props); - this.state = { showDetail: false, queryParams: {} }; + this.state = { + showDetail: false, + queryParams: props.query.parameters.reduce((prev, curr) => { + prev[curr.name] = curr; + return prev; + }, {}) + }; this.toggleQueryDetails = this.toggleQueryDetails.bind(this); this.runSavedQuery = this.runSavedQuery.bind(this); this.update = this.update.bind(this); - this.props.query && this.props.query.parameters.forEach(param => { - this.state.queryParams[param.name] = param; - }); } render () { @@ -58,7 +62,7 @@ class SavedQueryPreview extends React.Component { let params = query && query.parameters.map(param => { return <QueryParamRowComponent param={param} entryMode={true} - updateParam={this.update}/>; + saveParamChanges={this.update}/>; }); let paramsTable = !params.length ? null : @@ -114,15 +118,17 @@ class SavedQueryPreview extends React.Component { } update (param) { - this.state.queryParams[param.name] = param.param; + this.setState({ + queryParams: _.assign({}, this.state.queryParams, {[param.name]: param}) + }); } runSavedQuery () { let secretToken = UserStore.getUserDetails().secretToken; - let parameters = Object.keys(this.state.queryParams).map(name => { - let object = {}; - object[name] = this.state.queryParams[name].defaultValue; - return object; + let parameters = Object.keys(this.state.queryParams).map(paramName => { + return { + [paramName]: this.state.queryParams[paramName].defaultValue + }; }); AdhocQueryActions.runSavedQuery(secretToken, this.props.query.id, parameters); }
