Repository: lens Updated Branches: refs/heads/master 714aae99d -> 01a561c63
LENS-1182: Lens UI Should set database and refresh cubes pane on db switch Project: http://git-wip-us.apache.org/repos/asf/lens/repo Commit: http://git-wip-us.apache.org/repos/asf/lens/commit/01a561c6 Tree: http://git-wip-us.apache.org/repos/asf/lens/tree/01a561c6 Diff: http://git-wip-us.apache.org/repos/asf/lens/diff/01a561c6 Branch: refs/heads/master Commit: 01a561c63aa2d20db36dfe0f22cda798cdf5278e Parents: 714aae9 Author: Rajat Khandelwal <pro...@apache.org> Authored: Fri Jun 17 12:09:45 2016 +0530 Committer: Rajat Khandelwal <rajatgupt...@gmail.com> Committed: Fri Jun 17 12:09:45 2016 +0530 ---------------------------------------------------------------------- lens-ui/app/actions/AdhocQueryActions.js | 76 +++++++----- lens-ui/app/adapters/AdhocQueryAdapter.js | 10 ++ lens-ui/app/app.js | 10 +- lens-ui/app/components/AdhocQueryComponent.js | 2 +- lens-ui/app/components/CubeSchemaComponent.js | 128 +++++++++++++-------- lens-ui/app/components/CubeTreeComponent.js | 16 +-- lens-ui/app/components/DatabaseComponent.js | 66 ++++++----- lens-ui/app/components/QueryBoxComponent.js | 2 +- lens-ui/app/components/SidebarComponent.js | 4 +- lens-ui/app/components/TableTreeComponent.js | 66 +++++++---- lens-ui/app/constants/AdhocQueryConstants.js | 3 + lens-ui/app/stores/CubeStore.js | 39 ++++--- lens-ui/app/stores/DatabaseStore.js | 10 +- lens-ui/server.js | 2 +- 14 files changed, 270 insertions(+), 164 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/actions/AdhocQueryActions.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/actions/AdhocQueryActions.js b/lens-ui/app/actions/AdhocQueryActions.js index 2a4b47b..38f2794 100644 --- a/lens-ui/app/actions/AdhocQueryActions.js +++ b/lens-ui/app/actions/AdhocQueryActions.js @@ -64,6 +64,42 @@ function _saveQuery (secretToken, user, query, options) { }); }).catch(e => { console.error(e); }); } +function _getTables (secretToken, database) { + AdhocQueryAdapter.getTables(secretToken, database) + .then(function (tables) { + AppDispatcher.dispatch({ + actionType: AdhocQueryConstants.RECEIVE_TABLES, + payload: { tables: tables, database: database } + }); + }, function (error) { + // propagating the error message, couldn't fetch cubes + AppDispatcher.dispatch({ + actionType: AdhocQueryConstants.RECEIVE_TABLES_FAILED, + payload: { + responseCode: error.status, + responseMessage: error.statusText + } + }); + }); +} +function _getCubes (secretToken, database) { + AdhocQueryAdapter.getCubes(secretToken) + .then(function (cubes) { + AppDispatcher.dispatch({ + actionType: AdhocQueryConstants.RECEIVE_CUBES, + payload: { cubes: cubes, database: database } + }); + }, function (error) { + // propagating the error message, couldn't fetch cubes + AppDispatcher.dispatch({ + actionType: AdhocQueryConstants.RECEIVE_CUBES_FAILED, + payload: { + responseCode: error.status, + responseMessage: error.statusText + } + }); + }); +} let AdhocQueryActions = { getDatabases (secretToken) { @@ -83,18 +119,18 @@ let AdhocQueryActions = { }); }); }, - - getCubes (secretToken) { - AdhocQueryAdapter.getCubes(secretToken) - .then(function (cubes) { + setDatabase (secretToken, database) { + AdhocQueryAdapter.setDatabase(secretToken, database) + .then(function (success) { AppDispatcher.dispatch({ - actionType: AdhocQueryConstants.RECEIVE_CUBES, - payload: { cubes: cubes } + actionType: AdhocQueryConstants.SELECT_DATABASE, + payload: { database: database } }); + _getTables(secretToken, database); + _getCubes(secretToken, database); }, function (error) { - // propagating the error message, couldn't fetch cubes AppDispatcher.dispatch({ - actionType: AdhocQueryConstants.RECEIVE_CUBES_FAILED, + actionType: AdhocQueryConstants.SELECT_DATABASE_FAILED, payload: { responseCode: error.status, responseMessage: error.statusText @@ -102,6 +138,7 @@ let AdhocQueryActions = { }); }); }, + getCubes: _getCubes, getSavedQueries (secretToken, user, options) { AdhocQueryAdapter.getSavedQueries(secretToken, user, options) @@ -216,12 +253,12 @@ let AdhocQueryActions = { }); }, - getCubeDetails (secretToken, cubeName) { + getCubeDetails (secretToken, databaseName, cubeName) { AdhocQueryAdapter.getCubeDetails(secretToken, cubeName) .then(function (cubeDetails) { AppDispatcher.dispatch({ actionType: AdhocQueryConstants.RECEIVE_CUBE_DETAILS, - payload: { cubeDetails: cubeDetails } + payload: {database: databaseName, cubeDetails: cubeDetails } }); }, function (error) { AppDispatcher.dispatch({ @@ -300,24 +337,7 @@ let AdhocQueryActions = { }); }, - getTables (secretToken, database) { - AdhocQueryAdapter.getTables(secretToken, database) - .then(function (tables) { - AppDispatcher.dispatch({ - actionType: AdhocQueryConstants.RECEIVE_TABLES, - payload: { tables: tables, database: database } - }); - }, function (error) { - // propagating the error message, couldn't fetch cubes - AppDispatcher.dispatch({ - actionType: AdhocQueryConstants.RECEIVE_TABLES_FAILED, - payload: { - responseCode: error.status, - responseMessage: error.statusText - } - }); - }); - }, + getTables: _getTables, getTableDetails (secretToken, tableName, database) { AdhocQueryAdapter.getTableDetails(secretToken, tableName, database) http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/adapters/AdhocQueryAdapter.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/adapters/AdhocQueryAdapter.js b/lens-ui/app/adapters/AdhocQueryAdapter.js index 445d6f2..9d4b416 100644 --- a/lens-ui/app/adapters/AdhocQueryAdapter.js +++ b/lens-ui/app/adapters/AdhocQueryAdapter.js @@ -25,6 +25,7 @@ import Config from 'config.json'; let baseUrl = Config.baseURL; let urls = { getDatabases: 'metastore/databases', + setDatabases: 'metastore/databases/current', getCubes: 'metastore/cubes', query: 'queryapi/queries', // POST on this to execute, GET to fetch all getTables: 'metastore/nativetables', @@ -39,6 +40,15 @@ let AdhocQueryAdapter = { let url = baseUrl + urls.getDatabases; return BaseAdapter.get(url + '?sessionid=' + secretToken); }, + setDatabase (secretToken, database) { + let url = baseUrl + urls.setDatabases; + return BaseAdapter.put(url + '?sessionid=' + secretToken, database, { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + }, getCubes (secretToken) { let url = baseUrl + urls.getCubes; http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/app.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/app.js b/lens-ui/app/app.js index 73cb511..07d9acf 100644 --- a/lens-ui/app/app.js +++ b/lens-ui/app/app.js @@ -30,6 +30,7 @@ import About from './components/AboutComponent'; import App from './components/AppComponent'; import AdhocQuery from './components/AdhocQueryComponent'; import QueryResults from './components/QueryResultsComponent'; +import DatabaseComponent from './components/DatabaseComponent'; import CubeSchema from './components/CubeSchemaComponent'; import QueryDetailResult from './components/QueryDetailResultComponent'; import TableSchema from './components/TableSchemaComponent'; @@ -43,10 +44,11 @@ let routes = ( <Route name='results' handler={QueryResults}/> <Route name='savedqueries' handler={SavedQueries}/> <Route name='result' path='/results/:handle' handler={QueryDetailResult}/> - <Route name='cubeschema' path='schema/cube/:cubeName' handler={CubeSchema}/> - <Route name='tableschema' path='schema/table/:tableName' - handler={TableSchema}/> - + </Route> + <Route name='schema' path='schema/:databaseName/' handler={AdhocQuery} > + <Route name='cubeschema' path='cube/:cubeName' handler={CubeSchema}/> + <Route name='tableschema' path='table/:tableName' + handler={TableSchema}/> </Route> <Route name='about' handler={About} /> <DefaultRoute handler={AdhocQuery} /> http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/components/AdhocQueryComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/AdhocQueryComponent.js b/lens-ui/app/components/AdhocQueryComponent.js index 32fab33..aae823c 100644 --- a/lens-ui/app/components/AdhocQueryComponent.js +++ b/lens-ui/app/components/AdhocQueryComponent.js @@ -29,7 +29,7 @@ class AdhocQuery extends React.Component { return ( <section className='row'> <div className='col-md-4'> - <Sidebar /> + <Sidebar {...this.props}/> </div> <div className='col-md-8'> http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/components/CubeSchemaComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/CubeSchemaComponent.js b/lens-ui/app/components/CubeSchemaComponent.js index f56d9c2..dbb4cbc 100644 --- a/lens-ui/app/components/CubeSchemaComponent.js +++ b/lens-ui/app/components/CubeSchemaComponent.js @@ -24,37 +24,53 @@ import UserStore from '../stores/UserStore'; import AdhocQueryActions from '../actions/AdhocQueryActions'; import Loader from '../components/LoaderComponent'; -function getCubes () { - return CubeStore.getCubes(); +function getCubes (database) { + return CubeStore.getCubes(database); } function constructMeasureTable (cubeName, measures) { let table = measures.map((measure) => { - return ( - <tr key={cubeName + '|' + measure.name}> - <td>{ measure.name }</td> - <td>{ measure._type }</td> - <td>{ measure.default_aggr }</td> - <td>{ measure.display_string }</td> + if (typeof(measure) == "string") { + return ( + <tr key={cubeName + '|' + measure}> + <td>{ measure }</td> + </tr> + ); + } else { + return ( + <tr key={cubeName + '|' + measure.name}> + <td>{ measure.name }</td> + <td>{ measure._type }</td> + <td>{ measure.default_aggr }</td> + <td>{ measure.display_string }</td> + </tr> + ); + } + }); + + let header = ( + <tr> + <th>Name</th> + </tr> + ); + if (typeof(measures[0]) != "string") { + header = ( + <tr> + <th>Name</th> + <th>Type</th> + <th>Default Aggr</th> + <th>Description</th> </tr> + ); - }); + } return ( <div className='table-responsive'> <table className='table table-striped table-condensed'> <caption className='bg-primary text-center'>Measures</caption> - <thead> - <tr> - <th>Name</th> - <th>Type</th> - <th>Default Aggr</th> - <th>Description</th> - </tr> - </thead> - <tbody> - {table} - </tbody> + <thead>{header}</thead> + <tbody>{table}</tbody> </table> </div> ); @@ -62,35 +78,48 @@ function constructMeasureTable (cubeName, measures) { function constructDimensionTable (cubeName, dimensions) { let table = dimensions.map((dimension) => { - return ( - <tr key={cubeName + '|' + dimension.name}> - <td>{ dimension.name }</td> - <td>{ dimension._type }</td> - <td>{ dimension.ref_spec && dimension.ref_spec.chain_ref_column && + if (typeof(dimension) =="string") { + return ( + <tr key={cubeName + '|' + dimension}> + <td>{ dimension}</td> + </tr> + ); + } else { + return ( + <tr key={cubeName + '|' + dimension.name}> + <td>{ dimension.name }</td> + <td>{ dimension._type }</td> + <td>{ dimension.ref_spec && dimension.ref_spec.chain_ref_column && dimension.ref_spec.chain_ref_column.dest_table }</td> - <td>{ dimension.ref_spec && dimension.ref_spec.chain_ref_column && + <td>{ dimension.ref_spec && dimension.ref_spec.chain_ref_column && dimension.ref_spec.chain_ref_column.ref_col }</td> - <td>{ dimension.description }</td> + <td>{ dimension.description }</td> + </tr> + ); + } + }); + let header = ( + <tr> + <th>Name</th> + </tr> + ); + if (typeof(dimensions[0]) != "string") { + header = ( + <tr> + <th>Name</th> + <th>Type</th> + <th>Destination Table</th> + <th>Column</th> + <th>Description</th> </tr> ); - }); - + } return ( <div className='table-responsive'> <table className='table table-striped'> <caption className='bg-primary text-center'>Dimensions</caption> - <thead> - <tr> - <th>Name</th> - <th>Type</th> - <th>Destination Table</th> - <th>Column</th> - <th>Description</th> - </tr> - </thead> - <tbody> - {table} - </tbody> + <thead>{header}</thead> + <tbody>{table}</tbody> </table> </div> ); @@ -100,14 +129,11 @@ function constructDimensionTable (cubeName, dimensions) { class CubeSchema extends React.Component { constructor (props) { super(props); - this.state = {cube: {}}; + this.state = {cube: {}, database: props.params.databaseName}; this._onChange = this._onChange.bind(this); - // firing the action for the first time component is rendered - // it won't have a cube in the state. - let cubeName = props.params.cubeName; AdhocQueryActions - .getCubeDetails(UserStore.getUserDetails().secretToken, cubeName); + .getCubeDetails(UserStore.getUserDetails().secretToken, props.params.databaseName, props.params.cubeName); } componentDidMount () { @@ -121,18 +147,18 @@ class CubeSchema extends React.Component { componentWillReceiveProps (props) { // TODO are props updated automatically, unlike state? let cubeName = props.params.cubeName; - let cube = getCubes()[cubeName]; + let cube = getCubes(props.params.databaseName)[cubeName]; if (cube.isLoaded) { - this.setState({ cube: getCubes()[cubeName] }); + this.setState({ cube: cube, database: props.params.database }); return; } AdhocQueryActions - .getCubeDetails(UserStore.getUserDetails().secretToken, cubeName); + .getCubeDetails(UserStore.getUserDetails().secretToken, props.params.databaseName, cubeName); // empty the previous state - this.setState({ cube: {} }); + this.setState({ cube: {}, database: props.params.databaseName }); } render () { @@ -184,7 +210,7 @@ class CubeSchema extends React.Component { } _onChange () { - this.setState({cube: getCubes()[this.props.params.cubeName]}); + this.setState({cube: getCubes(this.props.params.databaseName)[this.props.params.cubeName]}); } } http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/components/CubeTreeComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/CubeTreeComponent.js b/lens-ui/app/components/CubeTreeComponent.js index e288476..702fa36 100644 --- a/lens-ui/app/components/CubeTreeComponent.js +++ b/lens-ui/app/components/CubeTreeComponent.js @@ -24,6 +24,7 @@ import { Link } from 'react-router'; import 'react-treeview/react-treeview.css'; import ClassNames from 'classnames'; +import DatabaseStore from '../stores/DatabaseStore'; import CubeStore from '../stores/CubeStore'; import AdhocQueryActions from '../actions/AdhocQueryActions'; import UserStore from '../stores/UserStore'; @@ -32,7 +33,7 @@ import '../styles/css/tree.css'; function getCubeData () { return { - cubes: CubeStore.getCubes() + cubes: CubeStore.getCubes(DatabaseStore.currentDatabase()) }; } @@ -44,17 +45,12 @@ class CubeTree extends React.Component { // comes with React.createClass, using constructor is the new // idiomatic way // https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html - this.state = { cubes: [], loading: true, isCollapsed: false }; + this.state = {database: props.database, cubes: [], loading: true, isCollapsed: false }; // no autobinding with ES6 so doing it manually, see link below // https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding this._onChange = this._onChange.bind(this); this.toggle = this.toggle.bind(this); - - // need to fire an action to fetch the cubes from server - // can't ask the store as it won't have any at the startup - // TODO optimize this, don't fire it everytime. - AdhocQueryActions.getCubes(UserStore.getUserDetails().secretToken); } componentDidMount () { @@ -77,16 +73,16 @@ class CubeTree extends React.Component { var cubeTree = Object.keys(this.state.cubes).map((cubeName, i) => { let cube = cubeHash[cubeName]; - let label = <Link to='cubeschema' params={{cubeName: cubeName}}> + let label = <Link to='cubeschema' params={{databaseName: this.state.database, cubeName: cubeName}}> <span className='node'>{cube.name}</span> </Link>; - let measureLabel = <Link to='cubeschema' params={{cubeName: cubeName}} + let measureLabel = <Link to='cubeschema' params={{databaseName: this.state.database, cubeName: cubeName}} query={{type: 'measures'}}> <span className='quiet'>Measures</span> </Link>; - let dimensionLabel = <Link to='cubeschema' params={{cubeName: cubeName}} + let dimensionLabel = <Link to='cubeschema' params={{databaseName: this.state.database, cubeName: cubeName}} query={{type: 'dimensions'}}> <span className='quiet'>Dimensions</span> </Link>; http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/components/DatabaseComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/DatabaseComponent.js b/lens-ui/app/components/DatabaseComponent.js index 09c9e7b..e99be00 100644 --- a/lens-ui/app/components/DatabaseComponent.js +++ b/lens-ui/app/components/DatabaseComponent.js @@ -24,6 +24,7 @@ import DatabaseStore from '../stores/DatabaseStore'; import AdhocQueryActions from '../actions/AdhocQueryActions'; import UserStore from '../stores/UserStore'; import Loader from '../components/LoaderComponent'; +import CubeTree from './CubeTreeComponent'; import TableTree from './TableTreeComponent'; function getDatabases () { @@ -37,13 +38,16 @@ class DatabaseComponent extends React.Component { databases: [], loading: true, isCollapsed: false, - selectedDatabase: '' + selectedDatabase: props.params.databaseName, }; this._onChange = this._onChange.bind(this); this.toggle = this.toggle.bind(this); this.setDatabase = this.setDatabase.bind(this); AdhocQueryActions.getDatabases(UserStore.getUserDetails().secretToken); + if (this.state.selectedDatabase) { + this.setDatabase(this.state.selectedDatabase); + } } componentDidMount () { @@ -74,39 +78,40 @@ class DatabaseComponent extends React.Component { <select className='form-control' id='db' onChange={this.setDatabase}> <option value=''>Select</option> {this.state.databases.map(database => { - return <option key={database} value={database}>{database}</option>; + return <option key={database} value={database} selected={database == this.state.selectedDatabase}>{database}</option>; })} </select> </div>); + if (this.state.loading) { - databaseComponent = <Loader size='4px' margin='2px' />; + databaseComponent = <Loader size='4px' margin='2px'/>; } else if (!this.state.databases.length) { databaseComponent = (<div className='alert-danger' - style={{padding: '8px 5px'}}> - <strong>Sorry, we couldn't find any databases.</strong> - </div>); + style={{padding: '8px 5px'}}> + <strong>Sorry, we couldn't find any databases.</strong> + </div>); } - return ( - <div className='panel panel-default'> - <div className='panel-heading'> - <h3 className='panel-title'> - Tables - <span className={collapseClass} onClick={this.toggle}></span> - </h3> - </div> - <div className={panelBodyClassName}> - {databaseComponent} - - { this.state.selectedDatabase && - <div> - <hr style={{marginTop: '10px', marginBottom: '10px'}}/> - <TableTree key={this.state.selectedDatabase} - database={this.state.selectedDatabase} /> - </div> - } - </div> + return (<div> + {databaseComponent} + { + this.state.selectedDatabase && + <div> + <hr style={{marginTop: '10px', marginBottom: '10px'}}/> + <CubeTree key={this.state.selectedDatabase} + database={this.state.selectedDatabase}/> + </div> + } + { + this.state.selectedDatabase && + <div> + <hr style={{marginTop: '10px', marginBottom: '10px'}}/> + <TableTree key={this.state.selectedDatabase} + database={this.state.selectedDatabase}/> + </div> + } + </div> ); } @@ -119,8 +124,15 @@ class DatabaseComponent extends React.Component { this.setState({ isCollapsed: !this.state.isCollapsed }); } - setDatabase (event) { - this.setState({selectedDatabase: event.target.value}); + setDatabase(event) { + var dbName = null; + if (typeof(event) == "string") { + dbName = event; + } else { + dbName = event.target.value; + } + AdhocQueryActions.setDatabase(UserStore.getUserDetails().secretToken, dbName); + this.setState({databases: getDatabases(), selectedDatabase: dbName, loading: false}); } } http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/components/QueryBoxComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/QueryBoxComponent.js b/lens-ui/app/components/QueryBoxComponent.js index ec6a06b..30eb643 100644 --- a/lens-ui/app/components/QueryBoxComponent.js +++ b/lens-ui/app/components/QueryBoxComponent.js @@ -393,7 +393,7 @@ class QueryBox extends React.Component { _onChangeCubeStore () { // cubes - let cubes = CubeStore.getCubes(); // hashmap + let cubes = CubeStore.getCubes(DatabaseStore.currentDatabase()); // hashmap Object.keys(cubes).forEach((cubeName) => { let cube = cubes[cubeName]; codeMirrorHints[cubeName] = []; http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/components/SidebarComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/SidebarComponent.js b/lens-ui/app/components/SidebarComponent.js index 37a2c59..7db1789 100644 --- a/lens-ui/app/components/SidebarComponent.js +++ b/lens-ui/app/components/SidebarComponent.js @@ -19,7 +19,6 @@ import React from 'react'; -import CubeTree from './CubeTreeComponent'; import Database from './DatabaseComponent'; import QueryOperations from './QueryOperationsComponent'; @@ -28,8 +27,7 @@ class Sidebar extends React.Component { return ( <section> <QueryOperations /> - <CubeTree /> - <Database /> + <Database {...this.props}/> </section> ); } http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/components/TableTreeComponent.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/components/TableTreeComponent.js b/lens-ui/app/components/TableTreeComponent.js index 2e62399..9a8351e 100644 --- a/lens-ui/app/components/TableTreeComponent.js +++ b/lens-ui/app/components/TableTreeComponent.js @@ -21,6 +21,7 @@ import React from 'react'; import TreeView from 'react-treeview'; import { Link } from 'react-router'; import 'react-treeview/react-treeview.css'; +import ClassNames from 'classnames'; import TableStore from '../stores/TableStore'; import AdhocQueryActions from '../actions/AdhocQueryActions'; @@ -65,7 +66,8 @@ function getTables (page, filterString, database) { return { totalPages: Math.ceil(allTables.length / pageSize), - tables: pageTables + tables: pageTables, + database: database, }; } @@ -77,7 +79,8 @@ class TableTree extends React.Component { totalPages: 0, page: 0, loading: true, - isCollapsed: false + isCollapsed: false, + database: props.database }; this._onChange = this._onChange.bind(this); this.prevPage = this.prevPage.bind(this); @@ -115,17 +118,15 @@ class TableTree extends React.Component { TableStore.removeChangeListener(this._onChange); } - render () { - let tableTree = ''; - + render() { // construct tree - tableTree = this.state.tables.map(table => { - let label = (<Link to='tableschema' params={{tableName: table.name}} - title={table.name} query={{database: this.props.database}}> - {table.name}</Link>); + let tableTreeInternal = this.state.tables.map(table => { + let label = (<Link to='tableschema' params={{databaseName: this.state.database, tableName: table.name}} + title={table.name} query={{database: this.props.database}}> + {table.name}</Link>); return ( <TreeView key={table.name} nodeLabel={label} - defaultCollapsed={!table.isLoaded}> + defaultCollapsed={!table.isLoaded}> {table.isLoaded ? table.columns.map(col => { return ( @@ -141,11 +142,11 @@ class TableTree extends React.Component { // show a loader when tree is loading if (this.state.loading) { - tableTree = <Loader size='4px' margin='2px' />; + tableTreeInternal = <Loader size='4px' margin='2px'/>; } else if (!this.state.tables.length) { - tableTree = (<div className='alert-danger' style={{padding: '8px 5px'}}> - <strong>Sorry, we couldn't find any.</strong> - </div>); + tableTreeInternal = (<div className='alert-danger' style={{padding: '8px 5px'}}> + <strong>Sorry, we couldn't find any.</strong> + </div>); } let pagination = this.state.tables.length ? @@ -162,20 +163,43 @@ class TableTree extends React.Component { </div> </div> ) : null; - - return ( + let tableTree = ( <div> { !this.state.loading && - <div className='form-group'> - <input type='search' className='form-control' - placeholder='Type to filter tables' - onChange={this._filter.bind(this)}/> - </div> + <div className='form-group'> + <input type='search' className='form-control' + placeholder='Type to filter tables' + onChange={this._filter.bind(this)}/> + </div> } {pagination} <div ref='tableTree' style={{maxHeight: '350px', overflowY: 'auto'}}> + {tableTreeInternal} + </div> + </div> + ); + let collapseClass = ClassNames({ + 'pull-right': true, + 'glyphicon': true, + 'glyphicon-chevron-up': !this.state.isCollapsed, + 'glyphicon-chevron-down': this.state.isCollapsed + }); + + let panelBodyClassName = ClassNames({ + 'panel-body': true, + 'hide': this.state.isCollapsed + }); + return ( + <div className='panel panel-default'> + <div className='panel-heading'> + <h3 className='panel-title'> + Tables + <span className={collapseClass} onClick={this.toggle}></span> + </h3> + </div> + <div className={panelBodyClassName} style={{maxHeight: '350px', overflowY: 'auto'}}> {tableTree} </div> </div> http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/constants/AdhocQueryConstants.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/constants/AdhocQueryConstants.js b/lens-ui/app/constants/AdhocQueryConstants.js index ea8cbd0..7eceb6f 100644 --- a/lens-ui/app/constants/AdhocQueryConstants.js +++ b/lens-ui/app/constants/AdhocQueryConstants.js @@ -47,6 +47,9 @@ const AdhocQueryConstants = KeyMirror({ RECEIVE_DATABASES: null, RECEIVE_DATABASES_FAILED: null, + SELECT_DATABASE: null, + SELECT_DATABASE_FAILED: null, + RECEIVE_QUERY_PARAMS_META: null, SAVE_QUERY_SUCCESS: null, http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/stores/CubeStore.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/stores/CubeStore.js b/lens-ui/app/stores/CubeStore.js index d4825a9..4441e54 100644 --- a/lens-ui/app/stores/CubeStore.js +++ b/lens-ui/app/stores/CubeStore.js @@ -24,34 +24,43 @@ import { EventEmitter } from 'events'; // private methods function receiveCubes (payload) { - payload.cubes && payload.cubes.stringList && + let currentDatabase = payload.database; + cubes[currentDatabase] = cubes[currentDatabase] || {}; + payload.database && payload.cubes && payload.cubes.stringList && payload.cubes.stringList.elements && payload.cubes.stringList.elements.forEach(cube => { - if (!cubes[cube]) { - cubes[cube] = { name: cube, isLoaded: false }; + if (!cubes[currentDatabase][cube]) { + cubes[currentDatabase][cube] = { name: cube, isLoaded: false }; } }); } function receiveCubeDetails (payload) { + cubes[payload.database] = cubes[payload.database] || {}; let cubeDetails = payload.cubeDetails && payload.cubeDetails.x_cube; - - let dimensions = cubeDetails.dim_attributes && - cubeDetails.dim_attributes.dim_attribute; - let measures = cubeDetails.measures && - cubeDetails.measures.measure; - - cubes[cubeDetails.name].measures = measures; - cubes[cubeDetails.name].dimensions = dimensions; - cubes[cubeDetails.name].isLoaded = true; + let dimensions = null; + let measures = null; + if (cubeDetails.type == 'x_base_cube') { + dimensions = cubeDetails.dim_attributes && + cubeDetails.dim_attributes.dim_attribute; + measures = cubeDetails.measures && + cubeDetails.measures.measure; + } else if (cubeDetails.type == 'x_derived_cube') { + dimensions = cubeDetails.dim_attr_names && cubeDetails.dim_attr_names.attr_name; + measures = cubeDetails.measure_names && cubeDetails.measure_names.measure_name; + } + cubes[payload.database][cubeDetails.name] = cubes[payload.database][cubeDetails.name] || { name: cubeDetails.name, isLoaded: false }; + cubes[payload.database][cubeDetails.name].measures = measures; + cubes[payload.database][cubeDetails.name].dimensions = dimensions; + cubes[payload.database][cubeDetails.name].isLoaded = true; } let CHANGE_EVENT = 'change'; var cubes = {}; - +var currentDatabase = null; let CubeStore = assign({}, EventEmitter.prototype, { - getCubes () { - return cubes; + getCubes (currentDatabase) { + return cubes[currentDatabase]; }, emitChange () { http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/app/stores/DatabaseStore.js ---------------------------------------------------------------------- diff --git a/lens-ui/app/stores/DatabaseStore.js b/lens-ui/app/stores/DatabaseStore.js index b13246e..a18e986 100644 --- a/lens-ui/app/stores/DatabaseStore.js +++ b/lens-ui/app/stores/DatabaseStore.js @@ -32,12 +32,14 @@ function receiveDatabases (payload) { let CHANGE_EVENT = 'change'; var databases = []; - +var currentDatabase = null; let DatabaseStore = assign({}, EventEmitter.prototype, { getDatabases () { return databases; }, - + currentDatabase() { + return currentDatabase; + }, emitChange () { this.emit(CHANGE_EVENT); }, @@ -57,6 +59,10 @@ AppDispatcher.register((action) => { receiveDatabases(action.payload); DatabaseStore.emitChange(); break; + case AdhocQueryConstants.SELECT_DATABASE: + currentDatabase = action.payload.database; + DatabaseStore.emitChange(); + break; } }); http://git-wip-us.apache.org/repos/asf/lens/blob/01a561c6/lens-ui/server.js ---------------------------------------------------------------------- diff --git a/lens-ui/server.js b/lens-ui/server.js index 736d862..12b94e7 100644 --- a/lens-ui/server.js +++ b/lens-ui/server.js @@ -33,7 +33,7 @@ app.use(logger('dev')); app.use(cookieParser()); process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; - +process.env['lensserver'] = process.env['lensserver'] || 'http://0.0.0.0:9999/lensapi/'; if (!process.env['lensserver']) { throw new Error('Specify LENS Server address in `lensserver` argument'); }