You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@griffin.apache.org by gu...@apache.org on 2018/10/08 04:54:44 UTC

incubator-griffin git commit: [GRIFFIN-195] Don't list all tables from UI

Repository: incubator-griffin
Updated Branches:
  refs/heads/master cb74c3490 -> 73b495b56


[GRIFFIN-195] Don't list all tables from UI

[GRIFFIN-195]
Initially, load only list of databases and list of table names.
Table information is loaded only if user clicks on specific table.
![2018-10-07 14 19 47](https://user-images.githubusercontent.com/867294/46587155-d0849b00-ca3c-11e8-91e4-655a16db6421.gif)

[GRIFFIN-196]
Search by table names.
![2018-10-07 14 20 11](https://user-images.githubusercontent.com/867294/46587161-d4182200-ca3c-11e8-99a7-4a4a55ca62a8.gif)

Author: Nikolay Sokolov <ch...@gmail.com>

Closes #431 from chemikadze/GRIFFIN-193.


Project: http://git-wip-us.apache.org/repos/asf/incubator-griffin/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-griffin/commit/73b495b5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-griffin/tree/73b495b5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-griffin/diff/73b495b5

Branch: refs/heads/master
Commit: 73b495b56526320106b935bbeb561ecdca986eed
Parents: cb74c34
Author: Nikolay Sokolov <ch...@gmail.com>
Authored: Mon Oct 8 12:54:37 2018 +0800
Committer: Lionel Liu <bh...@163.com>
Committed: Mon Oct 8 12:54:37 2018 +0800

----------------------------------------------------------------------
 griffin-doc/dev/dev-env-build.md                |   6 +
 .../core/metastore/hive/HiveMetaStoreProxy.java |   7 +-
 .../hive/HiveMetaStoreServiceImpl.java          |   4 +-
 .../measure/create-measure/ac/ac.component.css  |   4 +
 .../measure/create-measure/ac/ac.component.html |  17 +-
 .../measure/create-measure/ac/ac.component.ts   | 185 +++++++++++++------
 .../create-measure/pr/step1/step1.component.css |   4 +
 .../pr/step1/step1.component.html               |   6 +-
 .../create-measure/pr/step1/step1.component.ts  | 139 +++++++++-----
 .../measure-detail/measure-detail.component.ts  |   2 +-
 ui/angular/src/app/service/service.service.ts   |  10 +-
 ui/angular/src/styles.css                       |  10 +-
 12 files changed, 277 insertions(+), 117 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/griffin-doc/dev/dev-env-build.md
----------------------------------------------------------------------
diff --git a/griffin-doc/dev/dev-env-build.md b/griffin-doc/dev/dev-env-build.md
index bdf6012..d92a735 100644
--- a/griffin-doc/dev/dev-env-build.md
+++ b/griffin-doc/dev/dev-env-build.md
@@ -66,6 +66,12 @@ public BACKEND_SERVER = 'http://localhost:8080';
 ```
 Making the change, you can start service module locally, and test your UI module using local service.
 
+After that, you can run local server using following command:
+```
+cd ui/angular 
+../angular/node_modules/.bin/ng serve --port 8080
+```
+
 ### For measure module
 If you only wanna develop the measure module, you can ignore both of service or UI module.
 You can test your measure JAR built in the docker container, using the existed spark environment.

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreProxy.java
----------------------------------------------------------------------
diff --git a/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreProxy.java b/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreProxy.java
index 079cede..adb71e9 100644
--- a/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreProxy.java
+++ b/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreProxy.java
@@ -23,6 +23,7 @@ import javax.annotation.PreDestroy;
 
 import org.apache.hadoop.hive.conf.HiveConf;
 import org.apache.hadoop.hive.metastore.HiveMetaStoreClient;
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
@@ -52,10 +53,10 @@ public class HiveMetaStoreProxy {
     @Value("${hive.hmshandler.retry.interval}")
     private String interval;
 
-    private HiveMetaStoreClient client = null;
+    private IMetaStoreClient client = null;
 
     @Bean
-    public HiveMetaStoreClient initHiveMetastoreClient() {
+    public IMetaStoreClient initHiveMetastoreClient() {
         HiveConf hiveConf = new HiveConf();
         hiveConf.set("hive.metastore.local", "false");
         hiveConf.setIntVar(HiveConf.ConfVars.METASTORETHRIFTCONNECTIONRETRIES,
@@ -64,7 +65,7 @@ public class HiveMetaStoreProxy {
         hiveConf.setIntVar(HiveConf.ConfVars.HMSHANDLERATTEMPTS, attempts);
         hiveConf.setVar(HiveConf.ConfVars.HMSHANDLERINTERVAL, interval);
         try {
-            client = new HiveMetaStoreClient(hiveConf);
+            client = HiveMetaStoreClient.newSynchronizedClient(new HiveMetaStoreClient(hiveConf));
         } catch (Exception e) {
             LOGGER.error("Failed to connect hive metastore. {}", e);
         }

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreServiceImpl.java
----------------------------------------------------------------------
diff --git a/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreServiceImpl.java b/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreServiceImpl.java
index f5014e7..72d8c56 100644
--- a/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreServiceImpl.java
+++ b/service/src/main/java/org/apache/griffin/core/metastore/hive/HiveMetaStoreServiceImpl.java
@@ -24,7 +24,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.hadoop.hive.metastore.HiveMetaStoreClient;
+import org.apache.hadoop.hive.metastore.IMetaStoreClient;
 import org.apache.hadoop.hive.metastore.api.Table;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,7 +46,7 @@ public class HiveMetaStoreServiceImpl implements HiveMetaStoreService {
             .getLogger(HiveMetaStoreService.class);
 
     @Autowired
-    private HiveMetaStoreClient client = null;
+    private IMetaStoreClient client = null;
 
     @Value("${hive.metastore.dbname}")
     private String defaultDbName;

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/app/measure/create-measure/ac/ac.component.css
----------------------------------------------------------------------
diff --git a/ui/angular/src/app/measure/create-measure/ac/ac.component.css b/ui/angular/src/app/measure/create-measure/ac/ac.component.css
index aa8c215..f88e8cc 100644
--- a/ui/angular/src/app/measure/create-measure/ac/ac.component.css
+++ b/ui/angular/src/app/measure/create-measure/ac/ac.component.css
@@ -127,3 +127,7 @@ div.tree > treenode > div::before {
 div.tree > treenode > div > .node-wrapper > treenodeexpander > .toggle-children-wrapper {
   left: 22px
 }
+
+.select-schema .search-query {
+  width: 100%;
+}

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/app/measure/create-measure/ac/ac.component.html
----------------------------------------------------------------------
diff --git a/ui/angular/src/app/measure/create-measure/ac/ac.component.html b/ui/angular/src/app/measure/create-measure/ac/ac.component.html
index 648ae7d..5124901 100644
--- a/ui/angular/src/app/measure/create-measure/ac/ac.component.html
+++ b/ui/angular/src/app/measure/create-measure/ac/ac.component.html
@@ -70,10 +70,14 @@ under the License.
         <label class="stepDesc">This step let you choose the single source of truth for data quality comparision with
           target. Currently you can only select the attributes from one schema</label>
         <div class="container-fluid">
-          <div class="col-md-4 col-lg-4 col-sm-4">
+          <div class="col-md-4 col-lg-4 col-sm-4 select-schema">
             <fieldset>
               <legend>Please select schema</legend>
-              <tree-root [nodes]="nodeList" [options]="options"></tree-root>
+              <!-- ac form has Next button inside the form, so keypress handler is required to intercept form submit -->
+              <!-- apparently this is not required in pr.step1, probably because buttons are outside the form -->
+              <input #srcSearchBox id="srcSearchBox" type="text" class="search-query" placeholder="Search..."
+                     (keypress)="onKeyPress($event, srcSearchBox.value, srcTree)">
+              <tree-root #srcTree [nodes]="nodeList" [options]="options"></tree-root>
             </fieldset>
           </div>
           <div class="col-md-8 col-lg-8 col-sm-8">
@@ -130,12 +134,16 @@ under the License.
       <div id="step-2" *ngIf="currentStep == 2" class="formStep">
         <label class="stepDesc">This step let you choose the target for data quality comparision with source</label>
         <div class="container-fluid">
-          <div class="col-md-4 col-lg-4 col-sm-4">
+          <div class="col-md-4 col-lg-4 col-sm-4 select-schema">
             <fieldset>
               <legend>Please select schema</legend>
-              <tree-root [nodes]="nodeListTarget" [options]="targetOptions"></tree-root>
+              <!-- see comment on #srcSearchBox -->
+              <input #tgtSearchBox id="tgtSearchBox" type="text" class="search-query" placeholder="Search..."
+                     (keypress)="onKeyPress($event, tgtSearchBox.value, tgtTree)">
+              <tree-root #tgtTree [nodes]="nodeListTarget" [options]="targetOptions"></tree-root>
             </fieldset>
           </div>
+          <form>
           <div class="col-md-8 col-lg-8 col-sm-8">
             <fieldset>
               <legend>
@@ -177,6 +185,7 @@ under the License.
               </div>
             </fieldset>
           </div>
+          </form>
           <div class="form-group btn-container">
             <button class="btn btn-primary btn-o back-step btn-wide pull-left" (click)="prev(Form)">
               <i class="fa fa-arrow-circle-left"></i> Back

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/app/measure/create-measure/ac/ac.component.ts
----------------------------------------------------------------------
diff --git a/ui/angular/src/app/measure/create-measure/ac/ac.component.ts b/ui/angular/src/app/measure/create-measure/ac/ac.component.ts
index 5987d1e..3007f34 100644
--- a/ui/angular/src/app/measure/create-measure/ac/ac.component.ts
+++ b/ui/angular/src/app/measure/create-measure/ac/ac.component.ts
@@ -20,12 +20,13 @@ import {Component, OnInit, AfterViewChecked, ViewChild} from "@angular/core";
 import {FormControl} from "@angular/forms";
 import {FormsModule, Validator} from "@angular/forms";
 import {ServiceService} from "../../../service/service.service";
-import {TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions} from "angular-tree-component";
+import {TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions, TreeComponent} from "angular-tree-component";
 import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
 import {ToasterModule, ToasterService} from "angular2-toaster";
 import * as $ from "jquery";
 import {Router} from "@angular/router";
-import {HttpClient} from "@angular/common/http";
+import {HttpClient, HttpParams} from "@angular/common/http";
+import {ITreeNode} from "angular-tree-component/dist/defs/api";
 
 class node {
   name: string;
@@ -236,6 +237,12 @@ export class AcComponent implements OnInit, AfterViewChecked {
   public visible = false;
   public visibleAnimate = false;
 
+  @ViewChild("srcTree")
+  private tree: TreeComponent;
+
+  @ViewChild("tgtTree")
+  private tgtTree: TreeComponent;
+
   public hide(): void {
     this.visibleAnimate = false;
     setTimeout(() => (this.visible = false), 300);
@@ -534,17 +541,8 @@ export class AcComponent implements OnInit, AfterViewChecked {
             this.selectedAll = false;
             this.schemaCollection = [];
             TREE_ACTIONS.TOGGLE_EXPANDED(tree, node, $event);
-          } else if (node.data.cols) {
-            this.currentTable = node.data.name;
-            this.currentDB = node.data.parent;
-            this.schemaCollection = node.data.cols;
-            this.src_location = node.data.location;
-            this.src_name = "source" + new Date().getTime();
-            this.selectedAll = false;
-            this.selection = [];
-            for (let row of this.schemaCollection) {
-              row.selected = false;
-            }
+          } else if (node.data.cols !== undefined) {
+            this.onTableNodeClick(node, this.setSrcTable.bind(this));
           }
         }
       }
@@ -569,17 +567,8 @@ export class AcComponent implements OnInit, AfterViewChecked {
             this.selectionTarget = [];
             this.schemaCollectionTarget = [];
             TREE_ACTIONS.TOGGLE_EXPANDED(tree, node, $event);
-          } else if (node.data.cols) {
-            this.currentTableTarget = node.data.name;
-            this.currentDBTarget = node.data.parent;
-            this.schemaCollectionTarget = node.data.cols;
-            this.tgt_location = node.data.location;
-            this.tgt_name = "target" + new Date().getTime();
-            this.selectedAllTarget = false;
-            this.selectionTarget = [];
-            for (let row of this.schemaCollectionTarget) {
-              row.selected = false;
-            }
+          } else if (node.data.cols !== undefined) {
+            this.onTableNodeClick(node, this.setTargetTable.bind(this));
           }
         }
       }
@@ -655,39 +644,48 @@ export class AcComponent implements OnInit, AfterViewChecked {
   }
 
   ngOnInit() {
-    var allDataassets = this.serviceService.config.uri.dataassetlist;
-    this.http.get(allDataassets).subscribe(data => {
+    let allDatabases = this.serviceService.config.uri.dblist;
+    let getTableNames = this.serviceService.config.uri.tablenames;
+
+    this.http.get(allDatabases).subscribe((databases: Array<string>) => {
       this.nodeList = new Array();
+      this.nodeListTarget = this.nodeList;  // share same model instead of copying(?)
       let i = 1;
-      this.data = data;
-      for (let db in this.data) {
-        let new_node = new node();
-        new_node.name = db;
-        new_node.id = i;
-        new_node.isExpanded = true;
-        i++;
-        new_node.children = new Array();
-        for (let i = 0; i < this.data[db].length; i++) {
-          let new_child = new node();
-          new_child.name = this.data[db][i]["tableName"];
-          new_node.children.push(new_child);
-          new_child.isExpanded = false;
-          new_child.location = this.data[db][i]["sd"]["location"];
-          new_child.parent = db;
-          new_child.cols = Array<Col>();
-          for (let j = 0; j < this.data[db][i]["sd"]["cols"].length; j++) {
-            let new_col = new Col(
-              this.data[db][i]["sd"]["cols"][j].name,
-              this.data[db][i]["sd"]["cols"][j].type,
-              this.data[db][i]["sd"]["cols"][j].comment,
-              false
-            );
-            new_child.cols.push(new_col);
-          }
-        }
-        this.nodeList.push(new_node);
+      let pending = databases.length;
+      if (databases.length > 10) {
+        this.options.animateExpand = false;
+        this.updateTrees();
+      }
+      for (let dbName of databases) {
+        let dbNode = new node();
+        dbNode.name = dbName;
+        dbNode.id = i++;
+        dbNode.isExpanded = false;
+        dbNode.children = new Array();
+        let params = new HttpParams({fromString: "db="+dbName});
+        this.http.get(getTableNames, {params: params}).subscribe((tables: Array<string>) => {
+            for (let tableName of tables) {
+              let tableNode = new node();
+              tableNode.name = tableName;
+              dbNode.children.push(tableNode);
+              tableNode.isExpanded = true;
+              tableNode.location = null;
+              tableNode.parent = dbName;
+              tableNode.cols = null;
+            }
+            pending -= 1;
+            if (pending == 0) {
+              this.updateTrees();
+            }
+          },
+          () => {
+            pending -= 1;
+            if (pending == 0) {
+              this.updateTrees();
+            }
+          });
+        this.nodeList.push(dbNode);
       }
-      this.nodeListTarget = JSON.parse(JSON.stringify(this.nodeList));
     });
     this.src_size = "1day";
     this.tgt_size = "1day";
@@ -695,6 +693,85 @@ export class AcComponent implements OnInit, AfterViewChecked {
     this.tgt_timezone = this.tgtconfig.timezone;
   }
 
+  updateTrees() {
+    if (this.currentStep == 1) {
+      this.tree.treeModel.update();
+    } else if (this.currentStep == 2) {
+      this.tgtTree.treeModel.update();
+    }
+  }
+
+  onTableNodeClick(treeNode: ITreeNode, callback) {
+    let node: node = treeNode.data;
+    if (node.cols == null) {
+      let getTable = this.serviceService.config.uri.dbtable;
+      let dbName = node.parent;
+      let tableName = node.name;
+      let params = new HttpParams({fromString: "db="+dbName+"&table="+tableName});
+      this.http.get(getTable, {params: params}).subscribe(data => {
+        node.location = data["sd"]["location"];
+        node.cols = Array<Col>();
+        for (let j = 0; j < data["sd"]["cols"].length; j++) {
+          let new_col = new Col(
+            data["sd"]["cols"][j].name,
+            data["sd"]["cols"][j].type,
+            data["sd"]["cols"][j].comment,
+            false
+          );
+          node.cols.push(new_col);
+        }
+        callback(treeNode);
+      })
+    } else {
+      callback(treeNode);
+    }
+  }
+
+  setSrcTable(node: ITreeNode) {
+    this.currentTable = node.data.name;
+    this.currentDB = node.data.parent;
+    this.schemaCollection = node.data.cols;
+    this.src_location = node.data.location;
+    this.src_name = "source" + new Date().getTime();
+    this.selectedAll = false;
+    this.selection = [];
+    for (let row of this.schemaCollection) {
+      row.selected = false;
+    }
+  }
+
+  setTargetTable(node: ITreeNode) {
+    this.currentTableTarget = node.data.name;
+    this.currentDBTarget = node.data.parent;
+    this.schemaCollectionTarget = node.data.cols;
+    this.tgt_location = node.data.location;
+    this.tgt_name = "target" + new Date().getTime();
+    this.selectedAllTarget = false;
+    this.selectionTarget = [];
+    for (let row of this.schemaCollectionTarget) {
+      row.selected = false;
+    }
+  }
+
+  onKeyPress(event, query, tree) {
+    if (event.keyCode == 13) {
+      event.preventDefault();
+      this.onSearch(query, tree);
+    }
+  }
+
+  onSearch(query, tree) {
+    tree.treeModel.filterNodes((node) => {
+      let name;
+      if (node.data.children !== undefined) {
+        name = node.data.name + ".";
+      } else {
+        name = node.parent.data.name + "." + node.data.name;
+      }
+      return name.indexOf(query) >= 0;
+    });
+  }
+
   ngAfterViewChecked() {
     this.resizeWindow();
   }

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css
----------------------------------------------------------------------
diff --git a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css
index da8dfb0..63575ae 100644
--- a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css
+++ b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.css
@@ -138,3 +138,7 @@ table > tbody > tr > td, .table > thead > tr > th {
   word-wrap: break-word;
   min-width: 80px;
 }
+
+.select-schema .search-query {
+  width: 100%;
+}

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html
----------------------------------------------------------------------
diff --git a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html
index 3ec2a1e..827c680 100644
--- a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html
+++ b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.html
@@ -21,9 +21,11 @@ under the License.
     <label class="stepDesc">This step lets you select the single source of truth for data quality comparision with a
       target source.</label>
     <div class="container-fluid">
-      <div class="col-md-4 col-lg-4 col-sm-4">
+      <div class="col-md-4 col-lg-4 col-sm-4 select-schema">
         <fieldset>
           <legend>Please select schema</legend>
+          <input #schemaSearchBox id="schemaSearchBox" type="text" class="search-query" placeholder="Search..."
+                 (keyup.enter)="onSearch(schemaSearchBox.value)">
           <tree-root [nodes]="step1.nodeList" [options]="options"></tree-root>
         </fieldset>
       </div>
@@ -35,7 +37,7 @@ under the License.
           <div class="y-scrollable">
             <div style="margin-top:10px;">
               <label>View schema:</label>
-              <i style="color:#fff;font-weight: bold;">{{step1.currentDBstr}}{{step1.currentTable}}
+              <i style="color:#fff;font-weight: bold;">{{step1.currentDBStr}}{{step1.currentTable}}
               </i>
             </div>
             <div style="margin-top:5px;">

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.ts
----------------------------------------------------------------------
diff --git a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.ts b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.ts
index 5c1a450..a5b052e 100644
--- a/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.ts
+++ b/ui/angular/src/app/measure/create-measure/pr/step1/step1.component.ts
@@ -16,12 +16,13 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 */
-import {Component, OnInit, Input, Output, EventEmitter} from "@angular/core";
+import {Component, OnInit, Input, Output, EventEmitter, ViewChild} from "@angular/core";
 import {ServiceService} from "../../../../service/service.service";
-import {TREE_ACTIONS, ITreeOptions} from "angular-tree-component";
-import {HttpClient} from "@angular/common/http";
+import {TREE_ACTIONS, ITreeOptions, TreeComponent} from "angular-tree-component";
+import {HttpClient, HttpParams} from "@angular/common/http";
 import {AfterViewChecked, ElementRef} from "@angular/core";
 import {ProfilingStep1} from "../pr.component";
+import {ITreeNode} from "angular-tree-component/dist/defs/api";
 
 export class node {
   name: string;
@@ -80,6 +81,9 @@ export class PrStep1Component implements AfterViewChecked, OnInit {
   @Input() step1: ProfilingStep1;
   @Output() nextStep: EventEmitter<Object> = new EventEmitter<Object>();
 
+  @ViewChild(TreeComponent)
+  private tree: TreeComponent;
+
   options: ITreeOptions = {
     displayField: "name",
     isExpandedField: "expanded",
@@ -94,17 +98,8 @@ export class PrStep1Component implements AfterViewChecked, OnInit {
             this.step1.schemaCollection = [];
             this.selectedAll = false;
             TREE_ACTIONS.TOGGLE_EXPANDED(tree, node, $event);
-          } else if (node.data.cols) {
-            this.step1.currentTable = node.data.name;
-            this.step1.currentDB = node.data.parent;
-            this.step1.schemaCollection = node.data.cols;
-            this.step1.srcname = "source" + new Date().getTime();
-            this.step1.srclocation = node.data.location;
-            this.selectedAll = false;
-            this.step1.selection = [];
-            for (let row of this.step1.schemaCollection) {
-              row.selected = false;
-            }
+          } else if (node.data.cols !== undefined) {
+            this.onTableNodeClick(node);
           }
         }
       }
@@ -193,43 +188,103 @@ export class PrStep1Component implements AfterViewChecked, OnInit {
 
   ngOnInit() {
     if (this.step1.nodeList.length !== 0) return;
+    let allDatabases = this.serviceService.config.uri.dblist;
+    let getTableNames = this.serviceService.config.uri.tablenames;
 
-    let allDataassets = this.serviceService.config.uri.dataassetlist;
-
-    this.http.get(allDataassets).subscribe(data => {
+    this.http.get(allDatabases).subscribe((databases: Array<string>) => {
       this.step1.nodeList = new Array();
       let i = 1;
-      this.step1.data = data;
-      for (let db in this.step1.data) {
-        let new_node = new node();
-        new_node.name = db;
-        new_node.id = i;
-        new_node.isExpanded = true;
-        i++;
-        new_node.children = new Array();
-        for (let i = 0; i < this.step1.data[db].length; i++) {
-          let new_child = new node();
-          new_child.name = this.step1.data[db][i]["tableName"];
-          new_node.children.push(new_child);
-          new_child.isExpanded = false;
-          new_child.location = this.step1.data[db][i]["sd"]["location"];
-          new_child.parent = db;
-          new_child.cols = Array<Col>();
-          for (let j = 0; j < this.step1.data[db][i]["sd"]["cols"].length; j++) {
-            let new_col = new Col(
-              this.step1.data[db][i]["sd"]["cols"][j].name,
-              this.step1.data[db][i]["sd"]["cols"][j].type,
-              this.step1.data[db][i]["sd"]["cols"][j].comment,
-              false
-            );
-            new_child.cols.push(new_col);
+      let pending = databases.length;
+      if (databases.length > 10) {
+        this.options.animateExpand = false;
+        this.tree.treeModel.update();
+      }
+      for (let dbName of databases) {
+        let dbNode = new node();
+        dbNode.name = dbName;
+        dbNode.id = i++;
+        dbNode.isExpanded = false;
+        dbNode.children = new Array();
+        let params = new HttpParams({fromString: "db="+dbName});
+        this.http.get(getTableNames, {params: params}).subscribe((tables: Array<string>) => {
+          for (let tableName of tables) {
+            let tableNode = new node();
+            tableNode.name = tableName;
+            dbNode.children.push(tableNode);
+            tableNode.isExpanded = true;
+            tableNode.location = null;
+            tableNode.parent = dbName;
+            tableNode.cols = null;
+          }
+          pending -= 1;
+          if (pending == 0) {
+            this.tree.treeModel.update();
           }
+        },
+        () => {
+          pending -= 1;
+          if (pending == 0) {
+            this.tree.treeModel.update();
+          }
+        });
+        this.step1.nodeList.push(dbNode);
+      }
+    });
+  }
+
+  onTableNodeClick(treeNode: ITreeNode) {
+    let node: node = treeNode.data;
+    if (node.cols == null) {
+      let getTable = this.serviceService.config.uri.dbtable;
+      let dbName = node.parent;
+      let tableName = node.name;
+      let params = new HttpParams({fromString: "db="+dbName+"&table="+tableName});
+      this.http.get(getTable, {params: params}).subscribe(data => {
+        node.location = data["sd"]["location"];
+        node.cols = Array<Col>();
+        for (let j = 0; j < data["sd"]["cols"].length; j++) {
+          let new_col = new Col(
+            data["sd"]["cols"][j].name,
+            data["sd"]["cols"][j].type,
+            data["sd"]["cols"][j].comment,
+            false
+          );
+          node.cols.push(new_col);
         }
-        this.step1.nodeList.push(new_node);
+        this.setCurrentTable(treeNode);
+      })
+    } else {
+      this.setCurrentTable(treeNode);
+    }
+  }
+
+  onSearch(query) {
+    this.tree.treeModel.filterNodes((node) => {
+      let name;
+      if (node.data.children !== undefined) {
+        name = node.data.name + ".";
+      } else {
+        name = node.parent.data.name + "." + node.data.name;
       }
+      return name.indexOf(query) >= 0;
     });
   }
 
+  setCurrentTable(treeNode: ITreeNode) {
+    let node: node = treeNode.data;
+    this.step1.currentTable = node.name;
+    this.step1.currentDB = treeNode.parent.data.name;
+    this.step1.currentDBStr = this.step1.currentDB + ".";
+    this.step1.schemaCollection = node.cols;
+    this.step1.srcname = "source" + new Date().getTime();
+    this.step1.srclocation = node.location;
+    this.selectedAll = false;
+    this.step1.selection = [];
+    for (let row of this.step1.schemaCollection) {
+      row.selected = false;
+    }
+  }
+
   nextChildStep() {
     this.nextStep.emit(this.step1);
   }

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/app/measure/measure-detail/measure-detail.component.ts
----------------------------------------------------------------------
diff --git a/ui/angular/src/app/measure/measure-detail/measure-detail.component.ts b/ui/angular/src/app/measure/measure-detail/measure-detail.component.ts
index b19636b..8e10f0e 100644
--- a/ui/angular/src/app/measure/measure-detail/measure-detail.component.ts
+++ b/ui/angular/src/app/measure/measure-detail/measure-detail.component.ts
@@ -96,7 +96,7 @@ export class MeasureDetailComponent implements OnInit {
             this.ruleDes = this.ruleData["rule.description"].details
           }
           this.fetchData("source", 0);
-          if (this.ruleData.type === "ACCURACY") {
+          if (this.ruleData.type.toLowerCase() === "accuracy") {
             this.fetchData("target", 1);
           } else {
             this.targetDB = "";

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/app/service/service.service.ts
----------------------------------------------------------------------
diff --git a/ui/angular/src/app/service/service.service.ts b/ui/angular/src/app/service/service.service.ts
index d64c99c..0fa4b6f 100644
--- a/ui/angular/src/app/service/service.service.ts
+++ b/ui/angular/src/app/service/service.service.ts
@@ -34,10 +34,12 @@ export class ServiceService {
       base: this.BACKEND_SERVER + this.API_ROOT_PATH,
 
       login: this.BACKEND_SERVER + this.API_ROOT_PATH + "/login/authenticate",
-      dbtree:
-      this.BACKEND_SERVER + this.API_ROOT_PATH + "/metadata/hive/dbs/tables",
-      dataassetlist:
-      this.BACKEND_SERVER + this.API_ROOT_PATH + "/metadata/hive/dbs/tables",
+      
+      dbtree: this.BACKEND_SERVER + this.API_ROOT_PATH + "/metadata/hive/dbs/tables",
+      dataassetlist: this.BACKEND_SERVER + this.API_ROOT_PATH + "/metadata/hive/dbs/tables",
+      dblist: this.BACKEND_SERVER + this.API_ROOT_PATH + "/metadata/hive/dbs",
+      tablenames: this.BACKEND_SERVER + this.API_ROOT_PATH + "/metadata/hive/tables/names", // ?db=...
+      dbtable: this.BACKEND_SERVER + this.API_ROOT_PATH + "/metadata/hive/table", // ?db=...&table=...
 
       getdataasset: this.BACKEND_SERVER + this.API_ROOT_PATH + "/dataassets",
 

http://git-wip-us.apache.org/repos/asf/incubator-griffin/blob/73b495b5/ui/angular/src/styles.css
----------------------------------------------------------------------
diff --git a/ui/angular/src/styles.css b/ui/angular/src/styles.css
index e84cde3..3bbaf1b 100644
--- a/ui/angular/src/styles.css
+++ b/ui/angular/src/styles.css
@@ -875,7 +875,7 @@ a:hover {
   margin-bottom: 0;
 }
 
-.navbar-search .search-query {
+.search-query {
   padding: 4px 9px;
   font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
   font-size: 13px;
@@ -895,20 +895,20 @@ a:hover {
   transition: none;
 }
 
-.navbar-search .search-query:disabled {
+.search-query:disabled {
   background-color: #626262;
   cursor: not-allowed;
 }
 
-.navbar-search .search-query:-moz-placeholder {
+.search-query:-moz-placeholder {
   color: #cccccc;
 }
 
-.navbar-search .search-query::-webkit-input-placeholder {
+.search-query::-webkit-input-placeholder {
   color: #cccccc;
 }
 
-.navbar-search .search-query:focus, .navbar-search .search-query.focused {
+.search-query:focus, .search-query.focused {
   padding: 5px 10px;
   color: #333333;
   text-shadow: 0 1px 0 #ffffff;