You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lens.apache.org by am...@apache.org on 2015/09/23 11:11:57 UTC

[1/2] lens git commit: LENS-629 : A new improved web client for Lens

Repository: lens
Updated Branches:
  refs/heads/master 669e87272 -> 66f164b47


http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/QueryDetailResultComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/QueryDetailResultComponent.js b/lens-ui/app/components/QueryDetailResultComponent.js
new file mode 100644
index 0000000..b969a4a
--- /dev/null
+++ b/lens-ui/app/components/QueryDetailResultComponent.js
@@ -0,0 +1,192 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+
+import Loader from '../components/LoaderComponent';
+import AdhocQueryStore from '../stores/AdhocQueryStore';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+import UserStore from '../stores/UserStore';
+import QueryPreview from './QueryPreviewComponent';
+
+let interval = null;
+
+function isResultAvailableOnServer (handle) {
+
+  // always check before polling
+  let query = AdhocQueryStore.getQueries()[handle];
+  if (query && query.status && query.status.status === 'SUCCESSFUL') {
+    return true;
+  }
+  return false;
+}
+
+function fetchResult (secretToken, handle) {
+
+  // this condition checks the query object, else
+  // we fetch it with the handle that we have
+  if (isResultAvailableOnServer(handle)) {
+    let query = AdhocQueryStore.getQueries()[handle];
+    let mode = query.isPersistent ? 'PERSISTENT' : 'INMEMORY';
+    AdhocQueryActions.getQueryResult(secretToken, handle, mode);
+  } else {
+    AdhocQueryActions.getQuery(secretToken, handle);
+  }
+}
+
+function constructTable (tableData) {
+  if (!tableData.columns && !tableData.results) return;
+  let header = tableData.columns.map(column => {
+    return <th>{ column.name }</th>;
+  });
+  let rows = tableData.results
+    .map(row => {
+      return (<tr>{row.values.values.map(cell => {
+        return <td>{(cell && cell.value) || <span style={{color: 'red'}}>NULL</span>}</td>;
+      })}</tr>);
+  });
+
+  // in case the results are empty, happens when LENS server has restarted
+  // all in-memory results are wiped clean
+  if (!rows.length) {
+    let colWidth = tableData.columns.length;
+    rows = <tr>
+        <td colSpan={colWidth} style={{color: 'red', textAlign: 'center'}}>
+          Result set no longer available with server.</td>
+      </tr>;
+  }
+
+  return (
+    <div class="table-responsive">
+      <table className="table table-striped table-condensed">
+        <thead>
+          <tr>{header}</tr>
+        </thead>
+        <tbody>{rows}</tbody>
+      </table>
+    </div>
+  );
+}
+
+class QueryDetailResult extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = { loading: true, queryResult: {}, query: null };
+    this._onChange = this._onChange.bind(this);
+    this.pollForResult = this.pollForResult.bind(this);
+  }
+
+  componentDidMount () {
+    let secretToken = UserStore.getUserDetails().secretToken;
+    this.pollForResult(secretToken, this.props.params.handle);
+
+    AdhocQueryStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    clearInterval(interval);
+    AdhocQueryStore.removeChangeListener(this._onChange);
+  }
+
+  componentWillReceiveProps (props) {
+    this.state = { loading: true, queryResult: {}, query: null };
+    let secretToken = UserStore.getUserDetails().secretToken;
+    clearInterval(interval);
+    this.pollForResult(secretToken, props.params.handle);
+  }
+
+  render () {
+    let query = this.state.query;
+    let queryResult = this.state.queryResult;
+    let result = '';
+
+    // check if the query was persistent or in-memory
+    if (query && query.isPersistent && query.status.status === 'SUCCESSFUL') {
+      result = (<div className="text-center">
+        <a href={queryResult.downloadURL} download>
+          <span className="glyphicon glyphicon-download-alt	"></span> Click
+          here to download the results as a CSV file
+        </a>
+      </div>);
+    } else {
+      result = constructTable(this.state.queryResult);
+    }
+
+
+    if (this.state.loading) result = <Loader size="8px" margin="2px"></Loader>;
+
+    return (
+      <div className="panel panel-default">
+      <div className="panel-heading">
+        <h3 className="panel-title">Query Result</h3>
+      </div>
+      <div className="panel-body" style={{overflowY: 'auto', padding: '0px',
+        maxHeight: this.props.toggleQueryBox ? '260px': '480px'}}>
+        <div>
+          <QueryPreview key={query && query.queryHandle.handleId}
+            {...query} />
+        </div>
+        {result}
+      </div>
+    </div>
+    );
+  }
+
+  pollForResult (secretToken, handle) {
+
+    // fetch results immediately if present, don't wait for 5 seconds
+    // in setInterval below.
+    // FIXME if I put a return in if construct, setInterval won't execute which
+    // shouldn't but the backend API isn't stable enough, and if this call fails
+    // we'll not be able to show the results and it'll show a loader, thoughts?
+    fetchResult(secretToken, handle);
+
+    interval = setInterval(function () {
+      fetchResult(secretToken, handle);
+    }, 5000);
+  }
+
+  _onChange () {
+    let handle = this.props.params.handle;
+    let query = AdhocQueryStore.getQueries()[handle];
+    let result = AdhocQueryStore.getQueryResult(handle);
+    let loading = true;
+
+    let failed = query && query.status && query.status.status === 'FAILED';
+    let success = query && query.status && query.status.status === 'SUCCESSFUL';
+
+    if (failed || success && result) {
+      clearInterval(interval);
+      loading = false;
+    }
+
+    // check first if the query failed, clear the interval, and show it
+    // setState when query is successful AND we've the results OR it failed
+    let state = {
+      loading: loading,
+      queryResult: result || {}, // result can be undefined so guarding it
+      query: query
+    }
+
+    this.setState(state);
+
+  }
+}
+
+export default QueryDetailResult;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/QueryOperationsComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/QueryOperationsComponent.js b/lens-ui/app/components/QueryOperationsComponent.js
new file mode 100644
index 0000000..a17a636
--- /dev/null
+++ b/lens-ui/app/components/QueryOperationsComponent.js
@@ -0,0 +1,87 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import { Link } from 'react-router';
+import ClassNames from 'classnames';
+
+class QueryOperations extends React.Component {
+  constructor () {
+    super();
+    this.state = { isCollapsed: false };
+    this.toggle = this.toggle.bind(this);
+  }
+
+  toggle () {
+    this.setState({ isCollapsed: !this.state.isCollapsed });
+  }
+
+  render () {
+    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">
+            Queries
+            <span className={collapseClass} onClick={this.toggle}></span>
+          </h3>
+        </div>
+        <div className={panelBodyClassName}>
+          <ul style={{listStyle: 'none', paddingLeft: '0px',
+            marginBottom: '0px'}}>
+            <li><Link to="results">All</Link></li>
+            <li>
+              <Link to="results" query={{category: 'running'}}>
+                Running
+              </Link>
+            </li>
+            <li>
+              <Link to="results" query={{category: 'successful'}}>
+                Completed
+              </Link>
+            </li>
+            <li>
+              <Link to="results" query={{category: 'queued'}}>
+                Queued
+              </Link>
+            </li>
+            <li>
+              <Link to="results" query={{category: 'failed'}}>
+                Failed
+              </Link>
+            </li>
+          </ul>
+        </div>
+      </div>
+    );
+  }
+}
+
+export default QueryOperations;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/QueryPreviewComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/QueryPreviewComponent.js b/lens-ui/app/components/QueryPreviewComponent.js
new file mode 100644
index 0000000..fabe383
--- /dev/null
+++ b/lens-ui/app/components/QueryPreviewComponent.js
@@ -0,0 +1,176 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import Moment from 'moment';
+import { Link } from 'react-router';
+import CodeMirror from 'codemirror';
+import 'codemirror/mode/sql/sql.js';
+import 'codemirror/addon/runmode/runmode.js';
+
+import Loader from '../components/LoaderComponent';
+import UserStore from '../stores/UserStore';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+
+class QueryPreview extends React.Component {
+  constructor (props) {
+    super (props);
+    this.state = {showDetail: false};
+    this.toggleQueryDetails = this.toggleQueryDetails.bind(this);
+    this.cancelQuery = this.cancelQuery.bind(this);
+  }
+
+	render () {
+    let query = this.props;
+
+    if (!query.userQuery) return null;
+
+    // code below before the return prepares the data to render, turning out
+    // crude properties to glazed, garnished ones e.g. formatting of query
+    let codeTokens = [];
+
+    CodeMirror
+      .runMode(query.userQuery,
+        'text/x-mysql', function (text, style) {
+
+        // this method is called for every token and gives the
+        // token and style class for it.
+        codeTokens.push(<span className={'cm-' + style}>{text}</span>);
+
+      });
+
+    // figuring out the className for query status
+    // TODO optimize this construct
+    let statusTypes = {
+      'EXECUTED': 'success',
+      'SUCCESSFUL': 'success',
+      'FAILED': 'danger',
+      'CANCELED': 'danger',
+      'CLOSED': 'warning',
+      'QUEUED': 'info',
+      'RUNNING': 'info'
+    };
+
+    let statusClass = 'label-' + statusTypes[query.status.status] ||
+      'label-info';
+    let handle = query.queryHandle.handleId;
+    let executionTime = (query.finishTime - query.submissionTime)/(1000*60);
+    let statusType = query.status.status === 'ERROR'? 'Error: ' : 'Status: ';
+    let seeResult = '';
+    let statusMessage = query.status.status === 'SUCCESSFUL'?
+      query.status.statusMessage :
+      query.status.errorMessage;
+
+    if (query.status.status === 'SUCCESSFUL') {
+      seeResult = (<Link to="result" params={{handle: handle}}
+        className="btn btn-success btn-xs pull-right" style={{marginLeft: '5px'}}>
+        See Result
+      </Link>);
+    }
+
+
+    return (
+      <section>
+        <div className="panel panel-default">
+          <pre className="cm-s-default" style={{cursor: 'pointer',
+            border: '0px', marginBottom: '0px'}}
+            onClick={this.toggleQueryDetails}>
+
+            {codeTokens}
+
+            <label className={"pull-right label " + statusClass}>
+              {query.status.status}
+            </label>
+
+            {query.queryName && (
+              <label className="pull-right label label-primary"
+                style={{marginRight: '5px'}}>
+                {query.queryName}
+              </label>
+            )}
+
+          </pre>
+
+          {this.state.showDetail && (
+            <div className="panel-body" style={{borderTop: '1px solid #cccccc',
+            paddingBottom: '0px'}} key={'preview' + handle}>
+              <div className="row">
+                <div className="col-lg-4 col-sm-4">
+                  <span className="text-muted">Name </span>
+                  <strong>{ query.queryName || 'Not specified'}</strong>
+                </div>
+                <div className="col-lg-4 col-sm-4">
+                  <span className="text-muted">Submitted </span>
+                  <strong>
+                    { Moment(query.submissionTime).format('Do MMM YY, hh:mm:ss a')}
+                  </strong>
+                </div>
+                <div className="col-lg-4 col-sm-4">
+                  <span className="text-muted">Execution time </span>
+                  <strong>
+
+                    { executionTime > 0 ?
+                        Math.ceil(executionTime) +
+                          (executionTime > 1 ? ' mins': ' min') :
+                        'Still running'
+                    }
+                  </strong>
+                </div>
+              </div>
+              <div className="row">
+                <div
+                  className={'alert alert-' + statusTypes[query.status.status]}
+                  style={{marginBottom: '0px', padding: '5px 15px 5px 15px'}}>
+                    <p>
+                      <strong>{statusType}</strong>
+                      {statusMessage || query.status.status}
+
+                      {seeResult}
+
+                      <Link to="query" query={{handle: query.queryHandle.handleId}}
+                        className="pull-right">
+                        Edit Query
+                      </Link>
+
+                    </p>
+                </div>
+              </div>
+            </div>
+          )}
+        </div>
+      </section>
+    );
+  }
+
+  toggleQueryDetails () {
+    this.setState({ showDetail: !this.state.showDetail });
+  }
+
+  cancelQuery () {
+    let secretToken = UserStore.getUserDetails().secretToken;
+    let handle = this.props && this.props.queryHandle &&
+      this.props.queryHandle.handleId;
+
+    if (!handle)  return;
+
+    AdhocQueryActions.cancelQuery(secretToken, handle);
+  }
+}
+
+export default QueryPreview;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/QueryResultsComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/QueryResultsComponent.js b/lens-ui/app/components/QueryResultsComponent.js
new file mode 100644
index 0000000..6e4b8c2
--- /dev/null
+++ b/lens-ui/app/components/QueryResultsComponent.js
@@ -0,0 +1,123 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+
+import Loader from '../components/LoaderComponent';
+import AdhocQueryStore from '../stores/AdhocQueryStore';
+import UserStore from '../stores/UserStore';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+import QueryPreview from './QueryPreviewComponent';
+
+// this method fetches the results based on props.query.category
+function getResults (props) {
+  let email = UserStore.getUserDetails().email;
+  let secretToken = UserStore.getUserDetails().secretToken;
+
+  if (props.query.category) {
+
+    // fetch either running or completed results
+    AdhocQueryActions
+      .getQueries(secretToken, email, { state: props.query.category });
+  } else {
+
+    // fetch all
+    AdhocQueryActions.getQueries(secretToken, email);
+  }
+}
+
+function getQueries () {
+  return AdhocQueryStore.getQueries();
+}
+
+class QueryResults extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = { queries: {}, queriesReceived: false };
+    this._onChange = this._onChange.bind(this);
+
+    getResults(props);
+  }
+
+  componentDidMount () {
+    AdhocQueryStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    AdhocQueryStore.removeChangeListener(this._onChange);
+  }
+
+  componentWillReceiveProps (props) {
+    getResults(props);
+    this.setState({queries: {}, queriesReceived: false});
+  }
+
+  render () {
+    let queries = '';
+
+    let queryMap = this.state.queries;
+    queries = Object.keys(queryMap)
+      .sort(function (a, b) {
+        return queryMap[b].submissionTime - queryMap[a].submissionTime;
+      })
+      .map((queryHandle) => {
+      let query = queryMap[queryHandle];
+
+      return (
+        <QueryPreview key={query.queryHandle.handleId} {...query} />
+      );
+    }); // end of map
+
+    // FIXME find a better way to do it.
+    // show a loader when queries are empty, or no queries.
+    // this is managed by seeing the length of queries and
+    // a state variable 'queriesReceived'.
+    // if queriesReceived is true and the length is 0, show no queries else
+    // show a loader
+    let queriesLength = Object.keys(this.state.queries).length;
+
+    if (!queriesLength && !this.state.queriesReceived) {
+      queries = <Loader size="8px" margin="2px" />;
+    } else if (!queriesLength && this.state.queriesReceived) {
+      queries = <div className="alert alert-danger">
+        <strong>Sorry</strong>, there were no queries to be shown.
+      </div>;
+    }
+
+    return (
+      <section>
+        <div style={{border: '1px solid #dddddd', borderRadius: '4px',
+          padding: '0px 8px 8px 8px'}}>
+          <h3 style={{margin: '8px 10px'}}>Results</h3>
+          <hr style={{marginTop: '6px' }}/>
+          <div style={{overflowY: 'auto',
+            maxHeight: this.props.toggleQueryBox ? '300px': '600px'}}>
+            {queries}
+          </div>
+        </div>
+      </section>
+    );
+  }
+
+  _onChange () {
+    this.setState({ queries: getQueries(), queriesReceived: true});
+  }
+}
+
+export default QueryResults;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/RequireAuthenticationComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/RequireAuthenticationComponent.js b/lens-ui/app/components/RequireAuthenticationComponent.js
new file mode 100644
index 0000000..9a755b0
--- /dev/null
+++ b/lens-ui/app/components/RequireAuthenticationComponent.js
@@ -0,0 +1,37 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import UserStore from '../stores/UserStore';
+
+let RequireAuthentication = (Component) => {
+  return class Authenticated extends React.Component {
+    static willTransitionTo (transition) {
+      if (!UserStore.isUserLoggedIn()) {
+        transition.redirect('/login', {}, {'nextPath': transition.path});
+      }
+    }
+
+    render () {
+      return <Component {...this.props} />
+    }
+  }
+};
+
+export default RequireAuthentication;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/SidebarComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/SidebarComponent.js b/lens-ui/app/components/SidebarComponent.js
new file mode 100644
index 0000000..dcc8737
--- /dev/null
+++ b/lens-ui/app/components/SidebarComponent.js
@@ -0,0 +1,38 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+
+import CubeTree from './CubeTreeComponent';
+import Database from './DatabaseComponent';
+import QueryOperations from './QueryOperationsComponent';
+
+class Sidebar extends React.Component {
+  render() {
+    return (
+      <section>
+        <QueryOperations />
+        <CubeTree />
+        <Database />
+      </section>
+    );
+  }
+};
+
+export default Sidebar;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/TableSchemaComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/TableSchemaComponent.js b/lens-ui/app/components/TableSchemaComponent.js
new file mode 100644
index 0000000..67dc25a
--- /dev/null
+++ b/lens-ui/app/components/TableSchemaComponent.js
@@ -0,0 +1,131 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+
+import TableStore from '../stores/TableStore';
+import UserStore from '../stores/UserStore';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+import Loader from '../components/LoaderComponent';
+
+function getTable (tableName, database) {
+  let tables = TableStore.getTables(database);
+  return tables && tables[tableName];
+}
+
+class TableSchema extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {table: {}};
+    this._onChange = this._onChange.bind(this);
+
+    let secretToken = UserStore.getUserDetails().secretToken;
+    let tableName = props.params.tableName;
+    let database = props.query.database;
+    AdhocQueryActions.getTableDetails(secretToken, tableName, database);
+  }
+
+  componentDidMount () {
+    TableStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    TableStore.removeChangeListener(this._onChange);
+  }
+
+  componentWillReceiveProps (props) {
+    let tableName = props.params.tableName;
+    let database = props.query.database;
+    if (!TableStore.getTables(database)[tableName].isLoaded) {
+      let secretToken = UserStore.getUserDetails().secretToken;
+
+      AdhocQueryActions
+        .getTableDetails(secretToken, tableName, database);
+
+      // set empty state as we do not have the loaded data.
+      this.setState({table: {}});
+      return;
+    }
+
+    this.setState({
+      table: TableStore.getTables(database)[tableName]
+    });
+  }
+
+  render () {
+    let schemaSection = null;
+
+
+    if (this.state.table && !this.state.table.isLoaded) {
+      schemaSection = <Loader size="8px" margin="2px" />;
+    } else {
+      schemaSection = (<div className="row">
+          <div className="table-responsive">
+            <table className="table table-striped">
+              <thead>
+              <caption className="bg-primary text-center">Columns</caption>
+                <tr><th>Name</th><th>Type</th><th>Description</th></tr>
+              </thead>
+              <tbody>
+                {this.state.table &&
+                  this.state.table.columns.map(col => {
+                    return (
+                      <tr key={this.state.table.name + '|' + col.name}>
+                        <td>{col.name}</td>
+                        <td>{col.type}</td>
+                        <td>{col.comment || 'No description available'}</td>
+                      </tr>
+                    )
+                })}
+              </tbody>
+            </table>
+          </div>
+        </div>);
+    }
+
+    return (
+      <section>
+        <div className="panel panel-default">
+          <div className="panel-heading">
+            <h3 className="panel-title">Schema Details: &nbsp;
+              <strong className="text-primary">
+                 {this.props.query.database}.{this.props.params.tableName}
+              </strong>
+            </h3>
+          </div>
+          <div className="panel-body" style={{overflowY: 'auto',
+            maxHeight: this.props.toggleQueryBox ? '260px': '480px'}}>
+            {schemaSection}
+          </div>
+        </div>
+
+      </section>
+
+    );
+  }
+
+  _onChange () {
+    this.setState({
+      table: getTable(this.props.params.tableName,
+        this.props.query.database)
+    });
+  }
+}
+
+export default TableSchema;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/TableTreeComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/TableTreeComponent.js b/lens-ui/app/components/TableTreeComponent.js
new file mode 100644
index 0000000..026e443
--- /dev/null
+++ b/lens-ui/app/components/TableTreeComponent.js
@@ -0,0 +1,238 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+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';
+import UserStore from '../stores/UserStore';
+import Loader from '../components/LoaderComponent';
+import '../styles/css/tree.css';
+
+let filterString = '';
+
+function getState (page, filterString, database) {
+  let state = getTables(page, filterString, database);
+  state.page = page;
+  state.loading = false;
+  return state;
+}
+
+function getTables (page, filterString, database) {
+
+  // get all the tables
+  let tables = TableStore.getTables(database);
+  let pageSize = 10;
+  let allTables;
+  let startIndex;
+  let relevantIndexes;
+  let pageTables;
+
+  if (!filterString) {
+
+    // no need for filtering
+    allTables = Object.keys(tables);
+  } else {
+
+    // filter
+    allTables = Object.keys(tables).map(name => {
+      if (name.match(filterString)) return name;
+    }).filter(name => { return !!name; });
+  }
+
+  startIndex = (page - 1) * pageSize;
+  relevantIndexes = allTables.slice(startIndex, startIndex + pageSize);
+  pageTables = relevantIndexes.map(name => {
+    return tables[name];
+  });
+
+  return {
+    totalPages: Math.ceil(allTables.length/pageSize),
+    tables: pageTables
+  };
+}
+
+class TableTree extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {
+      tables: [],
+      totalPages: 0,
+      page: 0,
+      loading: true,
+      isCollapsed: false
+    };
+    this._onChange = this._onChange.bind(this);
+    this.prevPage = this.prevPage.bind(this);
+    this.nextPage = this.nextPage.bind(this);
+    this.toggle = this.toggle.bind(this);
+    this.validateClickEvent = this.validateClickEvent.bind(this);
+
+    if (!TableStore.getTables(props.database)) {
+      AdhocQueryActions
+        .getTables(UserStore.getUserDetails().secretToken, props.database);
+    } else {
+      let state = getState(1, '', props.database);
+      this.state = state;
+
+      // on page refresh only a single table is fetched, and hence we need to
+      // fetch others too.
+      if (!TableStore.areTablesCompletelyFetched(props.database)) {
+        AdhocQueryActions
+          .getTables(UserStore.getUserDetails().secretToken, props.database);
+      }
+    }
+  }
+
+  componentDidMount () {
+    TableStore.addChangeListener(this._onChange);
+
+    // listen for opening tree
+    this.refs.tableTree.getDOMNode()
+      .addEventListener('click', this.validateClickEvent);
+  }
+
+  componentWillUnmount () {
+    this.refs.tableTree.getDOMNode()
+      .removeEventListener('click', this.validateClickEvent);
+    TableStore.removeChangeListener(this._onChange);
+  }
+
+  render () {
+    let tableTree = '';
+
+    // 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>);
+      return (
+        <TreeView key={table.name} nodeLabel={label}
+          defaultCollapsed={true}>
+
+          {table.isLoaded ? table.columns.map(col => {
+            return (
+              <div className="treeNode" key={name + '|' + col.name}>
+                {col.name} ({col.type})
+              </div>
+            );
+          }) : <Loader size="4px" margin="2px" />}
+
+        </TreeView>
+      );
+    });
+
+    // show a loader when tree is loading
+    if (this.state.loading) {
+      tableTree = <Loader size="4px" margin="2px" />;
+    } else if (!this.state.tables.length) {
+      tableTree = (<div className="alert-danger" style={{padding: '8px 5px'}}>
+          <strong>Sorry, we couldn&#39;t find any tables.</strong>
+        </div>);
+    }
+
+    let pagination = this.state.tables.length ?
+      (
+        <div>
+          <div className="text-center">
+            <button className="btn btn-link glyphicon glyphicon-triangle-left page-back"
+              onClick={this.prevPage}>
+            </button>
+            <span>{this.state.page} of {this.state.totalPages}</span>
+            <button className="btn btn-link glyphicon glyphicon-triangle-right page-next"
+              onClick={this.nextPage}>
+            </button>
+          </div>
+        </div>
+      ) :
+      null;
+
+    return (
+      <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>
+        }
+
+        {pagination}
+
+        <div ref="tableTree" style={{maxHeight: '350px', overflowY: 'auto'}}>
+          {tableTree}
+        </div>
+      </div>
+    );
+  }
+
+  _onChange (page) {
+
+    // so that page doesn't reset to beginning
+    page = page || this.state.page || 1;
+    this.setState(getState(page, filterString, this.props.database));
+  }
+
+  getDetails (tableName, database) {
+
+    // find the table
+    let table = this.state.tables.filter(table => {
+      return tableName === table.name;
+    });
+
+    if (table.length && table[0].isLoaded) return;
+
+    AdhocQueryActions
+      .getTableDetails(UserStore.getUserDetails().secretToken, tableName,
+        database);
+  }
+
+  _filter (event) {
+    filterString = event.target.value;
+    this._onChange();
+  }
+
+  prevPage () {
+    if (this.state.page - 1) this._onChange(this.state.page - 1);
+  }
+
+  nextPage () {
+    if (this.state.page < this.state.totalPages) {
+      this._onChange(this.state.page + 1);
+    }
+  }
+
+  toggle () {
+    this.setState({ isCollapsed: !this.state.isCollapsed });
+  }
+
+  validateClickEvent (e) {
+    if (e.target && e.target.nodeName === 'DIV' &&
+      e.target.nextElementSibling.nodeName === 'A') {
+      this.getDetails(e.target.nextElementSibling.textContent, this.props.database);
+    }
+  }
+
+}
+
+export default TableTree;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/constants/AdhocQueryConstants.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/constants/AdhocQueryConstants.js b/lens-ui/app/constants/AdhocQueryConstants.js
new file mode 100644
index 0000000..3c4f93a
--- /dev/null
+++ b/lens-ui/app/constants/AdhocQueryConstants.js
@@ -0,0 +1,51 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import KeyMirror from 'keyMirror';
+
+const AdhocQueryConstants = KeyMirror({
+  RECEIVE_CUBES: null,
+  RECEIVE_CUBES_FAILED: null,
+
+  RECEIVE_QUERY_HANDLE: null,
+  RECEIVE_QUERY_HANDLE_FAILED: null,
+
+  RECEIVE_CUBE_DETAILS: null,
+  RECEIVE_CUBE_DETAILS_FAILED: null,
+
+  RECEIVE_QUERIES: null,
+  RECEIVE_QUERIES_FAILED: null,
+
+  RECEIVE_QUERY_RESULT: null,
+  RECEIVE_QUERY_RESULT_FAILED: null,
+
+  RECEIVE_TABLES: null,
+  RECEIVE_TABLES_FAILED: null,
+
+  RECEIVE_TABLE_DETAILS: null,
+  RECEIVE_TABLE_DETAILS_FAILED: null,
+
+  RECEIVE_QUERY: null,
+  RECEIVE_QUERY_FAILED: null,
+
+  RECEIVE_DATABASES: null,
+  RECEIVE_DATABASES_FAILED: null
+});
+
+export default AdhocQueryConstants;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/constants/AppConstants.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/constants/AppConstants.js b/lens-ui/app/constants/AppConstants.js
new file mode 100644
index 0000000..48cd93e
--- /dev/null
+++ b/lens-ui/app/constants/AppConstants.js
@@ -0,0 +1,27 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import KeyMirror from 'keyMirror';
+
+const AppConstants = KeyMirror({
+  AUTHENTICATION_SUCCESS: null,
+  AUTHENTICATION_FAILED: null
+});
+
+export default AppConstants;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/dispatcher/AppDispatcher.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/dispatcher/AppDispatcher.js b/lens-ui/app/dispatcher/AppDispatcher.js
new file mode 100644
index 0000000..31b267c
--- /dev/null
+++ b/lens-ui/app/dispatcher/AppDispatcher.js
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2014-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+import { Dispatcher } from 'flux';
+
+const AppDispatcher = new Dispatcher();
+
+export default AppDispatcher;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/stores/AdhocQueryStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/AdhocQueryStore.js b/lens-ui/app/stores/AdhocQueryStore.js
new file mode 100644
index 0000000..7420270
--- /dev/null
+++ b/lens-ui/app/stores/AdhocQueryStore.js
@@ -0,0 +1,138 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import assign from 'object-assign';
+import { EventEmitter } from 'events';
+
+import AppDispatcher from '../dispatcher/AppDispatcher';
+import AdhocQueryConstants from '../constants/AdhocQueryConstants';
+import Config from 'config.json';
+
+var CHANGE_EVENT = 'change';
+var adhocDetails = {
+
+  queryHandle: null,
+  queries: {},
+  queryResults: {}, // map with handle being the key
+  dbName: Config.dbName
+};
+
+// TODO remove this.
+function receiveQueryHandle (payload) {
+  let id = payload.queryHandle.getElementsByTagName('handleId')[0].textContent;
+  adhocDetails.queryHandle = id;
+}
+
+function receiveQueries (payload) {
+  let queries = payload.queries;
+  let queryObjects = {};
+
+  queries.forEach((query) => {
+    queryObjects[query.queryHandle.handleId] = query;
+  });
+
+  adhocDetails.queries = queryObjects;
+}
+
+function receiveQuery (payload) {
+  let query = payload.query;
+  adhocDetails.queries[query.queryHandle.handleId] = query;
+}
+
+function receiveQueryResult (payload) {
+  let queryResult = {};
+  queryResult.type = payload && payload.type;
+
+  if (queryResult.type === 'INMEMORY') {
+    let resultRows = payload.queryResult && payload.queryResult.rows &&
+      payload.queryResult.rows.rows || [];
+    let columns = payload.columns && payload.columns.columns &&
+      payload.columns.columns.columns;
+
+    adhocDetails.queryResults[payload.handle] = {};
+    adhocDetails.queryResults[payload.handle].results = resultRows;
+    adhocDetails.queryResults[payload.handle].columns = columns;
+  } else {
+
+    // persistent
+    adhocDetails.queryResults[payload.handle] = {};
+    adhocDetails.queryResults[payload.handle].downloadURL = payload.downloadURL;
+  }
+}
+
+let AdhocQueryStore = assign({}, EventEmitter.prototype, {
+  getQueries () {
+    return adhocDetails.queries;
+  },
+
+  getQueryResult (handle) {
+    return adhocDetails.queryResults[handle];
+  },
+
+  // always returns the last-run-query's handle
+  getQueryHandle () {
+    return adhocDetails.queryHandle;
+  },
+
+  clearQueryHandle () {
+    adhocDetails.queryHandle = null;
+  },
+
+  getDbName () {
+    return adhocDetails.dbName
+  },
+
+  emitChange () {
+    this.emit(CHANGE_EVENT);
+  },
+
+  addChangeListener (callback) {
+    this.on(CHANGE_EVENT, callback);
+  },
+
+  removeChangeListener (callback) {
+    this.removeListener(CHANGE_EVENT, callback);
+  }
+});
+
+AppDispatcher.register((action) => {
+  switch(action.actionType) {
+
+    case AdhocQueryConstants.RECEIVE_QUERY_HANDLE:
+      receiveQueryHandle(action.payload);
+      AdhocQueryStore.emitChange();
+      break;
+
+    case AdhocQueryConstants.RECEIVE_QUERIES:
+      receiveQueries(action.payload);
+      AdhocQueryStore.emitChange();
+      break;
+
+    case AdhocQueryConstants.RECEIVE_QUERY_RESULT:
+      receiveQueryResult(action.payload);
+      AdhocQueryStore.emitChange();
+      break;
+
+    case AdhocQueryConstants.RECEIVE_QUERY:
+      receiveQuery(action.payload);
+      AdhocQueryStore.emitChange();
+  }
+});
+
+export default AdhocQueryStore;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/stores/CubeStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/CubeStore.js b/lens-ui/app/stores/CubeStore.js
new file mode 100644
index 0000000..8b20b95
--- /dev/null
+++ b/lens-ui/app/stores/CubeStore.js
@@ -0,0 +1,84 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import AppDispatcher from '../dispatcher/AppDispatcher';
+import AdhocQueryConstants from '../constants/AdhocQueryConstants';
+import assign from 'object-assign';
+import { EventEmitter } from 'events';
+
+// private methods
+function receiveCubes (payload) {
+  payload.cubes.elements && payload.cubes.elements.forEach(cube => {
+    if (!cubes[cube]) {
+      cubes[cube] = { name: cube, isLoaded: false };
+    }
+  });
+}
+
+function receiveCubeDetails (payload) {
+  let cubeDetails = payload.cubeDetails;
+
+  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 CHANGE_EVENT = 'change';
+var cubes = {};
+
+let CubeStore = assign({}, EventEmitter.prototype, {
+  getCubes () {
+    return cubes;
+  },
+
+  emitChange () {
+    this.emit(CHANGE_EVENT);
+  },
+
+  addChangeListener (callback) {
+    this.on(CHANGE_EVENT, callback);
+  },
+
+  removeChangeListener (callback) {
+    this.removeListener(CHANGE_EVENT, callback);
+  }
+});
+
+AppDispatcher.register((action) => {
+  switch(action.actionType) {
+    case AdhocQueryConstants.RECEIVE_CUBES:
+      receiveCubes(action.payload);
+      CubeStore.emitChange();
+      break;
+
+    case AdhocQueryConstants.RECEIVE_CUBE_DETAILS:
+      receiveCubeDetails(action.payload);
+      CubeStore.emitChange();
+      break;
+
+  }
+});
+
+export default CubeStore;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/stores/DatabaseStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/DatabaseStore.js b/lens-ui/app/stores/DatabaseStore.js
new file mode 100644
index 0000000..9f4490b
--- /dev/null
+++ b/lens-ui/app/stores/DatabaseStore.js
@@ -0,0 +1,62 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import AppDispatcher from '../dispatcher/AppDispatcher';
+import AdhocQueryConstants from '../constants/AdhocQueryConstants';
+import assign from 'object-assign';
+import { EventEmitter } from 'events';
+
+function receiveDatabases (payload) {
+  databases = [];
+
+  databases = payload.databases.elements &&
+    payload.databases.elements.slice()
+}
+
+let CHANGE_EVENT = 'change';
+var databases = [];
+
+let DatabaseStore = assign({}, EventEmitter.prototype, {
+  getDatabases () {
+    return databases;
+  },
+
+  emitChange () {
+    this.emit(CHANGE_EVENT);
+  },
+
+  addChangeListener (callback) {
+    this.on(CHANGE_EVENT, callback);
+  },
+
+  removeChangeListener (callback) {
+    this.removeListener(CHANGE_EVENT, callback);
+  }
+});
+
+AppDispatcher.register((action) => {
+  switch(action.actionType) {
+    case AdhocQueryConstants.RECEIVE_DATABASES:
+      receiveDatabases(action.payload);
+      DatabaseStore.emitChange();
+      break;
+  }
+});
+
+export default DatabaseStore;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/stores/TableStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/TableStore.js b/lens-ui/app/stores/TableStore.js
new file mode 100644
index 0000000..299d9e8
--- /dev/null
+++ b/lens-ui/app/stores/TableStore.js
@@ -0,0 +1,102 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import AppDispatcher from '../dispatcher/AppDispatcher';
+import AdhocQueryConstants from '../constants/AdhocQueryConstants';
+import assign from 'object-assign';
+import { EventEmitter } from 'events';
+
+function receiveTables (payload) {
+  let database = payload.database;
+
+  if (!tables[database]) {
+    tables[database] = {};
+    tableCompleteness[database] = true;
+  }
+
+  payload.tables.elements &&
+    payload.tables.elements.forEach( table => {
+      if (!tables[database][table]) {
+        tables[database][table] = { name: table, isLoaded: false };
+      }
+    });
+}
+
+function receiveTableDetails (payload) {
+  if (payload.tableDetails) {
+    let database = payload.database;
+    let name = payload.tableDetails.name;
+    let table = assign({}, payload.tableDetails);
+    let columns = table.columns && table.columns.column || [];
+    table.columns = columns;
+
+    // check if tables contains the database and table entry,
+    // it won't be present when user directly arrived on this link.
+    if (!tables[database]) {
+      tables[database] = {};
+    }
+
+    if (!tables[database][name]) tables[database][name] = {};
+
+    tables[database][name] = table;
+    tables[database][name].isLoaded = true;
+  }
+}
+
+let CHANGE_EVENT = 'change';
+var tables = {};
+var tableCompleteness = {};
+
+let TableStore = assign({}, EventEmitter.prototype, {
+  getTables (database) {
+    return tables[database];
+  },
+
+  areTablesCompletelyFetched (database) {
+    return tableCompleteness[database];
+  },
+
+  emitChange () {
+    this.emit(CHANGE_EVENT);
+  },
+
+  addChangeListener (callback) {
+    this.on(CHANGE_EVENT, callback);
+  },
+
+  removeChangeListener (callback) {
+    this.removeListener(CHANGE_EVENT, callback);
+  }
+});
+
+AppDispatcher.register((action) => {
+  switch(action.actionType) {
+    case AdhocQueryConstants.RECEIVE_TABLES:
+      receiveTables(action.payload);
+      TableStore.emitChange();
+      break;
+
+    case AdhocQueryConstants.RECEIVE_TABLE_DETAILS:
+      receiveTableDetails(action.payload);
+      TableStore.emitChange();
+      break;
+  }
+});
+
+export default TableStore;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/stores/UserStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/UserStore.js b/lens-ui/app/stores/UserStore.js
new file mode 100644
index 0000000..47da021
--- /dev/null
+++ b/lens-ui/app/stores/UserStore.js
@@ -0,0 +1,132 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+
+import AppDispatcher from '../dispatcher/AppDispatcher';
+import AppConstants from '../constants/AppConstants';
+import assign from 'object-assign';
+import { EventEmitter } from 'events';
+
+var CHANGE_EVENT = 'change';
+var userDetails = {
+  isUserLoggedIn: false,
+  email: '',
+  secretToken: '',
+  publicKey: ''
+};
+
+// keeping these methods out of the UserStore class as
+// components shouldn't lay their hands on them ;)
+function authenticateUser (details) {
+  userDetails = {
+    isUserLoggedIn: true,
+    email: details.email,
+    secretToken: new XMLSerializer().serializeToString(details.secretToken),
+    publicKey: details.secretToken.getElementsByTagName('publicId')[0]
+      .textContent
+  };
+
+  // store the details in localStorage if available
+
+  if (window.localStorage) {
+    let adhocCred = assign({}, userDetails, { timestamp: Date.now() });
+    window.localStorage.setItem('adhocCred', JSON.stringify(adhocCred));
+  }
+}
+
+function unauthenticateUser (details) {
+
+  // details contains error code and message
+  // which are not stored but passsed along
+  // during emitChange()
+  userDetails = {
+    isUserLoggedIn: false,
+    email: '',
+    secretToken: ''
+  };
+
+  // remove from localStorage as well
+  if (window.localStorage) localStorage.setItem('adhocCred', '');
+}
+
+// exposing only necessary methods for the components.
+var UserStore = assign({}, EventEmitter.prototype, {
+  isUserLoggedIn () {
+
+    if (userDetails && userDetails.isUserLoggedIn) {
+
+      return userDetails.isUserLoggedIn;
+    } else if (window.localStorage && localStorage.getItem('adhocCred')) {
+
+      // check in localstorage
+      let credentials = JSON.parse(localStorage.getItem('adhocCred'));
+
+      // check if it's valid or not
+      if (Date.now() - credentials.timestamp > 1800000) return false;
+
+      delete credentials.timestamp;
+      userDetails = assign({}, credentials);
+
+      return userDetails.isUserLoggedIn;
+    }
+
+    return false;
+  },
+
+  getUserDetails () {
+    return userDetails;
+  },
+
+  logout () {
+    unauthenticateUser();
+    this.emitChange();
+  },
+
+  emitChange (errorHash) {
+    this.emit(CHANGE_EVENT, errorHash);
+  },
+
+  addChangeListener (callback) {
+    this.on(CHANGE_EVENT, callback);
+  },
+
+  removeChangeListener (callback) {
+    this.removeListener(CHANGE_EVENT, callback);
+  }
+});
+
+// registering callbacks with the dispatcher. So verbose?? I know right!
+AppDispatcher.register((action) => {
+  switch(action.actionType) {
+    case AppConstants.AUTHENTICATION_SUCCESS:
+      authenticateUser(action.payload);
+      UserStore.emitChange();
+      break;
+
+    case AppConstants.AUTHENTICATION_FAILED:
+      unauthenticateUser(action.payload);
+
+      // action.payload => { responseCode, responseMessage }
+      UserStore.emitChange(action.payload);
+      break;
+  }
+});
+
+export default UserStore;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/styles/css/global.css
----------------------------------------------------------------------
diff --git a/lens-ui/app/styles/css/global.css b/lens-ui/app/styles/css/global.css
new file mode 100644
index 0000000..131ab46
--- /dev/null
+++ b/lens-ui/app/styles/css/global.css
@@ -0,0 +1,18 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/styles/css/login.css
----------------------------------------------------------------------
diff --git a/lens-ui/app/styles/css/login.css b/lens-ui/app/styles/css/login.css
new file mode 100644
index 0000000..b400cfb
--- /dev/null
+++ b/lens-ui/app/styles/css/login.css
@@ -0,0 +1,57 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+
+ /*For login form*/
+.form-signin {
+  max-width: 330px;
+  padding: 15px;
+  margin: 0 auto;
+}
+.form-signin .form-signin-heading,
+.form-signin .checkbox {
+  margin-bottom: 10px;
+}
+.form-signin .checkbox {
+  font-weight: normal;
+}
+.form-signin .form-control {
+  position: relative;
+  height: auto;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+  padding: 10px;
+  font-size: 16px;
+}
+.form-signin .form-control:focus {
+  z-index: 2;
+}
+.form-signin input[type="email"] {
+  margin-bottom: -1px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.form-signin input[type="password"] {
+  margin-bottom: 10px;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+
+/*login style ends*/

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/styles/css/query-component.css
----------------------------------------------------------------------
diff --git a/lens-ui/app/styles/css/query-component.css b/lens-ui/app/styles/css/query-component.css
new file mode 100644
index 0000000..a82165e
--- /dev/null
+++ b/lens-ui/app/styles/css/query-component.css
@@ -0,0 +1,34 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+
+ @media (max-width: 768px) {
+    .btn.responsive {
+        width:100%;
+        margin-bottom: 10px;
+    }
+}
+
+div.CodeMirror {
+  max-height: 150px;
+}
+
+li.CodeMirror-hint {
+  max-width: 100%;
+}

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/styles/css/tree.css
----------------------------------------------------------------------
diff --git a/lens-ui/app/styles/css/tree.css b/lens-ui/app/styles/css/tree.css
new file mode 100644
index 0000000..402c9a0
--- /dev/null
+++ b/lens-ui/app/styles/css/tree.css
@@ -0,0 +1,51 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+
+ .node {
+  font-weight: bold;
+}
+
+.treeNode.measureNode, .treeNode.childNode {
+  color: blue;
+}
+
+.treeNode.dimensionNode {
+  color: darkgreen;
+}
+
+.quiet {
+  color: #666;
+}
+
+.treeNode:hover {
+  background-color: #eee;
+}
+
+.page-next, .page-back {
+  margin: 2px 8px;
+  cursor: pointer;
+}
+
+div.tree-view {
+  -o-text-overflow: ellipsis;   /* Opera */
+  text-overflow:    ellipsis;   /* IE, Safari (WebKit) */
+  overflow:hidden;              /* don't show excess chars */
+  white-space:nowrap;           /* force single line */
+}

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/styles/less/globals.less
----------------------------------------------------------------------
diff --git a/lens-ui/app/styles/less/globals.less b/lens-ui/app/styles/less/globals.less
new file mode 100644
index 0000000..c0704dc
--- /dev/null
+++ b/lens-ui/app/styles/less/globals.less
@@ -0,0 +1,23 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+
+ // IMPORTS
+
+@import "~bootstrap/less/bootstrap.less";

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/config.json
----------------------------------------------------------------------
diff --git a/lens-ui/config.json b/lens-ui/config.json
new file mode 100644
index 0000000..3316bf6
--- /dev/null
+++ b/lens-ui/config.json
@@ -0,0 +1,4 @@
+{
+  "isPersistent": true,
+  "baseURL": "/serverproxy/"
+}

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/index.html
----------------------------------------------------------------------
diff --git a/lens-ui/index.html b/lens-ui/index.html
new file mode 100644
index 0000000..9c20fe9
--- /dev/null
+++ b/lens-ui/index.html
@@ -0,0 +1,100 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+-->
+
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <base href="/">
+    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
+    <title>LENS UI</title>
+
+    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+    <!--[if lt IE 9]>
+      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+    <![endif]-->
+
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
+
+    <!-- style for the loader till JavaScript downloads-->
+    <style>
+      .loading-no-js {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+      }
+      .loading-bar {
+        display: inline-block;
+        width: 6px;
+        height: 36px;
+        border-radius: 4px;
+        animation: loading 1s ease-in-out infinite;
+      }
+      .loading-bar:nth-child(1) {
+        background-color: #3498db;
+        animation-delay: 0;
+      }
+      .loading-bar:nth-child(2) {
+        background-color: #c0392b;
+        animation-delay: 0.09s;
+      }
+      .loading-bar:nth-child(3) {
+        background-color: #f1c40f;
+        animation-delay: .18s;
+      }
+      .loading-bar:nth-child(4) {
+        background-color: #27ae60;
+        animation-delay: .27s;
+      }
+
+      @keyframes loading {
+        0% {
+          transform: scale(1);
+        }
+        20% {
+          transform: scale(1, 2.2);
+        }
+        40% {
+          transform: scale(1);
+        }
+      }
+    </style>
+  </head>
+  <body>
+
+  <div class="loading-no-js" id="loader-no-js">
+    <div class="loading-bar"></div>
+    <div class="loading-bar"></div>
+    <div class="loading-bar"></div>
+    <div class="loading-bar"></div>
+  </div>
+
+    <!-- everything goes into this section. Do anything but touch this. I dare you!-->
+    <section id="app">
+
+    </section>
+
+    <script src="target/assets/bundle.js"></script>
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/package.json
----------------------------------------------------------------------
diff --git a/lens-ui/package.json b/lens-ui/package.json
new file mode 100644
index 0000000..920b120
--- /dev/null
+++ b/lens-ui/package.json
@@ -0,0 +1,51 @@
+{
+  "name": "lens-ui",
+  "version": "1.0.0",
+  "description": "An exemplary front end solution for Apache LENS",
+  "main": "app/app.js",
+  "scripts": {
+    "start": "NODE_ENV=production node_modules/webpack/bin/webpack.js -p && lensserver='http://0.0.0.0:9999/lensapi/' port=8082 node server.js",
+    "dev": "lensserver='http://0.0.0.0:9999/lensapi/' port=8082 node server.js & node_modules/webpack/bin/webpack.js --watch",
+    "deploy": "NODE_ENV=production node_modules/webpack/bin/webpack.js -p"
+  },
+  "dependencies": {
+    "bluebird": "^2.9.34",
+    "bootstrap": "^3.3.4",
+    "classnames": "^2.1.2",
+    "codemirror": "^5.3.0",
+    "flux": "^2.0.3",
+    "halogen": "^0.1.8",
+    "keymirror": "^0.1.1",
+    "lodash": "^3.9.1",
+    "moment": "^2.10.3",
+    "object-assign": "^2.0.0",
+    "q": "^1.4.1",
+    "react": "^0.13.3",
+    "react-bootstrap": "^0.22.6",
+    "react-router": "^0.13.3",
+    "react-treeview": "^0.3.12",
+    "reqwest": "^1.1.5"
+  },
+  "devDependencies": {
+    "autoprefixer-loader": "^1.2.0",
+    "babel-core": "^5.4.3",
+    "babel-loader": "^5.3.2",
+    "babel-runtime": "^5.7.0",
+    "body-parser": "^1.13.2",
+    "cookie-parser": "^1.3.5",
+    "css-loader": "^0.13.1",
+    "express": "^4.12.4",
+    "express-session": "^1.11.3",
+    "file-loader": "^0.8.1",
+    "http-proxy": "^1.11.1",
+    "json-loader": "^0.5.2",
+    "less": "^2.5.0",
+    "less-loader": "^2.2.0",
+    "morgan": "^1.6.1",
+    "node-libs-browser": "^0.5.0",
+    "serve-favicon": "^2.3.0",
+    "style-loader": "^0.12.2",
+    "url-loader": "^0.5.5",
+    "webpack": "^1.9.7"
+  }
+}

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/pom.xml
----------------------------------------------------------------------
diff --git a/lens-ui/pom.xml b/lens-ui/pom.xml
new file mode 100644
index 0000000..69bcee5
--- /dev/null
+++ b/lens-ui/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <name>Lens UI</name>
+  <parent>
+    <artifactId>apache-lens</artifactId>
+    <groupId>org.apache.lens</groupId>
+    <version>2.4.0-beta-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>lens-ui</artifactId>
+  <packaging>pom</packaging>
+  <description>Lens UI client</description>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <version>${antrun.plugin.version}</version>
+        <configuration>
+          <target>
+            <zip destfile="target/${project.artifactId}-${project.version}" basedir="${project.basedir}" excludes="target/**" />
+          </target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>com.github.eirslett</groupId>
+        <artifactId>frontend-maven-plugin</artifactId>
+        <version>${frontend.maven.plugin}</version>
+        <executions>
+          <execution>
+            <id>install node and npm</id>
+            <goals>
+              <goal>install-node-and-npm</goal>
+            </goals>
+            <configuration>
+              <nodeVersion>${nodeVersion}</nodeVersion>
+              <npmVersion>${npmVersion}</npmVersion>
+              <nodeDownloadRoot>https://nodejs.org/dist/</nodeDownloadRoot>
+              <npmDownloadRoot>http://registry.npmjs.org/npm/-/</npmDownloadRoot>
+              <installDirectory>node</installDirectory>
+            </configuration>
+          </execution>
+          <execution>
+            <id>npm install</id>
+            <goals>
+              <goal>npm</goal>
+            </goals>
+            <!-- Optional configuration which provides for running any npm command -->
+            <configuration>
+              <arguments>install</arguments>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/server.js
----------------------------------------------------------------------
diff --git a/lens-ui/server.js b/lens-ui/server.js
new file mode 100644
index 0000000..e812018
--- /dev/null
+++ b/lens-ui/server.js
@@ -0,0 +1,79 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+var express = require('express');
+var path = require('path');
+var favicon = require('serve-favicon');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+var session = require('express-session');
+
+var app = express();
+var httpProxy = require('http-proxy');
+var proxy = httpProxy.createProxyServer();
+var port = process.env['port'] || 8082;
+
+app.use(logger('dev'));
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+app.use(cookieParser());
+
+process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
+
+if(!process.env['lensserver']){
+  throw new Error('Specify LENS Server address in `lensserver` argument');
+}
+
+console.log('Using this as your LENS Server Address: ', process.env['lensserver']);
+console.log('If this seems wrong, please edit `lensserver` argument in package.json. Do not forget to append http://\n');
+
+app.use( session({
+   secret            : 'SomethingYouKnow',
+   resave            : false,
+   saveUninitialized : true
+}));
+
+var fs = require('fs');
+
+app.use(express.static(path.resolve(__dirname, 'target', 'assets')));
+
+app.get('/target/assets/*', function (req, res) {
+  res.setHeader('Cache-Control', 'public');
+  res.end(fs.readFileSync(__dirname + req.path));
+});
+
+app.all('/serverproxy/*', function (req, res) {
+  req.url = req.url.replace('serverproxy', '');
+  proxy.web(req, res, {
+      target: process.env['lensserver']
+  }, function (e) { console.log('Handled error.'); });
+});
+
+app.get('*', function(req, res) {
+  res.end(fs.readFileSync(__dirname + '/index.html'));
+});
+
+var server = app.listen(port, function(err) {
+  if(err) throw err;
+
+  var port = server.address().port;
+
+  console.log('Ad hoc UI server listening at port: ', port);
+});

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/webpack.config.js
----------------------------------------------------------------------
diff --git a/lens-ui/webpack.config.js b/lens-ui/webpack.config.js
new file mode 100644
index 0000000..ab4021f
--- /dev/null
+++ b/lens-ui/webpack.config.js
@@ -0,0 +1,55 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+var webpack = require('webpack');
+var path = require('path');
+
+
+module.exports = {
+
+  entry: {
+    app: [
+      './app/app.js'
+    ]
+  },
+
+	output: {
+    path: path.join(__dirname, 'target', 'assets'),
+		filename: 'bundle.js'
+	},
+
+  plugins: [
+    new webpack.NoErrorsPlugin()
+  ],
+
+  resolve: {
+    modulesDirectories: ['app', 'node_modules', __dirname]
+  },
+
+	module: {
+		loaders: [
+      { test: /\.jsx?$/, loaders: ['babel'], include: path.join(__dirname, 'app') },
+			{ test: /\.css$/, loaders: ['style', 'css'] },
+      { test: /\.less$/, loaders: ['style', 'css', 'autoprefixer', 'less'] },
+      { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loaders: ['url?limit=10000&minetype=application/font-woff'] },
+      { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loaders: ['file'] },
+      { test: /\.json$/, loaders: ['json']}
+		]
+	}
+};

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 10f9bc1..dd26713 100644
--- a/pom.xml
+++ b/pom.xml
@@ -107,6 +107,11 @@
     <antrun.plugin.version>1.8</antrun.plugin.version>
     <cobertura.plugin.version>2.7</cobertura.plugin.version>
 
+    <!-- UI -->
+    <frontend.maven.plugin>0.0.23</frontend.maven.plugin>
+    <nodeVersion>v4.0.0</nodeVersion>
+    <npmVersion>2.7.6</npmVersion>
+
     <!-- debian -->
     <mvn.deb.build.dir>${project.build.directory}/debian</mvn.deb.build.dir>
 
@@ -547,6 +552,8 @@
             <exclude>**/*.iml</exclude>
             <exclude>**/.classpath</exclude>
             <exclude>**/.project</exclude>
+            <exclude>**/node/**</exclude>
+            <exclude>**/node_modules/**</exclude>
             <exclude>**/.checkstyle</exclude>
             <exclude>**/.settings/**</exclude>
             <exclude>**/maven-eclipse.xml</exclude>
@@ -567,6 +574,7 @@
             <exclude>**/codemirror.min.*</exclude>
             <exclude>**/*.js</exclude>
             <exclude>**/*.properties</exclude>
+            <exclude>**/*.json</exclude>
           </excludes>
         </configuration>
         <executions>
@@ -1522,6 +1530,7 @@
     <module>lens-ml-lib</module>
     <module>lens-ml-dist</module>
     <module>lens-regression</module>
+    <module>lens-ui</module>
   </modules>
 
   <profiles>


[2/2] lens git commit: LENS-629 : A new improved web client for Lens

Posted by am...@apache.org.
LENS-629 : A new improved web client for Lens


Project: http://git-wip-us.apache.org/repos/asf/lens/repo
Commit: http://git-wip-us.apache.org/repos/asf/lens/commit/66f164b4
Tree: http://git-wip-us.apache.org/repos/asf/lens/tree/66f164b4
Diff: http://git-wip-us.apache.org/repos/asf/lens/diff/66f164b4

Branch: refs/heads/master
Commit: 66f164b476ff316990435a9ea0c9cae36cbf383f
Parents: 669e872
Author: Ankeet Maini <an...@gmail.com>
Authored: Wed Sep 23 14:41:45 2015 +0530
Committer: Amareshwari Sriramadasu <am...@apache.org>
Committed: Wed Sep 23 14:41:45 2015 +0530

----------------------------------------------------------------------
 .gitignore                                      |   5 +
 lens-ui/LICENSE                                 | 201 +++++++++++++
 lens-ui/README.markdown                         |  85 ++++++
 lens-ui/app/actions/AdhocQueryActions.js        | 213 ++++++++++++++
 lens-ui/app/actions/LoginActions.js             |  51 ++++
 lens-ui/app/adapters/AdhocQueryAdapter.js       | 157 +++++++++++
 lens-ui/app/adapters/AuthenticationAdapter.js   |  55 ++++
 lens-ui/app/adapters/BaseAdapter.js             |  90 ++++++
 lens-ui/app/app.js                              |  58 ++++
 lens-ui/app/components/AboutComponent.js        |  33 +++
 lens-ui/app/components/AdhocQueryComponent.js   |  74 +++++
 lens-ui/app/components/AppComponent.js          |  40 +++
 lens-ui/app/components/CubeSchemaComponent.js   | 196 +++++++++++++
 lens-ui/app/components/CubeTreeComponent.js     | 175 ++++++++++++
 lens-ui/app/components/DatabaseComponent.js     | 127 +++++++++
 lens-ui/app/components/HeaderComponent.js       |  90 ++++++
 lens-ui/app/components/LoaderComponent.js       |  34 +++
 lens-ui/app/components/LoginComponent.js        | 109 +++++++
 lens-ui/app/components/LogoutComponent.js       |  42 +++
 lens-ui/app/components/QueryBoxComponent.js     | 282 +++++++++++++++++++
 .../components/QueryDetailResultComponent.js    | 192 +++++++++++++
 .../app/components/QueryOperationsComponent.js  |  87 ++++++
 lens-ui/app/components/QueryPreviewComponent.js | 176 ++++++++++++
 lens-ui/app/components/QueryResultsComponent.js | 123 ++++++++
 .../RequireAuthenticationComponent.js           |  37 +++
 lens-ui/app/components/SidebarComponent.js      |  38 +++
 lens-ui/app/components/TableSchemaComponent.js  | 131 +++++++++
 lens-ui/app/components/TableTreeComponent.js    | 238 ++++++++++++++++
 lens-ui/app/constants/AdhocQueryConstants.js    |  51 ++++
 lens-ui/app/constants/AppConstants.js           |  27 ++
 lens-ui/app/dispatcher/AppDispatcher.js         |  15 +
 lens-ui/app/stores/AdhocQueryStore.js           | 138 +++++++++
 lens-ui/app/stores/CubeStore.js                 |  84 ++++++
 lens-ui/app/stores/DatabaseStore.js             |  62 ++++
 lens-ui/app/stores/TableStore.js                | 102 +++++++
 lens-ui/app/stores/UserStore.js                 | 132 +++++++++
 lens-ui/app/styles/css/global.css               |  18 ++
 lens-ui/app/styles/css/login.css                |  57 ++++
 lens-ui/app/styles/css/query-component.css      |  34 +++
 lens-ui/app/styles/css/tree.css                 |  51 ++++
 lens-ui/app/styles/less/globals.less            |  23 ++
 lens-ui/config.json                             |   4 +
 lens-ui/index.html                              | 100 +++++++
 lens-ui/package.json                            |  51 ++++
 lens-ui/pom.xml                                 |  79 ++++++
 lens-ui/server.js                               |  79 ++++++
 lens-ui/webpack.config.js                       |  55 ++++
 pom.xml                                         |   9 +
 48 files changed, 4310 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 5a356e0..aca87dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,3 +56,8 @@ test-output/
 #Other
 bin/
 out/
+
+build/
+assets/
+node/
+node_modules/

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/LICENSE
----------------------------------------------------------------------
diff --git a/lens-ui/LICENSE b/lens-ui/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/lens-ui/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/README.markdown
----------------------------------------------------------------------
diff --git a/lens-ui/README.markdown b/lens-ui/README.markdown
new file mode 100644
index 0000000..98fc79e
--- /dev/null
+++ b/lens-ui/README.markdown
@@ -0,0 +1,85 @@
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+
+
+
+# LENS UI #
+
+This app is a front end client for Apache LENS server.
+- It lets you fire queries.
+- Discover cubes, its measures and dimensions.
+
+# Running #
+
+## Using Node Express Server ##
+- add your LENS Server address in **package.json** under `scripts` *start* and *dev* to `lensserver` argument, this is the value of `lens.server.base.url` property of your LENS installation
+- default port is 8082, if you'd like to change it please change the *port* argument in **package.json** under `scripts` *start* or *dev*
+- doing ```npm run dev``` starts the UI in dev mode, with the JavaScript assets in a non-minified way which help in debugging, also it'll be watching to the changes in the source and would generate the new assets automatically.
+- doing ```npm run start``` will minify and uglifiy the assets.
+
+```bash
+cd lens-ui
+npm run start
+```
+
+- point chrome to [http://localhost:8082](http://localhost:8082)
+
+## Using any other server ##
+
+```bash
+cd lens-ui
+npm install
+node_modules/webpack/bin/webpack
+```
+- you now have built assets in `assets` directory
+- add LENS server address *(lens.server.base.url)* in `config.json` file in `baseURL`
+- run your server, e.g python
+
+```bash
+python -m SimpleHTTPServer
+```
+- this will serve the `index.html` present at the root.
+- this will cause your browser to make cross domain requests and if you don't have CORS enabled on your server, the app won't be able to get any data from LENS server.
+  - to get around till you enable CORS on server side, open chrome in disabled web security mode.
+  - first, quit chrome completely, then see the following steps to run browsers in disabled security mode
+  - [start chrome in disabled security mode](https://blog.nraboy.com/2014/08/bypass-cors-errors-testing-apis-locally/)
+
+# Code Structure #
+
+- All JavaScript is in app directory.
+
+# Configurations #
+
+- The app can be configured to show either **INMEMORY** or **PERSISTENT** results.
+- The setting can be done by altering a boolean variable present in `config.json`
+  - `isPersistent`: true // results will be available as downloadable files
+  - `isPersistent`: false // results will be shown in table format
+- Any custom headers you wish to add can be added in `config.json` as a property
+  ```javascript
+  "headers": {
+    "Some-Header": "SomeValue",
+    "Another-Header": "AnotherValue"
+  }
+  ```
+
+# Environment #
+
+Built using:-
+
+- React
+- [Facebook's Flux implementation](https://www.npmjs.com/package/flux)
+- [Reqwest](https://www.npmjs.com/package/reqwest) for making ajax calls wrapped with JavaScript promises.

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/actions/AdhocQueryActions.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/actions/AdhocQueryActions.js b/lens-ui/app/actions/AdhocQueryActions.js
new file mode 100644
index 0000000..8c2d109
--- /dev/null
+++ b/lens-ui/app/actions/AdhocQueryActions.js
@@ -0,0 +1,213 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import AppDispatcher from '../dispatcher/AppDispatcher';
+import AdhocQueryConstants from '../constants/AdhocQueryConstants';
+import AdhocQueryAdapter from '../adapters/AdhocQueryAdapter';
+
+let AdhocQueryActions = {
+  getDatabases (secretToken) {
+    AdhocQueryAdapter.getDatabases(secretToken)
+      .then(function (databases) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_DATABASES,
+          payload: { databases: databases }
+        });
+      }, function (error) {
+
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_DATABASES_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+  },
+
+  getCubes (secretToken) {
+    AdhocQueryAdapter.getCubes(secretToken)
+      .then(function (cubes) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_CUBES,
+          payload: { cubes: cubes }
+        });
+      }, function (error) {
+
+        // propagating the error message, couldn't fetch cubes
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_CUBES_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+  },
+
+  executeQuery (secretToken, query, queryName) {
+    AdhocQueryAdapter.executeQuery(secretToken, query, queryName)
+      .then(function (queryHandle) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_QUERY_HANDLE,
+          payload: { queryHandle: queryHandle }
+        });
+      }, function (error) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_QUERY_HANDLE_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+  },
+
+  getCubeDetails (secretToken, cubeName) {
+    AdhocQueryAdapter.getCubeDetails(secretToken, cubeName)
+      .then(function (cubeDetails) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_CUBE_DETAILS,
+          payload: { cubeDetails: cubeDetails }
+        });
+      }, function (error) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_CUBE_DETAILS_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+  },
+
+  getQueries (secretToken, email, options) {
+    AdhocQueryAdapter.getQueries(secretToken, email, options)
+      .then(function (queries) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_QUERIES,
+          payload: { queries: queries }
+        });
+      }, function (error) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_QUERIES_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+  },
+
+  getQuery (secretToken, handle) {
+    AdhocQueryAdapter.getQuery(secretToken, handle)
+      .then(function (query) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_QUERY,
+          payload: { query: query }
+        });
+      }, function (error) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_QUERY_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+  },
+
+  getQueryResult (secretToken, handle, queryMode) {
+    AdhocQueryAdapter.getQueryResult(secretToken, handle, queryMode)
+      .then(function (result) {
+        let payload;
+        if (Object.prototype.toString.call(result).match('String')) {
+
+          // persistent
+          payload = { downloadURL: result, type: 'PERSISTENT', handle: handle };
+        } else if (Object.prototype.toString.call(result).match('Array')) {
+
+          // in-memory gives array
+          payload = {
+            queryResult: result[0],
+            columns: result[1],
+            handle: handle,
+            type: 'INMEMORY'
+          };
+        }
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_QUERY_RESULT,
+          payload: payload
+        });
+      }, function (error) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_QUERY_RESULT_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+  },
+
+  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
+          }
+        });
+      });
+  },
+
+  getTableDetails (secretToken, tableName, database) {
+    AdhocQueryAdapter.getTableDetails(secretToken, tableName, database)
+      .then(function (tableDetails) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_TABLE_DETAILS,
+          payload: { tableDetails: tableDetails, database: database }
+        });
+      }, function (error) {
+        AppDispatcher.dispatch({
+          actionType: AdhocQueryConstants.RECEIVE_TABLE_DETAILS_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+  },
+
+  cancelQuery (secretToken, handle) {
+    AdhocQueryAdapter.cancelQuery(secretToken, handle);
+    // TODO finish this up
+  }
+};
+
+export default AdhocQueryActions;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/actions/LoginActions.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/actions/LoginActions.js b/lens-ui/app/actions/LoginActions.js
new file mode 100644
index 0000000..3cb39d0
--- /dev/null
+++ b/lens-ui/app/actions/LoginActions.js
@@ -0,0 +1,51 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import AppDispatcher from '../dispatcher/AppDispatcher';
+import AppConstants from '../constants/AppConstants';
+import AuthenticationAdapter from '../adapters/AuthenticationAdapter';
+
+let LoginActions = {
+  authenticate (email, password) {
+    AuthenticationAdapter.authenticate(email, password)
+      .then(function (response) {
+
+        // authenticating user right away
+        AppDispatcher.dispatch({
+          actionType: AppConstants.AUTHENTICATION_SUCCESS,
+          payload: {
+            email: email,
+            secretToken: response
+          }
+        });
+      }, function (error) {
+
+        // propagating the error message
+        AppDispatcher.dispatch({
+          actionType: AppConstants.AUTHENTICATION_FAILED,
+          payload: {
+            responseCode: error.status,
+            responseMessage: error.statusText
+          }
+        });
+      });
+    }
+};
+
+export default LoginActions;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/adapters/AdhocQueryAdapter.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/adapters/AdhocQueryAdapter.js b/lens-ui/app/adapters/AdhocQueryAdapter.js
new file mode 100644
index 0000000..98f9b49
--- /dev/null
+++ b/lens-ui/app/adapters/AdhocQueryAdapter.js
@@ -0,0 +1,157 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import Promise from 'bluebird';
+
+import BaseAdapter from './BaseAdapter';
+import Config from 'config.json';
+
+let baseUrl = Config.baseURL;
+let urls = {
+  'getDatabases': 'metastore/databases',
+  'getCubes': 'metastore/cubes',
+  'query': 'queryapi/queries', // POST on this to execute, GET to fetch all
+  'getTables': 'metastore/nativetables'
+};
+
+let AdhocQueryAdapter = {
+  getDatabases (secretToken) {
+    let url = baseUrl + urls.getDatabases;
+    return BaseAdapter.get(url, { sessionid: secretToken });
+  },
+
+  getCubes (secretToken) {
+    let url = baseUrl + urls.getCubes;
+    return BaseAdapter.get(url, { sessionid: secretToken });
+  },
+
+  getCubeDetails (secretToken, cubeName) {
+    let url = baseUrl + urls.getCubes + '/' + cubeName;
+    return BaseAdapter.get(url, { sessionid: secretToken });
+  },
+
+  executeQuery (secretToken, query, queryName) {
+    let url = baseUrl + urls.query;
+
+    let formData = new FormData();
+    formData.append('sessionid', secretToken);
+    formData.append('query', query);
+    formData.append('operation', 'execute');
+
+    if (queryName)  formData.append('queryName', queryName);
+
+    return BaseAdapter.post(url, formData, {
+      contentType: 'multipart/form-data'
+    });
+  },
+
+  getQuery (secretToken, handle) {
+    let url = baseUrl + urls.query + '/' + handle;
+    return BaseAdapter.get(url, {sessionid: secretToken});
+  },
+
+  getQueries (secretToken, email, options) {
+    let url = baseUrl + urls.query;
+
+    let queryOptions = {};
+    queryOptions.sessionid = secretToken;
+    queryOptions.user = email;
+
+    if (options && options.state) {
+      queryOptions.state = options.state.toUpperCase();
+    }
+
+    return BaseAdapter.get(url, queryOptions)
+      .then(function (queryHandles) {
+
+        // FIXME limiting to 10 for now
+        //let handles = queryHandles.slice(0, 10);
+        return Promise.all(queryHandles.map((handle) => {
+          return BaseAdapter.get(url + '/' + handle.handleId, {
+            sessionid: secretToken,
+            queryHandle: handle.handleId
+          });
+        }));
+      });
+  },
+
+  getQueryResult (secretToken, handle, queryMode) {
+
+    // on page refresh, the store won't have queryMode so fetch query
+    // this is needed as we won't know in which mode the query was fired
+    if (!queryMode) {
+      this.getQuery(secretToken, handle).then((query) => {
+        queryMode = query.isPersistent;
+        queryMode = queryMode ? 'PERSISTENT': 'INMEMORY';
+        return this._inMemoryOrPersistent(secretToken, handle, queryMode);
+      });
+    } else {
+      return this._inMemoryOrPersistent(secretToken, handle, queryMode);
+    }
+  },
+
+  // a method used only internally to figure out
+  // whether to fetch INMEMORY or PERSISTENT results
+  _inMemoryOrPersistent (secretToken, handle, queryMode) {
+    return queryMode === 'PERSISTENT' ?
+      this.getDownloadURL(secretToken, handle) :
+      this.getInMemoryResults(secretToken, handle);
+  },
+
+  getTables (secretToken, database) {
+    let url = baseUrl + urls.getTables;
+    return BaseAdapter.get(url, {
+      sessionid: secretToken,
+      dbName: database
+    });
+  },
+
+  getTableDetails (secretToken, tableName, database) {
+    let url = baseUrl + urls.getTables + '/' + database + '.' + tableName;
+    return BaseAdapter.get(url, { sessionid: secretToken });
+  },
+
+  cancelQuery (secretToken, handle) {
+    let url = baseUrl + urls.query + '/' + handle + '?sessionid=' + secretToken;
+    return BaseAdapter.delete(url);
+  },
+
+  getDownloadURL (secretToken, handle) {
+    let downloadURL = baseUrl + urls.query + '/' + handle +
+      '/httpresultset?sessionid=' + secretToken;
+
+    return Promise.resolve(downloadURL);
+  },
+
+  getInMemoryResults (secretToken, handle) {
+    let resultUrl = baseUrl + urls.query + '/' + handle + '/resultset';
+    let results = BaseAdapter.get(resultUrl, {
+      sessionid: secretToken
+    });
+
+    let metaUrl = baseUrl + urls.query + '/' + handle + '/resultsetmetadata';
+    let meta = BaseAdapter.get(metaUrl, {
+      sessionid: secretToken
+    });
+
+    return Promise.all([results, meta]);
+  }
+};
+
+export default AdhocQueryAdapter;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/adapters/AuthenticationAdapter.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/adapters/AuthenticationAdapter.js b/lens-ui/app/adapters/AuthenticationAdapter.js
new file mode 100644
index 0000000..26a35f3
--- /dev/null
+++ b/lens-ui/app/adapters/AuthenticationAdapter.js
@@ -0,0 +1,55 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import BaseAdapter from './BaseAdapter';
+import Config from 'config.json';
+
+// these are required by lens API. Sad.
+let authUrl = Config.baseURL + 'session';
+let queryMode = Config.isPersistent;
+let sessionconf = `<?xml version="1.0" encoding="UTF-8"?>
+                  <conf>
+                    <properties>
+                      <entry>
+                        <key>lens.query.enable.persistent.resultset</key>
+                        <value>` + queryMode + `</value>
+                       </entry>
+                       <entry>
+                        <key>lens.query.enable.persistent.resultset.indriver</key>
+                        <value>false</value>
+                       </entry>
+                    </properties>
+                  </conf>`;
+
+let AuthenticationAdapter = {
+  authenticate (email, password) {
+
+    // preparing data as API accepts multipart/form-data :(
+    var formData = new FormData();
+    formData.append('username', email);
+    formData.append('password', password);
+    formData.append('sessionconf', sessionconf);
+
+    return BaseAdapter.post(authUrl, formData, {
+      contentType: 'multipart/form-data'
+    });
+  }
+};
+
+export default AuthenticationAdapter;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/adapters/BaseAdapter.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/adapters/BaseAdapter.js b/lens-ui/app/adapters/BaseAdapter.js
new file mode 100644
index 0000000..81b9ddc
--- /dev/null
+++ b/lens-ui/app/adapters/BaseAdapter.js
@@ -0,0 +1,90 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import reqwest from 'reqwest';
+import Promise from 'bluebird';
+
+import Config from 'config.json';
+
+function makeReqwest (url, method, data, options = {}) {
+  let reqwestOptions = {
+    url: url,
+    method: method,
+    contentType: 'application/json',
+    type: 'json',
+    headers: {}
+  };
+
+  if (Config.headers) reqwestOptions.headers = Config.headers;
+
+  // delete Content-Type and add Accept
+  reqwestOptions.headers['Accept'] = 'application/json';
+  delete reqwestOptions.headers['Content-Type'];
+  if (data) reqwestOptions.data = data;
+  if (options.contentType === 'multipart/form-data') {
+    reqwestOptions.processData = false;
+    reqwestOptions.contentType = 'multipart/form-data';
+
+    // because server can't handle JSON response on POST
+    delete reqwestOptions.type;
+    delete reqwestOptions.headers['Accept'];
+  }
+
+  return new Promise ((resolve, reject) => {
+    reqwest(reqwestOptions)
+      .then ((response) => {
+        resolve(response);
+      }, (error) => {
+        reject(error);
+      });
+  });
+}
+
+function deleteRequest (url, dataArray) {
+  return makeReqwest(url, 'delete', dataArray);
+}
+
+function get (url, dataArray, options) {
+  return makeReqwest(url, 'get', dataArray, options);
+}
+
+// TODO need to fix this unused 'options'. What params can it have?
+function postJson (url, data, options = {}) {
+  return makeReqwest(url, 'post', data, {contentType: 'application/json'});
+}
+
+function postFormData (url, data, options = {}) {
+  return makeReqwest(url, 'post', data, options);
+}
+
+let BaseAdapter = {
+  get: get,
+
+  post (url, data, options = {}) {
+    if (options.contentType) {
+      return postFormData(url, data, options);
+    } else {
+      return postJson(url, data, options);
+    }
+  },
+
+  delete: deleteRequest
+};
+
+export default BaseAdapter;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/app.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/app.js b/lens-ui/app/app.js
new file mode 100644
index 0000000..3e389a7
--- /dev/null
+++ b/lens-ui/app/app.js
@@ -0,0 +1,58 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import Router from 'react-router';
+import { DefaultRoute, Route, RouteHandler } from 'react-router';
+
+import About from './components/AboutComponent';
+import App from './components/AppComponent';
+import AdhocQuery from './components/AdhocQueryComponent';
+import Login from './components/LoginComponent';
+import Logout from './components/LogoutComponent';
+import QueryResults from './components/QueryResultsComponent';
+import CubeSchema from './components/CubeSchemaComponent';
+import QueryDetailResult from './components/QueryDetailResultComponent';
+import TableSchema from './components/TableSchemaComponent';
+import LoginActions from './actions/LoginActions';
+
+let routes = (
+  <Route name="app" path="/" handler={App} >
+    <Route name="login" handler={Login}/>
+    <Route name="logout" handler={Logout}/>
+    <Route name="query" path="query" handler={AdhocQuery} >
+      <Route name="results" handler={QueryResults}/>
+      <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="about" handler={About} />
+    <DefaultRoute handler={AdhocQuery} />
+  </Route>
+);
+
+Router.run(routes, Router.HistoryLocation, (Handler) => {
+  React.render(<Handler/>, document.getElementById('app'));
+
+  // and hide the loader which was loading in html while JavaScript
+  // was downloading
+  document.getElementById('loader-no-js').style.display = 'none';
+});

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/AboutComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/AboutComponent.js b/lens-ui/app/components/AboutComponent.js
new file mode 100644
index 0000000..c911080
--- /dev/null
+++ b/lens-ui/app/components/AboutComponent.js
@@ -0,0 +1,33 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+
+class About extends React.Component {
+  render() {
+    return (
+      <div className="jumbotron">
+        <h1>Hey there!</h1>
+        <p>Thanks for stopping by.</p>
+      </div>
+    );
+  }
+}
+
+export default About;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/AdhocQueryComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/AdhocQueryComponent.js b/lens-ui/app/components/AdhocQueryComponent.js
new file mode 100644
index 0000000..66ddf75
--- /dev/null
+++ b/lens-ui/app/components/AdhocQueryComponent.js
@@ -0,0 +1,74 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import { RouteHandler } from 'react-router';
+
+import QueryBox from './QueryBoxComponent';
+import Sidebar from './SidebarComponent';
+import RequireAuthentication from './RequireAuthenticationComponent';
+
+class AdhocQuery extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {toggleQueryBox: true}; // show box when true, hide on false
+    this.toggleQueryBox = this.toggleQueryBox.bind(this);
+  }
+
+  render() {
+    let toggleButtonClass = this.state.toggleQueryBox ? 'default' : 'primary';
+
+    return (
+      <section className="row">
+        <div className="col-md-4">
+          <Sidebar />
+        </div>
+
+        <div className="col-md-8">
+          <div className="panel panel-default">
+            <div className="panel-heading">
+              <h3 className="panel-title">
+                Compose
+                <button
+                  className={'btn btn-xs pull-right btn-' + toggleButtonClass}
+                  onClick={this.toggleQueryBox}>
+                  {this.state.toggleQueryBox ? 'Hide': 'Show'} Query Box
+                </button>
+              </h3>
+            </div>
+            <div className="panel-body" style={{padding: '0px'}}>
+              <QueryBox toggleQueryBox={this.state.toggleQueryBox} {...this.props}/>
+            </div>
+          </div>
+
+          <RouteHandler toggleQueryBox={this.state.toggleQueryBox}/>
+        </div>
+      </section>
+    );
+  }
+
+  // FIXME persist the state in the URL as well
+  toggleQueryBox () {
+    this.setState({toggleQueryBox: !this.state.toggleQueryBox});
+  }
+};
+
+let AuthenticatedAdhocQuery = RequireAuthentication(AdhocQuery);
+
+export default AuthenticatedAdhocQuery;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/AppComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/AppComponent.js b/lens-ui/app/components/AppComponent.js
new file mode 100644
index 0000000..d7a38f9
--- /dev/null
+++ b/lens-ui/app/components/AppComponent.js
@@ -0,0 +1,40 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import { RouteHandler } from 'react-router';
+
+import Header from './HeaderComponent';
+
+export default class AppComponent extends React.Component {
+
+  render() {
+    return (
+      <section>
+        <Header />
+
+        <div className="container-fluid">
+          <RouteHandler />
+        </div>
+      </section>
+    );
+  }
+}
+
+export default AppComponent;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/CubeSchemaComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/CubeSchemaComponent.js b/lens-ui/app/components/CubeSchemaComponent.js
new file mode 100644
index 0000000..593c54a
--- /dev/null
+++ b/lens-ui/app/components/CubeSchemaComponent.js
@@ -0,0 +1,196 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+
+import CubeStore from '../stores/CubeStore';
+import UserStore from '../stores/UserStore';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+import Loader from '../components/LoaderComponent';
+
+function getCubes () {
+  return CubeStore.getCubes();
+}
+
+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>
+      </tr>
+    );
+  });
+
+  return (
+    <div class="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>
+      </table>
+    </div>
+  );
+}
+
+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 &&
+          dimension.ref_spec.chain_ref_column.dest_table }</td>
+        <td>{ dimension.ref_spec && dimension.ref_spec.chain_ref_column &&
+          dimension.ref_spec.chain_ref_column.ref_col }</td>
+        <td>{ dimension.description }</td>
+      </tr>
+    );
+  });
+
+  return (
+    <div class="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>
+      </table>
+    </div>
+  );
+}
+
+// TODO add prop checking.
+class CubeSchema extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {cube: {}};
+    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);
+  }
+
+  componentDidMount () {
+    CubeStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    CubeStore.removeChangeListener(this._onChange);
+  }
+
+  componentWillReceiveProps (props) {
+    let cubeName = props.params.cubeName;
+    let cube = getCubes()[cubeName];
+
+    if (cube.isLoaded) {
+      this.setState({ cube: getCubes()[cubeName] });
+      return;
+    }
+
+    AdhocQueryActions
+      .getCubeDetails(UserStore.getUserDetails().secretToken, cubeName);
+
+    // empty the previous state
+    this.setState({ cube: {} });
+
+  }
+
+  render () {
+    let schemaSection;
+
+    // this will be empty if it's the first time so show a loader
+    if (!this.state.cube.isLoaded) {
+      schemaSection = <Loader size="8px" margin="2px" />;
+    } else {
+
+      // if we have cube state
+      let cube = this.state.cube;
+      if (this.props.query.type === 'measures') {
+
+        // show only measures
+        schemaSection = constructMeasureTable(cube.name, cube.measures);
+      } else if (this.props.query.type === 'dimensions') {
+
+        // show only dimensions
+        schemaSection = constructDimensionTable(cube.name, cube.dimensions);
+      } else {
+
+        // show both measures, dimensions
+        schemaSection = (
+          <div>
+            { constructMeasureTable(cube.name, cube.measures) }
+            { constructDimensionTable(cube.name, cube.dimensions) }
+          </div>
+        );
+      }
+    }
+
+    // TODO give max height to panel-body depending upon
+    // whether the query box is visible or not.
+    return (
+
+      <section>
+        <div className="panel panel-default">
+          <div className="panel-heading">
+            <h3 className="panel-title">Schema Details: &nbsp;
+              <strong className="text-primary">
+                 {this.props.params.cubeName}
+              </strong>
+            </h3>
+          </div>
+          <div className="panel-body" style={{overflowY: 'auto',
+            maxHeight: this.props.toggleQueryBox ? '260px': '480px'}}>
+            {schemaSection}
+          </div>
+        </div>
+
+      </section>
+    );
+  }
+
+  _onChange () {
+    this.setState({cube: getCubes()[this.props.params.cubeName]});
+  }
+}
+
+export default CubeSchema;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/CubeTreeComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/CubeTreeComponent.js b/lens-ui/app/components/CubeTreeComponent.js
new file mode 100644
index 0000000..241c12f
--- /dev/null
+++ b/lens-ui/app/components/CubeTreeComponent.js
@@ -0,0 +1,175 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import Alert from 'react-bootstrap';
+import TreeView from 'react-treeview';
+import assign from 'object-assign';
+import { Link } from 'react-router';
+import 'react-treeview/react-treeview.css';
+import ClassNames from 'classnames';
+
+import CubeStore from '../stores/CubeStore';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+import UserStore from '../stores/UserStore';
+import Loader from '../components/LoaderComponent';
+import '../styles/css/tree.css';
+
+function getCubeData () {
+  return {
+    cubes: CubeStore.getCubes()
+  };
+}
+
+class CubeTree extends React.Component {
+  constructor (props) {
+    super(props);
+
+    // setting the initial state, as getInitialState only
+    // 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 };
+
+    // 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 () {
+    CubeStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    CubeStore.removeChangeListener(this._onChange);
+  }
+
+  render() {
+
+    // cube tree structure sample
+    // [{
+    //   name: 'Cube-1',
+    //   measures: [{name: 'Measure-1'}, {name: 'Measure-1'}], // optional
+    //   dimensions: [{name: 'Dimension-1'}, {name: 'Dimension-1'}] //optional
+    // }, ...]
+
+    var cubeHash = assign({}, this.state.cubes);
+    var cubeTree = Object.keys(this.state.cubes).map((cubeName, i) => {
+      let cube = cubeHash[cubeName];
+
+      let label = <Link to="cubeschema" params={{cubeName: cubeName}}>
+          <span className="node">{cube.name}</span>
+        </Link>;
+
+      let measureLabel = <Link to="cubeschema" params={{cubeName: cubeName}}
+        query={{type: 'measures'}}>
+          <span className="quiet">Measures</span>
+        </Link>;
+
+      let dimensionLabel = <Link to="cubeschema" params={{cubeName: cubeName}}
+        query={{type: 'dimensions'}}>
+          <span className="quiet">Dimensions</span>
+        </Link>
+      return (
+        <TreeView key={cube.name + '|' + i} nodeLabel={label}
+          defaultCollapsed={true} onClick={this.getDetails.bind(this, cube.name)}>
+
+          <TreeView key={cube.name + '|measures'} nodeLabel={measureLabel}
+            defaultCollapsed={!cube.isLoaded}>
+            { cube.measures ? cube.measures.map(measure => {
+              return (
+                <div key={measure.name} className="treeNode measureNode">
+                  {measure.name} ({measure.default_aggr})
+                </div>
+              );
+            }): null }
+          </TreeView >
+
+          <TreeView key={cube.name + '|dimensions'} nodeLabel={dimensionLabel}
+            defaultCollapsed={!cube.isLoaded}>
+            { cube.dimensions ? cube.dimensions.map(dimension => {
+              return (
+                <div className="treeNode dimensionNode" key={dimension.name}>
+                  {dimension.name}
+                </div>
+              );
+            }): null }
+          </TreeView >
+
+        </TreeView >
+      );
+    });
+
+    if (this.state.loading) cubeTree = <Loader size="4px" margin="2px"/>;
+
+    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">
+            Cubes
+            <span className={collapseClass} onClick={this.toggle}></span>
+          </h3>
+        </div>
+        <div className={panelBodyClassName} style={{maxHeight: '350px', overflowY: 'auto'}}>
+          {cubeTree}
+        </div>
+      </div>
+    );
+  }
+
+  _onChange () {
+    let state = getCubeData();
+    state.loading = false;
+    this.setState(state);
+  }
+
+  // TODO: check its binding in the onClick method
+  // needs to be investigated
+  // https://facebook.github.io/react/tips/communicate-between-components.html
+  getDetails (cubeName) {
+    if (this.state.cubes[cubeName].isLoaded) return;
+
+    AdhocQueryActions
+      .getCubeDetails(UserStore.getUserDetails().secretToken, cubeName);
+  }
+
+  toggle () {
+    this.setState({isCollapsed: !this.state.isCollapsed});
+  }
+}
+
+export default CubeTree;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/DatabaseComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/DatabaseComponent.js b/lens-ui/app/components/DatabaseComponent.js
new file mode 100644
index 0000000..09ee2eb
--- /dev/null
+++ b/lens-ui/app/components/DatabaseComponent.js
@@ -0,0 +1,127 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import ClassNames from 'classnames';
+
+import DatabaseStore from '../stores/DatabaseStore';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+import UserStore from '../stores/UserStore';
+import Loader from '../components/LoaderComponent';
+import TableTree from './TableTreeComponent';
+
+function getDatabases () {
+  return DatabaseStore.getDatabases();
+}
+
+class DatabaseComponent extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {
+      databases: [],
+      loading: true,
+      isCollapsed: false,
+      selectedDatabase: ''
+    };
+    this._onChange = this._onChange.bind(this);
+    this.toggle = this.toggle.bind(this);
+    this.setDatabase = this.setDatabase.bind(this);
+
+    AdhocQueryActions.getDatabases(UserStore.getUserDetails().secretToken);
+  }
+
+  componentDidMount () {
+    DatabaseStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    DatabaseStore.removeChangeListener(this._onChange);
+  }
+
+  render () {
+    let databaseComponent = null;
+
+    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
+    });
+
+    databaseComponent = (<div>
+        <label className="control-label" id="db">Select a Database</label>
+        <select className="form-control" id="db" onChange={this.setDatabase}>
+          <option value="">Select</option>
+          {this.state.databases.map(database => {
+            return <option value={database}>{database}</option>;
+          })}
+        </select>
+      </div>);
+
+    if (this.state.loading) {
+      databaseComponent = <Loader size="4px" margin="2px"></Loader>;
+    } else if (!this.state.databases.length) {
+      databaseComponent = (<div className="alert-danger"
+          style={{padding: '8px 5px'}}>
+          <strong>Sorry, we couldn&#39;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>
+      </div>
+    );
+  }
+
+  _onChange () {
+    this.setState({ databases: getDatabases(), loading: false });
+  }
+
+  toggle () {
+    this.setState({ isCollapsed: !this.state.isCollapsed });
+  }
+
+  setDatabase (event) {
+    this.setState({selectedDatabase: event.target.value});
+  }
+}
+
+export default DatabaseComponent;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/HeaderComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/HeaderComponent.js b/lens-ui/app/components/HeaderComponent.js
new file mode 100644
index 0000000..5ec3397
--- /dev/null
+++ b/lens-ui/app/components/HeaderComponent.js
@@ -0,0 +1,90 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import Router from 'react-router';
+import { Link } from 'react-router';
+
+import Logout from './LogoutComponent';
+import UserStore from '../stores/UserStore';
+import Config from 'config.json';
+
+class Header extends React.Component {
+  constructor () {
+    super();
+    this.state = {userName: null};
+    this._onChange = this._onChange.bind(this);
+  }
+
+  componentDidMount () {
+    UserStore.addChangeListener(this._onChange);
+
+    // this component mounts later and CHANGE_EVENT has elapsed
+    // calling _onChange manually once to refresh the value
+    // FIXME is this wrong?
+    this._onChange();
+  }
+
+  componentWillUnmount () {
+    UserStore.removeChangeListener(this._onChange);
+  }
+
+  render () {
+    return (
+      <nav className="navbar navbar-inverse navbar-static-top">
+        <div className="container">
+          <div className="navbar-header">
+            <button type="button" className="navbar-toggle collapsed"
+                data-toggle="collapse" data-target="#navbar"
+                aria-expanded="false" aria-controls="navbar">
+              <span className="sr-only">Toggle navigation</span>
+              <span className="icon-bar"></span>
+              <span className="icon-bar"></span>
+              <span className="icon-bar"></span>
+            </button>
+            <Link className="navbar-brand" to="app">LENS Query<sup>&beta;</sup></Link>
+          </div>
+          <div id="navbar" className="collapse navbar-collapse">
+            <ul className="nav navbar-nav">
+              <li><Link to="about">About</Link></li>
+            </ul>
+
+            { this.state.userName &&
+              (<ul className="nav navbar-nav navbar-right">
+                <li>
+                  <Link to="logout" className="glyphicon glyphicon-log-out"
+                    title="Logout">
+                    <span> {this.state.userName}</span>
+                  </Link>
+                </li>
+              </ul>)
+            }
+
+          </div>
+        </div>
+      </nav>
+    );
+  }
+
+  _onChange () {
+    this.setState({userName: UserStore.getUserDetails().email});
+  }
+}
+
+export default Header;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/LoaderComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/LoaderComponent.js b/lens-ui/app/components/LoaderComponent.js
new file mode 100644
index 0000000..ba11c64
--- /dev/null
+++ b/lens-ui/app/components/LoaderComponent.js
@@ -0,0 +1,34 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import { GridLoader } from 'halogen';
+
+// TODO add warnings if size and margin props aren't sent.
+class Loader extends React.Component {
+  render () {
+    return (
+      <section style={{margin: '0 auto', maxWidth: '12%'}}>
+        <GridLoader {...this.props} color="#337ab7"/>
+      </section>
+    );
+  }
+}
+
+export default Loader;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/LoginComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/LoginComponent.js b/lens-ui/app/components/LoginComponent.js
new file mode 100644
index 0000000..cf95af9
--- /dev/null
+++ b/lens-ui/app/components/LoginComponent.js
@@ -0,0 +1,109 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import UserStore from '../stores/UserStore';
+import LoginActions from '../actions/LoginActions';
+
+import '../styles/css/login.css';
+
+var error = false;
+
+class Login extends React.Component {
+  constructor (props) {
+    super(props);
+    this.handleSubmit = this.handleSubmit.bind(this);
+    this._onChange = this._onChange.bind(this);
+    this.state = {
+      error: UserStore.isUserLoggedIn()
+    };
+  }
+
+  componentDidMount () {
+    UserStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    UserStore.removeChangeListener(this._onChange);
+  }
+
+  handleSubmit (event) {
+    event.preventDefault();
+    var email = this.refs.email.getDOMNode().value;
+    var pass = this.refs.pass.getDOMNode().value;
+
+    LoginActions.authenticate(email, pass);
+  }
+
+  render () {
+    return (
+      <section class="row" style={{margin: 'auto'}}>
+        <form className="form-signin" onSubmit={this.handleSubmit}>
+          <h2 className="form-signin-heading">Sign in</h2>
+          <label for="inputEmail" className="sr-only">Email address</label>
+          <input ref="email" id="inputEmail" className="form-control"
+            placeholder="Email address" required autofocus/>
+          <label for="inputPassword" className="sr-only">Password</label>
+          <input ref="pass" type="password" id="inputPassword"
+            className="form-control" placeholder="Password" required/>
+          <button className="btn btn-primary btn-block"
+            type="submit">Sign in</button>
+          {this.state.error && (
+            <div className="alert-danger text-center"
+              style={{marginTop: '5px', padding: '0px 3px'}}>
+              <h5>Sorry, authentication failed.</h5>
+              <small>{this.state.errorMessage}</small>
+            </div>
+          )}
+        </form>
+      </section>
+    );
+  }
+
+  _onChange (errorHash) {
+    if (errorHash) {
+      error = true;
+
+      // on error return immediately.
+      // need not go to router for a transition
+      return this.setState({
+        errorMessage: errorHash.responseCode + ': ' +
+          errorHash.responseMessage,
+        error: true
+      });
+    }
+
+    // user is authenticated here
+    var { router } = this.context;
+    var nextPath = router.getCurrentQuery().nextPath;
+
+    if (nextPath) {
+      router.replaceWith(nextPath);
+    } else {
+      router.replaceWith('/about');
+    }
+  }
+
+}
+
+Login.contextTypes = {
+  router: React.PropTypes.func
+};
+
+export default Login;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/LogoutComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/LogoutComponent.js b/lens-ui/app/components/LogoutComponent.js
new file mode 100644
index 0000000..3fc1627
--- /dev/null
+++ b/lens-ui/app/components/LogoutComponent.js
@@ -0,0 +1,42 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import { Link } from 'react-router';
+import Config from 'config.json';
+
+import UserStore from '../stores/UserStore';
+
+class Logout extends React.Component {
+
+  componentDidMount () {
+    UserStore.logout();
+  }
+
+  render () {
+    return (
+      <div className="jumbotron text-center">
+        <h3>You&#39;ve succesfully logged out.</h3>
+        <p><Link to="/">Login</Link> to use this awesome app!</p>
+      </div>
+    );
+  }
+}
+
+export default Logout;

http://git-wip-us.apache.org/repos/asf/lens/blob/66f164b4/lens-ui/app/components/QueryBoxComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/QueryBoxComponent.js b/lens-ui/app/components/QueryBoxComponent.js
new file mode 100644
index 0000000..6d5843c
--- /dev/null
+++ b/lens-ui/app/components/QueryBoxComponent.js
@@ -0,0 +1,282 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import React from 'react';
+import CodeMirror from 'codemirror';
+import 'codemirror/lib/codemirror.css';
+import 'codemirror/addon/edit/matchbrackets.js';
+import 'codemirror/addon/hint/sql-hint.js';
+import 'codemirror/addon/hint/show-hint.css';
+import 'codemirror/addon/hint/show-hint.js';
+import 'codemirror/mode/sql/sql.js';
+
+import UserStore from '../stores/UserStore';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+import AdhocQueryStore from '../stores/AdhocQueryStore';
+import CubeStore from '../stores/CubeStore';
+import TableStore from '../stores/TableStore';
+import DatabaseStore from '../stores/DatabaseStore';
+import Config from 'config.json';
+import '../styles/css/query-component.css';
+
+// keeping a handle to CodeMirror instance,
+// to be used to retrieve the contents of the editor
+let codeMirror = null;
+
+function setLimit (query) {
+  // since pagination is not enabled on server, limit the query to 1000
+  // check if user has specified existing limit > 1000, change it to 1000
+  // dumb way, checking only last two words for `limit <number>` pattern
+  let temp = query.split(' ');
+  if (temp.slice(-2)[0].toLowerCase() === 'limit') {
+
+    if (temp.slice(-1)[0] > 1000)  temp.splice(-1, 1, 1000);
+    query = temp.join(' ');
+  } else {
+    query += ' limit 1000';
+  }
+
+  return query;
+}
+
+function setCode (code) {
+  if (codeMirror) {
+    codeMirror.setValue(code);
+    codeMirror.focus();
+  }
+}
+
+// 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
+// used from Edit Query link
+function fetchQuery (props) {
+  if (props.query && props.query.handle) {
+    let query = AdhocQueryStore.getQueries()[props.query.handle];
+
+    if (query) {
+      setCode(query.userQuery);
+    }
+  }
+}
+
+function setupCodeMirror (domNode) {
+
+  // instantiating CodeMirror intance with some properties.
+  codeMirror = CodeMirror.fromTextArea(domNode, {
+    mode: 'text/x-sql',
+    indentWithTabs: true,
+    smartIndent: true,
+    lineNumbers: true,
+    matchBrackets : true,
+    autofocus: true,
+    lineWrapping: true
+  });
+}
+
+function updateAutoComplete () {
+
+  // add autocomplete hints to the query box
+  let hints = {};
+
+  // cubes
+  let cubes = CubeStore.getCubes(); // hashmap
+  Object.keys(cubes).forEach((cubeName) => {
+    let cube = cubes[cubeName];
+    hints[cubeName] = [];
+
+    if (cube.measures && cube.measures.length) {
+      cube.measures.forEach((measure) => {
+        hints[cubeName].push(measure.name);
+      });
+    }
+    if (cube.dimensions && cube.dimensions.length) {
+      cube.dimensions.forEach((dimension) => {
+        hints[cubeName].push(dimension.name);
+      });
+    }
+  });
+
+  //  tables
+  let databases = DatabaseStore.getDatabases() || [];
+  let tables = databases.map(db => {
+    if (TableStore.getTables(db)) {
+      return {
+        database: db,
+        tables: TableStore.getTables(db)
+      }
+    }
+  }).filter(item => { return !!item; }); // filtering undefined items
+
+  tables.forEach(tableObject => {
+    Object.keys(tableObject.tables).forEach(tableName => {
+      let table = tableObject.tables[tableName];
+      let qualifiedName = tableObject.database + '.' + tableName;
+      hints[qualifiedName] = [];
+      hints[tableName] = [];
+
+      if (table.columns && table.columns.length) {
+        table.columns.forEach((col) => {
+          hints[qualifiedName].push(col.name);
+          hints[tableName].push(col.name);
+          hints[col.name] = [];
+        });
+      }
+    });
+  });
+
+  codeMirror.options.hintOptions = { tables: hints };
+}
+
+class QueryBox extends React.Component {
+  constructor (props) {
+    super(props);
+    this.runQuery = this.runQuery.bind(this);
+    this._onChange = this._onChange.bind(this);
+
+    this.state = { querySubmitted: false, isRunQueryDisabled: true };
+  }
+
+  componentDidMount () {
+
+    var editor = this.refs.queryEditor.getDOMNode();
+    setupCodeMirror(editor);
+
+    // disable 'Run Query' button when editor is empty
+    // TODO: debounce this, as it'll happen on every key press. :(
+    codeMirror.on('change', () => {
+      codeMirror.getValue() ?
+        this.state.isRunQueryDisabled = false :
+        this.state.isRunQueryDisabled = true;
+
+      this._onChange();
+    });
+
+    // to remove the previous query's submission notification
+    codeMirror.on('focus', () => {
+      this.state.querySubmitted = false;
+    });
+
+    // add Cmd + Enter to fire runQuery
+    codeMirror.setOption("extraKeys", {
+    'Cmd-Enter': (instance) => {
+      this.runQuery();
+    },
+    'Ctrl-Space': 'autocomplete'
+  });
+
+    AdhocQueryStore.addChangeListener(this._onChange);
+    CubeStore.addChangeListener(this._onChange);
+    TableStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    AdhocQueryStore.addChangeListener(this._onChange);
+    CubeStore.addChangeListener(this._onChange);
+    TableStore.addChangeListener(this._onChange);
+  }
+
+  componentWillReceiveProps (props) {
+    fetchQuery(props);
+  }
+
+  render () {
+    let queryBoxClass = this.props.toggleQueryBox ? '': 'hide';
+
+    return (
+      <section className={queryBoxClass}>
+        <div style={{borderBottom: '1px solid #dddddd'}}>
+          <textarea ref="queryEditor"></textarea>
+        </div>
+
+        <div className="row" style={{padding: '6px 8px '}}>
+          <div className="col-lg-4 col-md-4 col-sm-4 col-xs-12">
+            <input type="text" className="form-control"
+              placeholder="Query Name (optional)" ref="queryName"/>
+          </div>
+          <div className="col-lg-6 col-md-6 col-sm-6 col-xs-12">
+            {this.state.querySubmitted && (
+              <div className="alert alert-info" style={{padding: '5px 4px',
+                marginBottom: '0px'}}>
+                Query has been submitted. Results are on their way!
+              </div>
+            )}
+          </div>
+          <div className="col-lg-2 col-md-2 col-sm-2 col-xs-12">
+            <button className="btn btn-primary responsive"
+              onClick={this.runQuery} disabled={this.state.isRunQueryDisabled}>
+              Run Query
+            </button>
+          </div>
+        </div>
+      </section>
+    );
+  }
+
+  runQuery () {
+    let queryName = this.refs.queryName.getDOMNode().value;
+    let secretToken = UserStore.getUserDetails().secretToken;
+    let query = codeMirror.getValue();
+
+    // set limit if mode is in-memory
+    if (!Config.isPersistent) query = setLimit(query);
+
+    AdhocQueryActions.executeQuery(secretToken, query, queryName);
+
+    // show user the query was posted successfully and empty the queryName
+    this.state.querySubmitted = true;
+    this.refs.queryName.getDOMNode().value = '';
+  }
+
+  _onChange () {
+
+    // renders the detail result component if server
+    // replied with a query handle.
+    // this should ideally happen only when the 'Run Query' button is
+    // clicked, and its action updates the store with query-handle.
+    let handle = AdhocQueryStore.getQueryHandle();
+    if (handle) {
+
+      // clear it else detail result component will be rendered
+      // every time the store emits a change event.
+      AdhocQueryStore.clearQueryHandle();
+
+      var { router } = this.context;
+      router.transitionTo('result', {handle: handle});
+    }
+
+    // TODO remove this.
+    // check if handle was passed as query param, and if that
+    // query was fetched and available in store now.
+    // if (this.props && this.props.query.handle) {
+    //
+    //   let query = AdhocQueryStore.getQueries()[this.props.query.handle];
+    //   if (query)  setCode(query.userQuery);
+    // }
+
+    updateAutoComplete();
+    this.setState(this.state);
+  }
+}
+
+QueryBox.contextTypes = {
+  router: React.PropTypes.func
+};
+
+export default QueryBox;