Repository: kylin
Updated Branches:
  refs/heads/2.x-staging d829340c5 -> 192bbf913


KYLIN-1128 cube,model  metadata clone


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

Branch: refs/heads/2.x-staging
Commit: 192bbf9130d04cd42fcdf1a0235e824da455a799
Parents: d829340
Author: jian <zhongj...@apache.org>
Authored: Fri Jan 22 01:20:39 2016 +0800
Committer: jian <zhongj...@apache.org>
Committed: Fri Jan 22 01:20:39 2016 +0800

----------------------------------------------------------------------
 .../org/apache/kylin/cube/model/CubeDesc.java   |   1 +
 .../kylin/rest/controller/CubeController.java   | 167 ++++++++++++++++---
 .../kylin/rest/controller/ModelController.java  |  46 ++++-
 webapp/app/js/controllers/cubes.js              |  69 ++++++++
 webapp/app/js/controllers/models.js             |  67 ++++++++
 webapp/app/js/services/cubes.js                 |   1 +
 webapp/app/js/services/models.js                |   1 +
 webapp/app/less/app.less                        |   8 +
 webapp/app/partials/cubes/cube_clone.html       |  63 +++++++
 webapp/app/partials/cubes/cubes.html            |   2 +
 webapp/app/partials/models/model_clone.html     |  63 +++++++
 webapp/app/partials/models/models.html          |   1 +
 webapp/app/partials/models/models_tree.html     |   7 +-
 13 files changed, 468 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/core-cube/src/main/java/org/apache/kylin/cube/model/CubeDesc.java
----------------------------------------------------------------------
diff --git a/core-cube/src/main/java/org/apache/kylin/cube/model/CubeDesc.java 
b/core-cube/src/main/java/org/apache/kylin/cube/model/CubeDesc.java
index 2c1e623..fe19ac9 100644
--- a/core-cube/src/main/java/org/apache/kylin/cube/model/CubeDesc.java
+++ b/core-cube/src/main/java/org/apache/kylin/cube/model/CubeDesc.java
@@ -810,6 +810,7 @@ public class CubeDesc extends RootPersistentEntity {
         newCubeDesc.setRetentionRange(cubeDesc.getRetentionRange());
         newCubeDesc.setEngineType(cubeDesc.getEngineType());
         newCubeDesc.setStorageType(cubeDesc.getStorageType());
+        newCubeDesc.setAggregationGroups(cubeDesc.getAggregationGroups());
         newCubeDesc.setConfig(cubeDesc.getConfig());
         newCubeDesc.updateRandomUuid();
         return newCubeDesc;

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/server/src/main/java/org/apache/kylin/rest/controller/CubeController.java
----------------------------------------------------------------------
diff --git 
a/server/src/main/java/org/apache/kylin/rest/controller/CubeController.java 
b/server/src/main/java/org/apache/kylin/rest/controller/CubeController.java
index f9ac14e..9afa750 100644
--- a/server/src/main/java/org/apache/kylin/rest/controller/CubeController.java
+++ b/server/src/main/java/org/apache/kylin/rest/controller/CubeController.java
@@ -18,7 +18,7 @@
 
 package org.apache.kylin.rest.controller;
 
-import java.io.IOException;
+import java.io.*;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -258,6 +258,123 @@ public class CubeController extends BasicController {
         }
     }
 
+
+    @RequestMapping(value = "/{cubeName}/clone", method = {RequestMethod.PUT})
+    @ResponseBody
+    public CubeInstance cloneCube(@PathVariable String cubeName, @RequestBody 
CubeRequest cubeRequest) {
+        String newCubeName = cubeRequest.getCubeName();
+        String project = cubeRequest.getProject();
+
+        CubeInstance cube = cubeService.getCubeManager().getCube(cubeName);
+        if (cube == null) {
+            throw new InternalErrorException("Cannot find cube " + cubeName);
+        }
+        CubeDesc cubeDesc = cube.getDescriptor();
+        CubeDesc newCubeDesc = CubeDesc.getCopyOf(cubeDesc);
+
+        newCubeDesc.setName(newCubeName);
+
+        CubeInstance newCube = null;
+        try {
+            newCube = cubeService.createCubeAndDesc(newCubeName, project, 
newCubeDesc);
+
+            //reload to avoid shallow clone
+            cubeService.getCubeDescManager().reloadCubeDescLocal(newCubeName);
+        } catch (IOException e) {
+            throw new InternalErrorException("Failed to clone cube ", e);
+        }
+
+        boolean isStreamingCube = false, cloneStreamingConfigSuccess = false, 
cloneKafkaConfigSuccess = false;
+
+
+        List<StreamingConfig> streamingConfigs = null;
+        try {
+            streamingConfigs = 
streamingService.listAllStreamingConfigs(cubeName);
+            if (streamingConfigs.size() != 0) {
+                isStreamingCube = true;
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        StreamingConfig newStreamingConfig = null;
+        KafkaConfig newKafkaConfig = null;
+
+        try {
+
+            if (isStreamingCube) {
+
+                isStreamingCube = true;
+                newStreamingConfig = streamingConfigs.get(0).clone();
+                newStreamingConfig.setName(newCubeName + "_STREAMING");
+                newStreamingConfig.updateRandomUuid();
+                newStreamingConfig.setLastModified(0);
+                newStreamingConfig.setCubeName(newCubeName);
+                try {
+                    streamingService.createStreamingConfig(newStreamingConfig);
+                    cloneStreamingConfigSuccess = true;
+                } catch (IOException e) {
+                    throw new InternalErrorException("Failed to clone 
streaming config. ", e);
+                }
+
+                //StreamingConfig name and KafkaConfig name is the same for 
same cube
+                String kafkaConfigName = streamingConfigs.get(0).getName();
+                KafkaConfig kafkaConfig = null;
+                try {
+                    kafkaConfig = 
kafkaConfigService.getKafkaConfig(kafkaConfigName);
+                    if (kafkaConfig != null) {
+                        newKafkaConfig = kafkaConfig.clone();
+                        newKafkaConfig.setName(newStreamingConfig.getName());
+                        newKafkaConfig.setLastModified(0);
+                        newKafkaConfig.updateRandomUuid();
+                    }
+                } catch (IOException e) {
+                    throw new InternalErrorException("Failed to get kafka 
config info. ", e);
+                }
+
+                try {
+                    kafkaConfigService.createKafkaConfig(newKafkaConfig);
+                    cloneKafkaConfigSuccess = true;
+                } catch (IOException e) {
+                    throw new InternalErrorException("Failed to clone 
streaming config. ", e);
+                }
+            }
+        } finally {
+
+            //rollback if failed
+            if (isStreamingCube) {
+                if (cloneStreamingConfigSuccess == false || 
cloneKafkaConfigSuccess == false) {
+                    try {
+                        cubeService.deleteCube(newCube);
+                    } catch (Exception ex) {
+                        throw new InternalErrorException("Failed, and failed 
to rollback on delete cube. " + " Caused by: " + ex.getMessage(), ex);
+                    }
+                    if (cloneStreamingConfigSuccess == true) {
+                        try {
+                            
streamingService.dropStreamingConfig(newStreamingConfig);
+                        } catch (IOException e) {
+                            throw new InternalErrorException("Failed to clone 
cube, and StreamingConfig created and failed to delete: " + 
e.getLocalizedMessage());
+                        }
+                    }
+                    if (cloneKafkaConfigSuccess == true) {
+                        try {
+                            kafkaConfigService.dropKafkaConfig(newKafkaConfig);
+                        } catch (IOException e) {
+                            throw new InternalErrorException("Failed to clone 
cube, and KafkaConfig created and failed to delete: " + 
e.getLocalizedMessage());
+                        }
+                    }
+
+                }
+
+            }
+        }
+
+        return newCube;
+
+    }
+
+
     @RequestMapping(value = "/{cubeName}/enable", method = {RequestMethod.PUT})
     @ResponseBody
     public CubeInstance enableCube(@PathVariable String cubeName) {
@@ -346,7 +463,7 @@ public class CubeController extends BasicController {
             throw new InternalErrorException(e.getLocalizedMessage(), e);
         }
 
-        boolean createStreamingConfigSuccess = false,createKafkaConfigSuccess 
= false;
+        boolean createStreamingConfigSuccess = false, createKafkaConfigSuccess 
= false;
         StreamingConfig streamingConfig = null;
         KafkaConfig kafkaConfig = null;
 
@@ -361,9 +478,10 @@ public class CubeController extends BasicController {
                     cubeRequest.setMessage("No KafkaConfig info defined.");
                     return cubeRequest;
                 }
-                if(streamingConfig == null){
+                if (streamingConfig == null) {
                     cubeRequest.setMessage("No StreamingConfig info defined.");
-                    return cubeRequest;                }
+                    return cubeRequest;
+                }
 
                 try {
                     streamingConfig.setUuid(UUID.randomUUID().toString());
@@ -383,23 +501,23 @@ public class CubeController extends BasicController {
                 }
 
             }
-        }finally {
+        } finally {
             //rollback if failed
             if (isStreamingCube) {
-                if(createStreamingConfigSuccess == false || 
createKafkaConfigSuccess == false){
+                if (createStreamingConfigSuccess == false || 
createKafkaConfigSuccess == false) {
                     try {
                         cubeService.deleteCube(cubeInstance);
                     } catch (Exception ex) {
                         throw new InternalErrorException("Failed to rollback 
on delete cube. " + " Caused by: " + ex.getMessage(), ex);
                     }
-                    if(createStreamingConfigSuccess == true){
+                    if (createStreamingConfigSuccess == true) {
                         try {
                             
streamingService.dropStreamingConfig(streamingConfig);
                         } catch (IOException e) {
                             throw new InternalErrorException("Failed to create 
cube, and StreamingConfig created and failed to delete: " + 
e.getLocalizedMessage());
                         }
                     }
-                    if(createKafkaConfigSuccess == true){
+                    if (createKafkaConfigSuccess == true) {
                         try {
                             kafkaConfigService.dropKafkaConfig(kafkaConfig);
                         } catch (IOException e) {
@@ -469,14 +587,14 @@ public class CubeController extends BasicController {
             return cubeRequest;
         }
 
-        boolean updateStreamingConfigSuccess = false,updateKafkaConfigSuccess 
= false;
+        boolean updateStreamingConfigSuccess = false, updateKafkaConfigSuccess 
= false;
 
         boolean isStreamingCube = cubeRequest.getStreamingCube() != null && 
cubeRequest.getStreamingCube().equals("true");
 
         //oldConfig is for recover use
-        StreamingConfig streamingConfig = null,oldStreamingConfig =null;
-        KafkaConfig kafkaConfig = null,oldKafkaConfig = null;
-        if(isStreamingCube){
+        StreamingConfig streamingConfig = null, oldStreamingConfig = null;
+        KafkaConfig kafkaConfig = null, oldKafkaConfig = null;
+        if (isStreamingCube) {
             streamingConfig = deserializeStreamingDesc(cubeRequest);
             kafkaConfig = deserializeKafkaDesc(cubeRequest);
             try {
@@ -498,7 +616,7 @@ public class CubeController extends BasicController {
                     return cubeRequest;
                 }
 
-                if(oldStreamingConfig == null){
+                if (oldStreamingConfig == null) {
                     streamingConfig.setUuid(UUID.randomUUID().toString());
                     try {
                         
streamingService.createStreamingConfig(streamingConfig);
@@ -507,7 +625,7 @@ public class CubeController extends BasicController {
                         logger.error("Failed to add StreamingConfig:" + 
e.getLocalizedMessage(), e);
                         throw new InternalErrorException("Failed to add 
StreamingConfig: " + e.getLocalizedMessage());
                     }
-                }else{
+                } else {
                     try {
                         streamingConfig = 
streamingService.updateStreamingConfig(streamingConfig);
                         updateStreamingConfigSuccess = true;
@@ -517,7 +635,7 @@ public class CubeController extends BasicController {
                         throw new InternalErrorException("Failed to update 
StreamingConfig: " + e.getLocalizedMessage());
                     }
                 }
-                if(oldKafkaConfig == null){
+                if (oldKafkaConfig == null) {
                     kafkaConfig.setUuid(UUID.randomUUID().toString());
                     try {
                         kafkaConfigService.createKafkaConfig(kafkaConfig);
@@ -527,7 +645,7 @@ public class CubeController extends BasicController {
                         throw new InternalErrorException("Failed to add 
KafkaConfig: " + e.getLocalizedMessage());
                     }
 
-                }else{
+                } else {
                     try {
                         kafkaConfig = 
kafkaConfigService.updateKafkaConfig(kafkaConfig);
                         updateKafkaConfigSuccess = true;
@@ -538,10 +656,10 @@ public class CubeController extends BasicController {
                 }
 
             }
-        }finally {
+        } finally {
             if (isStreamingCube) {
                 //recover cube desc
-                if(updateStreamingConfigSuccess == false || 
updateKafkaConfigSuccess ==false){
+                if (updateStreamingConfigSuccess == false || 
updateKafkaConfigSuccess == false) {
                     oldCubeDesc.setLastModified(desc.getLastModified());
                     CubeInstance cube = 
cubeService.getCubeManager().getCube(cubeRequest.getCubeName());
                     try {
@@ -551,9 +669,9 @@ public class CubeController extends BasicController {
                         throw new InternalErrorException("Failed to recover 
CubeDesc: " + e.getLocalizedMessage());
                     }
 
-                    if(updateStreamingConfigSuccess == true){
+                    if (updateStreamingConfigSuccess == true) {
 
-                        if(oldStreamingConfig!=null){
+                        if (oldStreamingConfig != null) {
 
                             
oldStreamingConfig.setLastModified(streamingConfig.getLastModified());
                             try {
@@ -562,7 +680,7 @@ public class CubeController extends BasicController {
                                 logger.error("Failed to recover 
StreamingConfig:" + e.getLocalizedMessage(), e);
                                 throw new InternalErrorException("Failed to 
recover StreamingConfig: " + e.getLocalizedMessage());
                             }
-                        } else{
+                        } else {
                             try {
                                 
streamingService.dropStreamingConfig(streamingConfig);
                             } catch (IOException e) {
@@ -572,8 +690,8 @@ public class CubeController extends BasicController {
                         }
                     }
 
-                    if(updateKafkaConfigSuccess == true){
-                        if(oldKafkaConfig!=null) {
+                    if (updateKafkaConfigSuccess == true) {
+                        if (oldKafkaConfig != null) {
                             
oldKafkaConfig.setLastModified(kafkaConfig.getLastModified());
                             try {
                                 
kafkaConfigService.updateKafkaConfig(oldKafkaConfig);
@@ -581,7 +699,7 @@ public class CubeController extends BasicController {
                                 logger.error("Failed to recover KafkaConfig:" 
+ e.getLocalizedMessage(), e);
                                 throw new InternalErrorException("Failed to 
recover KafkaConfig: " + e.getLocalizedMessage());
                             }
-                        }else{
+                        } else {
                             try {
                                 
kafkaConfigService.dropKafkaConfig(kafkaConfig);
                             } catch (IOException e) {
@@ -780,4 +898,5 @@ public class CubeController extends BasicController {
     public void setKafkaConfigService(KafkaConfigService kafkaConfigService) {
         this.kafkaConfigService = kafkaConfigService;
     }
+
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/server/src/main/java/org/apache/kylin/rest/controller/ModelController.java
----------------------------------------------------------------------
diff --git 
a/server/src/main/java/org/apache/kylin/rest/controller/ModelController.java 
b/server/src/main/java/org/apache/kylin/rest/controller/ModelController.java
index a409938..00b79b0 100644
--- a/server/src/main/java/org/apache/kylin/rest/controller/ModelController.java
+++ b/server/src/main/java/org/apache/kylin/rest/controller/ModelController.java
@@ -18,13 +18,15 @@
 
 package org.apache.kylin.rest.controller;
 
-import java.io.IOException;
+import java.io.*;
 import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.metadata.MetadataManager;
 import org.apache.kylin.metadata.model.DataModelDesc;
 import org.apache.kylin.metadata.project.ProjectInstance;
 import org.apache.kylin.rest.exception.BadRequestException;
@@ -149,6 +151,47 @@ public class ModelController extends BasicController {
         }
     }
 
+    @RequestMapping(value = "/{modelName}/clone", method = {RequestMethod.PUT})
+    @ResponseBody
+    public ModelRequest  cloneModel(@PathVariable String modelName, 
@RequestBody ModelRequest modelRequest) {
+        String project = modelRequest.getProject();
+        MetadataManager metaManager = 
MetadataManager.getInstance(KylinConfig.getInstanceFromEnv());
+        DataModelDesc modelDesc = metaManager.getDataModelDesc(modelName);
+        String newModelName = modelRequest.getModelName();
+
+        if (StringUtils.isEmpty(project)) {
+            logger.info("Project name should not be empty.");
+            throw new BadRequestException("Project name should not be empty.");
+        }
+
+        if (modelDesc == null || StringUtils.isEmpty(modelName)) {
+            logger.info("Model does not exist.");
+            throw new BadRequestException("Model does not exist.");
+        }
+
+        if (StringUtils.isEmpty(newModelName)) {
+            logger.info("New model name is empty.");
+            throw new BadRequestException("New model name is empty.");
+        }
+
+
+        DataModelDesc newModelDesc = DataModelDesc.getCopyOf(modelDesc);
+        newModelDesc.setName(newModelName);
+        try {
+            newModelDesc = modelService.createModelDesc(project, newModelDesc);
+
+            //reload avoid shallow
+            metaManager.reloadDataModelDesc(newModelName);
+        } catch (IOException e) {
+            throw new InternalErrorException("failed to clone DataModelDesc", 
e);
+        }
+
+        modelRequest.setUuid(newModelDesc.getUuid());
+        modelRequest.setSuccessful(true);
+        return modelRequest;
+    }
+
+
     private DataModelDesc deserializeDataModelDesc(ModelRequest modelRequest) {
         DataModelDesc desc = null;
         try {
@@ -190,4 +233,5 @@ public class ModelController extends BasicController {
         }
         return buffer.toString();
     }
+
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/webapp/app/js/controllers/cubes.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/cubes.js 
b/webapp/app/js/controllers/cubes.js
index b9a0f13..7ea626a 100644
--- a/webapp/app/js/controllers/cubes.js
+++ b/webapp/app/js/controllers/cubes.js
@@ -377,6 +377,22 @@ KylinApp
 
     };
 
+    $scope.cloneCube = function(cube){
+      $scope.loadDetail(cube).then(function () {
+        $modal.open({
+          templateUrl: 'cubeClone.html',
+          controller: cubeCloneCtrl,
+          windowClass:"clone-cube-window",
+          resolve: {
+            cube: function () {
+              return cube;
+            }
+          }
+        });
+      });
+    }
+
+
     $scope.cubeEdit = function (cube) {
       $location.path("cubes/edit/" + cube.name);
     }
@@ -402,6 +418,59 @@ KylinApp
     }
   });
 
+
+var cubeCloneCtrl = function ($scope, $modalInstance, CubeService, 
MessageService, $location, cube, MetaModel, SweetAlert,ProjectModel, 
loadingRequest) {
+  $scope.projectModel = ProjectModel;
+
+  $scope.targetObj={
+    cubeName:cube.descriptor+"_clone",
+    targetProject:$scope.projectModel.selectedProject
+  }
+
+  $scope.cancel = function () {
+    $modalInstance.dismiss('cancel');
+  };
+
+  $scope.cloneCube = function(){
+
+    $scope.cubeRequest = {
+      cubeName:$scope.targetObj.cubeName,
+      project:$scope.targetObj.targetProject
+    }
+
+    SweetAlert.swal({
+      title: '',
+      text: 'Are you sure to clone the cube? ',
+      type: '',
+      showCancelButton: true,
+      confirmButtonColor: '#DD6B55',
+      confirmButtonText: "Yes",
+      closeOnConfirm: true
+    }, function (isConfirm) {
+      if (isConfirm) {
+
+        loadingRequest.show();
+        CubeService.clone({cubeId: cube.name}, $scope.cubeRequest, function 
(result) {
+          loadingRequest.hide();
+          SweetAlert.swal('Success!', 'Clone cube successfully', 'success');
+          location.reload();
+        }, function (e) {
+          loadingRequest.hide();
+          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');
+          }
+        });
+      }
+    });
+  }
+
+}
+
+
 var jobSubmitCtrl = function ($scope, $modalInstance, CubeService, 
MessageService, $location, cube, metaModel, buildType, SweetAlert, 
loadingRequest) {
   $scope.cube = cube;
   $scope.metaModel = metaModel;

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/webapp/app/js/controllers/models.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/models.js 
b/webapp/app/js/controllers/models.js
index 63c2683..e32ccf3 100644
--- a/webapp/app/js/controllers/models.js
+++ b/webapp/app/js/controllers/models.js
@@ -122,6 +122,21 @@ KylinApp.controller('ModelsCtrl', function ($scope, $q, 
$routeParams, $location,
     });
   };
 
+  $scope.cloneModel = function(model){
+    $modal.open({
+      templateUrl: 'modelClone.html',
+      controller: modelCloneCtrl,
+      windowClass:"clone-cube-window",
+      resolve: {
+        model: function () {
+          return model;
+        }
+      }
+    });
+  }
+
+
+
   $scope.openModal = function (model) {
     $scope.modelsManager.selectedModel = model;
     $modal.open({
@@ -142,3 +157,55 @@ KylinApp.controller('ModelsCtrl', function ($scope, $q, 
$routeParams, $location,
   };
 
 });
+
+
+var modelCloneCtrl = function ($scope, $modalInstance, CubeService, 
MessageService, $location, model, MetaModel, SweetAlert,ProjectModel, 
loadingRequest,ModelService) {
+  $scope.projectModel = ProjectModel;
+
+  $scope.targetObj={
+    modelName:model.name+"_clone",
+    targetProject:$scope.projectModel.selectedProject
+  }
+
+  $scope.cancel = function () {
+    $modalInstance.dismiss('cancel');
+  };
+
+  $scope.cloneModel = function(){
+
+    $scope.modelRequest = {
+      modelName:$scope.targetObj.modelName,
+      project:$scope.targetObj.targetProject
+    }
+
+    SweetAlert.swal({
+      title: '',
+      text: 'Are you sure to clone the model? ',
+      type: '',
+      showCancelButton: true,
+      confirmButtonColor: '#DD6B55',
+      confirmButtonText: "Yes",
+      closeOnConfirm: true
+    }, function (isConfirm) {
+      if (isConfirm) {
+
+        loadingRequest.show();
+        ModelService.clone({modelId: model.name}, $scope.modelRequest, 
function (result) {
+          loadingRequest.hide();
+          SweetAlert.swal('Success!', 'Clone model successfully', 'success');
+          location.reload();
+        }, function (e) {
+          loadingRequest.hide();
+          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');
+          }
+        });
+      }
+    });
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/webapp/app/js/services/cubes.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/cubes.js b/webapp/app/js/services/cubes.js
index 3df6dd0..2e7a185 100644
--- a/webapp/app/js/services/cubes.js
+++ b/webapp/app/js/services/cubes.js
@@ -28,6 +28,7 @@ KylinApp.factory('CubeService', ['$resource', function 
($resource, config) {
     disable: {method: 'PUT', params: {action: 'disable'}, isArray: false},
     enable: {method: 'PUT', params: {action: 'enable'}, isArray: false},
     purge: {method: 'PUT', params: {action: 'purge'}, isArray: false},
+    clone: {method: 'PUT', params: {action: 'clone'}, isArray: false},
     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/kylin/blob/192bbf91/webapp/app/js/services/models.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/models.js b/webapp/app/js/services/models.js
index 76af969..ae19dd4 100644
--- a/webapp/app/js/services/models.js
+++ b/webapp/app/js/services/models.js
@@ -21,6 +21,7 @@ KylinApp.factory('ModelService', ['$resource', function 
($resource, config) {
         list: {method: 'GET', params: {}, isArray: true},
         'get': {method: 'GET',isArray:true},
         drop: {method: 'DELETE', params: {}, isArray: false},
+        clone: {method: 'PUT', params: {action: 'clone'}, isArray: false},
         save: {method: 'POST', params: {}, isArray: false},
         update: {method: 'PUT', params: {}, isArray: false}
     });

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/webapp/app/less/app.less
----------------------------------------------------------------------
diff --git a/webapp/app/less/app.less b/webapp/app/less/app.less
index c42b7b9..3884e8f 100644
--- a/webapp/app/less/app.less
+++ b/webapp/app/less/app.less
@@ -672,3 +672,11 @@ ul.messenger .messenger-message-inner,.ngCellText {
 .progress.progress-striped {
   background-color:#DCDCDC;
 }
+
+.clone-cube-window .modal-dialog {
+  width: 600px; /* desired relative width */
+}
+
+.tooltip {
+  z-index:1240000 !important;
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/webapp/app/partials/cubes/cube_clone.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/cubes/cube_clone.html 
b/webapp/app/partials/cubes/cube_clone.html
new file mode 100644
index 0000000..edbc137
--- /dev/null
+++ b/webapp/app/partials/cubes/cube_clone.html
@@ -0,0 +1,63 @@
+<!--
+* 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.
+-->
+
+<script type="text/ng-template" id="cubeClone.html">
+  <div class="modal-header">
+    <h4 tooltip="submit">CUBE CLONE</h4>
+  </div>
+  <div class="modal-body" style="background-color: white">
+
+    <div class="row">
+      <div class="col-md-2"></div>
+      <div class="col-md-8">
+        <div class="row">
+          <div class="form-group">
+            <b>Target Project is:</b>
+            <br/>
+            <select ng-required="projectModel.projects.length" chosen 
ng-model="targetObj.targetProject"
+                    ng-init="newAccess.permission=permissions.READ.value;"
+                    ng-options="project.name as project.name for project in 
projectModel.projects "
+                    style="width: 100% !important;"
+                    data-placeholder="select a project"
+                    class="chosen-select">
+              <option value=""></option>
+            </select>
+          </div>
+        </div>
+      </div>
+      <div class="col-md-2"></div>
+    </div>
+    <div class="row">
+      <div class="col-md-2"></div>
+      <div class="col-md-8">
+        <div class="row">
+          <div class="form-group">
+            <b>New Cube Name:</b>
+            <br/>
+            <input type="text" class="form-control" 
ng-model="targetObj.cubeName"/>
+          </div>
+        </div>
+      </div>
+      <div class="col-md-2"></div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button class="btn btn-success" ng-click="cloneCube()">Submit</button>
+    <button class="btn btn-primary" ng-click="cancel()">Close</button>
+  </div>
+</script>

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/webapp/app/partials/cubes/cubes.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/cubes/cubes.html 
b/webapp/app/partials/cubes/cubes.html
index 2b456f4..a1f5e1c 100644
--- a/webapp/app/partials/cubes/cubes.html
+++ b/webapp/app/partials/cubes/cubes.html
@@ -91,6 +91,7 @@
                         <li ng-if="cube.status!='DISABLED'"><a 
ng-click="disable(cube)">Disable</a></li>
                         <li ng-if="cube.status=='DISABLED'"><a 
ng-click="enable(cube)">Enable</a></li>
                         <li ng-if="cube.status=='DISABLED'"><a 
ng-click="purge(cube)">Purge</a></li>
+                        <li><a ng-click="cloneCube(cube)">Clone</a></li>
 
                     </ul>
                 </div>
@@ -138,5 +139,6 @@
 <div ng-include="'partials/jobs/job_merge.html'"></div>
 <div ng-include="'partials/projects/project_create.html'"></div>
 <div ng-include="'partials/models/model_detail.html'"></div>
+<div ng-include="'partials/cubes/cube_clone.html'"></div>
 
 </div>

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/webapp/app/partials/models/model_clone.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/models/model_clone.html 
b/webapp/app/partials/models/model_clone.html
new file mode 100644
index 0000000..de1b562
--- /dev/null
+++ b/webapp/app/partials/models/model_clone.html
@@ -0,0 +1,63 @@
+<!--
+* 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.
+-->
+
+<script type="text/ng-template" id="modelClone.html">
+  <div class="modal-header">
+    <h4 tooltip="submit">MODEL CLONE</h4>
+  </div>
+  <div class="modal-body" style="background-color: white">
+
+    <div class="row">
+      <div class="col-md-2"></div>
+      <div class="col-md-8">
+        <div class="row">
+          <div class="form-group">
+            <b>Target Project is:</b>
+            <br/>
+            <select ng-required="projectModel.projects.length" chosen 
ng-model="targetObj.targetProject"
+                    ng-init="newAccess.permission=permissions.READ.value;"
+                    ng-options="project.name as project.name for project in 
projectModel.projects "
+                    style="width: 100% !important;"
+                    data-placeholder="select a project"
+                    class="chosen-select">
+              <option value=""></option>
+            </select>
+          </div>
+        </div>
+      </div>
+      <div class="col-md-2"></div>
+    </div>
+    <div class="row">
+      <div class="col-md-2"></div>
+      <div class="col-md-8">
+        <div class="row">
+          <div class="form-group">
+            <b>New Model Name:</b>
+            <br/>
+            <input type="text" class="form-control" 
ng-model="targetObj.modelName"/>
+          </div>
+        </div>
+      </div>
+      <div class="col-md-2"></div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button class="btn btn-success" ng-click="cloneModel()">Submit</button>
+    <button class="btn btn-primary" ng-click="cancel()">Close</button>
+  </div>
+</script>

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/webapp/app/partials/models/models.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/models/models.html 
b/webapp/app/partials/models/models.html
index 36b4d54..4d8c7ac 100644
--- a/webapp/app/partials/models/models.html
+++ b/webapp/app/partials/models/models.html
@@ -63,3 +63,4 @@
 <div ng-include="'partials/jobs/job_merge.html'"></div>
 <div ng-include="'partials/projects/project_create.html'"></div>
 <div ng-include="'partials/models/model_detail.html'"></div>
+<div ng-include="'partials/models/model_clone.html'"></div>

http://git-wip-us.apache.org/repos/asf/kylin/blob/192bbf91/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 c7328a6..3e4a6d3 100644
--- a/webapp/app/partials/models/models_tree.html
+++ b/webapp/app/partials/models/models_tree.html
@@ -41,7 +41,7 @@
   <div>
     <h3 class="text-info">Models</h3>
   </div>
-    <div style="width:100%; height:{{window}}px; overflow:auto;margin-top: 
20px;">
+    <div style="width:100%; height:{{window}}px; overflow:auto;margin-top: 
20px;" class="cube_model_trees">
 
         <ul class="list-group models-tree">
           <li class="list-group-item" ng-repeat="model in 
modelsManager.models">
@@ -51,8 +51,9 @@
 
             <div class="pull-right" showonhoverparent style="display:none;">
 
-              <a href="models/edit/{{model.name}}"  
ng-if="userService.hasRole('ROLE_MODELER')"><span class="fa fa-pencil fa-lg 
fa-fw"></span></a>
-              <a ng-click="dropModel(model)" 
style="cursor:pointer;margin-right: 8px;" 
ng-if="userService.hasRole('ROLE_MODELER')"><span class="fa fa-trash-o fa-lg 
fa-fw"></span></a>
+              <a href="models/edit/{{model.name}}" data-placement="bottom" 
title="Edit Model" ng-if="userService.hasRole('ROLE_MODELER')"><span class="fa 
fa-pencil fa-lg fa-fw"></span></a>
+              <a ng-click="cloneModel(model)" title="Clone Model"  
style="cursor:pointer;margin-right: 8px;" 
ng-if="userService.hasRole('ROLE_MODELER')"><span class="fa fa-copy fa-lg 
fa-fw"></span></a>
+              <a ng-click="dropModel(model)" title="Drop Model"  
style="cursor:pointer;margin-right: 8px;" 
ng-if="userService.hasRole('ROLE_MODELER')"><span class="fa fa-trash-o fa-lg 
fa-fw"></span></a>
             </div>
 
           </li>

Reply via email to