You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by ma...@apache.org on 2015/05/15 07:21:48 UTC

[19/52] [abbrv] incubator-kylin git commit: show models list page

show models list page


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

Branch: refs/heads/0.8.0
Commit: 7b526242ce17ec6f59ecf1e87b44d0bf309bc58a
Parents: 286e520
Author: jiazhong <ji...@ebay.com>
Authored: Fri Mar 13 16:28:25 2015 +0800
Committer: honma <ho...@ebay.com>
Committed: Fri May 15 11:35:49 2015 +0800

----------------------------------------------------------------------
 .../apache/kylin/rest/service/ModelService.java |   1 +
 webapp/app/index.html                           |  16 +
 webapp/app/js/controllers/cubeModel.js          |  25 +-
 webapp/app/js/controllers/modelDimensions.js    | 402 +++++++++++++++++++
 webapp/app/js/controllers/modelMeasures.js      |  27 ++
 webapp/app/js/controllers/modelRefresh.js       |  23 ++
 webapp/app/js/controllers/modelSchema.js        | 209 ++++++++++
 webapp/app/js/controllers/models.js             |  79 ++++
 webapp/app/js/model/jobListModel.js             |   4 +
 webapp/app/js/model/metaModel.js                |   3 +
 webapp/app/js/model/modelConfig.js              |  26 ++
 webapp/app/js/model/modelList.js                |  52 +++
 webapp/app/js/services/models.js                |  26 ++
 webapp/app/js/services/tree.js                  |  71 ++--
 webapp/app/js/services/users.js                 |   6 +-
 webapp/app/less/app.less                        |   6 +-
 .../app/partials/cubeDesigner/data_model.html   | 199 ---------
 webapp/app/partials/header.html                 |   4 +-
 .../app/partials/modelDesigner/data_model.html  | 199 +++++++++
 .../app/partials/modelDesigner/incremental.html |  94 +++++
 .../modelDesigner/model_dimensions.html         | 354 ++++++++++++++++
 .../partials/modelDesigner/model_measures.html  | 185 +++++++++
 webapp/app/partials/models/model_detail.html    |  43 ++
 webapp/app/partials/models/model_edit.html      |  34 ++
 webapp/app/partials/models/model_schema.html    |  68 ++++
 webapp/app/partials/models/models.html          | 117 ++++++
 webapp/app/routes.json                          |   8 +
 27 files changed, 2038 insertions(+), 243 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/server/src/main/java/org/apache/kylin/rest/service/ModelService.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/service/ModelService.java b/server/src/main/java/org/apache/kylin/rest/service/ModelService.java
index ddf2594..093efe3 100644
--- a/server/src/main/java/org/apache/kylin/rest/service/ModelService.java
+++ b/server/src/main/java/org/apache/kylin/rest/service/ModelService.java
@@ -58,6 +58,7 @@ public class ModelService extends BasicService {
         } else {
             //TO-DO
 //            models = listAllModels(projectName);
+            models=Collections.emptyList();
         }
 
         List<DataModelDesc> filterModels = new ArrayList();

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/index.html
----------------------------------------------------------------------
diff --git a/webapp/app/index.html b/webapp/app/index.html
index 44021a9..9e7317f 100644
--- a/webapp/app/index.html
+++ b/webapp/app/index.html
@@ -127,11 +127,16 @@
 <script src="js/services/tree.js"></script>
 <script src="js/services/users.js"></script>
 <script src="js/services/ngLoading.js"></script>
+<!--New GUI-->
+<script src="js/services/models.js"></script>
 
 <script src="js/model/cubeConfig.js"></script>
 <script src="js/model/jobConfig.js"></script>
 <script src="js/model/projectConfig.js"></script>
 <script src="js/model/tableConfig.js"></script>
+<!--New GUI-->
+<script src="js/model/modelConfig.js"></script>
+
 <script src="js/model/cubeDescModel.js"></script>
 <script src="js/model/metaModel.js"></script>
 <script src="js/model/projectModel.js"></script>
@@ -139,6 +144,9 @@
 <script src="js/model/cubeListModel.js"></script>
 <script src="js/model/jobListModel.js"></script>
 
+<!--New GUI-->
+<script src="js/model/modelList.js"></script>
+
 <script src="js/controllers/page.js"></script>
 <script src="js/controllers/index.js"></script>
 <script src="js/controllers/access.js"></script>
@@ -158,6 +166,14 @@
 <script src="js/controllers/cubeFilter.js"></script>
 <script src="js/controllers/cubeRefresh.js"></script>
 <script src="js/controllers/cubeAdvanceSetting.js"></script>
+<!--New GUI-->
+<script src="js/controllers/modelSchema.js"></script>
+<script src="js/controllers/modelDimensions.js"></script>
+<script src="js/controllers/modelRefresh.js"></script>
+
+<!--New GUI-->
+<script src="js/controllers/models.js"></script>
+
 <!-- endref -->
 
 <!-- ref:remove -->

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/controllers/cubeModel.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/cubeModel.js b/webapp/app/js/controllers/cubeModel.js
index 2874742..038f7be 100644
--- a/webapp/app/js/controllers/cubeModel.js
+++ b/webapp/app/js/controllers/cubeModel.js
@@ -18,7 +18,28 @@
 
 'use strict';
 
-KylinApp.controller('CubeModelCtrl', function ($scope, $modal,cubeConfig,ModelService,MetaModel,SweetAlert,$log) {
+KylinApp.controller('CubeModelCtrl', function ($scope, $modal,cubeConfig,ModelService,MetaModel,SweetAlert,CubeGraphService,$log) {
+
+
+    $scope.buildGraph = function (cube) {
+        CubeGraphService.buildTree(cube);
+    };
+    $scope.cleanStatus = function(model){
+
+        if (!model)
+        {
+            return;
+        }
+        var newModel = jQuery.extend(true, {}, model);
+        delete newModel.visiblePage;
+        delete newModel.showDetail;
+
+        angular.forEach(newModel.dimensions, function(dimension, index){
+            delete dimension.status;
+        });
+
+        return newModel;
+    };
 
     $scope.cubeConfig = cubeConfig;
     var DataModel = function () {
@@ -49,7 +70,7 @@ KylinApp.controller('CubeModelCtrl', function ($scope, $modal,cubeConfig,ModelSe
 
     $scope.newLookup = Lookup();
 
-    var lookupList = $scope.metaModel.model.lookups;
+    var lookupList = $scope.model.lookups;
 
     $scope.openLookupModal = function () {
         var modalInstance = $modal.open({

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/controllers/modelDimensions.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/modelDimensions.js b/webapp/app/js/controllers/modelDimensions.js
new file mode 100644
index 0000000..aa197e6
--- /dev/null
+++ b/webapp/app/js/controllers/modelDimensions.js
@@ -0,0 +1,402 @@
+/*
+ * 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.
+*/
+
+'use strict';
+
+KylinApp.controller('ModelDimensionsCtrl', function ($scope, $modal,MetaModel) {
+
+    // Available columns list derived from cube data model.
+    $scope.availableColumns = {};
+
+    // Columns selected and disabled status bound to UI, group by table.
+    $scope.selectedColumns = {};
+
+    // Available tables cache: 1st is the fact table, next are lookup tables.
+    $scope.availableTables = [];
+
+
+    /**
+     * Helper func to get columns that dimensions based on, three cases:
+     * 1. normal dimension: column array.
+     * 2. hierarchy dimension: column array, the array index is the hierarchy level.
+     * 3. derived dimension: derived columns array.
+     * TODO new cube schema change
+     */
+    var dimCols = function (dim) {
+        var referredCols = [];
+
+        // Case 3.
+        if (dim.derived && dim.derived.length) {
+            referredCols = referredCols.concat(dim.derived);
+        }
+
+        // Case 2.
+        if (dim.hierarchy && dim.column.length) {
+            referredCols = referredCols.concat(dim.column);
+        }
+
+        // Case 1.
+        if (!dim.derived && !dim.hierarchy) {
+            referredCols.push(dim.column);
+        }
+
+        return referredCols;
+    };
+
+    // Dump available columns plus column table name, whether is from lookup table.
+    $scope.initColumns = function () {
+        var factTable = $scope.model.fact_table;
+
+        // At first dump the columns of fact table.
+        var cols = $scope.getColumnsByTable(factTable);
+
+        // Initialize selected available.
+        var factAvailable = {};
+        var factSelectAvailable = {};
+
+        for (var i = 0; i < cols.length; i++) {
+            cols[i].table = factTable;
+            cols[i].isLookup = false;
+
+            factAvailable[cols[i].name] = cols[i];
+
+            // Default not selected and not disabled.
+            factSelectAvailable[cols[i].name] = {selected: false, disabled: false};
+        }
+
+        $scope.availableColumns[factTable] = factAvailable;
+        $scope.selectedColumns[factTable] = factSelectAvailable;
+        $scope.availableTables.push(factTable);
+
+        // Then dump each lookup tables.
+        var lookups = $scope.model.lookups;
+
+        for (var j = 0; j < lookups.length; j++) {
+            var cols2 = $scope.getColumnsByTable(lookups[j].table);
+
+            // Initialize selected available.
+            var lookupAvailable = {};
+            var lookupSelectAvailable = {};
+
+            for (var k = 0; k < cols2.length; k++) {
+                cols2[k].table = lookups[j].table;
+                cols2[k].isLookup = true;
+
+                lookupAvailable[cols2[k].name] = cols2[k];
+
+                // Default not selected and not disabled.
+                lookupSelectAvailable[cols2[k].name] = {selected: false, disabled: false};
+            }
+
+            $scope.availableColumns[lookups[j].table] = lookupAvailable;
+            $scope.selectedColumns[lookups[j].table] = lookupSelectAvailable;
+            $scope.availableTables.push(lookups[j].table);
+        }
+    };
+
+    // Check column status: selected or disabled based on current cube dimensions.
+    $scope.initColumnStatus = function () {
+        angular.forEach($scope.model.dimensions, function (dim) {
+            var cols = dimCols(dim);
+
+            angular.forEach(cols, function (colName) {
+                $scope.selectedColumns[dim.table][colName] = {selected: true, disabled: true};
+            });
+        });
+    };
+
+    // Initialize data for columns widget in auto-gen when add/edit cube.
+    if ($scope.state.mode == 'edit') {
+        $scope.initColumns();
+    }
+
+    // Initialize params for add/edit dimension.
+    $scope.dimState = {
+        editing: false,
+        editingIndex: -1,
+        filter: ''
+    };
+
+    // Init the dimension, dimension name default as the column key. TODO new cube schema change.
+    var Dimension = function (table, selectedCols, dimType) {
+        var origin = {name: '', table: table,hierarchy:false,derived:null,column:null};
+
+        switch (dimType) {
+            case 'normal':
+                // Default name as 1st column name.
+                if (table && selectedCols.length) {
+                    origin.name = table + '.' + selectedCols[0];
+                }
+
+                origin.column = selectedCols;
+                break;
+
+            case 'derived':
+                if (table && selectedCols.length) {
+                    origin.name = table + '_derived';
+                }
+
+                origin.derived = selectedCols;
+                break;
+
+            case 'hierarchy':
+                if (table && selectedCols.length) {
+                    origin.name = table + '_hierarchy';
+                }
+
+                origin.hierarchy = true;
+                origin.column = selectedCols;
+                break;
+        }
+
+        return origin;
+    };
+
+    // Since old schema may be both derived and hierarchy. TODO new cube schema change.
+    $scope.getDimType = function (dim) {
+        var types = [];
+
+        if (dim.derived && dim.derived.length) {
+            types.push('derived');
+        }
+
+        if (dim.hierarchy && dim.column.length) {
+            types.push('hierarchy');
+        }
+
+        if (!types.length) {
+            types.push('normal');
+        }
+
+        return types;
+    };
+
+    var dimList = $scope.model.dimensions;
+
+    // Open add/edit dimension modal.
+    $scope.openDimModal = function (dimType) {
+        var modalInstance = $modal.open({
+            templateUrl: 'addEditDimension.html',
+            controller: cubeDimModalCtrl,
+            backdrop: 'static',
+            scope: $scope,
+            resolve: {
+                dimType: function () {
+                    // For old schema compatibility, convert into array here. TODO new cube schema change.
+                    return angular.isArray(dimType) ? dimType : [dimType];
+                }
+            }
+        });
+
+        modalInstance.result.then(function () {
+            if (!$scope.dimState.editing) {
+                $scope.doneAddDim();
+            } else {
+                $scope.doneEditDim();
+            }
+
+        }, function () {
+            $scope.cancelDim();
+        });
+    };
+
+    // Controller for cube dimension add/edit modal.
+    var cubeDimModalCtrl = function ($scope, $modalInstance, dimType) {
+        $scope.dimType = dimType;
+
+        $scope.ok = function () {
+            $modalInstance.close();
+        };
+
+        $scope.cancel = function () {
+            $modalInstance.dismiss('cancel');
+        };
+    };
+
+    $scope.addDim = function (dimType) {
+        $scope.newDimension = Dimension('', [], dimType);
+
+        $scope.openDimModal(dimType);
+    };
+
+    $scope.editDim = function (dim) {
+        $scope.dimState.editingIndex = dimList.indexOf(dim);
+        $scope.dimState.editing = true;
+
+        // Make a copy of model will be editing.
+        $scope.newDimension = angular.copy(dim);
+
+        $scope.openDimModal($scope.getDimType(dim));
+    };
+
+    $scope.doneAddDim = function () {
+        // Push new dimension which bound user input data.
+        dimList.push(angular.copy($scope.newDimension));
+
+        $scope.resetParams();
+    };
+
+    $scope.doneEditDim = function () {
+        // Copy edited model to destination model.
+        angular.copy($scope.newDimension, dimList[$scope.dimState.editingIndex]);
+
+        $scope.resetParams();
+    };
+
+    $scope.cancelDim = function () {
+        $scope.resetParams();
+    };
+
+    $scope.removeDim = function (dim) {
+        dimList.splice(dimList.indexOf(dim), 1);
+    };
+
+    $scope.resetParams = function () {
+        $scope.dimState.editing = false;
+        $scope.dimState.editingIndex = -1;
+
+        $scope.newDimension = {};
+    };
+
+    // Open auto-gen dimension modal.
+    $scope.openAutoGenModal = function (dimType) {
+        // Init columns status.
+        $scope.initColumnStatus();
+
+        var modalInstance = $modal.open({
+            templateUrl: 'autoGenDimension.html',
+            controller: cubeAutoGenDimModalCtrl,
+            backdrop: 'static',
+            scope: $scope
+        });
+
+        modalInstance.result.then(function () {
+            $scope.autoGenDims();
+        }, function () {
+            $scope.resetGenDims();
+        });
+    };
+
+    // Controller for cube dimension auto-gen modal.
+    var cubeAutoGenDimModalCtrl = function ($scope, $modalInstance) {
+        $scope.ok = function () {
+            $modalInstance.close();
+        };
+
+        $scope.cancel = function () {
+            $modalInstance.dismiss('cancel');
+        };
+    };
+
+    // Helper func to get the selected status in auto gen.
+    $scope.getSelectedCols = function () {
+        var selectedCols = {};
+
+        angular.forEach($scope.selectedColumns, function (value, table) {
+            angular.forEach(value, function (status, colName) {
+                if (status.selected && !status.disabled) {
+                    if (!selectedCols[table]) {
+                        selectedCols[table] = [];
+                    }
+
+                    selectedCols[table].push(colName);
+                }
+            });
+        });
+
+        return selectedCols;
+    };
+
+    // Auto generate dimensions.
+    $scope.autoGenDims = function () {
+        var selectedCols = $scope.getSelectedCols();
+
+        angular.forEach(selectedCols, function (cols, table) {
+            if ($scope.model.fact_table == table) {
+                // Fact table: for each selected column, create one normal dimension.
+                for (var i = 0; i < cols.length; i++) {
+                    dimList.push(Dimension(table, [cols[i]], 'normal'));
+                }
+            } else {
+                // Per lookup table, create one derived dimension for all its selected columns;
+                if (cols.length) {
+                    dimList.push(Dimension(table, cols, 'derived'));
+                }
+            }
+        });
+    };
+
+    // Just reset the selected status of columns.
+    $scope.resetGenDims = function () {
+        var selectedCols = $scope.getSelectedCols();
+
+        angular.forEach(selectedCols, function (cols, table) {
+            for (var i = 0; i < cols.length; i++) {
+                $scope.selectedColumns[table][cols[i]].selected = false;
+            }
+        });
+    };
+
+    // Check whether there is column conflicts.
+    $scope.dimConflicts = [];
+
+    $scope.$watch('cubeMetaFrame.dimensions', function (newVal, oldVal) {
+        if (!newVal || !newVal.length) {
+            return;
+        }
+
+        var referredCols = {};
+
+        angular.forEach(newVal, function (curDim) {
+            var table = curDim.table;
+            var cols = dimCols(curDim);
+
+            for (var i = 0; i < cols.length; i++) {
+                var key = table + '.' + cols[i];
+
+                if (!referredCols[key]) {
+                    referredCols[key] = [];
+                }
+
+                referredCols[key].push({id: curDim.id, name: curDim.name});
+            }
+        });
+
+        var conflicts = [];
+
+        angular.forEach(referredCols, function (dims, key) {
+            if (dims.length > 1) {
+                // More than 1 dimensions has referred this column.
+                var colInfo = key.split('.');
+                conflicts.push({table: colInfo[0], column: colInfo[1], dims: dims});
+            }
+        });
+
+        $scope.dimConflicts = conflicts;
+    }, true);
+
+
+
+    if ($scope.state.mode == 'edit') {
+        $scope.$on('$destroy', function () {
+           // $scope.dimensionsAdapter();
+            // Emit dimensions edit event in order to re-generate row key.
+            $scope.$emit('DimensionsEdited');
+        });
+    }
+});

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/controllers/modelMeasures.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/modelMeasures.js b/webapp/app/js/controllers/modelMeasures.js
new file mode 100644
index 0000000..01cd35a
--- /dev/null
+++ b/webapp/app/js/controllers/modelMeasures.js
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Created by jiazhong on 2015/3/13.
+ */
+
+'use strict';
+
+KylinApp.controller('ModelMeasuresCtrl', function ($scope) {
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/controllers/modelRefresh.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/modelRefresh.js b/webapp/app/js/controllers/modelRefresh.js
new file mode 100644
index 0000000..04b5bf7
--- /dev/null
+++ b/webapp/app/js/controllers/modelRefresh.js
@@ -0,0 +1,23 @@
+/*
+ * 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.
+*/
+
+'use strict';
+
+KylinApp.controller('ModelRefreshCtrl', function ($scope, $modal,cubeConfig,ModelService,MetaModel) {
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/controllers/modelSchema.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/modelSchema.js b/webapp/app/js/controllers/modelSchema.js
new file mode 100644
index 0000000..638e119
--- /dev/null
+++ b/webapp/app/js/controllers/modelSchema.js
@@ -0,0 +1,209 @@
+/*
+ * 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.
+*/
+
+'use strict';
+
+KylinApp.controller('ModelSchemaCtrl', function ($scope, QueryService, UserService, ProjectService, AuthenticationService,$filter,ModelService,MetaModel,CubeDescModel,CubeList,TableModel,ProjectModel,$log) {
+
+    $log.info($scope.model);
+
+    $scope.projects = [];
+    $scope.newDimension = null;
+    $scope.newMeasure = null;
+
+
+    $scope.wizardSteps = [
+        {title: 'Data Model', src: 'partials/modelDesigner/data_model.html', isComplete: false},
+        {title: 'Dimensions', src: 'partials/modelDesigner/model_dimensions.html', isComplete: false},
+        {title: 'Measures', src: 'partials/modelDesigner/model_measures.html', isComplete: false},
+        {title: 'Refresh Setting', src: 'partials/modelDesigner/incremental.html', isComplete: false}
+    ];
+
+    $scope.curStep = $scope.wizardSteps[0];
+
+
+    // ~ init
+    if (!$scope.state) {
+        $scope.state = {mode: "view"};
+    }
+
+    $scope.$watch('cubeMetaFrame', function (newValue, oldValue) {
+        if(!newValue){
+            return;
+        }
+        if ($scope.cubeMode=="editExistCube"&&newValue && !newValue.project) {
+            initProject();
+        }
+
+    });
+
+    // ~ public methods
+    $scope.filterProj = function(project){
+        return $scope.userService.hasRole('ROLE_ADMIN') || $scope.hasPermission(project,$scope.permissions.ADMINISTRATION.mask);
+    };
+
+    $scope.addNewMeasure = function (measure) {
+        $scope.newMeasure = (!!measure)? measure:CubeDescModel.createMeasure();
+    };
+
+    $scope.clearNewMeasure = function () {
+        $scope.newMeasure = null;
+    };
+
+    $scope.saveNewMeasure = function () {
+        if ($scope.cubeMetaFrame.measures.indexOf($scope.newMeasure) === -1) {
+            $scope.cubeMetaFrame.measures.push($scope.newMeasure);
+        }
+        $scope.newMeasure = null;
+    };
+
+    //map right return type for param
+    $scope.measureReturnTypeUpdate = function(){
+        if($scope.newMeasure.function.expression!=="COUNT_DISTINCT"){
+
+            var column = $scope.newMeasure.function.parameter.value;
+            var colType = $scope.getColumnType(column, $scope.metaModel.model.fact_table); // $scope.getColumnType defined in cubeEdit.js
+
+
+            switch($scope.newMeasure.function.expression){
+                case "SUM":
+                    if(colType==="smallint"||colType==="int"||colType==="bigint"){
+                        $scope.newMeasure.function.returntype= 'bigint';
+                    }else{
+                        $scope.newMeasure.function.returntype= 'decimal';
+                    }
+                    break;
+                case "MIN":
+                case "MAX":
+                    $scope.newMeasure.function.returntype = colType;
+                    break;
+                case "COUNT":
+                    $scope.newMeasure.function.returntype = "bigint";
+                    break;
+                default:
+                    $scope.newMeasure.function.returntype = "";
+                    break;
+            }
+        }
+    }
+
+    $scope.addNewRowkeyColumn = function () {
+        $scope.cubeMetaFrame.rowkey.rowkey_columns.push({
+            "column": "",
+            "length": 0,
+            "dictionary": "true",
+            "mandatory": false
+        });
+    };
+
+    $scope.addNewAggregationGroup = function () {
+        $scope.cubeMetaFrame.rowkey.aggregation_groups.push([]);
+    };
+
+    $scope.refreshAggregationGroup = function (list, index, aggregation_groups) {
+        if (aggregation_groups) {
+            list[index] = aggregation_groups;
+        }
+    };
+
+    $scope.removeElement = function (arr, element) {
+        var index = arr.indexOf(element);
+        if (index > -1) {
+            arr.splice(index, 1);
+        }
+    };
+
+    $scope.open = function ($event) {
+        $event.preventDefault();
+        $event.stopPropagation();
+
+        $scope.opened = true;
+    };
+
+    $scope.preView = function () {
+        var stepIndex = $scope.wizardSteps.indexOf($scope.curStep);
+        if (stepIndex >= 1) {
+            $scope.curStep.isComplete = false;
+            $scope.curStep = $scope.wizardSteps[stepIndex - 1];
+        }
+    };
+
+    $scope.nextView = function () {
+        var stepIndex = $scope.wizardSteps.indexOf($scope.curStep);
+
+        if (stepIndex < ($scope.wizardSteps.length - 1)) {
+            $scope.curStep.isComplete = true;
+            $scope.curStep = $scope.wizardSteps[stepIndex + 1];
+
+            AuthenticationService.ping(function (data) {
+                UserService.setCurUser(data);
+            });
+        }
+    };
+
+    $scope.goToStep = function(stepIndex){
+        for(var i=0;i<$scope.wizardSteps.length;i++){
+            if(i<=stepIndex){
+                $scope.wizardSteps[i].isComplete = true;
+            }else{
+                $scope.wizardSteps[i].isComplete = false;
+            }
+        }
+        if (stepIndex < ($scope.wizardSteps.length)) {
+            $scope.curStep = $scope.wizardSteps[stepIndex];
+
+            AuthenticationService.ping(function (data) {
+                UserService.setCurUser(data);
+            });
+        }
+    }
+
+    // ~ private methods
+    function initProject() {
+        ProjectService.list({}, function (projects) {
+            $scope.projects = projects;
+
+            var cubeName = (!!$scope.routeParams.cubeName)? $scope.routeParams.cubeName:$scope.state.cubeName;
+            if (cubeName) {
+                var projName = null;
+                if(ProjectModel.getSelectedProject()){
+                    projName=ProjectModel.getSelectedProject();
+                }else{
+                    angular.forEach($scope.projects, function (project, index) {
+                        angular.forEach(project.realizations, function (unit, index) {
+                            if (!projName && unit.type=="CUBE"&&unit.realization === cubeName) {
+                                projName = project.name;
+                            }
+                        });
+                    });
+                }
+
+                if(!ProjectModel.getSelectedProject()){
+                    ProjectModel.setSelectedProject(projName);
+                    TableModel.aceSrcTbLoaded();
+                }
+
+                $scope.cubeMetaFrame.project = projName;
+            }
+
+            angular.forEach($scope.projects, function (project, index) {
+                $scope.listAccess(project, 'ProjectInstance');
+            });
+        });
+    }
+});

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/controllers/models.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/models.js b/webapp/app/js/controllers/models.js
new file mode 100644
index 0000000..1777723
--- /dev/null
+++ b/webapp/app/js/controllers/models.js
@@ -0,0 +1,79 @@
+/*
+ * 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.
+*/
+
+'use strict';
+
+KylinApp
+    .controller('ModelsCtrl', function ($scope, $q, $routeParams, $location, $modal, MessageService, CubeDescService, CubeService, JobService, UserService,  ProjectService,SweetAlert,loadingRequest,$log,modelConfig,ProjectModel,ModelService,MetaModel,ModelList) {
+        $scope.modelList = ModelList;
+        $scope.modelConfig = modelConfig;
+        ModelList.removeAll();
+        $scope.loading = false;
+        $scope.action = {};
+
+        $scope.listParams={
+            cubeName: $routeParams.cubeName,
+            projectName: $routeParams.projectName
+        };
+
+        $scope.list = function (offset, limit) {
+            if(!$scope.projectModel.projects.length){
+                return [];
+            }
+            offset = (!!offset) ? offset : 0;
+            limit = (!!limit) ? limit : 20;
+
+            var queryParam = {offset: offset, limit: limit};
+            if ($scope.listParams.modelName) {
+                queryParam.modelName = $scope.listParams.modelName;
+            }
+            queryParam.projectName = $scope.projectModel.selectedProject;
+
+            $scope.loading = true;
+
+            var defer = $q.defer();
+            return ModelList.list(queryParam).then(function(resp){
+                $scope.loading = false;
+                defer.resolve(resp);
+                defer.promise;
+            },function(resp){
+                $scope.loading = false;
+                defer.resolve([]);
+                defer.promise;
+            });
+        };
+
+
+        $scope.loadDetail = function (model) {
+            $log.info(model);
+        };
+
+
+        $scope.$watch('projectModel.selectedProject', function (newValue, oldValue) {
+            if(newValue!=oldValue||newValue==null){
+                ModelList.removeAll();
+                $scope.reload();
+            }
+
+        });
+        $scope.reload = function () {
+            // trigger reload action in pagination directive
+            $scope.action.reload = !$scope.action.reload;
+        };
+
+    });

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/model/jobListModel.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/model/jobListModel.js b/webapp/app/js/model/jobListModel.js
index 764791a..404bf20 100755
--- a/webapp/app/js/model/jobListModel.js
+++ b/webapp/app/js/model/jobListModel.js
@@ -16,6 +16,10 @@
  * limitations under the License.
 */
 
+/*
+ *jobListModel will manage data in list job page
+ */
+
 KylinApp.service('JobList',function(JobService,$q){
     var _this = this;
     this.jobs=[];

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/model/metaModel.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/model/metaModel.js b/webapp/app/js/model/metaModel.js
index a21c83c..021dda7 100644
--- a/webapp/app/js/model/metaModel.js
+++ b/webapp/app/js/model/metaModel.js
@@ -16,6 +16,9 @@
  * limitations under the License.
 */
 
+/**
+ *MetaModel will manage model info of cube
+ */
 KylinApp.service('MetaModel',function(){
 
     //data model when edit model

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/model/modelConfig.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/model/modelConfig.js b/webapp/app/js/model/modelConfig.js
new file mode 100644
index 0000000..e910ce5
--- /dev/null
+++ b/webapp/app/js/model/modelConfig.js
@@ -0,0 +1,26 @@
+/*
+ * 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.
+*/
+
+KylinApp.constant('modelConfig', {
+//  models config
+    theaditems : [
+    {attr: 'name', name: 'Name'},
+    {attr: 'factTable', name: 'Fact Table'},
+    {attr: 'date_modified', name: 'Date Modified'}
+     ]
+    });

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/model/modelList.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/model/modelList.js b/webapp/app/js/model/modelList.js
new file mode 100644
index 0000000..30e9b70
--- /dev/null
+++ b/webapp/app/js/model/modelList.js
@@ -0,0 +1,52 @@
+/*
+ * 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.
+*/
+
+KylinApp.service('ModelList',function(ModelService,$q){
+    var models=[];
+    var _this = this;
+
+    this.list = function(queryParam){
+
+        var defer = $q.defer();
+        ModelService.list(queryParam, function (_models) {
+            angular.forEach(_models, function (models, index) {
+                if(models.name){
+//                    $scope.listAccess(models, 'modelsInstance');
+                }
+            });
+            _models = _.filter(_models,function(models){return models.name!=undefined});
+            _this.models = _this.models.concat(_models);
+            defer.resolve(_this.models.length);
+        },function(){
+            defer.reject("Failed to load models");
+        });
+        return defer.promise;
+
+    };
+
+    this.removemodels = function(models){
+        var modelsIndex = _this.models.indexOf(models);
+        if (modelsIndex > -1) {
+            _this.models.splice(modelsIndex, 1);
+        }
+    }
+
+    this.removeAll = function(){
+        _this.models=[];
+    };
+});

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/services/models.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/models.js b/webapp/app/js/services/models.js
new file mode 100644
index 0000000..7698e49
--- /dev/null
+++ b/webapp/app/js/services/models.js
@@ -0,0 +1,26 @@
+/*
+ * 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.
+*/
+
+KylinApp.factory('ModelService', ['$resource', function ($resource, config) {
+    return $resource(Config.service.url + 'models/:modelId/:propName/:propValue/:action', {}, {
+        list: {method: 'GET', params: {}, isArray: true},
+        drop: {method: 'DELETE', params: {}, isArray: false},
+        save: {method: 'POST', params: {}, isArray: false},
+        update: {method: 'PUT', params: {}, isArray: false},
+    });
+}]);

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/services/tree.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/tree.js b/webapp/app/js/services/tree.js
index 0a565af..d6f346f 100755
--- a/webapp/app/js/services/tree.js
+++ b/webapp/app/js/services/tree.js
@@ -22,15 +22,15 @@ KylinApp.service('CubeGraphService', function () {
         width = 1100 - margin.right - margin.left,
         height = 600;
 
-    this.buildTree = function (cube) {
-        $("#cube_graph_" + cube.name).empty();
+    this.buildTree = function (model) {
+        $("#model_graph_" + model.name).empty();
 
         var tree = d3.layout.tree().size([height, width - 160]);
         var diagonal = d3.svg.diagonal().projection(function (d) {
             return [d.y, d.x];
         });
 
-        var svg = d3.select("#cube_graph_" + cube.name).append("svg:svg")
+        var svg = d3.select("#model_graph_" + model.name).append("svg:svg")
             .attr("width", width + margin.right + margin.left)
             .attr("height", height)
             .append("svg:g")
@@ -38,14 +38,13 @@ KylinApp.service('CubeGraphService', function () {
 
         var graphData = {
             "type": "fact",
-            "name": cube.model.fact_table,
+            "name": model.fact_table,
             "children": []
         };
 
-        cube.graph = (!!cube.graph) ? cube.graph : {};
+        model.graph = (!!model.graph) ? model.graph : {};
 
-      //angular.forEach(cube.detail.dimensions, function (dimension, index) {
-      angular.forEach(cube.model.lookups, function (lookup, index) {
+      angular.forEach(model.lookups, function (lookup, index) {
         if (lookup.join && lookup.join.primary_key.length > 0) {
 
           var dimensionNode;
@@ -76,9 +75,9 @@ KylinApp.service('CubeGraphService', function () {
         }
       });
 
-      angular.forEach(cube.detail.dimensions, function (dimension, index) {
+      angular.forEach(model.dimensions, function (dimension, index) {
         // for dimension on lookup table
-        if(cube.model.fact_table!==dimension.table){
+        if(model.fact_table!==dimension.table){
             var lookup = _.find(graphData.children,function(item){
               return item.name === dimension.table;
             });
@@ -130,28 +129,28 @@ KylinApp.service('CubeGraphService', function () {
 
         };
       });
-        cube.graph.columnsCount = 0;
-        cube.graph.tree = tree;
-        cube.graph.root = graphData;
-        cube.graph.svg = svg;
-        cube.graph.diagonal = diagonal;
-        cube.graph.i = 0;
-
-        cube.graph.root.x0 = height / 2;
-        cube.graph.root.y0 = 0;
-        update(cube.graph.root, cube);
+        model.graph.columnsCount = 0;
+        model.graph.tree = tree;
+        model.graph.root = graphData;
+        model.graph.svg = svg;
+        model.graph.diagonal = diagonal;
+        model.graph.i = 0;
+
+        model.graph.root.x0 = height / 2;
+        model.graph.root.y0 = 0;
+        update(model.graph.root, model);
     }
 
-    function update(source, cube) {
+    function update(source, model) {
         var duration = 750;
 
         // Compute the new tree layout.
-        var nodes = cube.graph.tree.nodes(cube.graph.root).reverse();
+        var nodes = model.graph.tree.nodes(model.graph.root).reverse();
 
         // Update the nodes
-        var node = cube.graph.svg.selectAll("g.node")
+        var node = model.graph.svg.selectAll("g.node")
             .data(nodes, function (d) {
-                return d.id || (d.id = ++cube.graph.i);
+                return d.id || (d.id = ++model.graph.i);
             });
 
         var nodeEnter = node.enter().append("svg:g")
@@ -181,22 +180,22 @@ KylinApp.service('CubeGraphService', function () {
                     d.children = null;
 
                     if (d.type == 'dimension') {
-                        cube.graph.columnsCount -= d._children.length;
+                        model.graph.columnsCount -= d._children.length;
                     }
                 } else {
                     d.children = d._children;
                     d._children = null;
 
                     if (d.type == 'dimension') {
-                        cube.graph.columnsCount += d.children.length;
+                        model.graph.columnsCount += d.children.length;
                     }
                 }
 
                 var perColumn = 35;
-                var newHeight = (((cube.graph.columnsCount * perColumn > height) ? cube.graph.columnsCount * perColumn : height));
-                $("#cube_graph_" + cube.name + " svg").height(newHeight);
-                cube.graph.tree.size([newHeight, width - 160]);
-                update(d, cube);
+                var newHeight = (((model.graph.columnsCount * perColumn > height) ? model.graph.columnsCount * perColumn : height));
+                $("#model_graph_" + model.name + " svg").height(newHeight);
+                model.graph.tree.size([newHeight, width - 160]);
+                update(d, model);
             });
 
         nodeEnter.append("svg:text")
@@ -210,7 +209,7 @@ KylinApp.service('CubeGraphService', function () {
                     var joinTip = "";
 
                     angular.forEach(d.join.primary_key, function (pk, index) {
-                        joinTip += ( cube.graph.root.name + "." + d.join.foreign_key[index] + " = " + d.name + "." + pk + "<br>");
+                        joinTip += ( model.graph.root.name + "." + d.join.foreign_key[index] + " = " + d.name + "." + pk + "<br>");
                     });
 
                     d.tooltip = d3.select("body")
@@ -286,8 +285,8 @@ KylinApp.service('CubeGraphService', function () {
             .remove();
 
         // Update the links…
-        var link = cube.graph.svg.selectAll("path.link")
-            .data(cube.graph.tree.links(nodes), function (d) {
+        var link = model.graph.svg.selectAll("path.link")
+            .data(model.graph.tree.links(nodes), function (d) {
                 return d.target.id;
             });
 
@@ -296,23 +295,23 @@ KylinApp.service('CubeGraphService', function () {
             .attr("class", "link")
             .attr("d", function (d) {
                 var o = {x: source.x0, y: source.y0};
-                return cube.graph.diagonal({source: o, target: o});
+                return model.graph.diagonal({source: o, target: o});
             })
             .transition()
             .duration(duration)
-            .attr("d", cube.graph.diagonal);
+            .attr("d", model.graph.diagonal);
 
         // Transition links to their new position.
         link.transition()
             .duration(duration)
-            .attr("d", cube.graph.diagonal);
+            .attr("d", model.graph.diagonal);
 
         // Transition exiting nodes to the parent's new position.
         link.exit().transition()
             .duration(duration)
             .attr("d", function (d) {
                 var o = {x: source.x, y: source.y};
-                return cube.graph.diagonal({source: o, target: o});
+                return model.graph.diagonal({source: o, target: o});
             })
             .remove();
 

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/js/services/users.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/users.js b/webapp/app/js/services/users.js
index 7308a98..55c1508 100644
--- a/webapp/app/js/services/users.js
+++ b/webapp/app/js/services/users.js
@@ -18,9 +18,9 @@
 
 KylinApp.service('UserService', function ($http, $q) {
     var roles = {
-        'ROLE_MODELER': '/cubes',
-        'ROLE_ANALYST': '/cubes',
-        'ROLE_ADMIN': '/cubes'
+        'ROLE_MODELER': '/models',
+        'ROLE_ANALYST': '/models',
+        'ROLE_ADMIN': '/models'
     };
     var curUser = {};
 

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/less/app.less
----------------------------------------------------------------------
diff --git a/webapp/app/less/app.less b/webapp/app/less/app.less
index f60a698..7296eb9 100644
--- a/webapp/app/less/app.less
+++ b/webapp/app/less/app.less
@@ -213,6 +213,10 @@ table .radio {
 .cube-detail {
   background-color: white;
 }
+.model-detail {
+  background-color: white;
+}
+
 
 /* Query content */
 .query-content {
@@ -316,7 +320,7 @@ pre {
 }
 
 //cube graph
-.cube_graph {
+.model_graph {
   svg {
     width: auto;
     height: auto;

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/partials/cubeDesigner/data_model.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/cubeDesigner/data_model.html b/webapp/app/partials/cubeDesigner/data_model.html
deleted file mode 100644
index 4aeb1da..0000000
--- a/webapp/app/partials/cubeDesigner/data_model.html
+++ /dev/null
@@ -1,199 +0,0 @@
-<!--
-* 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.
--->
-
-<div ng-controller="CubeModelCtrl">
-    <ng-form name="model_form">
-
-    <!-- Fact Table Name -->
-    <div class="form-group">
-        <div class="row">
-            <label class="col-xs-12 col-sm-2 concube.detailtrol-label no-padding-right font-color-default">
-                <b>Fact Table</b>
-            </label>
-            <div class="col-xs-12 col-sm-6">
-                <typeahead ng-if="state.mode=='edit'" items="tableModel.selectProjectTables" prompt="Fact Table Name"
-                           title="name" model="metaModel.model.fact_table" required="true"></typeahead>
-                <span ng-if="state.mode=='view'">{{metaModel.model.fact_table}}</span>
-            </div>
-        </div>
-    </div>
-
-    <!-- Lookup Tables Summary -->
-    <div class="dataTables_wrapper form-inline no-footer">
-        <div class="row">
-            <div class="col-xs-6" ng-if="state.mode=='edit'">
-                <button type="button" class="btn btn-primary" ng-disabled="!metaModel.model.fact_table.length"
-                        ng-click="openLookupModal()">
-                    <i class="fa fa-plus"></i> Add Lookup Table
-                </button>
-            </div>
-            <div class="col-xs-6" ng-if="state.mode!='edit'">
-                <b>{{metaModel.model.lookups.length ? 'Lookup Tables' : 'No Lookup Tables'}}</b>
-            </div>
-            <div class="col-xs-6">
-                <span class="pull-right input-icon input-icon-right nav-search" style="margin-left: 22px;" ng-if="metaModel.model.lookups.length">
-                    <input type="text" placeholder="Filter ..." class="nav-search-input" ng-model="lookupState.filter"/>
-                    <i class="ace-icon fa fa-search nav-search-icon"></i>
-                </span>
-            </div>
-        </div>
-        <table class="table table-striped table-hover" ng-if="metaModel.model.lookups.length">
-            <thead>
-            <tr>
-                <th>ID</th>
-                <th>Table Name</th>
-                <th>Join Type</th>
-                <th>Join Condition</th>
-                <th ng-if="state.mode=='edit'">Actions</th>
-            </tr>
-            </thead>
-            <tbody>
-            <tr ng-repeat="lookup in metaModel.model.lookups | filter:lookupState.filter track by $index">
-                <td>
-                    <b>{{$index + 1}}</b>
-                </td>
-                <!-- Table Name -->
-                <td>
-                    <span tooltip="Lookup Table Name">{{lookup.table}}</span>
-                </td>
-                <!-- Join Type -->
-                <td>
-                    <span>{{lookup.join.type}}</span>
-                </td>
-                <!-- Join Condition -->
-                <td>
-                    <ul class="list-unstyled">
-                        <li ng-repeat="pk in lookup.join.primary_key track by $index">
-                            <code>{{lookup.table + '.' + pk}} = {{metaModel.model.fact_table + '.' + lookup.join.foreign_key[$index]}}</code>
-                        </li>
-                    </ul>
-                </td>
-                <td ng-if="state.mode=='edit'">
-                    <!-- edit button -->
-                    <button class="btn btn-xs btn-info" ng-disabled="lookupState.editing"
-                            ng-click="editLookup(lookup)" tooltip="Edit Lookup"><i class="fa fa-pencil"></i>
-                    </button>
-                    <!-- remove button -->
-                    <button class="btn btn-xs btn-danger" ng-disabled="lookupState.editing"
-                            ng-click="removeLookup(lookup)" tooltip="Remove Lookup"><i class="fa fa-trash-o"></i>
-                    </button>
-                </td>
-            </tr>
-            </tbody>
-        </table>
-    </div>
-    </ng-form>
-
-    <!-- Add Lookup Table Form -->
-    <script type="text/ng-template" id="dataModelLookupTable.html">
-        <div class="modal-header">
-            <h4 class="box-title lighter">{{lookupState.editing ? 'Edit' : 'Add'}} Lookup</h4>
-        </div>
-        <div class="modal-body">
-            <div class="row">
-                <div class="col-xs-8">
-                    <ng-form name="lookup_form">
-                    <!--Table Name-->
-                    <div class="form-group">
-                        <div class="row">
-                            <label class="control-label col-xs-12 col-sm-3 no-padding-right font-color-default"><b>Lookup Table Name</b></label>
-                            <div class="col-xs-12 col-sm-6">
-                                <typeahead items="tableModel.selectProjectTables" prompt="Lookup Table Name" title="name" model="newLookup.table"></typeahead>
-                            </div>
-                        </div>
-                    </div>
-
-                    <!--Join Type and Columns-->
-                    <div class="form-group">
-                        <div class="row">
-                            <label class="col-sm-3 control-label font-color-default"><b>Join Type</b></label>
-                            <div class="col-sm-6">
-                                <select class="form-control" chosen ng-model="newLookup.join.type"
-                                        ng-options="joinType.value as joinType.name for joinType in cubeConfig.joinTypes">
-                                    <option value=""></option>
-                                </select>
-                            </div>
-                        </div>
-                    </div>
-                    <div class="form-group">
-                        <div class="row">
-                            <div class="col-xs-9">
-                                <div ng-repeat="joinIndex in [] | range: newLookup.join.primary_key.length">
-                                    <div>
-                                        <select style="width: 45%" chosen data-placeholder="Lookup Table Column"
-                                                ng-model="newLookup.join.primary_key[$index]"
-                                                ng-options="columns.name as columns.name for columns in getColumnsByTable(newLookup.table)" >
-                                            <option value=""></option>
-                                        </select>
-                                        <b>=</b>
-                                        <select style="width: 45%" chosen data-placeholder="Fact Table Column"
-                                                ng-model="newLookup.join.foreign_key[$index]"
-                                                ng-options="columns.name as columns.name for columns in getColumnsByTable(metaModel.model.fact_table)" >
-                                            <option value=""></option>
-                                        </select>
-                                        <button class="pull-right btn btn-xs btn-danger" style="cursor: pointer" tooltip="Delete"
-                                                ng-click="removeJoin($index);">
-                                            <i class="fa fa-trash-o pointer"></i>
-                                        </button>
-                                    </div>
-                                    <div class="space-4"></div>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                    <div class="form-group">
-                        <div class="row">
-                            <div class="col-sm-3">
-                                <button class="btn btn-xs btn-info"
-                                        ng-if="newLookup.join.type"
-                                        ng-click="addNewJoin();">
-                                    <i class="fa fa-plus"></i> New Join Condition</button>
-                            </div>
-                        </div>
-                    </div>
-
-                    </ng-form>
-                </div>
-
-                <!--Tips-->
-                <div class="col-xs-4">
-                    <div class="box box-solid">
-                        <div class="box-header">
-                            <h4 class="box-title">Tips</h4>
-                        </div>
-                        <div class="box-body">
-                            <div class="row">
-                                <div class="col-xs-12">
-                                    <ol class="text-info">
-                                        <li>Pick up lookup table at first</li>
-                                        <li>Specify join relationship between chosen lookup table and fact table</li>
-                                        <li>Join Type have to be same as will be used in query</li>
-                                    </ol>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-        <div class="modal-footer">
-            <button class="btn btn-primary" ng-disabled="lookup_form.$invalid || !newLookup.join.primary_key.length" ng-click="ok()">OK</button>
-            <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
-        </div>
-    </script>
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/partials/header.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/header.html b/webapp/app/partials/header.html
index 18a9b66..9a55034 100644
--- a/webapp/app/partials/header.html
+++ b/webapp/app/partials/header.html
@@ -43,8 +43,8 @@
                     <li class="{{activeTab=='query'?'purple':'green'}}" ng-if="userService.isAuthorized()">
                         <a href="query">Insight</a>
                     </li>
-                    <li class="{{activeTab=='cubes'?'purple':'green'}}" ng-if="userService.isAuthorized()">
-                        <a href="cubes">Model</a>
+                    <li class="{{activeTab=='models'?'purple':'green'}}" ng-if="userService.isAuthorized()">
+                        <a href="models">Model</a>
                     </li>
                     <li class="{{activeTab=='jobs'?'purple':'green'}}" ng-if="userService.isAuthorized()">
                         <a href="jobs">Monitor</a>

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/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
new file mode 100644
index 0000000..c8cc5a7
--- /dev/null
+++ b/webapp/app/partials/modelDesigner/data_model.html
@@ -0,0 +1,199 @@
+<!--
+* 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.
+-->
+
+<div ng-controller="CubeModelCtrl">
+    <ng-form name="model_form">
+
+    <!-- Fact Table Name -->
+    <div class="form-group">
+        <div class="row">
+            <label class="col-xs-12 col-sm-2 concube.detailtrol-label no-padding-right font-color-default">
+                <b>Fact Table</b>
+            </label>
+            <div class="col-xs-12 col-sm-6">
+                <typeahead ng-if="state.mode=='edit'" items="tableModel.selectProjectTables" prompt="Fact Table Name"
+                           title="name" model="model.fact_table" required="true"></typeahead>
+                <span ng-if="state.mode=='view'">{{model.fact_table}}</span>
+            </div>
+        </div>
+    </div>
+
+    <!-- Lookup Tables Summary -->
+    <div class="dataTables_wrapper form-inline no-footer">
+        <div class="row">
+            <div class="col-xs-6" ng-if="state.mode=='edit'">
+                <button type="button" class="btn btn-primary" ng-disabled="!model.fact_table.length"
+                        ng-click="openLookupModal()">
+                    <i class="fa fa-plus"></i> Add Lookup Table
+                </button>
+            </div>
+            <div class="col-xs-6" ng-if="state.mode!='edit'">
+                <b>{{model.lookups.length ? 'Lookup Tables' : 'No Lookup Tables'}}</b>
+            </div>
+            <div class="col-xs-6">
+                <span class="pull-right input-icon input-icon-right nav-search" style="margin-left: 22px;" ng-if="model.lookups.length">
+                    <input type="text" placeholder="Filter ..." class="nav-search-input" ng-model="lookupState.filter"/>
+                    <i class="ace-icon fa fa-search nav-search-icon"></i>
+                </span>
+            </div>
+        </div>
+        <table class="table table-striped table-hover" ng-if="model.lookups.length">
+            <thead>
+            <tr>
+                <th>ID</th>
+                <th>Table Name</th>
+                <th>Join Type</th>
+                <th>Join Condition</th>
+                <th ng-if="state.mode=='edit'">Actions</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr ng-repeat="lookup in model.lookups | filter:lookupState.filter track by $index">
+                <td>
+                    <b>{{$index + 1}}</b>
+                </td>
+                <!-- Table Name -->
+                <td>
+                    <span tooltip="Lookup Table Name">{{lookup.table}}</span>
+                </td>
+                <!-- Join Type -->
+                <td>
+                    <span>{{lookup.join.type}}</span>
+                </td>
+                <!-- Join Condition -->
+                <td>
+                    <ul class="list-unstyled">
+                        <li ng-repeat="pk in lookup.join.primary_key track by $index">
+                            <code>{{lookup.table + '.' + pk}} = {{model.fact_table + '.' + lookup.join.foreign_key[$index]}}</code>
+                        </li>
+                    </ul>
+                </td>
+                <td ng-if="state.mode=='edit'">
+                    <!-- edit button -->
+                    <button class="btn btn-xs btn-info" ng-disabled="lookupState.editing"
+                            ng-click="editLookup(lookup)" tooltip="Edit Lookup"><i class="fa fa-pencil"></i>
+                    </button>
+                    <!-- remove button -->
+                    <button class="btn btn-xs btn-danger" ng-disabled="lookupState.editing"
+                            ng-click="removeLookup(lookup)" tooltip="Remove Lookup"><i class="fa fa-trash-o"></i>
+                    </button>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+    </ng-form>
+
+    <!-- Add Lookup Table Form -->
+    <script type="text/ng-template" id="dataModelLookupTable.html">
+        <div class="modal-header">
+            <h4 class="box-title lighter">{{lookupState.editing ? 'Edit' : 'Add'}} Lookup</h4>
+        </div>
+        <div class="modal-body">
+            <div class="row">
+                <div class="col-xs-8">
+                    <ng-form name="lookup_form">
+                    <!--Table Name-->
+                    <div class="form-group">
+                        <div class="row">
+                            <label class="control-label col-xs-12 col-sm-3 no-padding-right font-color-default"><b>Lookup Table Name</b></label>
+                            <div class="col-xs-12 col-sm-6">
+                                <typeahead items="tableModel.selectProjectTables" prompt="Lookup Table Name" title="name" model="newLookup.table"></typeahead>
+                            </div>
+                        </div>
+                    </div>
+
+                    <!--Join Type and Columns-->
+                    <div class="form-group">
+                        <div class="row">
+                            <label class="col-sm-3 control-label font-color-default"><b>Join Type</b></label>
+                            <div class="col-sm-6">
+                                <select class="form-control" chosen ng-model="newLookup.join.type"
+                                        ng-options="joinType.value as joinType.name for joinType in cubeConfig.joinTypes">
+                                    <option value=""></option>
+                                </select>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <div class="row">
+                            <div class="col-xs-9">
+                                <div ng-repeat="joinIndex in [] | range: newLookup.join.primary_key.length">
+                                    <div>
+                                        <select style="width: 45%" chosen data-placeholder="Lookup Table Column"
+                                                ng-model="newLookup.join.primary_key[$index]"
+                                                ng-options="columns.name as columns.name for columns in getColumnsByTable(newLookup.table)" >
+                                            <option value=""></option>
+                                        </select>
+                                        <b>=</b>
+                                        <select style="width: 45%" chosen data-placeholder="Fact Table Column"
+                                                ng-model="newLookup.join.foreign_key[$index]"
+                                                ng-options="columns.name as columns.name for columns in getColumnsByTable(model.fact_table)" >
+                                            <option value=""></option>
+                                        </select>
+                                        <button class="pull-right btn btn-xs btn-danger" style="cursor: pointer" tooltip="Delete"
+                                                ng-click="removeJoin($index);">
+                                            <i class="fa fa-trash-o pointer"></i>
+                                        </button>
+                                    </div>
+                                    <div class="space-4"></div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <div class="row">
+                            <div class="col-sm-3">
+                                <button class="btn btn-xs btn-info"
+                                        ng-if="newLookup.join.type"
+                                        ng-click="addNewJoin();">
+                                    <i class="fa fa-plus"></i> New Join Condition</button>
+                            </div>
+                        </div>
+                    </div>
+
+                    </ng-form>
+                </div>
+
+                <!--Tips-->
+                <div class="col-xs-4">
+                    <div class="box box-solid">
+                        <div class="box-header">
+                            <h4 class="box-title">Tips</h4>
+                        </div>
+                        <div class="box-body">
+                            <div class="row">
+                                <div class="col-xs-12">
+                                    <ol class="text-info">
+                                        <li>Pick up lookup table at first</li>
+                                        <li>Specify join relationship between chosen lookup table and fact table</li>
+                                        <li>Join Type have to be same as will be used in query</li>
+                                    </ol>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="modal-footer">
+            <button class="btn btn-primary" ng-disabled="lookup_form.$invalid || !newLookup.join.primary_key.length" ng-click="ok()">OK</button>
+            <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
+        </div>
+    </script>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-kylin/blob/7b526242/webapp/app/partials/modelDesigner/incremental.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/modelDesigner/incremental.html b/webapp/app/partials/modelDesigner/incremental.html
new file mode 100644
index 0000000..3238b1c
--- /dev/null
+++ b/webapp/app/partials/modelDesigner/incremental.html
@@ -0,0 +1,94 @@
+<!--
+* 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.
+-->
+
+<div ng-controller="ModelRefreshCtrl">
+<div class="row">
+    <div class="col-xs-8">
+
+        <div class="form-group" ng-hide="true">
+            <div class="row">
+                <label class="control-label col-xs-12 col-sm-3 no-padding-right font-color-default"><b>Partition Type</b></label>
+                <div class="col-xs-12 col-sm-6">
+                    <select class="form-control"
+                        ng-if="state.mode=='edit'"
+                        chosen ng-model="model.partition_desc.partition_type"
+                        ng-options="ddt as ddt for ddt in cubeConfig.cubePartitionTypes">
+                        <option value=""></option>
+                    </select>
+                    <span ng-if="state.mode=='view'">{{model.partition_desc.partition_type}}</span>
+                </div>
+            </div>
+        </div>
+
+        <!--Partition Column-->
+        <div class="form-group">
+            <div class="row">
+                <label class="control-label col-xs-12 col-sm-3 no-padding-right font-color-default"><b>Partition Date Column</b></label>
+                <div class="col-xs-12 col-sm-6">
+
+                    <select style="width: 100%" chosen data-placeholder="e.g. DEFAULT.TEST_KYLIN_FACT.CAL_DT"
+                            ng-required="model.partition_desc.partition_date_start"
+                            ng-model="model.partition_desc.partition_date_column"
+                            ng-if="state.mode=='edit'"
+                            data-placement=""
+                            ng-options="model.fact_table+'.'+columns.name as model.fact_table+'.'+columns.name for columns in getPartitonColumns(model.fact_table)" >
+                        <option value="">--Select Partition Column--</option>
+                    </select>
+                    <p class="text-red"  ng-if="state.mode=='edit'">(data format in column should be 'YYYY-MM-DD')</p>
+                    <span ng-if="state.mode=='view'">
+                        {{!!(model.partition_desc.partition_date_column)?model.partition_desc.partition_date_column: ''}}</span>
+                </div>
+            </div>
+        </div>
+
+        <!--Data Range Start-->
+        <div class="form-group">
+            <div class="row">
+                <label class="control-label col-xs-12 col-sm-3 no-padding-right font-color-default"><b>Start Date</b></label>
+                <div class="col-xs-12 col-sm-6">
+                  <!--edit model will convert in MetaModel.converDateToGMT-->
+                    <input type="text" class="form-control" datepicker-popup="yyyy-MM-dd"
+                           ng-model="model.partition_desc.partition_date_start" ng-if="state.mode=='edit'"
+                           placeholder="Click to choose start date..." is-open="opened" />
+                  <!--vier model will convert use filter-->
+                    <span ng-if="state.mode=='view'&&model.partition_desc.partition_date_column">{{(model.partition_desc.partition_date_start)|reverseToGMT0 }}</span>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="col-xs-4">
+        <div class="box box-solid">
+            <div class="box-header">
+                <h4 class="box-title">Tips</h4>
+            </div>
+            <div class="box-body">
+                <div class="row">
+                    <div class="col-xs-12">
+                        <ol class="text-info">
+                            <li>Not required,leave as default if cube always need full build</li>
+                            <li>Partition column will select 'date' or 'string' type column from fact table</li>
+                            <li>If column selected,please indicate start date to just pull certain data from source</li>
+                        </ol>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+</div>