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>