You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by li...@apache.org on 2016/12/09 04:25:50 UTC

[08/15] kylin git commit: KYLIN-2180 Add project config

KYLIN-2180 Add project config

Signed-off-by: Yang Li <li...@apache.org>


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

Branch: refs/heads/master-cdh5.7
Commit: ea60803e20ac17489f513bf6af29b183829db020
Parents: 2814ce7
Author: kangkaisen <ka...@live.com>
Authored: Fri Nov 11 16:21:32 2016 +0800
Committer: Yang Li <li...@apache.org>
Committed: Thu Dec 8 21:09:11 2016 +0800

----------------------------------------------------------------------
 .../apache/kylin/common/KylinConfigTest.java    | 13 ++-
 .../org/apache/kylin/cube/model/CubeDesc.java   | 11 ++-
 .../kylin/cube/ProjectSpecificConfigTest.java   | 60 +++++++++++++
 .../kylin/metadata/project/ProjectInstance.java | 25 +++++-
 .../kylin/metadata/project/ProjectManager.java  | 13 +--
 .../localmeta/project/default.json              | 10 ++-
 .../rest/request/CreateProjectRequest.java      | 11 +++
 .../rest/request/UpdateProjectRequest.java      | 11 +++
 .../kylin/rest/service/ProjectService.java      |  9 +-
 webapp/app/js/controllers/page.js               | 56 ++++++++++--
 .../app/partials/projects/project_create.html   | 93 ++++++++++++++------
 11 files changed, 255 insertions(+), 57 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/core-common/src/test/java/org/apache/kylin/common/KylinConfigTest.java
----------------------------------------------------------------------
diff --git a/core-common/src/test/java/org/apache/kylin/common/KylinConfigTest.java b/core-common/src/test/java/org/apache/kylin/common/KylinConfigTest.java
index d05f58b..209986f 100644
--- a/core-common/src/test/java/org/apache/kylin/common/KylinConfigTest.java
+++ b/core-common/src/test/java/org/apache/kylin/common/KylinConfigTest.java
@@ -30,7 +30,6 @@ import org.junit.Test;
 import com.google.common.collect.Maps;
 
 public class KylinConfigTest extends LocalFileMetadataTestCase {
-
     @Before
     public void setUp() throws Exception {
         this.createTestMetadata();
@@ -49,33 +48,33 @@ public class KylinConfigTest extends LocalFileMetadataTestCase {
         assertEquals("test1", override.get("test1"));
         assertEquals("test2", override.get("test2"));
     }
-    
+
     @Test
     public void testBackwardCompatibility() {
         KylinConfig config = KylinConfig.getInstanceFromEnv();
         final String oldk = "kylin.test.bcc.old.key";
         final String newk = "kylin.test.bcc.new.key";
-        
+
         assertNull(config.getOptional(oldk));
         assertNotNull(config.getOptional(newk));
-        
+
         Map<String, String> override = Maps.newHashMap();
         override.put(oldk, "1");
         KylinConfigExt ext = KylinConfigExt.createInstance(config, override);
         assertEquals(ext.getOptional(oldk), null);
         assertEquals(ext.getOptional(newk), "1");
         assertNotEquals(config.getOptional(newk), "1");
-        
+
         config.setProperty(oldk, "2");
         assertEquals(config.getOptional(newk), "2");
     }
-    
+
     @Test
     public void testExtShareTheBase() {
         KylinConfig config = KylinConfig.getInstanceFromEnv();
         Map<String, String> override = Maps.newHashMap();
         KylinConfig configExt = KylinConfigExt.createInstance(config, override);
-        
+
         assertTrue(config.properties == configExt.properties);
         config.setProperty("1234", "1234");
         assertEquals("1234", configExt.getOptional("1234"));

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/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 f8c316c..56a8b7e 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
@@ -529,7 +529,16 @@ public class CubeDesc extends RootPersistentEntity implements IEngineAware {
 
         // note CubeDesc.name == CubeInstance.name
         List<ProjectInstance> ownerPrj = ProjectManager.getInstance(config).findProjects(RealizationType.CUBE, name);
-        logger.info("CubeDesc '" + name + "' is owned by " + ownerPrj);
+
+        // cube inherit the project override props
+        if (ownerPrj.size() == 1) {
+            Map<String, String> prjOverrideProps = ownerPrj.get(0).getOverrideKylinProps();
+            for (Entry<String, String> entry : prjOverrideProps.entrySet()) {
+                if (!overrideKylinProps.containsKey(entry.getKey())) {
+                    overrideKylinProps.put(entry.getKey(), entry.getValue());
+                }
+            }
+        }
 
         this.config = KylinConfigExt.createInstance(config, overrideKylinProps);
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/core-cube/src/test/java/org/apache/kylin/cube/ProjectSpecificConfigTest.java
----------------------------------------------------------------------
diff --git a/core-cube/src/test/java/org/apache/kylin/cube/ProjectSpecificConfigTest.java b/core-cube/src/test/java/org/apache/kylin/cube/ProjectSpecificConfigTest.java
new file mode 100644
index 0000000..5008daa
--- /dev/null
+++ b/core-cube/src/test/java/org/apache/kylin/cube/ProjectSpecificConfigTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package org.apache.kylin.cube;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.LocalFileMetadataTestCase;
+import org.apache.kylin.cube.model.CubeDesc;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ProjectSpecificConfigTest extends LocalFileMetadataTestCase {
+
+    @Before
+    public void setUp() throws Exception {
+        this.createTestMetadata();
+    }
+
+    @After
+    public void after() throws Exception {
+        this.cleanupTestMetadata();
+    }
+
+    @Test
+    public void testProject1() {
+        KylinConfig baseConfig = KylinConfig.getInstanceFromEnv();
+        CubeDesc cubeDesc = CubeDescManager.getInstance(baseConfig).getCubeDesc("ssb");
+        verifyProjectOverride(baseConfig, cubeDesc.getConfig());
+    }
+
+    @Test
+    public void testProject2() {
+        KylinConfig baseConfig = KylinConfig.getInstanceFromEnv();
+        CubeInstance cube = CubeManager.getInstance(baseConfig).getCube("ssb");
+        verifyProjectOverride(baseConfig, cube.getConfig());
+    }
+
+    private void verifyProjectOverride(KylinConfig base, KylinConfig override) {
+        assertEquals("whoami@kylin.apache.org", base.getKylinOwner());
+        assertEquals("kylin@kylin.apache.org", override.getKylinOwner());
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java
index 1afc603..74648cd 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectInstance.java
@@ -19,12 +19,14 @@
 package org.apache.kylin.metadata.project;
 
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Set;
 import java.util.TreeSet;
 
 import javax.annotation.Nullable;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.kylin.common.persistence.ResourceStore;
 import org.apache.kylin.common.persistence.RootPersistentEntity;
@@ -78,6 +80,10 @@ public class ProjectInstance extends RootPersistentEntity {
     @JsonProperty("ext_filters")
     private Set<String> extFilters = new TreeSet<String>();
 
+    @JsonProperty("overrideKylinProps")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private LinkedHashMap<String, String> overrideKylinProps;
+
     public String getResourcePath() {
         return concatResourcePath(name);
     }
@@ -93,7 +99,7 @@ public class ProjectInstance extends RootPersistentEntity {
         return project.toUpperCase();
     }
 
-    public static ProjectInstance create(String name, String owner, String description, List<RealizationEntry> realizationEntries, List<String> models) {
+    public static ProjectInstance create(String name, String owner, String description, LinkedHashMap<String, String> overrideProps, List<RealizationEntry> realizationEntries, List<String> models) {
         ProjectInstance projectInstance = new ProjectInstance();
 
         projectInstance.updateRandomUuid();
@@ -102,6 +108,11 @@ public class ProjectInstance extends RootPersistentEntity {
         projectInstance.setDescription(description);
         projectInstance.setStatus(ProjectStatusEnum.ENABLED);
         projectInstance.setCreateTimeUTC(System.currentTimeMillis());
+        if (overrideProps != null) {
+            projectInstance.setOverrideKylinProps(overrideProps);
+        } else {
+            projectInstance.setOverrideKylinProps(new LinkedHashMap<String, String>());
+        }
         if (realizationEntries != null)
             projectInstance.setRealizationEntries(realizationEntries);
         else
@@ -285,6 +296,14 @@ public class ProjectInstance extends RootPersistentEntity {
         }
     }
 
+    public LinkedHashMap<String, String> getOverrideKylinProps() {
+        return overrideKylinProps;
+    }
+
+    public void setOverrideKylinProps(LinkedHashMap<String, String> overrideKylinProps) {
+        this.overrideKylinProps = overrideKylinProps;
+    }
+
     public void init() {
         if (name == null)
             name = ProjectInstance.DEFAULT_PROJECT_NAME;
@@ -296,6 +315,10 @@ public class ProjectInstance extends RootPersistentEntity {
         if (tables == null)
             tables = new TreeSet<String>();
 
+        if (overrideKylinProps == null) {
+            overrideKylinProps = new LinkedHashMap<>();
+        }
+
         if (StringUtils.isBlank(this.name))
             throw new IllegalStateException("Project name must not be blank");
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
index b547843..ca4f7f1 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
@@ -20,6 +20,7 @@ package org.apache.kylin.metadata.project;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -138,7 +139,6 @@ public class ProjectManager {
     }
 
     private ProjectInstance reloadProjectLocalAt(String path) throws IOException {
-
         ProjectInstance projectInstance = getStore().getResource(path, ProjectInstance.class, PROJECT_SERIALIZER);
         if (projectInstance == null) {
             logger.warn("reload project at path:" + path + " not found, this:" + this.toString());
@@ -162,12 +162,12 @@ public class ProjectManager {
         return projectMap.get(projectName);
     }
 
-    public ProjectInstance createProject(String projectName, String owner, String description) throws IOException {
+    public ProjectInstance createProject(String projectName, String owner, String description, LinkedHashMap<String, String> overrideProps) throws IOException {
         logger.info("Creating project " + projectName);
 
         ProjectInstance currentProject = getProject(projectName);
         if (currentProject == null) {
-            currentProject = ProjectInstance.create(projectName, owner, description, null, null);
+            currentProject = ProjectInstance.create(projectName, owner, description, overrideProps, null, null);
         } else {
             throw new IllegalStateException("The project named " + projectName + "already exists");
         }
@@ -207,9 +207,9 @@ public class ProjectManager {
     }
 
     //update project itself
-    public ProjectInstance updateProject(ProjectInstance project, String newName, String newDesc) throws IOException {
+    public ProjectInstance updateProject(ProjectInstance project, String newName, String newDesc, LinkedHashMap<String, String> overrideProps) throws IOException {
         if (!project.getName().equals(newName)) {
-            ProjectInstance newProject = this.createProject(newName, project.getOwner(), newDesc);
+            ProjectInstance newProject = this.createProject(newName, project.getOwner(), newDesc, overrideProps);
 
             newProject.setCreateTimeUTC(project.getCreateTimeUTC());
             newProject.recordUpdateTime(System.currentTimeMillis());
@@ -225,6 +225,7 @@ public class ProjectManager {
         } else {
             project.setName(newName);
             project.setDescription(newDesc);
+            project.setOverrideKylinProps(overrideProps);
 
             if (project.getUuid() == null)
                 project.updateRandomUuid();
@@ -291,7 +292,7 @@ public class ProjectManager {
         String newProjectName = norm(project);
         ProjectInstance newProject = getProject(newProjectName);
         if (newProject == null) {
-            newProject = this.createProject(newProjectName, user, "This is a project automatically added when adding realization " + realizationName + "(" + type + ")");
+            newProject = this.createProject(newProjectName, user, "This is a project automatically added when adding realization " + realizationName + "(" + type + ")", null);
         }
         newProject.addRealizationEntry(type, realizationName);
         updateProject(newProject);

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/examples/test_case_data/localmeta/project/default.json
----------------------------------------------------------------------
diff --git a/examples/test_case_data/localmeta/project/default.json b/examples/test_case_data/localmeta/project/default.json
index 695f3b7..cd3eae8 100644
--- a/examples/test_case_data/localmeta/project/default.json
+++ b/examples/test_case_data/localmeta/project/default.json
@@ -41,6 +41,11 @@
       "name": "test_kylin_cube_with_view_inner_join_empty",
       "type": "CUBE",
       "realization": "test_kylin_cube_with_view_inner_join_empty"
+    },
+    {
+      "name": "ssb",
+      "type": "CUBE",
+      "realization": "ssb"
     }
   ],
   "tables": [
@@ -59,5 +64,8 @@
     "test_kylin_left_join_model_desc",
     "test_kylin_left_join_view_model_desc",
     "test_streaming_table_model_desc"
-  ]
+  ],
+  "overrideKylinProps" :{
+    "kylin.storage.hbase.owner-tag": "kylin@kylin.apache.org"
+  }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/server-base/src/main/java/org/apache/kylin/rest/request/CreateProjectRequest.java
----------------------------------------------------------------------
diff --git a/server-base/src/main/java/org/apache/kylin/rest/request/CreateProjectRequest.java b/server-base/src/main/java/org/apache/kylin/rest/request/CreateProjectRequest.java
index 71cd1c4..00fe1eb 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/request/CreateProjectRequest.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/request/CreateProjectRequest.java
@@ -18,11 +18,14 @@
 
 package org.apache.kylin.rest.request;
 
+import java.util.LinkedHashMap;
+
 /**
  */
 public class CreateProjectRequest {
     private String name;
     private String description;
+    private LinkedHashMap<String, String> overrideKylinProps;
 
     public CreateProjectRequest() {
     }
@@ -43,4 +46,12 @@ public class CreateProjectRequest {
         this.description = description;
     }
 
+    public LinkedHashMap<String, String> getOverrideKylinProps() {
+        return overrideKylinProps;
+    }
+
+    public void setOverrideKylinProps(LinkedHashMap<String, String> overrideKylinProps) {
+        this.overrideKylinProps = overrideKylinProps;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/server-base/src/main/java/org/apache/kylin/rest/request/UpdateProjectRequest.java
----------------------------------------------------------------------
diff --git a/server-base/src/main/java/org/apache/kylin/rest/request/UpdateProjectRequest.java b/server-base/src/main/java/org/apache/kylin/rest/request/UpdateProjectRequest.java
index 29ba162..f253c9c 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/request/UpdateProjectRequest.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/request/UpdateProjectRequest.java
@@ -18,12 +18,15 @@
 
 package org.apache.kylin.rest.request;
 
+import java.util.LinkedHashMap;
+
 /**
  */
 public class UpdateProjectRequest {
     private String formerProjectName;
     private String newProjectName;
     private String newDescription;
+    private LinkedHashMap<String, String> overrideKylinProps;
 
     public UpdateProjectRequest() {
     }
@@ -52,4 +55,12 @@ public class UpdateProjectRequest {
     public void setNewProjectName(String newProjectName) {
         this.newProjectName = newProjectName;
     }
+
+    public LinkedHashMap<String, String> getOverrideKylinProps() {
+        return overrideKylinProps;
+    }
+
+    public void setOverrideKylinProps(LinkedHashMap<String, String> overrideKylinProps) {
+        this.overrideKylinProps = overrideKylinProps;
+    }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/server-base/src/main/java/org/apache/kylin/rest/service/ProjectService.java
----------------------------------------------------------------------
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/ProjectService.java b/server-base/src/main/java/org/apache/kylin/rest/service/ProjectService.java
index b4cceb2..283cf4a 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/ProjectService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/ProjectService.java
@@ -20,6 +20,7 @@ package org.apache.kylin.rest.service;
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 
 import org.apache.kylin.metadata.project.ProjectInstance;
@@ -53,13 +54,15 @@ public class ProjectService extends BasicService {
     public ProjectInstance createProject(CreateProjectRequest projectRequest) throws IOException {
         String projectName = projectRequest.getName();
         String description = projectRequest.getDescription();
+        LinkedHashMap<String, String> overrideProps = projectRequest.getOverrideKylinProps();
+
         ProjectInstance currentProject = getProjectManager().getProject(projectName);
 
         if (currentProject != null) {
             throw new InternalErrorException("The project named " + projectName + " already exists");
         }
         String owner = SecurityContextHolder.getContext().getAuthentication().getName();
-        ProjectInstance createdProject = getProjectManager().createProject(projectName, owner, description);
+        ProjectInstance createdProject = getProjectManager().createProject(projectName, owner, description, overrideProps);
         accessService.init(createdProject, AclPermission.ADMINISTRATION);
         logger.debug("New project created.");
 
@@ -71,19 +74,19 @@ public class ProjectService extends BasicService {
         String formerProjectName = projectRequest.getFormerProjectName();
         String newProjectName = projectRequest.getNewProjectName();
         String newDescription = projectRequest.getNewDescription();
+        LinkedHashMap<String, String> overrideProps = projectRequest.getOverrideKylinProps();
 
         if (currentProject == null) {
             throw new InternalErrorException("The project named " + formerProjectName + " does not exists");
         }
 
-        ProjectInstance updatedProject = getProjectManager().updateProject(currentProject, newProjectName, newDescription);
+        ProjectInstance updatedProject = getProjectManager().updateProject(currentProject, newProjectName, newDescription, overrideProps);
 
         logger.debug("Project updated.");
 
         return updatedProject;
     }
 
-
     @PostFilter(Constant.ACCESS_POST_FILTER_READ)
     public List<ProjectInstance> listProjects(final Integer limit, final Integer offset) {
         List<ProjectInstance> projects = listAllProjects(limit, offset);

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/webapp/app/js/controllers/page.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/page.js b/webapp/app/js/controllers/page.js
index c65a264..2b6bc64 100644
--- a/webapp/app/js/controllers/page.js
+++ b/webapp/app/js/controllers/page.js
@@ -25,8 +25,8 @@ KylinApp.controller('PageCtrl', function ($scope, $q, AccessService, $modal, $lo
     $log.debug(data);
     kylinConfig.initWebConfigInfo();
   });
-  $rootScope.userAction={
-   'islogout':false
+  $rootScope.userAction = {
+    'islogout': false
   }
   $scope.kylinConfig = kylinConfig;
 
@@ -162,7 +162,7 @@ KylinApp.controller('PageCtrl', function ($scope, $q, AccessService, $modal, $lo
 
   $scope.$watch('projectModel.selectedProject', function (newValue, oldValue) {
     if (newValue != oldValue) {
-      if(!$rootScope.userAction.islogout) {
+      if (!$rootScope.userAction.islogout) {
         //$log.log("project updated in page controller,from:"+oldValue+" To:"+newValue);
         $cookieStore.put("project", $scope.projectModel.selectedProject);
       }
@@ -195,12 +195,21 @@ var projCtrl = function ($scope, $location, $modalInstance, ProjectService, Mess
     projectIdx: -1
   };
   $scope.isEdit = false;
-  $scope.proj = {name: '', description: ''};
+  $scope.proj = {name: '', description: '', overrideKylinProps: {}};
+  $scope.convertedProperties = [];
 
   if (project) {
     $scope.state.isEdit = true;
     $scope.state.oldProjName = project.name;
     $scope.proj = project;
+
+    for (var key in $scope.proj.overrideKylinProps) {
+      $scope.convertedProperties.push({
+        name: key,
+        value: $scope.proj.overrideKylinProps[key]
+      });
+    }
+
     for (var i = 0; i < projects.length; i++) {
       if (projects[i].name === $scope.state.oldProjName) {
         $scope.state.projectIdx = i;
@@ -215,7 +224,8 @@ var projCtrl = function ($scope, $location, $modalInstance, ProjectService, Mess
       var requestBody = {
         formerProjectName: $scope.state.oldProjName,
         newProjectName: $scope.proj.name,
-        newDescription: $scope.proj.description
+        newDescription: $scope.proj.description,
+        overrideKylinProps: $scope.proj.overrideKylinProps
       };
       ProjectService.update({}, requestBody, function (newProj) {
         SweetAlert.swal('Success!', 'Project update successfully!', 'success');
@@ -239,10 +249,6 @@ var projCtrl = function ($scope, $location, $modalInstance, ProjectService, Mess
       ProjectService.save({}, $scope.proj, function (newProj) {
         SweetAlert.swal('Success!', 'New project created successfully!', 'success');
         $modalInstance.dismiss('cancel');
-//                if(projects) {
-//                    projects.push(newProj);
-//                }
-//                ProjectModel.addProject(newProj);
         $cookieStore.put("project", newProj.name);
         location.reload();
       }, function (e) {
@@ -264,4 +270,36 @@ var projCtrl = function ($scope, $location, $modalInstance, ProjectService, Mess
     $modalInstance.dismiss('cancel');
   };
 
+  $scope.addNewProperty = function () {
+    if ($scope.proj.overrideKylinProps.hasOwnProperty('')) {
+      return;
+    }
+    $scope.proj.overrideKylinProps[''] = '';
+    $scope.convertedProperties.push({
+      name: '',
+      value: ''
+    });
+  };
+
+  $scope.refreshPropertiesObj = function () {
+    $scope.proj.overrideKylinProps = {};
+    angular.forEach($scope.convertedProperties, function (item, index) {
+      $scope.proj.overrideKylinProps[item.name] = item.value;
+    })
+  };
+
+
+  $scope.refreshProperty = function (list, index, item) {
+    $scope.convertedProperties[index] = item;
+    $scope.refreshPropertiesObj();
+  };
+
+
+  $scope.removeProperty = function (arr, index, item) {
+    if (index > -1) {
+      arr.splice(index, 1);
+    }
+    delete $scope.proj.overrideKylinProps[item.name];
+  }
+
 };

http://git-wip-us.apache.org/repos/asf/kylin/blob/ea60803e/webapp/app/partials/projects/project_create.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/projects/project_create.html b/webapp/app/partials/projects/project_create.html
index 36cbaf5..2dc76da 100644
--- a/webapp/app/partials/projects/project_create.html
+++ b/webapp/app/partials/projects/project_create.html
@@ -17,40 +17,75 @@
 -->
 
 <script type="text/ng-template" id="project.html">
-    <ng-form name="proj_create_form">
-        <div class="modal-header">
-            <h4>New Project</h4>
-        </div>
-        <div class="modal-body" style="background-color: white">
-            <div class="form-group">
-                <label><b>Project Name</b></label>
+  <ng-form name="proj_create_form">
+    <div class="modal-header">
+      <h4>New Project</h4>
+    </div>
+    <div class="modal-body" style="background-color: white">
+      <div class="form-group">
+        <label><b>Project Name</b></label>
 
-                <div class="clearfix">
-                    <input name="name_input" type="text" class="form-control" ng-model="proj.name" required
-                           placeholder="You can use letters, numbers, and underscore characters '_'"
-                           ng-maxlength = 100 ng-pattern="/^\w+$/" />
-                    <span class="text-warning" ng-if="proj_create_form.name_input.$error.required  && proj_create_form.name_input.$dirty"
-                            >&nbsp;The project name is required</span>
-                    <span class="text-warning" ng-if="proj_create_form.name_input.$invalid && proj_create_form.name_input.$dirty && !proj_create_form.name_input.$error.required"
-                            >&nbsp;The project name is invalid</span>
-                </div>
-            </div>
-            <div class="form-group">
-                <label><b>Project Description</b></label>
+        <div class="clearfix">
+          <input name="name_input" type="text" class="form-control" ng-model="proj.name" required
+                 placeholder="You can use letters, numbers, and underscore characters '_'"
+                 ng-maxlength=100 ng-pattern="/^\w+$/"/>
+                    <span class="text-warning"
+                          ng-if="proj_create_form.name_input.$error.required  && proj_create_form.name_input.$dirty"
+                    >&nbsp;The project name is required</span>
+                    <span class="text-warning"
+                          ng-if="proj_create_form.name_input.$invalid && proj_create_form.name_input.$dirty && !proj_create_form.name_input.$error.required"
+                    >&nbsp;The project name is invalid</span>
+        </div>
+      </div>
+      <div class="form-group">
+        <label><b>Project Description</b></label>
 
-                <div class="clearfix">
+        <div class="clearfix">
                     <textarea name="description_input" type="text" class="form-control"
                               placeholder="Project Description.."
                               ng-model="proj.description"
-                            ></textarea>
-                </div>
+                    ></textarea>
+        </div>
+      </div>
+      <div>
+        <label><b>Project Config</b></label>
+        <div ng-repeat=" prop in convertedProperties track by $index " class="form-group">
+          <div class="row">
+            <label class="col-xs-12 col-sm-6 control-label no-padding-right" ng-class="{'has-error':prop.name==''}">
+              <input ng-model="prop.name" placeholder="key" ng-change="refreshProperty(convertedProperties,$index,prop)"
+                     class="form-control"/>
+              <small style="font-weight: normal !important;" class="help-block" ng-show="prop.name==''">Property name is
+                required.
+              </small>
+            </label>
+            <div class="col-xs-12 col-sm-5" ng-class="{'has-error':prop.value==''}">
+              <input ng-model="prop.value" placeholder="value"
+                     ng-change="refreshProperty(convertedProperties,$index,prop)" class="form-control"/>
+              <small class="help-block" ng-show="prop.value==''">Property value is required.</small>
             </div>
+            <div class="col-sm-1">
+              <button class="btn btn-xs btn-info" ng-click="removeProperty(convertedProperties,$index,prop)"><i
+                class="fa fa-minus"></i>
+              </button>
+            </div>
+
+          </div>
         </div>
-        <div class="modal-footer">
-            <button class="btn btn-primary" ng-click="cancel()">Close</button>
-            <button class="btn btn-success" ng-click="createOrUpdate()" ng-disabled="proj_create_form.$invalid">
-                Submit
-            </button>
+        <!--Add Project Property-->
+        <div class="form-group">
+          <button class="btn btn-sm btn-info" ng-click="addNewProperty();">
+            <i class="fa fa-plus"></i> Property
+          </button>
         </div>
-    </ng-form>
-</script>
\ No newline at end of file
+
+      </div>
+    </div>
+    <div class="modal-footer">
+      <button class="btn btn-primary" ng-click="cancel()">Close</button>
+      <button class="btn btn-success" ng-click="createOrUpdate()" ng-disabled="proj_create_form.$invalid">
+        Submit
+      </button>
+    </div>
+  </ng-form>
+</script>
+