You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lens.apache.org by ra...@apache.org on 2015/10/09 06:17:54 UTC

[49/50] [abbrv] lens git commit: LENS-782: UI support for the saved, parametrized query feature

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/components/QueryResultsComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/QueryResultsComponent.js b/lens-ui/app/components/QueryResultsComponent.js
index 6e4b8c2..01f0e30 100644
--- a/lens-ui/app/components/QueryResultsComponent.js
+++ b/lens-ui/app/components/QueryResultsComponent.js
@@ -19,7 +19,7 @@
 
 import React from 'react';
 
-import Loader from '../components/LoaderComponent';
+import Loader from './LoaderComponent';
 import AdhocQueryStore from '../stores/AdhocQueryStore';
 import UserStore from '../stores/UserStore';
 import AdhocQueryActions from '../actions/AdhocQueryActions';
@@ -31,12 +31,10 @@ function getResults (props) {
   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);
   }
@@ -77,12 +75,12 @@ class QueryResults extends React.Component {
         return queryMap[b].submissionTime - queryMap[a].submissionTime;
       })
       .map((queryHandle) => {
-      let query = queryMap[queryHandle];
+        let query = queryMap[queryHandle];
 
-      return (
-        <QueryPreview key={query.queryHandle.handleId} {...query} />
-      );
-    }); // end of map
+        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.
@@ -93,9 +91,9 @@ class QueryResults extends React.Component {
     let queriesLength = Object.keys(this.state.queries).length;
 
     if (!queriesLength && !this.state.queriesReceived) {
-      queries = <Loader size="8px" margin="2px" />;
+      queries = <Loader size='8px' margin='2px' />;
     } else if (!queriesLength && this.state.queriesReceived) {
-      queries = <div className="alert alert-danger">
+      queries = <div className='alert alert-danger'>
         <strong>Sorry</strong>, there were no queries to be shown.
       </div>;
     }
@@ -105,9 +103,8 @@ class QueryResults extends React.Component {
         <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'}}>
+          <hr style={{marginTop: '6px'}}/>
+          <div>
             {queries}
           </div>
         </div>
@@ -116,7 +113,7 @@ class QueryResults extends React.Component {
   }
 
   _onChange () {
-    this.setState({ queries: getQueries(), queriesReceived: true});
+    this.setState({queries: getQueries(), queriesReceived: true});
   }
 }
 

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/components/RequireAuthenticationComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/RequireAuthenticationComponent.js b/lens-ui/app/components/RequireAuthenticationComponent.js
index 9a755b0..7b1e956 100644
--- a/lens-ui/app/components/RequireAuthenticationComponent.js
+++ b/lens-ui/app/components/RequireAuthenticationComponent.js
@@ -29,9 +29,9 @@ let RequireAuthentication = (Component) => {
     }
 
     render () {
-      return <Component {...this.props} />
+      return <Component {...this.props} />;
     }
-  }
+  };
 };
 
 export default RequireAuthentication;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/components/SavedQueriesComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/SavedQueriesComponent.js b/lens-ui/app/components/SavedQueriesComponent.js
new file mode 100644
index 0000000..378fa13
--- /dev/null
+++ b/lens-ui/app/components/SavedQueriesComponent.js
@@ -0,0 +1,180 @@
+/**
+* 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 AdhocQueryActions from '../actions/AdhocQueryActions';
+import SavedQueryStore from '../stores/SavedQueryStore';
+import UserStore from '../stores/UserStore';
+import Loader from './LoaderComponent';
+import SavedQueryPreview from './SavedQueryPreviewComponent';
+
+class SavedQueries extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {
+      loading: true,
+      savedQueries: null,
+      page: null,
+      totalPages: null
+    };
+
+    this._onChange = this._onChange.bind(this);
+    this.prev = this.prev.bind(this);
+    this.next = this.next.bind(this);
+    this._getPaginatedSavedQueries = this._getPaginatedSavedQueries.bind(this);
+
+    let secretToken = UserStore.getUserDetails().secretToken;
+    let user = UserStore.getUserDetails().email;
+    AdhocQueryActions.getSavedQueries(secretToken, user);
+  }
+
+  componentDidMount () {
+    SavedQueryStore.addChangeListener(this._onChange);
+  }
+
+  componentWillUnmount () {
+    SavedQueryStore.removeChangeListener(this._onChange);
+  }
+
+  render () {
+    let loading = this.state.loading ? <Loader size='4px' margin='2px'/> : null;
+
+    let queries = !this.state.savedQueries ? null :
+      Object.keys(this.state.savedQueries).map(queryId => {
+        return <SavedQueryPreview key={'saved|' + queryId}
+          query={this.state.savedQueries[queryId]} />;
+      });
+
+    // no saved queries
+    if (!this.state.loading && !queries) {
+      queries = (<div className='alert-danger' style={{padding: '8px 5px'}}>
+          <strong>Sorry, we couldn&#39;t find any saved queries.</strong>
+        </div>);
+    }
+
+    if (!this.state.loading && this.state.totalPages == 0) {
+      queries = (<div className='alert-danger' style={{padding: '8px 5px'}}>
+          <strong>You&#39;ve not saved any query.</strong>
+        </div>);
+    }
+
+    var pagination = (
+      <div className='pull-right'>
+        <button onClick={this.prev}
+          className='btn btn-link glyphicon glyphicon-triangle-left'>
+        </button>
+          <small>
+            { this.state.page && this.state.totalPages &&
+              (this.state.page + ' of ' + this.state.totalPages)
+            }
+          </small>
+        <button onClick={this.next}
+          className='btn btn-link glyphicon glyphicon-triangle-right'>
+        </button>
+      </div>
+    );
+
+    return (
+      <section>
+        <div style={{border: '1px solid #dddddd', borderRadius: '4px',
+          padding: '0px 8px 8px 8px'}}>
+          <h3 style={{margin: '8px 10px'}}>
+            Saved Queries
+            {pagination}
+          </h3>
+          <hr style={{marginTop: '6px'}}/>
+          <div>
+            {loading}
+            {queries}
+          </div>
+        </div>
+      </section>
+    );
+  }
+
+  prev () {
+    if (this.state.page > 1) this._onChange(this.state.page - 1);
+  }
+
+  next () {
+    if (this.state.page < this.state.totalPages) {
+      this._onChange(this.state.page + 1);
+    }
+  }
+
+  _onChange (page) {
+    // done to filter success/error messages from store
+    if (typeof page === 'object') page = this.state.page;
+
+    var PAGE_SIZE = 10;
+    page = page || this.state.page || 1;
+    var state = {
+      savedQueries: this._getPaginatedSavedQueries(page, PAGE_SIZE),
+      page: page,
+      totalPages: Math.ceil(SavedQueryStore.getTotalRecords() / PAGE_SIZE)
+    };
+    state.loading = !!(!state.savedQueries && state.totalPages);
+    this.setState(state);
+  }
+
+  _getPaginatedSavedQueries (pageNumber, pageSize) {
+    if (!pageNumber && !pageSize) return;
+
+    var token = UserStore.getUserDetails().secretToken;
+    var email = UserStore.getUserDetails().email;
+    var savedQueries = SavedQueryStore.getSavedQueries();
+    var savedQueriesLength = savedQueries && Object.keys(savedQueries).length;
+    var totalQueries = SavedQueryStore.getTotalRecords();
+    var relevantSavedQueries = null;
+    var startIndex = (pageNumber - 1) * pageSize;
+    if (savedQueriesLength > startIndex) {
+      relevantSavedQueries = Object.keys(savedQueries)
+        .slice(startIndex, startIndex + pageSize);
+      if ((totalQueries != savedQueriesLength) && (relevantSavedQueries.length < pageSize) && totalQueries) {
+        // call backend
+        AdhocQueryActions.getSavedQueries(token, email, {
+          offset: startIndex,
+          pageSize: pageSize
+        });
+
+        this.setState({loading: true});
+      }
+    } else {
+      // trigger action
+      if (!totalQueries) return;
+
+      AdhocQueryActions.getSavedQueries(token, email, {
+        offset: startIndex,
+        pageSize: pageSize
+      });
+
+      this.setState({loading: true});
+    }
+
+    var filteredQueries = relevantSavedQueries && relevantSavedQueries.map(id => {
+      return savedQueries[id];
+    });
+
+    return filteredQueries;
+  }
+
+}
+
+export default SavedQueries;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/components/SavedQueryPreviewComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/SavedQueryPreviewComponent.js b/lens-ui/app/components/SavedQueryPreviewComponent.js
new file mode 100644
index 0000000..4f9459a
--- /dev/null
+++ b/lens-ui/app/components/SavedQueryPreviewComponent.js
@@ -0,0 +1,136 @@
+/**
+* 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 CodeMirror from 'codemirror';
+import 'codemirror/mode/sql/sql.js';
+import 'codemirror/addon/runmode/runmode.js';
+
+import QueryParamRowComponent from './QueryParamRowComponent';
+import AdhocQueryActions from '../actions/AdhocQueryActions';
+import UserStore from '../stores/UserStore';
+
+class SavedQueryPreview extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = { showDetail: false, queryParams: {} };
+    this.toggleQueryDetails = this.toggleQueryDetails.bind(this);
+    this.runSavedQuery = this.runSavedQuery.bind(this);
+    this.update = this.update.bind(this);
+    this.props.query && this.props.query.parameters.forEach(param => {
+      this.state.queryParams[param.name] = param;
+    });
+  }
+
+  render () {
+    let query = this.props.query;
+
+    if (!query.query) 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.query,
+        '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>);
+      });
+
+    let params = query && query.parameters.map(param => {
+      return <QueryParamRowComponent param={param} entryMode={true}
+        updateParam={this.update}/>;
+    });
+
+    let paramsTable = !params.length ? null :
+      (<table className='table table-striped table-condensed'>
+        <thead>
+          <tr><th>Param</th><th>Display Name</th><th>Data Type</th>
+          <th>Collection Type</th><th>Value</th></tr>
+        </thead>
+        <tbody>
+          {params}
+        </tbody>
+      </table>);
+
+    return (
+      <section ref='preview'>
+        <div className='panel panel-default'>
+          <div className='panel-heading blue-header' style={{cursor: 'pointer', padding: '2px 8px'}}
+            onClick={this.toggleQueryDetails}>
+            <h5 className='panel-title' style={{marginBottom: '4px'}}>
+              {query.name || 'Unnamed Query'}
+            </h5>
+            <small className='italics'>
+              { query.description || 'No description available' }
+            </small>
+          </div>
+
+          {this.state.showDetail && (
+            <div className='panel-body' style={{borderTop: '1px solid #cccccc',
+              padding: '0px'}} key={'preview' + query.id}>
+              <pre className='cm-s-default' style={{
+                border: '0px', marginBottom: '0px'}}>
+                {codeTokens}
+
+                <Link to='query' query={{savedquery: query.id}}
+                  className='btn btn-default pull-right'>
+                  <i className='fa fa-pencil fa-lg'></i>
+                </Link>
+                <button className='btn btn-default pull-right' onClick={this.runSavedQuery}>
+                  <i className='fa fa-play fa-lg'></i>
+                </button>
+              </pre>
+
+              {paramsTable}
+            </div>
+          )}
+        </div>
+      </section>
+    );
+  }
+
+  toggleQueryDetails () {
+    this.setState({ showDetail: !this.state.showDetail });
+  }
+
+  update (param) {
+    this.state.queryParams[param.name] = param.param;
+  }
+
+  runSavedQuery () {
+    let secretToken = UserStore.getUserDetails().secretToken;
+    let parameters = Object.keys(this.state.queryParams).map(name => {
+      let object = {};
+      object[name] = this.state.queryParams[name].defaultValue;
+      return object;
+    });
+    AdhocQueryActions.runSavedQuery(secretToken, this.props.query.id, parameters);
+  }
+}
+
+SavedQueryPreview.propTypes = {
+  query: React.PropTypes.object,
+  params: React.PropTypes.object
+};
+
+export default SavedQueryPreview;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/components/SidebarComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/SidebarComponent.js b/lens-ui/app/components/SidebarComponent.js
index dcc8737..37a2c59 100644
--- a/lens-ui/app/components/SidebarComponent.js
+++ b/lens-ui/app/components/SidebarComponent.js
@@ -24,7 +24,7 @@ import Database from './DatabaseComponent';
 import QueryOperations from './QueryOperationsComponent';
 
 class Sidebar extends React.Component {
-  render() {
+  render () {
     return (
       <section>
         <QueryOperations />
@@ -33,6 +33,6 @@ class Sidebar extends React.Component {
       </section>
     );
   }
-};
+}
 
 export default Sidebar;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/components/TableSchemaComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/TableSchemaComponent.js b/lens-ui/app/components/TableSchemaComponent.js
index 67dc25a..7abab95 100644
--- a/lens-ui/app/components/TableSchemaComponent.js
+++ b/lens-ui/app/components/TableSchemaComponent.js
@@ -22,7 +22,7 @@ import React from 'react';
 import TableStore from '../stores/TableStore';
 import UserStore from '../stores/UserStore';
 import AdhocQueryActions from '../actions/AdhocQueryActions';
-import Loader from '../components/LoaderComponent';
+import Loader from './LoaderComponent';
 
 function getTable (tableName, database) {
   let tables = TableStore.getTables(database);
@@ -71,15 +71,14 @@ class TableSchema extends React.Component {
   render () {
     let schemaSection = null;
 
-
     if (this.state.table && !this.state.table.isLoaded) {
-      schemaSection = <Loader size="8px" margin="2px" />;
+      schemaSection = <Loader size='8px' margin='2px' />;
     } else {
-      schemaSection = (<div className="row">
-          <div className="table-responsive">
-            <table className="table table-striped">
+      schemaSection = (<div className='row'>
+          <div className='table-responsive'>
+            <table className='table table-striped'>
               <thead>
-              <caption className="bg-primary text-center">Columns</caption>
+              <caption className='bg-primary text-center'>Columns</caption>
                 <tr><th>Name</th><th>Type</th><th>Description</th></tr>
               </thead>
               <tbody>
@@ -91,8 +90,9 @@ class TableSchema extends React.Component {
                         <td>{col.type}</td>
                         <td>{col.comment || 'No description available'}</td>
                       </tr>
-                    )
-                })}
+                    );
+                  })
+                }
               </tbody>
             </table>
           </div>
@@ -101,16 +101,15 @@ class TableSchema extends React.Component {
 
     return (
       <section>
-        <div className="panel panel-default">
-          <div className="panel-heading">
-            <h3 className="panel-title">Schema Details: &nbsp;
-              <strong className="text-primary">
+        <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'}}>
+          <div className='panel-body'>
             {schemaSection}
           </div>
         </div>
@@ -128,4 +127,9 @@ class TableSchema extends React.Component {
   }
 }
 
+TableSchema.propTypes = {
+  query: React.PropTypes.object,
+  params: React.PropTypes.object
+};
+
 export default TableSchema;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/components/TableTreeComponent.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/components/TableTreeComponent.js b/lens-ui/app/components/TableTreeComponent.js
index 026e443..59965d4 100644
--- a/lens-ui/app/components/TableTreeComponent.js
+++ b/lens-ui/app/components/TableTreeComponent.js
@@ -21,12 +21,11 @@ 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 Loader from './LoaderComponent';
 import '../styles/css/tree.css';
 
 let filterString = '';
@@ -39,8 +38,7 @@ function getState (page, filterString, database) {
 }
 
 function getTables (page, filterString, database) {
-
-  // get all the tables
+  // get all the native tables
   let tables = TableStore.getTables(database);
   let pageSize = 10;
   let allTables;
@@ -49,11 +47,9 @@ function getTables (page, filterString, database) {
   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;
@@ -67,7 +63,7 @@ function getTables (page, filterString, database) {
   });
 
   return {
-    totalPages: Math.ceil(allTables.length/pageSize),
+    totalPages: Math.ceil(allTables.length / pageSize),
     tables: pageTables
   };
 }
@@ -123,20 +119,20 @@ class TableTree extends React.Component {
 
     // construct tree
     tableTree = this.state.tables.map(table => {
-      let label = (<Link to="tableschema" params={{tableName: table.name}}
+      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}>
+          defaultCollapsed={!table.isLoaded}>
 
           {table.isLoaded ? table.columns.map(col => {
             return (
-              <div className="treeNode" key={name + '|' + col.name}>
+              <div className='treeNode' key={table.name + '|' + col.name}>
                 {col.name} ({col.type})
               </div>
             );
-          }) : <Loader size="4px" margin="2px" />}
+          }) : <Loader size='4px' margin='2px' />}
 
         </TreeView>
       );
@@ -144,42 +140,41 @@ class TableTree extends React.Component {
 
     // show a loader when tree is loading
     if (this.state.loading) {
-      tableTree = <Loader size="4px" margin="2px" />;
+      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>
+      tableTree = (<div className='alert-danger' style={{padding: '8px 5px'}}>
+          <strong>Sorry, we couldn&#39;t find any.</strong>
         </div>);
     }
 
     let pagination = this.state.tables.length ?
       (
         <div>
-          <div className="text-center">
-            <button className="btn btn-link glyphicon glyphicon-triangle-left page-back"
+          <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"
+            <button className='btn btn-link glyphicon glyphicon-triangle-right page-next'
               onClick={this.nextPage}>
             </button>
           </div>
         </div>
-      ) :
-      null;
+      ) : null;
 
     return (
       <div>
         { !this.state.loading &&
-          <div className="form-group">
-            <input type="search" className="form-control"
-              placeholder="Type to filter tables"
+          <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'}}>
+        <div ref='tableTree' style={{maxHeight: '350px', overflowY: 'auto'}}>
           {tableTree}
         </div>
       </div>
@@ -187,14 +182,12 @@ class TableTree extends React.Component {
   }
 
   _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;
@@ -235,4 +228,8 @@ class TableTree extends React.Component {
 
 }
 
+TableTree.propTypes = {
+  database: React.PropTypes.string.isRequired
+};
+
 export default TableTree;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/constants/AdhocQueryConstants.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/constants/AdhocQueryConstants.js b/lens-ui/app/constants/AdhocQueryConstants.js
index 3c4f93a..ea8cbd0 100644
--- a/lens-ui/app/constants/AdhocQueryConstants.js
+++ b/lens-ui/app/constants/AdhocQueryConstants.js
@@ -17,7 +17,7 @@
 * under the License.
 */
 
-import KeyMirror from 'keyMirror';
+import KeyMirror from 'keymirror';
 
 const AdhocQueryConstants = KeyMirror({
   RECEIVE_CUBES: null,
@@ -45,7 +45,14 @@ const AdhocQueryConstants = KeyMirror({
   RECEIVE_QUERY_FAILED: null,
 
   RECEIVE_DATABASES: null,
-  RECEIVE_DATABASES_FAILED: null
+  RECEIVE_DATABASES_FAILED: null,
+
+  RECEIVE_QUERY_PARAMS_META: null,
+
+  SAVE_QUERY_SUCCESS: null,
+  SAVE_QUERY_FAILED: null,
+
+  RECEIVE_SAVED_QUERY: null
 });
 
 export default AdhocQueryConstants;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/constants/AppConstants.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/constants/AppConstants.js b/lens-ui/app/constants/AppConstants.js
index 48cd93e..075cffb 100644
--- a/lens-ui/app/constants/AppConstants.js
+++ b/lens-ui/app/constants/AppConstants.js
@@ -17,11 +17,12 @@
 * under the License.
 */
 
-import KeyMirror from 'keyMirror';
+import KeyMirror from 'keymirror';
 
 const AppConstants = KeyMirror({
   AUTHENTICATION_SUCCESS: null,
-  AUTHENTICATION_FAILED: null
+  AUTHENTICATION_FAILED: null,
+  RECEIVE_USERNAME: null
 });
 
 export default AppConstants;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/dispatcher/AppDispatcher.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/dispatcher/AppDispatcher.js b/lens-ui/app/dispatcher/AppDispatcher.js
index 31b267c..b87320c 100644
--- a/lens-ui/app/dispatcher/AppDispatcher.js
+++ b/lens-ui/app/dispatcher/AppDispatcher.js
@@ -5,7 +5,6 @@
  * 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';

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/stores/AdhocQueryStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/AdhocQueryStore.js b/lens-ui/app/stores/AdhocQueryStore.js
index 7420270..3f880bf 100644
--- a/lens-ui/app/stores/AdhocQueryStore.js
+++ b/lens-ui/app/stores/AdhocQueryStore.js
@@ -33,7 +33,6 @@ var adhocDetails = {
   dbName: Config.dbName
 };
 
-// TODO remove this.
 function receiveQueryHandle (payload) {
   let id = payload.queryHandle.getElementsByTagName('handleId')[0].textContent;
   adhocDetails.queryHandle = id;
@@ -69,7 +68,6 @@ function receiveQueryResult (payload) {
     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;
@@ -87,19 +85,13 @@ let AdhocQueryStore = assign({}, EventEmitter.prototype, {
 
   // always returns the last-run-query's handle
   getQueryHandle () {
-    return adhocDetails.queryHandle;
-  },
-
-  clearQueryHandle () {
+    let handle = adhocDetails.queryHandle;
     adhocDetails.queryHandle = null;
+    return handle;
   },
 
-  getDbName () {
-    return adhocDetails.dbName
-  },
-
-  emitChange () {
-    this.emit(CHANGE_EVENT);
+  emitChange (hash) {
+    this.emit(CHANGE_EVENT, hash);
   },
 
   addChangeListener (callback) {
@@ -112,7 +104,7 @@ let AdhocQueryStore = assign({}, EventEmitter.prototype, {
 });
 
 AppDispatcher.register((action) => {
-  switch(action.actionType) {
+  switch (action.actionType) {
 
     case AdhocQueryConstants.RECEIVE_QUERY_HANDLE:
       receiveQueryHandle(action.payload);
@@ -132,6 +124,11 @@ AppDispatcher.register((action) => {
     case AdhocQueryConstants.RECEIVE_QUERY:
       receiveQuery(action.payload);
       AdhocQueryStore.emitChange();
+      break;
+
+    case AdhocQueryConstants.RECEIVE_QUERY_HANDLE_FAILED:
+      AdhocQueryStore.emitChange(action.payload);
+      break;
   }
 });
 

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/stores/CubeStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/CubeStore.js b/lens-ui/app/stores/CubeStore.js
index 8b20b95..09b469f 100644
--- a/lens-ui/app/stores/CubeStore.js
+++ b/lens-ui/app/stores/CubeStore.js
@@ -44,7 +44,6 @@ function receiveCubeDetails (payload) {
   cubes[cubeDetails.name].isLoaded = true;
 }
 
-
 let CHANGE_EVENT = 'change';
 var cubes = {};
 
@@ -67,7 +66,7 @@ let CubeStore = assign({}, EventEmitter.prototype, {
 });
 
 AppDispatcher.register((action) => {
-  switch(action.actionType) {
+  switch (action.actionType) {
     case AdhocQueryConstants.RECEIVE_CUBES:
       receiveCubes(action.payload);
       CubeStore.emitChange();

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/stores/DatabaseStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/DatabaseStore.js b/lens-ui/app/stores/DatabaseStore.js
index 9f4490b..79894cf 100644
--- a/lens-ui/app/stores/DatabaseStore.js
+++ b/lens-ui/app/stores/DatabaseStore.js
@@ -26,7 +26,7 @@ function receiveDatabases (payload) {
   databases = [];
 
   databases = payload.databases.elements &&
-    payload.databases.elements.slice()
+    payload.databases.elements.slice();
 }
 
 let CHANGE_EVENT = 'change';
@@ -51,7 +51,7 @@ let DatabaseStore = assign({}, EventEmitter.prototype, {
 });
 
 AppDispatcher.register((action) => {
-  switch(action.actionType) {
+  switch (action.actionType) {
     case AdhocQueryConstants.RECEIVE_DATABASES:
       receiveDatabases(action.payload);
       DatabaseStore.emitChange();

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/stores/SavedQueryStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/SavedQueryStore.js b/lens-ui/app/stores/SavedQueryStore.js
new file mode 100644
index 0000000..fb2869b
--- /dev/null
+++ b/lens-ui/app/stores/SavedQueryStore.js
@@ -0,0 +1,99 @@
+/**
+* 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';
+
+let savedQueries = {};
+let offset = 0;
+let totalRecords = 0;
+let CHANGE_EVENT = 'change';
+
+function receiveSavedQueries (payload) {
+  payload && payload.resoures.forEach(query => {
+    savedQueries[query.id] = query;
+  });
+
+  totalRecords = payload && payload.totalCount;
+}
+
+function receiveSavedQuery (payload) {
+  if (!savedQueries[payload.id]) totalRecords++;
+  savedQueries[payload.id] = payload;
+}
+
+let SavedQueryStore = assign({}, EventEmitter.prototype, {
+  getSavedQueries () {
+    return savedQueries;
+  },
+
+  getTotalRecords () {
+    return totalRecords;
+  },
+
+  getOffset () {
+    return offset;
+  },
+
+  emitChange (hash) {
+    this.emit(CHANGE_EVENT, hash);
+  },
+
+  addChangeListener (callback) {
+    this.on(CHANGE_EVENT, callback);
+  },
+
+  removeChangeListener (callback) {
+    this.removeListener(CHANGE_EVENT, callback);
+  }
+});
+
+AppDispatcher.register((action) => {
+  switch (action.actionType) {
+    case AdhocQueryConstants.RECEIVE_SAVED_QUERIES:
+      receiveSavedQueries(action.payload);
+      SavedQueryStore.emitChange();
+      break;
+
+    case AdhocQueryConstants.RECEIVE_QUERY_PARAMS_META:
+      SavedQueryStore.emitChange({type: 'params', params: action.payload});
+      break;
+
+    case AdhocQueryConstants.SAVE_QUERY_SUCCESS:
+      SavedQueryStore.emitChange({
+        type: 'success',
+        message: action.payload,
+        id: action.payload && action.payload.id
+      });
+      break;
+
+    case AdhocQueryConstants.SAVE_QUERY_FAILED:
+      SavedQueryStore.emitChange({type: 'failure', message: action.payload});
+      break;
+
+    case AdhocQueryConstants.RECEIVE_SAVED_QUERY:
+      receiveSavedQuery(action.payload);
+      SavedQueryStore.emitChange();
+      break;
+  }
+});
+
+export default SavedQueryStore;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/stores/TableStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/TableStore.js b/lens-ui/app/stores/TableStore.js
index 299d9e8..ee35b84 100644
--- a/lens-ui/app/stores/TableStore.js
+++ b/lens-ui/app/stores/TableStore.js
@@ -31,7 +31,7 @@ function receiveTables (payload) {
   }
 
   payload.tables.elements &&
-    payload.tables.elements.forEach( table => {
+    payload.tables.elements.forEach(table => {
       if (!tables[database][table]) {
         tables[database][table] = { name: table, isLoaded: false };
       }
@@ -86,7 +86,7 @@ let TableStore = assign({}, EventEmitter.prototype, {
 });
 
 AppDispatcher.register((action) => {
-  switch(action.actionType) {
+  switch (action.actionType) {
     case AdhocQueryConstants.RECEIVE_TABLES:
       receiveTables(action.payload);
       TableStore.emitChange();

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/stores/UserStore.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/stores/UserStore.js b/lens-ui/app/stores/UserStore.js
index 47da021..ee5506f 100644
--- a/lens-ui/app/stores/UserStore.js
+++ b/lens-ui/app/stores/UserStore.js
@@ -17,8 +17,6 @@
 * under the License.
 */
 
-import React from 'react';
-
 import AppDispatcher from '../dispatcher/AppDispatcher';
 import AppConstants from '../constants/AppConstants';
 import assign from 'object-assign';
@@ -28,8 +26,7 @@ var CHANGE_EVENT = 'change';
 var userDetails = {
   isUserLoggedIn: false,
   email: '',
-  secretToken: '',
-  publicKey: ''
+  secretToken: ''
 };
 
 // keeping these methods out of the UserStore class as
@@ -38,13 +35,10 @@ function authenticateUser (details) {
   userDetails = {
     isUserLoggedIn: true,
     email: details.email,
-    secretToken: new XMLSerializer().serializeToString(details.secretToken),
-    publicKey: details.secretToken.getElementsByTagName('publicId')[0]
-      .textContent
+    secretToken: new XMLSerializer().serializeToString(details.secretToken)
   };
 
   // store the details in localStorage if available
-
   if (window.localStorage) {
     let adhocCred = assign({}, userDetails, { timestamp: Date.now() });
     window.localStorage.setItem('adhocCred', JSON.stringify(adhocCred));
@@ -52,7 +46,6 @@ function authenticateUser (details) {
 }
 
 function unauthenticateUser (details) {
-
   // details contains error code and message
   // which are not stored but passsed along
   // during emitChange()
@@ -69,12 +62,9 @@ function unauthenticateUser (details) {
 // 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'));
 
@@ -114,7 +104,7 @@ var UserStore = assign({}, EventEmitter.prototype, {
 
 // registering callbacks with the dispatcher. So verbose?? I know right!
 AppDispatcher.register((action) => {
-  switch(action.actionType) {
+  switch (action.actionType) {
     case AppConstants.AUTHENTICATION_SUCCESS:
       authenticateUser(action.payload);
       UserStore.emitChange();

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/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
index 131ab46..7670703 100644
--- a/lens-ui/app/styles/css/global.css
+++ b/lens-ui/app/styles/css/global.css
@@ -16,3 +16,25 @@
 * specific language governing permissions and limitations
 * under the License.
 */
+
+.no-padding {
+  padding: 0px;
+}
+
+.glyphicon.glyphicon-chevron-up, .glyphicon.glyphicon-chevron-down {
+  cursor: pointer;
+}
+
+.italics {
+  font-style: italic;
+}
+
+/*.panel-default > .blue-header {
+  background: rgba(51, 122, 183, 0.79);
+  color: white;
+}*/
+
+.panel-default > .blue-header:HOVER {
+  /*background: rgba(51, 122, 183, 1);*/
+  transform: scale(1.018);
+}

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/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
index b400cfb..194e19e 100644
--- a/lens-ui/app/styles/css/login.css
+++ b/lens-ui/app/styles/css/login.css
@@ -17,8 +17,7 @@
 * under the License.
 */
 
-
- /*For login form*/
+/*For login form*/
 .form-signin {
   max-width: 330px;
   padding: 15px;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/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
index a82165e..e29758e 100644
--- a/lens-ui/app/styles/css/query-component.css
+++ b/lens-ui/app/styles/css/query-component.css
@@ -17,8 +17,7 @@
 * under the License.
 */
 
-
- @media (max-width: 768px) {
+@media (max-width: 768px) {
     .btn.responsive {
         width:100%;
         margin-bottom: 10px;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/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
index 402c9a0..61cc4e7 100644
--- a/lens-ui/app/styles/css/tree.css
+++ b/lens-ui/app/styles/css/tree.css
@@ -17,8 +17,7 @@
 * under the License.
 */
 
-
- .node {
+.node {
   font-weight: bold;
 }
 

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/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
index c0704dc..05cc30e 100644
--- a/lens-ui/app/styles/less/globals.less
+++ b/lens-ui/app/styles/less/globals.less
@@ -17,7 +17,6 @@
 * under the License.
 */
 
-
- // IMPORTS
+// IMPORTS
 
 @import "~bootstrap/less/bootstrap.less";

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/app/utils/ErrorParser.js
----------------------------------------------------------------------
diff --git a/lens-ui/app/utils/ErrorParser.js b/lens-ui/app/utils/ErrorParser.js
new file mode 100644
index 0000000..8bd0d90
--- /dev/null
+++ b/lens-ui/app/utils/ErrorParser.js
@@ -0,0 +1,53 @@
+/**
+* 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.
+*/
+
+let ErrorParser = {
+  getMessage (errorXML) {
+    let errors = [];
+
+    errors = Array.prototype.slice.call(errorXML.getElementsByTagName('error'))
+      .map(error => {
+        return {
+          code: error.getElementsByTagName('code')[0].textContent,
+          message: error.getElementsByTagName('message')[0].textContent
+        };
+      })
+      .sort((a, b) => {
+        return parseInt(a.code, 10) - parseInt(b.code, 10);
+      })
+      // removes duplicate error messages
+      .filter((item, pos, array) => {
+        return !pos || (item.code != (array[pos - 1] && array[pos - 1].code));
+      })
+      // removes not so helpful `Internal Server Error`
+      .filter(error => {
+        return error.code != 1001;
+      });
+
+    if (errors && errors.length == 0) {
+      errors[0] = {};
+      errors[0].code = 500;
+      errors[0].message = 'Oh snap! Something went wrong. Please try again later.';
+    }
+
+    return errors;
+  }
+};
+
+export default ErrorParser;

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/index.html
----------------------------------------------------------------------
diff --git a/lens-ui/index.html b/lens-ui/index.html
index 9c20fe9..62df140 100644
--- a/lens-ui/index.html
+++ b/lens-ui/index.html
@@ -80,6 +80,7 @@
         }
       }
     </style>
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
   </head>
   <body>
 

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/package.json
----------------------------------------------------------------------
diff --git a/lens-ui/package.json b/lens-ui/package.json
index 920b120..b21302b 100644
--- a/lens-ui/package.json
+++ b/lens-ui/package.json
@@ -1,7 +1,6 @@
 {
   "name": "lens-ui",
-  "version": "1.0.0",
-  "description": "An exemplary front end solution for Apache LENS",
+  "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",
@@ -13,39 +12,45 @@
     "bootstrap": "^3.3.4",
     "classnames": "^2.1.2",
     "codemirror": "^5.3.0",
+    "cookie-parser": "~1.3.5",
+    "debug": "~2.2.0",
+    "express": "~4.12.4",
+    "express-session": "latest",
     "flux": "^2.0.3",
     "halogen": "^0.1.8",
     "keymirror": "^0.1.1",
     "lodash": "^3.9.1",
     "moment": "^2.10.3",
+    "morgan": "~1.5.3",
     "object-assign": "^2.0.0",
     "q": "^1.4.1",
+    "qwest": "^2.0.7",
     "react": "^0.13.3",
-    "react-bootstrap": "^0.22.6",
+    "react-bootstrap": "0.25.1",
     "react-router": "^0.13.3",
     "react-treeview": "^0.3.12",
-    "reqwest": "^1.1.5"
+    "react-widgets": "^2.8.0",
+    "serve-favicon": "~2.2.1"
   },
   "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"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://git-wip-us.apache.org/repos/asf/lens.git"
   }
 }

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/server.js
----------------------------------------------------------------------
diff --git a/lens-ui/server.js b/lens-ui/server.js
index e812018..736d862 100644
--- a/lens-ui/server.js
+++ b/lens-ui/server.js
@@ -19,41 +19,42 @@
 
 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']){
+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
+app.use(session({
+  secret            : 'SomethingYouKnow',
+  resave            : false,
+  saveUninitialized : true
 }));
 
-var fs = require('fs');
-
+var fs = require('fs')
+;
 app.use(express.static(path.resolve(__dirname, 'target', 'assets')));
 
+app.get('/health', function (req, res) {
+  res.status(200).send('Adhoc Query UI is up and running.');
+});
+
 app.get('/target/assets/*', function (req, res) {
   res.setHeader('Cache-Control', 'public');
   res.end(fs.readFileSync(__dirname + req.path));
@@ -62,18 +63,18 @@ app.get('/target/assets/*', function (req, res) {
 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.'); });
+    target: process.env['lensserver']
+  }, function (e) {
+    console.error('Proxy Error: ', e);
+  });
 });
 
-app.get('*', function(req, res) {
+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;
+var server = app.listen(port, function (err) {
+  if (err) throw err;
 
-  console.log('Ad hoc UI server listening at port: ', port);
+  console.log('Ad hoc UI server listening at %s', port);
 });

http://git-wip-us.apache.org/repos/asf/lens/blob/86714211/lens-ui/webpack.config.js
----------------------------------------------------------------------
diff --git a/lens-ui/webpack.config.js b/lens-ui/webpack.config.js
index ab4021f..9977049 100644
--- a/lens-ui/webpack.config.js
+++ b/lens-ui/webpack.config.js
@@ -20,7 +20,6 @@
 var webpack = require('webpack');
 var path = require('path');
 
-
 module.exports = {
 
   entry: {
@@ -29,27 +28,30 @@ module.exports = {
     ]
   },
 
-	output: {
+  output: {
     path: path.join(__dirname, 'target', 'assets'),
-		filename: 'bundle.js'
-	},
+    filename: 'bundle.js'
+  },
 
   plugins: [
     new webpack.NoErrorsPlugin()
   ],
 
+  devtool: 'source-map',
+
   resolve: {
     modulesDirectories: ['app', 'node_modules', __dirname]
   },
 
-	module: {
-		loaders: [
+  module: {
+    loaders: [
       { test: /\.jsx?$/, loaders: ['babel'], include: path.join(__dirname, 'app') },
-			{ test: /\.css$/, loaders: ['style', 'css'] },
+      { 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']}
-		]
-	}
+      { test: /\.json$/, loaders: ['json'] },
+      { test: /\.gif$/, loader: 'url-loader?mimetype=image/png' }
+    ]
+  }
 };