You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by li...@apache.org on 2016/02/21 12:52:10 UTC

[15/52] [abbrv] kylin git commit: KYLIN-1024 Load hive tables with selecting mode

KYLIN-1024 Load hive tables with selecting mode


Project: http://git-wip-us.apache.org/repos/asf/kylin/repo
Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/75970c3b
Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/75970c3b
Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/75970c3b

Branch: refs/heads/1.x-HBase1.1.3
Commit: 75970c3b1b576fdf1624e71eb70373e164541bc1
Parents: 0c13797
Author: janzhongi <ji...@ebay.com>
Authored: Sun Jan 31 01:58:12 2016 +0800
Committer: janzhongi <ji...@ebay.com>
Committed: Sun Jan 31 01:58:12 2016 +0800

----------------------------------------------------------------------
 .../apache/kylin/common/util/HiveClient.java    |   8 +
 conf/kylin.properties                           |   3 +-
 .../test_case_data/sandbox/kylin.properties     |   3 +
 .../kylin/rest/controller/TableController.java  |  44 ++
 webapp/app/js/controllers/sourceMeta.js         | 176 ++++++-
 .../app/js/directives/angular-tree-control.js   | 524 ++++++++++++-------
 webapp/app/js/model/tableModel.js               |   6 -
 webapp/app/js/services/kylinProperties.js       |   7 +
 webapp/app/js/services/tables.js                |   7 +-
 .../app/partials/tables/source_table_tree.html  |  27 +
 webapp/bower.json                               |   2 +-
 11 files changed, 601 insertions(+), 206 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/common/src/main/java/org/apache/kylin/common/util/HiveClient.java
----------------------------------------------------------------------
diff --git a/common/src/main/java/org/apache/kylin/common/util/HiveClient.java b/common/src/main/java/org/apache/kylin/common/util/HiveClient.java
index 41f0a9a..c56cb6c 100644
--- a/common/src/main/java/org/apache/kylin/common/util/HiveClient.java
+++ b/common/src/main/java/org/apache/kylin/common/util/HiveClient.java
@@ -133,6 +133,14 @@ public class HiveClient {
         return getBasicStatForTable(new org.apache.hadoop.hive.ql.metadata.Table(table), StatsSetupConst.NUM_FILES);
     }
 
+    public List<String> getHiveDbNames() throws Exception {
+        return getMetaStoreClient().getAllDatabases();
+    }
+
+    public List<String> getHiveTableNames(String database) throws Exception {
+        return getMetaStoreClient().getAllTables(database);
+    }
+
     /**
      * COPIED FROM org.apache.hadoop.hive.ql.stats.StatsUtil for backward compatibility
      * 

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/conf/kylin.properties
----------------------------------------------------------------------
diff --git a/conf/kylin.properties b/conf/kylin.properties
index e196d7a..e0727ed 100644
--- a/conf/kylin.properties
+++ b/conf/kylin.properties
@@ -125,7 +125,8 @@ kylin.web.hadoop=
 kylin.web.diagnostic=
 #contact mail on web page ,optional
 kylin.web.contact_mail=
-
+#set the number of databases and tables of each database shown on the hive tree
+kylin.web.hive.limit=20
 ###########################config info for front#######################
 
 #env DEV|QA|PROD

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/examples/test_case_data/sandbox/kylin.properties
----------------------------------------------------------------------
diff --git a/examples/test_case_data/sandbox/kylin.properties b/examples/test_case_data/sandbox/kylin.properties
index efdd6d4..3a7db5e 100644
--- a/examples/test_case_data/sandbox/kylin.properties
+++ b/examples/test_case_data/sandbox/kylin.properties
@@ -120,6 +120,9 @@ kylin.web.diagnostic=
 #contact mail on web page ,optional
 kylin.web.contact_mail=
 
+#set the number of databases and tables of each database shown on the hive tree
+kylin.web.hive.limit=20
+
 ###########################config info for front#######################
 
 #env DEV|QA|PROD

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/controller/TableController.java b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
index 5e817e3..f899d99 100644
--- a/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
+++ b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.common.util.HiveClient;
 import org.apache.kylin.metadata.MetadataConstants;
 import org.apache.kylin.metadata.model.ColumnDesc;
 import org.apache.kylin.metadata.model.TableDesc;
@@ -190,6 +191,49 @@ public class TableController extends BasicController {
         return descs;
     }
 
+
+    /**
+     * Show all databases in Hive
+     *
+     * @return Hive databases list
+     * @throws IOException
+     */
+    @RequestMapping(value = "/hive", method = { RequestMethod.GET })
+    @ResponseBody
+    private static List<String> showHiveDatabases() throws IOException {
+        HiveClient hiveClient = new HiveClient();
+        List<String> results = null;
+
+        try {
+            results = hiveClient.getHiveDbNames();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new IOException(e);
+        }
+        return results;
+    }
+
+    /**
+     * Show all tables in a Hive database
+     *
+     * @return Hive table list
+     * @throws IOException
+     */
+    @RequestMapping(value = "/hive/{database}", method = { RequestMethod.GET })
+    @ResponseBody
+    private static List<String> showHiveTables(@PathVariable String database) throws IOException {
+        HiveClient hiveClient = new HiveClient();
+        List<String> results = null;
+
+        try {
+            results = hiveClient.getHiveTableNames(database);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new IOException(e);
+        }
+        return results;
+    }
+
     public void setCubeService(CubeService cubeService) {
         this.cubeMgmtService = cubeService;
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/webapp/app/js/controllers/sourceMeta.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/sourceMeta.js b/webapp/app/js/controllers/sourceMeta.js
index d05dc7f..5c32f87 100755
--- a/webapp/app/js/controllers/sourceMeta.js
+++ b/webapp/app/js/controllers/sourceMeta.js
@@ -101,13 +101,187 @@ KylinApp
       });
     };
 
-    var ModalInstanceCtrl = function ($scope, $location, $modalInstance, tableNames, MessageService, projectName, scope) {
+    $scope.openTreeModal = function () {
+      $modal.open({
+        templateUrl: 'addHiveTableFromTree.html',
+        controller: ModalInstanceCtrl,
+        resolve: {
+          tableNames: function () {
+            return $scope.tableNames;
+          },
+          projectName:function(){
+            return  $scope.projectModel.selectedProject;
+          },
+          scope: function () {
+            return $scope;
+          }
+        }
+      });
+    };
+
+    var ModalInstanceCtrl = function ($scope, $location, $modalInstance, kylinConfig,tableNames, MessageService, projectName, scope) {
       $scope.tableNames = "";
       $scope.projectName = projectName;
       $scope.cancel = function () {
         $modalInstance.dismiss('cancel');
       };
+
+      $scope.treeOptions = {multiSelection: true};
+      $scope.selectedNodes = [];
+      $scope.hiveLimit =  kylinConfig.getHiveLimit();
+
+      $scope.loadHive = function () {
+        if($scope.hiveLoaded)
+          return;
+        TableService.showHiveDatabases({}, function (databases) {
+          $scope.dbNum = databases.length;
+          if (databases.length > 0) {
+            $scope.hiveMap = {};
+            for (var i = 0; i < databases.length; i++) {
+              var dbName = databases[i];
+              var hiveData = {"dbname":dbName,"tables":[],"expanded":false};
+              $scope.hive.push(hiveData);
+              $scope.hiveMap[dbName] = i;
+            }
+          }
+          $scope.hiveLoaded = true;
+          $scope.showMoreDatabases();
+        });
+      }
+
+      $scope.showMoreTables = function(hiveTables, node){
+        var shownTimes = parseInt(node.children.length / $scope.hiveLimit);
+        var from = $scope.hiveLimit * shownTimes;
+        var to = 0;
+        var hasMore = false;
+        if(from + $scope.hiveLimit > hiveTables.length) {
+          to = hiveTables.length - 1;
+        } else {
+          to = from + $scope.hiveLimit - 1;
+          hasMore = true;
+        }
+        if(!angular.isUndefined(node.children[from])){
+          node.children.pop();
+        }
+
+        for(var idx = from; idx <= to; idx++){
+          node.children.push({"label":node.label+'.'+hiveTables[idx],"id":idx-from+1,"children":[]});
+        }
+
+        if(hasMore){
+          var loading = {"label":"","id":65535,"children":[]};
+          node.children.push(loading);
+        }
+      }
+
+      $scope.showAllTables = function(hiveTables, node){
+        var shownTimes = parseInt(node.children.length / $scope.hiveLimit);
+        var from = $scope.hiveLimit * shownTimes;
+        var to = hiveTables.length - 1;
+        if(!angular.isUndefined(node.children[from])){
+          node.children.pop();
+        }
+        for(var idx = from; idx <= to; idx++){
+          node.children.push({"label":node.label+'.'+hiveTables[idx],"id":idx-from+1,"children":[]});
+        }
+      }
+
+      $scope.showMoreDatabases = function(){
+        var shownTimes = parseInt($scope.treedata.length / $scope.hiveLimit);
+        var from = $scope.hiveLimit * shownTimes;
+        var to = 0;
+        var hasMore = false;
+        if(from + $scope.hiveLimit > $scope.hive.length) {
+          to = $scope.hive.length - 1;
+        } else {
+          to = from + $scope.hiveLimit - 1;
+          hasMore = true;
+        }
+        if(!angular.isUndefined($scope.treedata[from])){
+          $scope.treedata.pop();
+        }
+
+        for(var idx = from; idx <= to; idx++){
+          var children = [];
+          var loading = {"label":"","id":0,"children":[]};
+          children.push(loading);
+          $scope.treedata.push({"label":$scope.hive[idx].dbname,"id":idx+1,"children":children,"expanded":false});
+        }
+
+        if(hasMore){
+          var loading = {"label":"","id":65535,"children":[0]};
+          $scope.treedata.push(loading);
+        }
+      }
+
+      $scope.showAllDatabases = function(){
+        var shownTimes = parseInt($scope.treedata.length / $scope.hiveLimit);
+        var from = $scope.hiveLimit * shownTimes;
+        var to = $scope.hive.length - 1;
+
+        if(!angular.isUndefined($scope.treedata[from])){
+          $scope.treedata.pop();
+        }
+
+        for(var idx = from; idx <= to; idx++){
+          var children = [];
+          var loading = {"label":"","id":0,"children":[]};
+          children.push(loading);
+          $scope.treedata.push({"label":$scope.hive[idx].dbname,"id":idx+1,"children":children,"expanded":false});
+        }
+      }
+
+      $scope.showMoreClicked = function($parentNode){
+        if($parentNode == null){
+          $scope.showMoreDatabases();
+        } else {
+          $scope.showMoreTables($scope.hive[$scope.hiveMap[$parentNode.label]].tables,$parentNode);
+        }
+      }
+
+      $scope.showAllClicked = function($parentNode){
+        if($parentNode == null){
+          $scope.showAllDatabases();
+        } else {
+          $scope.showAllTables($scope.hive[$scope.hiveMap[$parentNode.label]].tables,$parentNode);
+        }
+      }
+
+      $scope.showToggle = function(node) {
+        if(node.expanded == false){
+          TableService.showHiveTables({"database": node.label},function (hive_tables){
+            var tables = [];
+            for (var i = 0; i < hive_tables.length; i++) {
+              tables.push(hive_tables[i]);
+            }
+            $scope.hive[$scope.hiveMap[node.label]].tables = tables;
+            $scope.showMoreTables(tables,node);
+            node.expanded = true;
+          });
+        }
+      }
+
+      $scope.showSelected = function(node) {
+
+      }
+
+      if(angular.isUndefined($scope.hive) || angular.isUndefined($scope.hiveLoaded) || angular.isUndefined($scope.treedata) ){
+        $scope.hive = [];
+        $scope.hiveLoaded = false;
+        $scope.treedata = [];
+        $scope.loadHive();
+      }
+
       $scope.add = function () {
+
+        if($scope.tableNames.length === 0 && $scope.selectedNodes.length > 0) {
+          for(var i = 0; i <  $scope.selectedNodes.length; i++){
+            if($scope.selectedNodes[i].label.indexOf(".") >= 0){
+              $scope.tableNames += ($scope.selectedNodes[i].label) += ',';
+            }
+          }
+        }
+
         if ($scope.tableNames.trim() === "") {
           SweetAlert.swal('', 'Please input table(s) you want to synchronize.', 'info');
           return;

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/webapp/app/js/directives/angular-tree-control.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/directives/angular-tree-control.js b/webapp/app/js/directives/angular-tree-control.js
index 6a7d26c..6fca987 100644
--- a/webapp/app/js/directives/angular-tree-control.js
+++ b/webapp/app/js/directives/angular-tree-control.js
@@ -20,208 +20,344 @@
  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  */
-  (function ( angular ) {
-    'use strict';
-
-    angular.module( 'treeControl', [] )
-        .directive( 'treecontrol', ['$compile', function( $compile ) {
-            /**
-             * @param cssClass - the css class
-             * @param addClassProperty - should we wrap the class name with class=""
-             */
-            function classIfDefined(cssClass, addClassProperty) {
-                if (cssClass) {
-                    if (addClassProperty)
-                        return 'class="' + cssClass + '"';
-                    else
-                        return cssClass;
+
+(function ( angular ) {
+  'use strict';
+
+  angular.module( 'treeControl', [] )
+    .directive( 'treecontrol', ['$compile', function( $compile ) {
+      /**
+       * @param cssClass - the css class
+       * @param addClassProperty - should we wrap the class name with class=""
+       */
+      function classIfDefined(cssClass, addClassProperty) {
+        if (cssClass) {
+          if (addClassProperty)
+            return 'class="' + cssClass + '"';
+          else
+            return cssClass;
+        }
+        else
+          return "";
+      }
+
+      function ensureDefault(obj, prop, value) {
+        if (!obj.hasOwnProperty(prop))
+          obj[prop] = value;
+      }
+
+      return {
+        restrict: 'EA',
+        require: "treecontrol",
+        transclude: true,
+        scope: {
+          treeModel: "=",
+          selectedNode: "=?",
+          selectedNodes: "=?",
+          expandedNodes: "=?",
+          onSelection: "&",
+          onNodeToggle: "&",
+          options: "=?",
+          orderBy: "@",
+          reverseOrder: "@",
+          filterExpression: "=?",
+          filterComparator: "=?",
+          onDblclick: "&"
+        },
+        controller: ['$scope', function( $scope ) {
+
+          function defaultIsLeaf(node) {
+            return !node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0;
+          }
+
+          function shallowCopy(src, dst) {
+            if (angular.isArray(src)) {
+              dst = dst || [];
+
+              for ( var i = 0; i < src.length; i++) {
+                dst[i] = src[i];
+              }
+            } else if (angular.isObject(src)) {
+              dst = dst || {};
+
+              for (var key in src) {
+                if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
+                  dst[key] = src[key];
                 }
-                else
-                    return "";
+              }
             }
 
-            function ensureDefault(obj, prop, value) {
-                if (!obj.hasOwnProperty(prop))
-                    obj[prop] = value;
+            return dst || src;
+          }
+          function defaultEquality(a, b) {
+            if (a === undefined || b === undefined)
+              return false;
+            a = shallowCopy(a);
+            a[$scope.options.nodeChildren] = [];
+            b = shallowCopy(b);
+            b[$scope.options.nodeChildren] = [];
+            return angular.equals(a, b);
+          }
+
+          $scope.options = $scope.options || {};
+          ensureDefault($scope.options, "multiSelection", false);
+          ensureDefault($scope.options, "nodeChildren", "children");
+          ensureDefault($scope.options, "dirSelectable", "true");
+          ensureDefault($scope.options, "injectClasses", {});
+          ensureDefault($scope.options.injectClasses, "ul", "");
+          ensureDefault($scope.options.injectClasses, "li", "");
+          ensureDefault($scope.options.injectClasses, "liSelected", "");
+          ensureDefault($scope.options.injectClasses, "iExpanded", "");
+          ensureDefault($scope.options.injectClasses, "iCollapsed", "");
+          ensureDefault($scope.options.injectClasses, "iLeaf", "");
+          ensureDefault($scope.options.injectClasses, "label", "");
+          ensureDefault($scope.options.injectClasses, "labelSelected", "");
+          ensureDefault($scope.options, "equality", defaultEquality);
+          ensureDefault($scope.options, "isLeaf", defaultIsLeaf);
+
+          $scope.selectedNodes = $scope.selectedNodes || [];
+          $scope.expandedNodes = $scope.expandedNodes || [];
+          $scope.expandedNodesMap = {};
+          for (var i=0; i < $scope.expandedNodes.length; i++) {
+            $scope.expandedNodesMap[""+i] = $scope.expandedNodes[i];
+          }
+          $scope.parentScopeOfTree = $scope.$parent;
+
+
+          function isSelectedNode(node) {
+            if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode)))
+              return true;
+            else if ($scope.options.multiSelection && $scope.selectedNodes) {
+              for (var i = 0; (i < $scope.selectedNodes.length); i++) {
+                if ($scope.options.equality(node, $scope.selectedNodes[i])) {
+                  return true;
+                }
+              }
+              return false;
             }
+          }
+
+          $scope.headClass = function(node) {
+            var liSelectionClass = classIfDefined($scope.options.injectClasses.liSelected, false);
+            var injectSelectionClass = "";
+            if (liSelectionClass && isSelectedNode(node))
+              injectSelectionClass = " " + liSelectionClass;
+            if ($scope.options.isLeaf(node))
+              return "tree-leaf" + injectSelectionClass;
+            if ($scope.expandedNodesMap[this.$id])
+              return "tree-expanded" + injectSelectionClass;
+            else
+              return "tree-collapsed" + injectSelectionClass;
+          };
 
-            return {
-                restrict: 'EA',
-                require: "treecontrol",
-                transclude: true,
-                scope: {
-                    treeModel: "=",
-                    selectedNode: "=?",
-                    onSelection: "&",
-                    options: "=?",
-                    onDblclick: "&"
-                },
-                controller: function( $scope ) {
-                    function defaultIsSelectable() {
-                      return true;
-                    }
-
-                    $scope.options = $scope.options || {};
-                    ensureDefault($scope.options, "nodeChildren", "children");
-                    ensureDefault($scope.options, "dirSelectable", "true");
-                    ensureDefault($scope.options, "injectClasses", {});
-                    ensureDefault($scope.options.injectClasses, "ul", "");
-                    ensureDefault($scope.options.injectClasses, "li", "");
-                    ensureDefault($scope.options.injectClasses, "liSelected", "");
-                    ensureDefault($scope.options.injectClasses, "iExpanded", "");
-                    ensureDefault($scope.options.injectClasses, "iCollapsed", "");
-                    ensureDefault($scope.options.injectClasses, "iLeaf", "");
-                    ensureDefault($scope.options.injectClasses, "label", "");
-                    ensureDefault($scope.options.injectClasses, "labelSelected", "");
-                    ensureDefault($scope.options, "isSelectable", defaultIsSelectable);
-                  ensureDefault($scope.options, "equality", function (a, b) {
-                        if (a === undefined || b === undefined)
-                            return false;
-                        a = angular.copy(a); a[$scope.options.nodeChildren] = [];
-                        b = angular.copy(b); b[$scope.options.nodeChildren] = [];
-                        return angular.equals(a, b);
-                    });
-
-                    $scope.expandedNodes = {};
-                    $scope.parentScopeOfTree = $scope.$parent;
-
-                    $scope.headClass = function(node) {
-                        var liSelectionClass = classIfDefined($scope.options.injectClasses.liSelected, false);
-                        var injectSelectionClass = "";
-                        if (liSelectionClass && (this.$id == $scope.selectedScope))
-                            injectSelectionClass = " " + liSelectionClass;
-                        if (!node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0)
-                            return "tree-leaf" + injectSelectionClass
-                        if ($scope.expandedNodes[this.$id])
-                            return "tree-expanded" + injectSelectionClass;
-                        else
-                            return "tree-collapsed" + injectSelectionClass;
-                    };
-
-                    $scope.iBranchClass = function() {
-                        if ($scope.expandedNodes[this.$id])
-                            return classIfDefined($scope.options.injectClasses.iExpanded);
-                        else
-                            return classIfDefined($scope.options.injectClasses.iCollapsed);
-                    };
-
-                    $scope.nodeExpanded = function() {
-                        return !!$scope.expandedNodes[this.$id];
-                    };
-
-                    $scope.selectNodeHead = function() {
-                        $scope.expandedNodes[this.$id] = ($scope.expandedNodes[this.$id] === undefined ? this.node : undefined);
-                    };
-
-                    $scope.selectNodeLabel = function( selectedNode ){
-                        if (selectedNode[$scope.options.nodeChildren] && selectedNode[$scope.options.nodeChildren].length > 0 &&
-                          (!$scope.options.dirSelectable||!$scope.options.isSelectable(selectedNode))) {
-                            this.selectNodeHead();
-                        }
-                        else {
-                            $scope.selectedScope = this.$id;
-                            $scope.selectedNode = selectedNode;
-                            if ($scope.onSelection)
-                                $scope.onSelection({node: selectedNode});
-                        }
-                    };
-
-                    $scope.dblClickNode = function(selectedNode){
-                        if($scope.onDblclick!=null){
-                           $scope.onDblclick({node:selectedNode});
-                        }
-                    }
-
-                    $scope.selectedClass = function() {
-                        var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false);
-                        var injectSelectionClass = "";
-                        if (labelSelectionClass && (this.$id == $scope.selectedScope))
-                            injectSelectionClass = " " + labelSelectionClass;
-
-                        return (this.$id == $scope.selectedScope)?"tree-selected" + injectSelectionClass:"";
-                    };
-
-                    //tree template
-                    var template =
-                        '<ul '+classIfDefined($scope.options.injectClasses.ul, true)+'>' +
-                            '<li ng-repeat="node in node.' + $scope.options.nodeChildren+'" ng-class="headClass(node)" '+classIfDefined($scope.options.injectClasses.li, true)+'>' +
-                            '<i class="tree-branch-head" ng-class="iBranchClass()" ng-click="selectNodeHead(node)"></i>' +
-                            '<i class="tree-leaf-head '+classIfDefined($scope.options.injectClasses.iLeaf, false)+'"></i>' +
-                            '<div class="tree-label '+classIfDefined($scope.options.injectClasses.label, false)+'" ng-class="selectedClass()" ng-click="selectNodeLabel(node)" ng-dblclick="dblClickNode(node)" tree-transclude></div>' +
-                            '<treeitem ng-if="nodeExpanded()"></treeitem>' +
-                            '</li>' +
-                            '</ul>';
-
-                    return {
-                        template: $compile(template)
-                    }
-                },
-                compile: function(element, attrs, childTranscludeFn) {
-                    return function ( scope, element, attrs, treemodelCntr ) {
-
-                        scope.$watch("treeModel", function updateNodeOnRootScope(newValue) {
-                            if (angular.isArray(newValue)) {
-                                if (angular.isDefined(scope.node) && angular.equals(scope.node[scope.options.nodeChildren], newValue))
-                                    return;
-                                scope.node = {};
-                                scope.node[scope.options.nodeChildren] = newValue;
-                            }
-                            else {
-                                if (angular.equals(scope.node, newValue))
-                                    return;
-                                scope.node = newValue;
-                            }
-                        });
-
-                        //Rendering template for a root node
-                        treemodelCntr.template( scope, function(clone) {
-                            element.html('').append( clone );
-                        });
-                        // save the transclude function from compile (which is not bound to a scope as apposed to the one from link)
-                        // we can fix this to work with the link transclude function with angular 1.2.6. as for angular 1.2.0 we need
-                        // to keep using the compile function
-                        scope.$treeTransclude = childTranscludeFn;
-                    }
+          $scope.iBranchClass = function() {
+            if ($scope.expandedNodesMap[this.$id])
+              return classIfDefined($scope.options.injectClasses.iExpanded);
+            else
+              return classIfDefined($scope.options.injectClasses.iCollapsed);
+          };
+
+          $scope.nodeExpanded = function() {
+            return !!$scope.expandedNodesMap[this.$id];
+          };
+
+          $scope.selectNodeHead = function() {
+            var expanding = $scope.expandedNodesMap[this.$id] === undefined;
+            $scope.expandedNodesMap[this.$id] = (expanding ? this.node : undefined);
+            if (expanding) {
+              $scope.expandedNodes.push(this.node);
+            }
+            else {
+              var index;
+              for (var i=0; (i < $scope.expandedNodes.length) && !index; i++) {
+                if ($scope.options.equality($scope.expandedNodes[i], this.node)) {
+                  index = i;
                 }
-            };
-        }])
-        .directive("treeitem", function() {
-            return {
-                restrict: 'E',
-                require: "^treecontrol",
-                link: function( scope, element, attrs, treemodelCntr) {
-                    // Rendering template for the current node
-                    treemodelCntr.template(scope, function(clone) {
-                        element.html('').append(clone);
-                    });
+              }
+              if (index != undefined)
+                $scope.expandedNodes.splice(index, 1);
+            }
+            if ($scope.onNodeToggle)
+              $scope.onNodeToggle({node: this.node, expanded: expanding});
+          };
+
+          $scope.selectNodeLabel = function( selectedNode ){
+            if(selectedNode[$scope.options.nodeChildren] && selectedNode[$scope.options.nodeChildren].length > 0){
+              this.selectNodeHead();
+            }
+            if($scope.options.dirSelectable || !(selectedNode[$scope.options.nodeChildren] && selectedNode[$scope.options.nodeChildren].length > 0) )
+             {
+              var selected = false;
+              if ($scope.options.multiSelection) {
+                var pos = $scope.selectedNodes.indexOf(selectedNode);
+                if (pos === -1) {
+                  $scope.selectedNodes.push(selectedNode);
+                  selected = true;
+                } else {
+                  $scope.selectedNodes.splice(pos, 1);
+                }
+              } else {
+                if ($scope.selectedNode != selectedNode) {
+                  $scope.selectedNode = selectedNode;
+                  selected = true;
                 }
+                else {
+                  $scope.selectedNode = undefined;
+                }
+              }
+              if ($scope.onSelection)
+                $scope.onSelection({node: selectedNode, selected: selected});
             }
-        })
-        .directive("treeTransclude", function() {
-            return {
-                link: function(scope, element, attrs, controller) {
-                    angular.forEach(scope.expandedNodes, function (node, id) {
-                        if (scope.options.equality(node, scope.node)) {
-                            scope.expandedNodes[scope.$id] = scope.node;
-                            scope.expandedNodes[id] = undefined;
-                        }
-                    });
-                    if (scope.options.equality(scope.node, scope.selectedNode)) {
-                        scope.selectNodeLabel(scope.node);
-                    }
-
-                    // create a scope for the transclusion, whos parent is the parent of the tree control
-                    scope.transcludeScope = scope.parentScopeOfTree.$new();
-                    scope.transcludeScope.node = scope.node;
-                    scope.$on('$destroy', function() {
-                        scope.transcludeScope.$destroy();
-                    });
-
-                    scope.$treeTransclude(scope.transcludeScope, function(clone) {
-                        element.empty();
-                        element.append(clone);
-                    });
+          };
+
+
+          $scope.dblClickNode = function(selectedNode){
+            if($scope.onDblclick!=null){
+              $scope.onDblclick({node:selectedNode});
+            }
+          }
+
+          $scope.selectedClass = function() {
+            var isThisNodeSelected = isSelectedNode(this.node);
+            var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false);
+            var injectSelectionClass = "";
+            if (labelSelectionClass && isThisNodeSelected)
+              injectSelectionClass = " " + labelSelectionClass;
+
+            return isThisNodeSelected?"tree-selected" + injectSelectionClass:"";
+          };
+
+          //tree template
+          var orderBy = $scope.orderBy ? ' | orderBy:orderBy:reverseOrder' : '';
+          var template =
+            '<ul '+classIfDefined($scope.options.injectClasses.ul, true)+'>' +
+            '<li ng-repeat="node in node.' + $scope.options.nodeChildren + ' | filter:filterExpression:filterComparator ' + orderBy + '" ng-class="headClass(node)" '+classIfDefined($scope.options.injectClasses.li, true)+'>' +
+            '<i class="tree-branch-head" ng-class="iBranchClass()" ng-click="selectNodeHead(node)"></i>' +
+            '<i class="tree-leaf-head '+classIfDefined($scope.options.injectClasses.iLeaf, false)+'"></i>' +
+            '<div class="tree-label '+classIfDefined($scope.options.injectClasses.label, false)+'" ng-class="selectedClass()" ng-click="selectNodeLabel(node)" ng-dblclick="dblClickNode(node)" tree-transclude></div>' +
+            '<treeitem ng-if="nodeExpanded()"></treeitem>' +
+            '</li>' +
+            '</ul>';
+
+          this.template = $compile(template);
+        }],
+        compile: function(element, attrs, childTranscludeFn) {
+          return function ( scope, element, attrs, treemodelCntr ) {
+
+            scope.$watch("treeModel", function updateNodeOnRootScope(newValue) {
+              if (angular.isArray(newValue)) {
+                if (angular.isDefined(scope.node) && angular.equals(scope.node[scope.options.nodeChildren], newValue))
+                  return;
+                scope.node = {};
+                scope.synteticRoot = scope.node;
+                scope.node[scope.options.nodeChildren] = newValue;
+              }
+              else {
+                if (angular.equals(scope.node, newValue))
+                  return;
+                scope.node = newValue;
+              }
+            });
+
+            scope.$watchCollection('expandedNodes', function(newValue) {
+              var notFoundIds = 0;
+              var newExpandedNodesMap = {};
+              var $liElements = element.find('li');
+              var existingScopes = [];
+              // find all nodes visible on the tree and the scope $id of the scopes including them
+              angular.forEach($liElements, function(liElement) {
+                var $liElement = angular.element(liElement);
+                var liScope = $liElement.scope();
+                existingScopes.push(liScope);
+              });
+              // iterate over the newValue, the new expanded nodes, and for each find it in the existingNodesAndScopes
+              // if found, add the mapping $id -> node into newExpandedNodesMap
+              // if not found, add the mapping num -> node into newExpandedNodesMap
+              angular.forEach(newValue, function(newExNode) {
+                var found = false;
+                for (var i=0; (i < existingScopes.length) && !found; i++) {
+                  var existingScope = existingScopes[i];
+                  if (scope.options.equality(newExNode, existingScope.node)) {
+                    newExpandedNodesMap[existingScope.$id] = existingScope.node;
+                    found = true;
+                  }
                 }
+                if (!found)
+                  newExpandedNodesMap[notFoundIds++] = newExNode;
+              });
+              scope.expandedNodesMap = newExpandedNodesMap;
+            });
+
+//                        scope.$watch('expandedNodesMap', function(newValue) {
+//
+//                        });
+
+            //Rendering template for a root node
+            treemodelCntr.template( scope, function(clone) {
+              element.html('').append( clone );
+            });
+            // save the transclude function from compile (which is not bound to a scope as apposed to the one from link)
+            // we can fix this to work with the link transclude function with angular 1.2.6. as for angular 1.2.0 we need
+            // to keep using the compile function
+            scope.$treeTransclude = childTranscludeFn;
+          }
+        }
+      };
+    }])
+    .directive("treeitem", function() {
+      return {
+        restrict: 'E',
+        require: "^treecontrol",
+        link: function( scope, element, attrs, treemodelCntr) {
+          // Rendering template for the current node
+          treemodelCntr.template(scope, function(clone) {
+            element.html('').append(clone);
+          });
+        }
+      }
+    })
+    .directive("treeTransclude", function() {
+      return {
+        link: function(scope, element, attrs, controller) {
+          if (!scope.options.isLeaf(scope.node)) {
+            angular.forEach(scope.expandedNodesMap, function (node, id) {
+              if (scope.options.equality(node, scope.node)) {
+                scope.expandedNodesMap[scope.$id] = scope.node;
+                scope.expandedNodesMap[id] = undefined;
+              }
+            });
+          }
+          if (!scope.options.multiSelection && scope.options.equality(scope.node, scope.selectedNode)) {
+            scope.selectedNode = scope.node;
+          } else if (scope.options.multiSelection) {
+            var newSelectedNodes = [];
+            for (var i = 0; (i < scope.selectedNodes.length); i++) {
+              if (scope.options.equality(scope.node, scope.selectedNodes[i])) {
+                newSelectedNodes.push(scope.node);
+              }
             }
-        });
+            scope.selectedNodes = newSelectedNodes;
+          }
+
+          // create a scope for the transclusion, whos parent is the parent of the tree control
+          scope.transcludeScope = scope.parentScopeOfTree.$new();
+          scope.transcludeScope.node = scope.node;
+          scope.transcludeScope.$parentNode = (scope.$parent.node === scope.synteticRoot)?null:scope.$parent.node;
+          scope.transcludeScope.$index = scope.$index;
+          scope.transcludeScope.$first = scope.$first;
+          scope.transcludeScope.$middle = scope.$middle;
+          scope.transcludeScope.$last = scope.$last;
+          scope.transcludeScope.$odd = scope.$odd;
+          scope.transcludeScope.$even = scope.$even;
+          scope.$on('$destroy', function() {
+            scope.transcludeScope.$destroy();
+          });
+
+          scope.$treeTransclude(scope.transcludeScope, function(clone) {
+            element.empty();
+            element.append(clone);
+          });
+        }
+      }
+    });
 })( angular );

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/webapp/app/js/model/tableModel.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/model/tableModel.js b/webapp/app/js/model/tableModel.js
index 2b9c8ea..ca9c47c 100755
--- a/webapp/app/js/model/tableModel.js
+++ b/webapp/app/js/model/tableModel.js
@@ -48,12 +48,6 @@ KylinApp.service('TableModel', function (ProjectModel, $q, TableService) {
 
   this.treeOptions = {
     nodeChildren: "columns",
-    isSelectable:function(node){
-      if(node.id||node.uuid){//db
-        return true;
-      }
-      return false;
-    },
     injectClasses: {
       ul: "a1",
       li: "a2",

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/webapp/app/js/services/kylinProperties.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/kylinProperties.js b/webapp/app/js/services/kylinProperties.js
index a03403b..82158ec 100644
--- a/webapp/app/js/services/kylinProperties.js
+++ b/webapp/app/js/services/kylinProperties.js
@@ -20,6 +20,7 @@ KylinApp.service('kylinConfig', function (AdminService, $log) {
   var _config;
   var timezone;
   var deployEnv;
+  var deployEnv = 20;
 
 
   this.init = function () {
@@ -61,6 +62,12 @@ KylinApp.service('kylinConfig', function (AdminService, $log) {
     }
     return this.deployEnv.toUpperCase();
   }
+  this.getHiveLimit = function () {
+    if (!this.hiveLimit) {
+      this.hiveLimit = this.getProperty("kylin.web.hive.limit");
+    }
+    return this.hiveLimit;
+  }
 
   //fill config info for Config from backend
   this.initWebConfigInfo = function () {

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/webapp/app/js/services/tables.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/tables.js b/webapp/app/js/services/tables.js
index 1f1f15a..e98603b 100755
--- a/webapp/app/js/services/tables.js
+++ b/webapp/app/js/services/tables.js
@@ -17,12 +17,13 @@
  */
 
 KylinApp.factory('TableService', ['$resource', function ($resource, config) {
-  return $resource(Config.service.url + 'tables/:tableName/:action', {}, {
+  return $resource(Config.service.url + 'tables/:tableName/:action/:database', {}, {
     list: {method: 'GET', params: {}, cache: true, isArray: true},
     get: {method: 'GET', params: {}, isArray: false},
     getExd: {method: 'GET', params: {action: 'exd-map'}, isArray: false},
     reload: {method: 'PUT', params: {action: 'reload'}, isArray: false},
     loadHiveTable: {method: 'POST', params: {}, isArray: false},
-    genCardinality: {method: 'PUT', params: {action: 'cardinality'}, isArray: false}
-  });
+    genCardinality: {method: 'PUT', params: {action: 'cardinality'}, isArray: false},
+    showHiveDatabases: {method: 'GET', params: {action:'hive'}, cache: true, isArray: true},
+    showHiveTables: {method: 'GET', params: {action:'hive'}, cache: true, isArray: true}  });
 }]);

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/webapp/app/partials/tables/source_table_tree.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/tables/source_table_tree.html b/webapp/app/partials/tables/source_table_tree.html
index bdbe079..c048685 100755
--- a/webapp/app/partials/tables/source_table_tree.html
+++ b/webapp/app/partials/tables/source_table_tree.html
@@ -26,6 +26,8 @@
         <div class="col-xs-5" style="padding-left: 0px;margin-top: 20px;">
             <div class="pull-right">
                 <a class="btn btn-xs btn-primary" tooltip="Load Hive Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openModal()"><i class="fa fa-download"></i></a>
+                <a class="btn btn-xs btn-info" tooltip="Load Hive Table From Tree"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openTreeModal()"><i class="fa fa-download"></i></a>
+
                 <a class="btn btn-xs btn-success" tooltip="Refresh Tables" ng-click="aceSrcTbChanged()"><i class="fa fa-refresh"></i></a>
             </div>
         </div>
@@ -60,3 +62,28 @@
     <button class="btn btn-primary" ng-click="cancel()">Cancel</button>
   </div>
 </script>
+
+<script type="text/ng-template" id="addHiveTableFromTree.html">
+  <div class="modal-header"><button class="close" type="button" data-dismiss="modal" ng-click="cancel()">×</button>
+    <h4>Load Hive Table Metadata From Tree</h4>
+  </div>
+  <div class="modal-body">
+    <span><strong>Project: </strong>{{ $parent.projectName!=null?$parent.projectName:'NULL'}}</span>
+    <div class="form-group searchBox">
+      <input type="text" placeholder="Filter ..." class="nav-search-input" ng-model="predicate" />
+    </div>
+    <loading ng-if="!hiveLoaded" text="Loading Databases..."></loading>
+    <treecontrol class="tree-light check" tree-model="treedata" selected-nodes="selectedNodes" filter-expression="predicate" on-selection="showSelected(node)" on-node-toggle="showToggle(node)" options="treeOptions">
+      <div ng-if="node.label==''&&node.id==0"><img src="image/ajax-loader.gif">Loading Tables...</div>
+      <button class="btn btn-xs btn-primary" ng-if="node.label==''&&node.id==65535" ng-click="showMoreClicked($parentNode)">Show More</button>
+      <button class="btn btn-xs btn-primary" ng-if="node.label==''&&node.id==65535" ng-click="showAllClicked($parentNode)">Show All</button>
+      {{node.label}}
+    </treecontrol>
+  </div>
+
+  <div class="modal-footer">
+    <button class="btn btn-primary" ng-click="add()">Sync</button>
+    <button class="btn btn-primary" ng-click="cancel()">Cancel</button>
+  </div>
+
+</script>

http://git-wip-us.apache.org/repos/asf/kylin/blob/75970c3b/webapp/bower.json
----------------------------------------------------------------------
diff --git a/webapp/bower.json b/webapp/bower.json
index ff769b5..2c1ac44 100755
--- a/webapp/bower.json
+++ b/webapp/bower.json
@@ -13,7 +13,7 @@
     "font-awesome": "4.3.0",
     "angular-ui-ace": "0.1.0",
     "angular-base64": "2.0.1",
-    "angular-tree-control": "0.1.4",
+    "angular-tree-control": "0.2.8",
     "angularLocalStorage": "~0.3.0",
     "messenger": "1.4.1",
     "moment": "2.5.1",