You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by zh...@apache.org on 2015/09/23 10:40:30 UTC

incubator-kylin git commit: KYLIN-1042, [Data Source]support create Table Schema from Streaming Source Record

Repository: incubator-kylin
Updated Branches:
  refs/heads/2.x-staging cc15d63f0 -> 1ea587081


KYLIN-1042, [Data Source]support create Table Schema from Streaming Source Record


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

Branch: refs/heads/2.x-staging
Commit: 1ea587081743fb5eec00760a494edce0a606b39b
Parents: cc15d63
Author: jiazhong <ji...@ebay.com>
Authored: Fri Sep 18 18:35:30 2015 +0800
Committer: jiazhong <ji...@ebay.com>
Committed: Wed Sep 23 16:37:11 2015 +0800

----------------------------------------------------------------------
 .../kylin/rest/controller/TableController.java  |  27 +-
 .../kylin/rest/request/StreamingRequest.java    |  43 ++
 webapp/app/js/controllers/sourceMeta.js         | 209 ++++++++-
 webapp/app/js/model/tableConfig.js              |   5 +-
 webapp/app/js/services/tables.js                |   1 +
 webapp/app/less/app.less                        |   5 +
 webapp/app/less/component.less                  |   4 +
 webapp/app/partials/cubeDesigner/info.html      |   4 +-
 .../app/partials/modelDesigner/data_model.html  |   4 +-
 .../app/partials/modelDesigner/model_info.html  |   4 +-
 webapp/app/partials/models/models_tree.html     |   5 +
 .../app/partials/tables/source_table_tree.html  |   2 +-
 webapp/app/partials/tables/table_detail.html    | 439 ++++++++++++-------
 13 files changed, 570 insertions(+), 182 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/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 133b5ad..39af7db 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
@@ -19,19 +19,18 @@
 package org.apache.kylin.rest.controller;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.JsonUtil;
 import org.apache.kylin.metadata.MetadataConstants;
+import org.apache.kylin.metadata.MetadataManager;
 import org.apache.kylin.metadata.model.ColumnDesc;
 import org.apache.kylin.metadata.model.TableDesc;
 import org.apache.kylin.rest.exception.InternalErrorException;
 import org.apache.kylin.rest.request.CardinalityRequest;
+import org.apache.kylin.rest.request.StreamingRequest;
 import org.apache.kylin.rest.response.TableDescResponse;
 import org.apache.kylin.rest.service.CubeService;
 import org.slf4j.Logger;
@@ -129,6 +128,22 @@ public class TableController extends BasicController {
         return result;
     }
 
+
+    @RequestMapping(value = "/addStreamingSrc", method = { RequestMethod.POST })
+    @ResponseBody
+    public Map<String, String> addStreamingTable(@RequestBody StreamingRequest request) throws IOException {
+        Map<String, String> result = new HashMap<String, String>();
+        String project = request.getProject();
+        TableDesc desc = JsonUtil.readValue(request.getTableData(),TableDesc.class);
+        desc.setUuid(UUID.randomUUID().toString());
+        MetadataManager metaMgr = MetadataManager.getInstance(KylinConfig.getInstanceFromEnv());
+        metaMgr.saveSourceTable(desc);
+        cubeMgmtService.syncTableToProject(new String[]{desc.getName()},project);
+        result.put("success","true");
+        return result;
+    }
+
+
     /**
      * Regenerate table cardinality
      *

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/server/src/main/java/org/apache/kylin/rest/request/StreamingRequest.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/request/StreamingRequest.java b/server/src/main/java/org/apache/kylin/rest/request/StreamingRequest.java
new file mode 100644
index 0000000..3bcf9d7
--- /dev/null
+++ b/server/src/main/java/org/apache/kylin/rest/request/StreamingRequest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+
+package org.apache.kylin.rest.request;
+
+import java.lang.String;public class StreamingRequest {
+
+    private String project;
+
+    private String tableData;
+
+    public String getProject() {
+        return project;
+    }
+
+    public void setProject(String project) {
+        this.project = project;
+    }
+
+    public String getTableData() {
+        return tableData;
+    }
+
+    public void setTableData(String tableData) {
+        this.tableData = tableData;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/js/controllers/sourceMeta.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/sourceMeta.js b/webapp/app/js/controllers/sourceMeta.js
index 942c079..9d405dc 100755
--- a/webapp/app/js/controllers/sourceMeta.js
+++ b/webapp/app/js/controllers/sourceMeta.js
@@ -152,12 +152,211 @@ KylinApp
         })
       }
     };
-    $scope.trimType = function (typeName) {
-      if (typeName.match(/VARCHAR/i)) {
-        typeName = "VARCHAR";
+
+
+    //streaming model
+    $scope.openStreamingSourceModal = function () {
+      $modal.open({
+        templateUrl: 'addStreamingSource.html',
+        controller: StreamingSourceCtrl,
+        resolve: {
+          tableNames: function () {
+            return $scope.tableNames;
+          },
+          projectName: function () {
+            return $scope.projectModel.selectedProject;
+          },
+          scope: function () {
+            return $scope;
+          }
+        }
+      });
+    };
+
+    var StreamingSourceCtrl = function ($scope, $location, $modalInstance, tableNames, MessageService, projectName, scope, tableConfig) {
+      $scope.streamingPrefix = "STREAMING_";
+      $scope.projectName = projectName;
+      $scope.tableConfig = tableConfig;
+      $scope.streaming = {
+        sourceSchema: '',
+        'parseResult': {}
       }
 
-      return typeName.trim().toLowerCase();
-    }
+      $scope.table = {
+        name: '',
+        sourceValid:false
+      }
+
+      $scope.cancel = function () {
+        $modalInstance.dismiss('cancel');
+      };
+
+      $scope.streamingOnLoad = function () {
+        console.log($scope.streaming.sourceSchema);
+      }
+
+      $scope.columnList = [];
+
+      $scope.streamingOnChange = function () {
+        console.log($scope.streaming.sourceSchema);
+        try {
+          $scope.streaming.parseResult = JSON.parse($scope.streaming.sourceSchema);
+        } catch (error) {
+          $scope.table.sourceValid = false;
+          console.log(error);
+          return;
+        }
+        $scope.table.sourceValid = true;
+        var columnList = [];
+        for (var key in $scope.streaming.parseResult) {
+          var defaultType="varchar(256)";
+          var _value = $scope.streaming.parseResult[key];
+          var defaultChecked = "Y";
+          if(typeof _value ==="string"){
+            defaultType="varchar(256)";
+          }else if(typeof _value ==="number"){
+            if(_value <= 2147483647){
+              if(_value.toString().indexOf(".")!=-1){
+                defaultType="decimal";
+              }else{
+                defaultType="int";
+              }
+            }else{
+              defaultType="timestamp";
+            }
+          }
+          if(defaultType=="timestamp"){
+            defaultChecked = "N";
+          }
+          columnList.push({
+            'name': key,
+            'checked': defaultChecked,
+            'type': defaultType,
+            'fromSource':'Y'
+          });
+
+
+
+          //var formatList = [];
+          //var
+          columnList = _.sortBy(columnList, function (i) { return i.type; });
+        }
+
+          var timeMeasure = ['year_start','month_start','day_start','hour_start','min_start'];
+          for(var i = 0;i<timeMeasure.length;i++){
+            var defaultCheck = 'Y';
+            if(timeMeasure[i]=='min_start'){
+              defaultCheck = 'N';
+            }
+            columnList.push({
+              'name': timeMeasure[i],
+              'checked': defaultCheck,
+              'type': 'timestamp',
+              'fromSource':'N'
+            });
+          }
+
+        if($scope.columnList.length==0){
+          $scope.columnList = columnList;
+        }
+
+        angular.forEach(columnList,function(item){
+          var included = false;
+          for(var i=0;i<$scope.columnList.length;i++){
+            if($scope.columnList[i].name==item.name){
+              included = true;
+              break;
+            }
+          }
+          if(!included){
+            $scope.columnList.push(item);
+          }
+        })
+
+      }
+
+      $scope.form={};
+      $scope.rule={
+        'timestampColumnConflict':false
+      }
+      $scope.syncStreamingSchema = function () {
+        $scope.form['setStreamingSchema'].$sbumitted = true;
+        if(!$scope.streaming.sourceSchema||$scope.streaming.sourceSchema===""){
+          return;
+        }
+
+        if(!$scope.table.name||$scope.table.name===""){
+          return;
+        }
+
+        var timestampCount = 0;
+        angular.forEach($scope.columnList,function(item){
+          if(item.checked == "Y"&&item.type=="timestamp"&&item.fromSource=='Y'){
+            timestampCount++;
+          }
+        })
+
+        if(timestampCount!=1){
+          $scope.rule.timestampColumnConflict = true;
+          return;
+        }
+
+        var columns = [];
+        angular.forEach($scope.columnList,function(column,$index){
+          if (column.checked == "Y") {
+            var columnInstance = {
+              "id": ++$index,
+              "name": column.name,
+              "datatype": column.type
+            }
+            columns.push(columnInstance);
+          }
+        })
+
+
+        $scope.tableData = {
+          "name": $scope.streamingPrefix+$scope.table.name,
+          "columns": columns,
+          'database':'Default'
+        }
+
+        SweetAlert.swal({
+          title: '',
+          text: 'Are you sure to create the streaming table info?',
+          type: '',
+          showCancelButton: true,
+          confirmButtonColor: '#DD6B55',
+          confirmButtonText: "Yes",
+          closeOnConfirm: true
+        }, function (isConfirm) {
+          if (isConfirm) {
+            loadingRequest.show();
+            TableService.addStreamingSrc({}, {project: $scope.projectName,tableData:angular.toJson($scope.tableData)}, function (request) {
+              if(request.success){
+                loadingRequest.hide();
+                SweetAlert.swal('', 'Create Streaming Table Schema Successfully.', 'success');
+                $scope.cancel();
+                scope.aceSrcTbLoaded(true);
+                return;
+              }else{
+                SweetAlert.swal('Oops...', "Failed to take action.", 'error');
+              }
+              //end loading
+              loadingRequest.hide();
+            }, function (e) {
+              if (e.data && e.data.exception) {
+                var message = e.data.exception;
+                var msg = !!(message) ? message : 'Failed to take action.';
+                SweetAlert.swal('Oops...', msg, 'error');
+              } else {
+                SweetAlert.swal('Oops...', "Failed to take action.", 'error');
+              }
+              loadingRequest.hide();
+            });
+          }
+        });
+      }
+    };
+
   });
 

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/js/model/tableConfig.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/model/tableConfig.js b/webapp/app/js/model/tableConfig.js
index f994b09..3dd7b25 100644
--- a/webapp/app/js/model/tableConfig.js
+++ b/webapp/app/js/model/tableConfig.js
@@ -22,6 +22,9 @@ KylinApp.constant('tableConfig', {
     {attr: 'name', name: 'Name'},
     {attr: 'datatype', name: 'Data Type'},
     {attr: 'cardinality', name: 'Cardinality'}
-  ]
+  ],
+  dataTypes:["tinyint","smallint","int","bigint","float","double","decimal","timestamp","date","string","varchar(256)","char","boolean","binary"]
+
+
 
 });

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/js/services/tables.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/tables.js b/webapp/app/js/services/tables.js
index 1f1f15a..3b5e9f4 100755
--- a/webapp/app/js/services/tables.js
+++ b/webapp/app/js/services/tables.js
@@ -23,6 +23,7 @@ KylinApp.factory('TableService', ['$resource', function ($resource, config) {
     getExd: {method: 'GET', params: {action: 'exd-map'}, isArray: false},
     reload: {method: 'PUT', params: {action: 'reload'}, isArray: false},
     loadHiveTable: {method: 'POST', params: {}, isArray: false},
+    addStreamingSrc: {method: 'POST', params: {action:'addStreamingSrc'}, isArray: false},
     genCardinality: {method: 'PUT', params: {action: 'cardinality'}, isArray: false}
   });
 }]);

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/less/app.less
----------------------------------------------------------------------
diff --git a/webapp/app/less/app.less b/webapp/app/less/app.less
index a7651fe..fc80698 100644
--- a/webapp/app/less/app.less
+++ b/webapp/app/less/app.less
@@ -661,3 +661,8 @@ ul.messenger .messenger-message-inner,.ngCellText {
   -o-transition: width 0.5s;;
   transition: width 0.5s;;
 }
+
+.form-group.required .control-label:after {
+  content:"*";
+  color:red;
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/less/component.less
----------------------------------------------------------------------
diff --git a/webapp/app/less/component.less b/webapp/app/less/component.less
index a160752..929006f 100644
--- a/webapp/app/less/component.less
+++ b/webapp/app/less/component.less
@@ -996,3 +996,7 @@ ul.abn-tree li.abn-tree-row a{
 .sweet-alert .lead.text-muted{
   word-break:break-all;
 }
+
+.modal-body.streaming-source .ace_editor{
+  height: 600px !important;
+}

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/partials/cubeDesigner/info.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/cubeDesigner/info.html b/webapp/app/partials/cubeDesigner/info.html
index 1684516..f680487 100644
--- a/webapp/app/partials/cubeDesigner/info.html
+++ b/webapp/app/partials/cubeDesigner/info.html
@@ -20,7 +20,7 @@
     <div class="col-xs-8">
         <ng-form name="forms.cube_info_form" novalidate>
             <!--Project-->
-            <div class="form-group">
+            <div class="form-group required">
                 <div class="row">
                   <label class="col-xs-12 col-sm-3 control-label no-padding-right">
                     <b>Model Name</b>
@@ -40,7 +40,7 @@
             </div>
 
             <!--Cube Name-->
-            <div class="form-group">
+            <div class="form-group required">
                 <div class="row">
                     <label class="col-xs-12 col-sm-3 control-label no-padding-right font-color-default">
                         <b>Cube Name</b>

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/partials/modelDesigner/data_model.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/modelDesigner/data_model.html b/webapp/app/partials/modelDesigner/data_model.html
index 60cbacb..e100287 100644
--- a/webapp/app/partials/modelDesigner/data_model.html
+++ b/webapp/app/partials/modelDesigner/data_model.html
@@ -20,9 +20,9 @@
     <ng-form name="forms.data_model_form">
 
     <!-- Fact Table Name -->
-    <div class="form-group">
+    <div class="form-group required">
         <div class="row">
-            <label class="col-xs-12 col-sm-2 concube.detailtrol-label no-padding-right font-color-default">
+            <label class="col-xs-12 col-sm-2 control-label concube.detailtrol-label no-padding-right font-color-default">
                 <b>Fact Table</b>
             </label>
             <div class="col-xs-12 col-sm-6" ng-class="{'has-error':forms.data_model_form.innerform.typeahead.$invalid && (forms.data_model_form.innerform.typeahead.$dirty||forms.data_model_form.$sbumitted)}">

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/partials/modelDesigner/model_info.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/modelDesigner/model_info.html b/webapp/app/partials/modelDesigner/model_info.html
index 18fac09..aed4325 100644
--- a/webapp/app/partials/modelDesigner/model_info.html
+++ b/webapp/app/partials/modelDesigner/model_info.html
@@ -22,7 +22,7 @@
         <ng-form name="forms.model_info_form" novalidate>
 
             <!--Model Name-->
-            <div class="form-group">
+            <div class="form-group required">
                 <div class="row">
                     <label class="col-xs-12 col-sm-3 control-label no-padding-right font-color-default">
                         <b>Model Name</b>
@@ -96,4 +96,4 @@
         </div><!-- /.box-body -->
     </div>
     </div>
-</div>
\ No newline at end of file
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/partials/models/models_tree.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/models/models_tree.html b/webapp/app/partials/models/models_tree.html
index b04d481..722d65f 100644
--- a/webapp/app/partials/models/models_tree.html
+++ b/webapp/app/partials/models/models_tree.html
@@ -43,6 +43,11 @@
           <li ng-if="userService.hasRole('ROLE_ADMIN')">
             <a href="models/add"  ng-if="userService.hasRole('ROLE_MODELER')"><i class="fa fa-star"></i> New Model</a>
           </li>
+
+          <li ng-if="userService.hasRole('ROLE_ADMIN')">
+            <a href="streaming/add"  ng-if="userService.hasRole('ROLE_MODELER')"><i class="fa fa-area-chart"></i>New Streaming</a>
+          </li>
+
         </ul>
       </div>
 

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/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 2e5de36..ff95d10 100755
--- a/webapp/app/partials/tables/source_table_tree.html
+++ b/webapp/app/partials/tables/source_table_tree.html
@@ -26,7 +26,7 @@
         <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-success" tooltip="Refresh Tables" ng-click="aceSrcTbChanged()"><i class="fa fa-refresh"></i></a>
+                <a class="btn btn-xs btn-primary" tooltip="Add Streaming Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openStreamingSourceModal()"><i class="fa fa-area-chart"></i></a>
             </div>
         </div>
 

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/1ea58708/webapp/app/partials/tables/table_detail.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/tables/table_detail.html b/webapp/app/partials/tables/table_detail.html
index dc1492d..e692269 100644
--- a/webapp/app/partials/tables/table_detail.html
+++ b/webapp/app/partials/tables/table_detail.html
@@ -16,187 +16,300 @@
 * limitations under the License.
 -->
 
-<div  ng-controller="SourceMetaCtrl" class="nav-tabs-custom">
-    <div class="col-xs-12" ng-show="tableModel.selectedSrcDb&&tableModel.selectedSrcTable.name">
-        <h3 class="text-info">Table Schema:{{ tableModel.selectedSrcTable.name}}</h3>
-        <div class="tabbable nav-tabs-custom">
-            <ul class="nav nav-tabs">
-                <li class="active">
-                    <a data-toggle="tab" href="#column">Columns</a>
-                </li>
-                <li>
-                    <a data-toggle="tab" href="#schema">Extend Information</a>
-                </li>
-            </ul>
-            <div class="tab-content">
-                <!--Schema-->
-                <div id="schema" class="tab-pane">
-                    <div ng-if="tableModel.selectedSrcTable.uuid" class="table-responsive">
-                        <table class="table">
-                            <tbody>
-                            <tr>
-                                <th style="width:20%">NAME</th>
-                                <td>{{ tableModel.selectedSrcTable.name}}</td>
-                            </tr>
-                            <tr>
-                                <th>Hive DATABASE</th>
-                                <td>{{tableModel.selectedSrcTable.database}}</td>
-                            </tr>
-                            <tr>
-                                <th>SNAPSHOT TIME</th>
-                                <td>{{tableModel.selectedSrcTable.exd.lastUpdateTime | utcToConfigTimeZone}}</td>
-                            </tr>
-                            <tr>
-                                <th>LOCATION</th>
-                                <td>{{tableModel.selectedSrcTable.exd.location}}</td>
-                            </tr>
-                            <tr>
-                                <th>INPUT FORMAT</th>
-                                <td>{{tableModel.selectedSrcTable.exd.inputformat}}</td>
-                            </tr>
-                            <tr>
-                                <th>OUTPUT FORMAT</th>
-                                <td>{{tableModel.selectedSrcTable.exd.outputformat}}</td>
-                            </tr>
-                            <tr>
-                                <th>OWNER</th>
-                                <td><a href="mailto:{{tableModel.selectedSrcTable.exd.owner}}">{{tableModel.selectedSrcTable.exd.owner}}</a></td>
-                            </tr>
-                            <tr>
-                                <th>TOTAL FILE NUMBER</th>
-                                <td>{{tableModel.selectedSrcTable.exd.totalNumberFiles}}</td>
-                            </tr>
-                            <tr>
-                                <th>TOTAL FILE SIZE</th>
-                                <td>{{tableModel.selectedSrcTable.exd.totalFileSize}}</td>
-                            </tr>
-                            <tr>
-                                <th>PARTITIONED</th>
-                                <td>{{tableModel.selectedSrcTable.exd.partitioned}}</td>
-                            </tr>
-                            <tr>
-                                <th>PARTITION COLUMNS</th>
-                                <td>{{tableModel.selectedSrcTable.exd.partitionColumns}}</td>
-                            </tr>
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-                <!--Columns-->
-                <div id="column" class="tab-pane active">
-                    <div class="profile-user-info">
-                        <div>
-                            <label class="table-header-text">Columns</label>
+<div ng-controller="SourceMetaCtrl" class="nav-tabs-custom">
+  <div class="col-xs-12" ng-show="tableModel.selectedSrcDb&&tableModel.selectedSrcTable.name">
+    <h3 class="text-info">Table Schema:{{ tableModel.selectedSrcTable.name}}</h3>
+
+    <div class="tabbable nav-tabs-custom">
+      <ul class="nav nav-tabs">
+        <li class="active">
+          <a data-toggle="tab" href="#column">Columns</a>
+        </li>
+        <li>
+          <a data-toggle="tab" href="#schema">Extend Information</a>
+        </li>
+      </ul>
+      <div class="tab-content">
+        <!--Schema-->
+        <div id="schema" class="tab-pane">
+          <div ng-if="tableModel.selectedSrcTable.uuid" class="table-responsive">
+            <table class="table">
+              <tbody>
+              <tr>
+                <th style="width:20%">NAME</th>
+                <td>{{ tableModel.selectedSrcTable.name}}</td>
+              </tr>
+              <tr>
+                <th>Hive DATABASE</th>
+                <td>{{tableModel.selectedSrcTable.database}}</td>
+              </tr>
+              <tr>
+                <th>SNAPSHOT TIME</th>
+                <td>{{tableModel.selectedSrcTable.exd.lastUpdateTime | utcToConfigTimeZone}}</td>
+              </tr>
+              <tr>
+                <th>LOCATION</th>
+                <td>{{tableModel.selectedSrcTable.exd.location}}</td>
+              </tr>
+              <tr>
+                <th>INPUT FORMAT</th>
+                <td>{{tableModel.selectedSrcTable.exd.inputformat}}</td>
+              </tr>
+              <tr>
+                <th>OUTPUT FORMAT</th>
+                <td>{{tableModel.selectedSrcTable.exd.outputformat}}</td>
+              </tr>
+              <tr>
+                <th>OWNER</th>
+                <td><a
+                  href="mailto:{{tableModel.selectedSrcTable.exd.owner}}">{{tableModel.selectedSrcTable.exd.owner}}</a>
+                </td>
+              </tr>
+              <tr>
+                <th>TOTAL FILE NUMBER</th>
+                <td>{{tableModel.selectedSrcTable.exd.totalNumberFiles}}</td>
+              </tr>
+              <tr>
+                <th>TOTAL FILE SIZE</th>
+                <td>{{tableModel.selectedSrcTable.exd.totalFileSize}}</td>
+              </tr>
+              <tr>
+                <th>PARTITIONED</th>
+                <td>{{tableModel.selectedSrcTable.exd.partitioned}}</td>
+              </tr>
+              <tr>
+                <th>PARTITION COLUMNS</th>
+                <td>{{tableModel.selectedSrcTable.exd.partitionColumns}}</td>
+              </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+        <!--Columns-->
+        <div id="column" class="tab-pane active">
+          <div class="profile-user-info">
+            <div>
+              <label class="table-header-text">Columns</label>
                             <span class="input-icon form-search nav-search pull-right">
-                                <input type="text" placeholder="Filter ..." class="nav-search-input" ng-model="columnName"/>
+                                <input type="text" placeholder="Filter ..." class="nav-search-input"
+                                       ng-model="columnName"/>
                                 <i class="ace-icon fa fa-search nav-search-icon"></i>
                             </span>
-                        </div>
-                        <div class="space-6"></div>
-                        <div ng-if="(tableModel.selectedSrcTable.columns | filter: columnName).length>0">
-                            <table class="table table-hover table-striped list">
-                                <thead>
-                                <tr style="cursor: pointer">
-                                    <th ng-repeat="theaditem in tableConfig.theaditems"
-                                        ng-click="state.filterAttr= theaditem.attr;state.reverseColumn=theaditem.attr;state.filterReverse=!state.filterReverse;">
-                                        {{theaditem.name}}
-                                        <i ng-if="state.reverseColumn!= theaditem.attr"
-                                           class="fa fa-unsorted"></i>
-                                        <i ng-if="state.reverseColumn== theaditem.attr && !state.filterReverse"
-                                           class="fa fa-sort-asc"></i>
-                                        <i ng-if="state.reverseColumn== theaditem.attr && state.filterReverse"
-                                           class="fa fa-sort-desc"></i>
-                                    </th>
-                                </tr>
-                                </thead>
-
-                                <tr ng-repeat="column in tableModel.selectedSrcTable.columns | filter: columnName | orderObjectBy:state.filterAttr:state.filterReverse">
-                                    <td style="{{(tableModel.selectedSrcTable.selectedSrcColumn.id == column.id)? 'background-color:#EBF9FE':''}}">
-                                        {{ column.id}}
-                                    </td>
-                                    <td style="{{(tableModel.selectedSrcTable.selectedSrcColumn.id == column.id)? 'background-color:#EBF9FE':''}}">
-                                        {{ column.name}}
-                                    </td>
-                                    <td style="{{(tableModel.selectedSrcTable.selectedSrcColumn.id == column.id)? 'background-color:#EBF9FE':''}}">
-                                        {{ column.datatype}}
-                                    </td>
-                                    <td style="{{(tableModel.selectedSrcTable.selectedSrcColumn.id == column.id)? 'background-color:#EBF9FE':''}}">
-                                        <!--{{ tableModel.selectedSrcTable.cardinality[column.name]}}-->
-                                        {{column.cardinality}}
-                                    </td>
-                                </tr>
-                            </table>
-                        </div>
-                        <div ng-if="(tableModel.selectedSrcTable.columns | filter: columnName).length == 0" no-result
-                             text="No Matched Table Column."></div>
-                        <div ng-if="!!!tableModel.selectedSrcTable.uuid">
-                            <div no-result text="No Table Selected."></div>
-                        </div>
-                    </div>
-                </div>
             </div>
+            <div class="space-6"></div>
+            <div ng-if="(tableModel.selectedSrcTable.columns | filter: columnName).length>0">
+              <table class="table table-hover table-striped list">
+                <thead>
+                <tr style="cursor: pointer">
+                  <th ng-repeat="theaditem in tableConfig.theaditems"
+                      ng-click="state.filterAttr= theaditem.attr;state.reverseColumn=theaditem.attr;state.filterReverse=!state.filterReverse;">
+                    {{theaditem.name}}
+                    <i ng-if="state.reverseColumn!= theaditem.attr"
+                       class="fa fa-unsorted"></i>
+                    <i ng-if="state.reverseColumn== theaditem.attr && !state.filterReverse"
+                       class="fa fa-sort-asc"></i>
+                    <i ng-if="state.reverseColumn== theaditem.attr && state.filterReverse"
+                       class="fa fa-sort-desc"></i>
+                  </th>
+                </tr>
+                </thead>
+
+                <tr
+                  ng-repeat="column in tableModel.selectedSrcTable.columns | filter: columnName | orderObjectBy:state.filterAttr:state.filterReverse">
+                  <td
+                    style="{{(tableModel.selectedSrcTable.selectedSrcColumn.id == column.id)? 'background-color:#EBF9FE':''}}">
+                    {{ column.id}}
+                  </td>
+                  <td
+                    style="{{(tableModel.selectedSrcTable.selectedSrcColumn.id == column.id)? 'background-color:#EBF9FE':''}}">
+                    {{ column.name}}
+                  </td>
+                  <td
+                    style="{{(tableModel.selectedSrcTable.selectedSrcColumn.id == column.id)? 'background-color:#EBF9FE':''}}">
+                    {{ column.datatype}}
+                  </td>
+                  <td
+                    style="{{(tableModel.selectedSrcTable.selectedSrcColumn.id == column.id)? 'background-color:#EBF9FE':''}}">
+                    <!--{{ tableModel.selectedSrcTable.cardinality[column.name]}}-->
+                    {{column.cardinality}}
+                  </td>
+                </tr>
+              </table>
+            </div>
+            <div ng-if="(tableModel.selectedSrcTable.columns | filter: columnName).length == 0" no-result
+                 text="No Matched Table Column."></div>
+            <div ng-if="!!!tableModel.selectedSrcTable.uuid">
+              <div no-result text="No Table Selected."></div>
+            </div>
+          </div>
         </div>
+      </div>
     </div>
+  </div>
 
 
-    <!--show load hive table tip when no models list-->
-    <div ng-show="!tableModel.selectedSrcDb.length" style="margin-top:40px;">
+  <!--show load hive table tip when no models list-->
+  <div ng-show="!tableModel.selectedSrcDb.length" style="margin-top:40px;">
 
-        <!--project selected tip-->
-        <div ng-if="projectModel.getSelectedProject();" class="box box-primary">
-            <div class="box-header with-border">
-                <i class="fa fa-bullhorn"></i>
-                <h3 class="box-title">No tables</h3>
-            </div>
-            <div class="box-body">
-                <div>
-                    <a tooltip="Load Hive Table" href="javascript:void(0);" ng-if="userService.hasRole('ROLE_MODELER')" ng-click="openModal()">Click here to load your hive table</a>
-                </div>
-            </div><!-- /.box-body -->
-        </div>
-        <!--roject not selected tip-->
-        <div ng-if="!projectModel.getSelectedProject();" class="box box-primary">
-            <div class="box-header with-border">
-                <i class="icon fa fa-warning"></i>
-                <h3 class="box-title">No project selected</h3>
-            </div>
-            <div class="box-body">
-                <div class="callout callout-danger">
-                    <p  class="text-danger">
-                       Please select your project first
-                    </p>
-                </div>
-            </div><!-- /.box-body -->
+    <!--project selected tip-->
+    <div ng-if="projectModel.getSelectedProject();" class="box box-primary">
+      <div class="box-header with-border">
+        <i class="fa fa-bullhorn"></i>
+
+        <h3 class="box-title">No tables</h3>
+      </div>
+      <div class="box-body">
+        <div>
+          <a tooltip="Load Hive Table" href="javascript:void(0);" ng-if="userService.hasRole('ROLE_MODELER')"
+             ng-click="openModal()">Click here to load your hive table</a>
         </div>
+      </div>
+      <!-- /.box-body -->
     </div>
+    <!--roject not selected tip-->
+    <div ng-if="!projectModel.getSelectedProject();" class="box box-primary">
+      <div class="box-header with-border">
+        <i class="icon fa fa-warning"></i>
 
-    <!--show load hive table tip when no models list-->
-    <div ng-show="tableModel.selectedSrcDb.length&&!tableModel.selectedSrcTable.name" style="margin-top:40px;">
-        <div class="box box-primary">
-            <div class="box-header with-border">
-                <i class="fa fa-bullhorn"></i>
-                <h3 class="box-title">No table selected</h3>
-            </div>
-            <div class="box-body">
-                Select your table
-            </div><!-- /.box-body -->
+        <h3 class="box-title">No project selected</h3>
+      </div>
+      <div class="box-body">
+        <div class="callout callout-danger">
+          <p class="text-danger">
+            Please select your project first
+          </p>
         </div>
+      </div>
+      <!-- /.box-body -->
     </div>
+  </div>
 
+  <!--show load hive table tip when no models list-->
+  <div ng-show="tableModel.selectedSrcDb.length&&!tableModel.selectedSrcTable.name" style="margin-top:40px;">
+    <div class="box box-primary">
+      <div class="box-header with-border">
+        <i class="fa fa-bullhorn"></i>
 
-    <script type="text/ng-template" id="addHiveTable.html">
-        <div class="modal-header">
-            <h4>Load Hive Table Metadata</h4>
-        </div>
-        <div class="modal-body">
-            <span><strong>Project: </strong>{{ $parent.projectName!=null?$parent.projectName:'NULL'}}</span>
-            <label for="tables"> Table Names:(Seperate with comma)</label>
+        <h3 class="box-title">No table selected</h3>
+      </div>
+      <div class="box-body">
+        Select your table
+      </div>
+      <!-- /.box-body -->
+    </div>
+  </div>
+
+
+  <script type="text/ng-template" id="addHiveTable.html">
+    <div class="modal-header">
+      <h4>Load Hive Table Metadata</h4>
+    </div>
+    <div class="modal-body">
+      <span><strong>Project: </strong>{{ $parent.projectName!=null?$parent.projectName:'NULL'}}</span>
+      <label for="tables"> Table Names:(Seperate with comma)</label>
             <textarea ng-model="$parent.tableNames" class="form-control" id="tables"
                       placeholder="table1,table2  By default,system will choose 'Default' as database,you can specify database like this 'database.table'"></textarea>
+    </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>
+
+  <script type="text/ng-template" id="addStreamingSource.html">
+    <div class="modal-header">
+      <h2>Create Streaming Table Schema</h2>
+    </div>
+
+    <div class="modal-body streaming-source" style="height: 480px;">
+      <div class="col-xs-5">
+        <p class="text-info">
+          Need to input streaming source record here, will detect the source schema and create a table schema for
+          streaming.
+        </p>
+
+        <div style="padding:15px;" class="has-error">
+          <small class="help-block" ng-show="streaming.sourceSchema==''&&form.setStreamingSchema.$sbumitted">Please
+            input Streaming source record to generate schema.
+          </small>
+        </div>
+        <div style="margin-bottom: 20px;">
+          <span class="label label-info">JSON</span>
         </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 ng-model="streaming.sourceSchema" ui-ace="{
+    useWrapMode : true,
+    mode:'json',
+    onLoad: streamingOnLoad
+  }">
+
         </div>
-    </script>
+      </div>
+      <div class="col-xs-1" style="margin-top:300px;text-align:center;">
+        <button type="button" class="btn btn-primary" ng-click="streamingOnChange()"><i
+          class="fa fa-angle-double-right fa-5" style="font-size:2em;"></i></button>
+      </div>
+      <div class="col-xs-6" ng-show="table.sourceValid">
+        <ol class="text-info" style="margin-bottom: 30px;">
+          <li>Choose one 'timestamp' type column for streaming table.</li>
+          <li>Uncheck the 'timestamp' type column which will not be used.</li>
+        </ol>
+        <form class="form-horizontal" name="form.setStreamingSchema" novalidate>
+          <div class="form-group required">
+            <label class="col-xs-4 control-label" style="text-align: left;">Table Name</label>
+
+            <div class="col-xs-8"
+                 ng-class="{'has-error':form.setStreamingSchema.streamingObject.$invalid && (form.setStreamingSchema.streamingObject.$dirty||form.setStreamingSchema.$sbumitted)}">
+              <input type="text" name="streamingObject" required="" ng-model="table.name" class="form-control"/>
+              <small class="help-block"
+                     ng-show="form.setStreamingSchema.streamingObject.$error.required&&(form.setStreamingSchema.streamingObject.$dirty||form.setStreamingSchema.$sbumitted)">
+                Table name is required.
+              </small>
+            </div>
+          </div>
+        </form>
+        <table class="table table-hover table-bordered">
+          <tr>
+            <th>Check As Column</th>
+            <th>Column</th>
+            <th>Column Type</th>
+            <th>Comment</th>
+          </tr>
+          <tr ng-repeat="column in columnList">
+            <td><label style="width:100%;cursor: pointer;" for="{{column.name}}"><input style="width:1em;height:1em;"
+                                                                                        type="checkbox"
+                                                                                        id="{{column.name}}"
+                                                                                        ng-model="column.checked"
+                                                                                        ng-true-value="Y"
+                                                                                        ng-false-value="N"/></label>
+            </td>
+            <td>{{column.name}}</td>
+            <td>
+              <select chosen ng-model="column.type"
+                      ng-options="type as type for type in tableConfig.dataTypes"
+                      data-placeholder="select a column type"
+                      style="width: 200px !important;"
+                      class="chosen-select">
+              </select>
+            </td>
+            <td>
+              <label ng-if="column.type=='timestamp'&&column.fromSource=='Y'" class="badge badge-info">TIMESTAMP</label>
+              <label ng-if="column.fromSource=='N'" class="badge badge-info">AUTO APPEND</label>
+            </td>
+          </tr>
+        </table>
+
+        <div class="has-error" ng-if="rule.timestampColumnConflict">
+          <small class="help-block">
+            You should choose one, and only one 'timestamp' type column generated from source schema.
+          </small>
+        </div>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <button class="btn btn-primary" ng-click="syncStreamingSchema()" ng-disabled="form.setStreamingSchema.$invalid">
+        Submit
+      </button>
+      <button class="btn btn-primary" ng-click="cancel()">Cancel</button>
+    </div>
+  </script>
 </div>