You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by xx...@apache.org on 2020/06/15 02:51:27 UTC

[kylin] 11/15: KYLIN-4488 Frontend support for auto-migration to allow user to do cube migration by self service

This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit 569552d974105b55547bc9941ffbe004f4e0486d
Author: Zhong, Yanghong <nj...@apache.org>
AuthorDate: Mon May 18 13:35:57 2020 +0800

    KYLIN-4488 Frontend support for auto-migration to allow user to do cube migration by self service
---
 .../kylin/rest/controller/MigrationController.java |  16 +++
 webapp/app/js/controllers/cubes.js                 | 157 +++++++++++++++++++++
 webapp/app/js/services/cubes.js                    |  17 ++-
 webapp/app/partials/cubes/cube_migrate.html        |  91 ++++++++++++
 webapp/app/partials/cubes/cubes.html               |   5 +
 5 files changed, 285 insertions(+), 1 deletion(-)

diff --git a/cube-migration/src/main/java/org/apache/kylin/rest/controller/MigrationController.java b/cube-migration/src/main/java/org/apache/kylin/rest/controller/MigrationController.java
index 9588e51..106d51f 100644
--- a/cube-migration/src/main/java/org/apache/kylin/rest/controller/MigrationController.java
+++ b/cube-migration/src/main/java/org/apache/kylin/rest/controller/MigrationController.java
@@ -45,6 +45,7 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 import com.fasterxml.jackson.core.JsonParseException;
@@ -82,6 +83,21 @@ public class MigrationController extends BasicController {
         return cube;
     }
 
+    @RequestMapping(value = "/{cubeName}/migrateRuleCheck", method = { RequestMethod.GET })
+    @ResponseBody
+    public String migrationRuleCheck(@PathVariable String cubeName, @RequestParam String projectName,
+            @RequestParam(value = "targetHost", required = false) String targetHost) {
+        CubeInstance cube = getCubeInstance(cubeName);
+        try {
+            MigrationRuleSet.Context ctx = new MigrationRuleSet.Context(queryService, cube, getTargetHost(targetHost),
+                    projectName);
+            return migrationService.checkRule(ctx);
+        } catch (Exception e) {
+            logger.error("Request migration failed.", e);
+            throw new BadRequestException(e.getMessage());
+        }
+    }
+    
     @RequestMapping(value = "/{cubeName}/migrateRequest", method = { RequestMethod.PUT })
     @ResponseBody
     public String requestMigration(@PathVariable String cubeName, @RequestBody MigrationRequest request) {
diff --git a/webapp/app/js/controllers/cubes.js b/webapp/app/js/controllers/cubes.js
index 2d9c032..ddc037e 100644
--- a/webapp/app/js/controllers/cubes.js
+++ b/webapp/app/js/controllers/cubes.js
@@ -581,6 +581,39 @@ KylinApp.controller('CubesCtrl', function ($scope, $q, $routeParams, $location,
       });
     };
 
+    $scope.startMigrateCube = function(cube, action) {
+      $scope.loadDetail(cube).then(function () {
+        switch (action) {
+          case 0:
+            var template = 'cubeMigrate.html';
+            break;
+          case 1:
+            var template = 'migrateApprove.html';
+            break;
+          case -1:
+            var template = 'migrateReject.html';
+            break;
+          default:
+            var template = '';
+        }
+
+        if (!template) {
+          return;
+        }
+
+        $modal.open({
+          templateUrl: template,
+          controller: cubeMigrateCtrl,
+          windowClass:"clone-cube-window",
+          resolve: {
+            cube: function () {
+              return cube;
+            }
+          }
+        });
+      });
+    };
+
     $scope.listCubeAccess = function (cube) {
       //check project auth for user
       $scope.cubeProjectEntity = _.find($scope.projectModel.projects, function(project) {return project.name == $scope.projectModel.selectedProject;});
@@ -1080,3 +1113,127 @@ var lookupRefreshCtrl = function($scope, scope, CubeList, $modalInstance, CubeSe
   };
 
 };
+
+var cubeMigrateCtrl = function ($scope, $modalInstance, CubeService, cube, ProjectModel, loadingRequest, SweetAlert) {
+  $scope.migrate={
+    targetProject: ProjectModel.selectedProject,
+    cubeValidate: true,
+    lockProjectName: false
+  }
+
+  $scope.cancel = function () {
+    $modalInstance.dismiss('cancel');
+  };
+
+  $scope.validate = function () {
+    $scope.MigrationRequest = {
+      projectName:$scope.migrate.targetProject
+    }
+    loadingRequest.show();
+    CubeService.ruleCheck({cubeId: cube.name, projectName: $scope.migrate.targetProject}, function(result) {
+      loadingRequest.hide();
+      $scope.migrate.cubeValidate = false;
+      $scope.migrate.lockProjectName = true;
+      if (result.length > 0) {
+        SweetAlert.swal('Attention', result, 'warning');
+      }
+    }, 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');
+      }
+    });
+  }
+
+  $scope.migrateCube = function(){
+    if(!$scope.migrate.targetProject){
+      SweetAlert.swal('Oops...', "Please input target project name.", 'info');
+      return;
+    }
+
+    $scope.MigrationRequest = {
+      projectName:$scope.migrate.targetProject
+    }
+
+    SweetAlert.swal({
+      title: '',
+      text: 'Are you sure to migrate the cube? ',
+      type: '',
+      showCancelButton: true,
+      confirmButtonColor: '#DD6B55',
+      confirmButtonText: "Yes",
+      closeOnConfirm: true
+    }, function (isConfirm) {
+      if (isConfirm) {
+        loadingRequest.show();
+        CubeService.migrate({cubeId: cube.name}, $scope.MigrationRequest, function (result) {
+          loadingRequest.hide();
+          $modalInstance.dismiss('cancel');
+          SweetAlert.swal('Success!', 'Your Migration Request has been well received. Please check your email to get timely update of this request.', 'success');
+        }, function (e) {
+          loadingRequest.hide();
+          if (e.data && e.data.exception) {
+            var msg = e.data.exception;
+            if(e.status == '400' && (e.data.exception.indexOf("QueryLatencyRule") > -1 || e.data.exception.indexOf("ExpansionRateRule") > -1)){
+              msg += '. Please refer to the guidance to optimize your cube design';
+            }
+            msg += '. For any question, please contact support team.';
+            SweetAlert.swal('Oops...', msg, 'error');
+          } else {
+            SweetAlert.swal('Oops...', 'Failed to take action.', 'error');
+          }
+        });
+      }
+    });
+  };
+
+  $scope.migrateApprove = function(){
+    $scope.MigrationRequest = {
+      projectName:$scope.migrate.targetProject
+    }
+    loadingRequest.show();
+    CubeService.approve({cubeId: cube.name}, $scope.MigrationRequest, function (result) {
+      loadingRequest.hide();
+      $modalInstance.dismiss('cancel');
+      SweetAlert.swal('Success!', 'Approve cube migration successfully', 'success');
+    }, 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');
+      }
+    });
+  };
+
+  $scope.migrateReject = function(){
+    if(!$scope.migrate.reason) {
+      SweetAlert.swal('Oops...', 'Please enter reason for refusal.', 'info');
+      return;
+    }
+    $scope.MigrationRequest = {
+      reason: $scope.migrate.reason
+    };
+    loadingRequest.show();
+    CubeService.reject({cubeId: cube.name}, $scope.MigrationRequest, function (result) {
+      loadingRequest.hide();
+      $modalInstance.dismiss('cancel');
+      SweetAlert.swal('Success!', 'Reject cube migration successfully', 'success');
+    }, 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');
+      }
+    });
+  }
+};
diff --git a/webapp/app/js/services/cubes.js b/webapp/app/js/services/cubes.js
index 65a4d5d..522d043 100644
--- a/webapp/app/js/services/cubes.js
+++ b/webapp/app/js/services/cubes.js
@@ -79,7 +79,22 @@ KylinApp.factory('CubeService', ['$resource', function ($resource, config) {
       }
     },
     optimize: {method: 'PUT', params: {action: 'optimize'}, isArray: false},
+    ruleCheck: {
+      method: 'GET',
+      params: {
+        action: 'migrateRuleCheck'
+      },
+      isArray: false,
+      interceptor: {
+        response: function(response) {
+          return response.data;
+        }
+      }
+    },
     lookupRefresh: {method: 'PUT', params: {action: 'refresh_lookup'}, isArray: false},
-    checkDuplicateCubeName: {method: 'GET', params: {action: 'validate'}, isArray: false}
+    checkDuplicateCubeName: {method: 'GET', params: {action: 'validate'}, isArray: false},
+    migrate: {method: 'PUT', params: {action: 'migrateRequest'}, isArray: false},
+    approve: {method: 'PUT', params: {action: 'migrateApprove'}, isArray: false},
+    reject: {method: 'PUT', params: {action: 'migrateReject'}, isArray: false}
   });
 }]);
diff --git a/webapp/app/partials/cubes/cube_migrate.html b/webapp/app/partials/cubes/cube_migrate.html
new file mode 100644
index 0000000..51b0900
--- /dev/null
+++ b/webapp/app/partials/cubes/cube_migrate.html
@@ -0,0 +1,91 @@
+
+<!--
+* 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="cubeMigrate.html">
+  <div class="modal-header">
+    <h4 tooltip="submit">MIGRATE CUBE</h4>
+  </div>
+  <div class="modal-body" style="background-color: white">
+    <div class="row">
+      <div class="col-md-8 col-md-offset-2">
+        <div class="form-group">
+          <b>Target Project Name:</b>
+          <br/>
+          <p style="color:red;font-size:12px;">You can change the below project name if it's different on Kylin Prod.</p>
+          <input type="text" class="form-control" ng-readonly="migrate.lockProjectName" ng-model="migrate.targetProject" />
+        </div>
+      </div>
+    </div>
+    <div class="row">
+      <div class="col-md-8 col-md-offset-2">
+        <div class="form-group">
+          <div ng-show="migrate.cubeValidate" >
+            <b>Cube Validation:</b>
+            <br/>
+            <button style="height:40px;width:150px;" class="btn btn-info" ng-click="validate()">Validate</button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button class="btn btn-success"  ng-disabled="migrate.cubeValidate" ng-click="migrateCube()">Submit</button>
+    <button class="btn btn-primary" ng-click="cancel()">Close</button>
+  </div>
+</script>
+
+<script type="text/ng-template" id="migrateApprove.html">
+  <div class="modal-header">
+    <h4 tooltip="submit">MIGRATE APPROVE</h4>
+  </div>
+  <div class="modal-body" style="background-color: white">
+    <div class="row">
+      <div class="col-md-8 col-md-offset-2">
+        <div class="form-group">
+          <b>Target Project Name:</b>
+          <br/>
+          <input type="text" class="form-control" ng-model="migrate.targetProject" value={{migrate.targetProject}}/>
+        </div>
+      </div>
+    </div>
+  <div class="modal-footer">
+    <button class="btn btn-success" ng-click="migrateApprove()">Approve</button>
+    <button class="btn btn-primary" ng-click="cancel()">Close</button>
+  </div>
+</script>
+
+<script type="text/ng-template" id="migrateReject.html">
+  <div class="modal-header">
+    <h4 tooltip="submit">MIGRATE REJECT</h4>
+  </div>
+  <div class="modal-body" style="background-color: white">
+    <div class="row">
+      <div class="col-md-8 col-md-offset-2">
+        <div class="form-group">
+          <b>Please enter reject reason:</b>
+          <br/>
+          <textarea ng-model="migrate.reason" placeholder="Reject Reason..." rows="5" cols="30" class="form-control" style="overflow:scroll"></textarea>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button class="btn btn-success" ng-click="migrateReject()">Reject</button>
+    <button class="btn btn-primary" ng-click="cancel()">Close</button>
+  </div>
+</script>
diff --git a/webapp/app/partials/cubes/cubes.html b/webapp/app/partials/cubes/cubes.html
index 65ebc87..dba3c8b 100644
--- a/webapp/app/partials/cubes/cubes.html
+++ b/webapp/app/partials/cubes/cubes.html
@@ -102,6 +102,7 @@
                         <li ng-if="cube.status=='DISABLED' && (userService.hasRole('ROLE_ADMIN') || hasPermission('cube',cube, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask))"><a ng-click="startDeleteSegment(cube)">Delete Segment</a></li>
                         <li ng-if="cube.status=='DISABLED' && (userService.hasRole('ROLE_ADMIN') || hasPermission('cube',cube, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask))"><a ng-click="purge(cube)">Purge</a></li>
                         <li ng-if="cube.status!='DESCBROKEN' && (userService.hasRole('ROLE_ADMIN') || hasPermission('cube',cube, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask))"><a ng-click="cloneCube(cube)">Clone</a></li>
+                        <li ng-if="cube.status=='READY' && (userService.hasRole('ROLE_ADMIN') || hasPermission(cube, permissions.ADMINISTRATION.mask) || hasPermission(cubeProjectEntity, permissions.ADMINISTRATION.mask))"><a ng-click="startMigrateCube(cube, 0);">Migrate</a></li>
 
                     </ul>
                     <ul ng-if="(userService.hasRole('ROLE_ADMIN') || hasPermission('cube', cube, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask, permissions.OPERATION.mask)) && cube.streamingV2 && actionLoaded" class="dropdown-menu" role="menu" style="right:0;left:auto;">
@@ -116,6 +117,7 @@
                         <li ng-if="cube.status=='DISABLED'"><a ng-click="enable(cube, $index)">Enable</a></li>
                         <li ng-if="cube.status=='DISABLED'"><a ng-click="purge(cube, $index)">Purge</a></li>
                         <li><a ng-click="cloneCube(cube)">Clone</a></li>
+                        <li ng-if="cube.status=='READY' && (userService.hasRole('ROLE_ADMIN') || hasPermission(cube, permissions.ADMINISTRATION.mask))"><a ng-click="startMigrateCube(cube, 0);">Migrate</a></li>
                     </ul>
                     <span ng-if="!(userService.hasRole('ROLE_ADMIN') || hasPermission('cube',cube, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask, permissions.OPERATION.mask))" class="dropdown-menu" role="menu">
                       N/A
@@ -130,6 +132,8 @@
                     <ul class="dropdown-menu" role="menu" style="right:0;left:auto;">
                         <li ng-if="cube.status!='READY'"><a href="cubes/edit/{{cube.name}}/descriptionjson">Edit CubeDesc</a></li>
                         <li><a href="cubes/view/{{cube.name}}/instancejson">View Cube</a></li>
+                        <li ng-if="cube.status=='READY' && kylinConfig.getDeployEnv().indexOf('QA') > -1"><a ng-click="startMigrateCube(cube, 1)">Approve Migration</a></li>
+                        <li ng-if="cube.status=='READY' && kylinConfig.getDeployEnv().indexOf('QA') > -1"><a ng-click="startMigrateCube(cube, -1)">Reject Migration</a></li>
                     </ul>
                 </div>
             </td>
@@ -160,6 +164,7 @@
 <div ng-include="'partials/models/model_detail.html'"></div>
 <div ng-include="'partials/cubes/cube_clone.html'"></div>
 <div ng-include="'partials/cubes/cube_delete_segment.html'"></div>
+<div ng-include="'partials/cubes/cube_migrate.html'"></div>
 <div ng-include="'partials/jobs/lookup_refresh.html'"></div>
 <div ng-include="'partials/streaming/cubeAssignment.html'"></div>
 </div>