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:37 UTC

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

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/current-release-line
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;