KYLIN-3982 Add measures without purging segments
diff --git a/core-cube/src/main/java/org/apache/kylin/cube/CubeInstance.java b/core-cube/src/main/java/org/apache/kylin/cube/CubeInstance.java
index ad99377..e5fe614 100644
--- a/core-cube/src/main/java/org/apache/kylin/cube/CubeInstance.java
+++ b/core-cube/src/main/java/org/apache/kylin/cube/CubeInstance.java
@@ -40,9 +40,12 @@
 import org.apache.kylin.cube.cuboid.CuboidScheduler;
 import org.apache.kylin.cube.cuboid.TreeCuboidScheduler;
 import org.apache.kylin.cube.model.CubeDesc;
+import org.apache.kylin.measure.MeasureInstance;
+import org.apache.kylin.measure.MeasureManager;
 import org.apache.kylin.metadata.model.ColumnDesc;
 import org.apache.kylin.metadata.model.DataModelDesc;
 import org.apache.kylin.metadata.model.IBuildable;
+import org.apache.kylin.metadata.model.ISegment;
 import org.apache.kylin.metadata.model.JoinTableDesc;
 import org.apache.kylin.metadata.model.MeasureDesc;
 import org.apache.kylin.metadata.model.SegmentRange;
@@ -182,7 +185,29 @@
     }
 
     public Segments<CubeSegment> getMergingSegments(CubeSegment mergedSegment) {
-        return segments.getMergingSegments(mergedSegment);
+        Segments<CubeSegment> mergingSegments = segments.getMergingSegments(mergedSegment);
+        checkAlignedMeasure(mergingSegments);
+        return mergingSegments;
+    }
+
+    private boolean checkAlignedMeasure(Segments<CubeSegment> mergingSegments) {
+        int maxMeasureNumber = 0;
+        boolean isUnaligned = false;
+        MeasureManager measureManager = MeasureManager.getInstance(getConfig());
+        for (ISegment seg : mergingSegments) {
+            List<MeasureInstance> measuresOnSeg = measureManager.getMeasuresOnSegment(getName(), seg.getName());
+            if (measuresOnSeg.size() > maxMeasureNumber) {
+                if (maxMeasureNumber > 0) {
+                    isUnaligned = true;
+                }
+                maxMeasureNumber = measuresOnSeg.size();
+            }
+        }
+        if (isUnaligned) {
+            throw new UnsupportedOperationException("Can't merge segment, because they have different measure number. Max measure number is "
+                    + maxMeasureNumber + ", you can refresh other segments to make them aligned.");
+        }
+        return true;
     }
 
     public CubeSegment getOriginalSegmentToRefresh(CubeSegment refreshedSegment) {
diff --git a/core-cube/src/main/java/org/apache/kylin/cube/gridtable/CuboidToGridTableMappingFilterNullCol.java b/core-cube/src/main/java/org/apache/kylin/cube/gridtable/CuboidToGridTableMappingFilterNullCol.java
index 6d06ed4..c701fdb 100644
--- a/core-cube/src/main/java/org/apache/kylin/cube/gridtable/CuboidToGridTableMappingFilterNullCol.java
+++ b/core-cube/src/main/java/org/apache/kylin/cube/gridtable/CuboidToGridTableMappingFilterNullCol.java
@@ -28,6 +28,7 @@
 import org.apache.kylin.metadata.model.FunctionDesc;
 import org.apache.kylin.metadata.model.TblColRef;
 
+import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -131,6 +132,19 @@
     }
 
     @Override
+    public ImmutableBitSet makeGridTableColumns(Collection<? extends FunctionDesc> metrics) {
+        BitSet result = new BitSet();
+        for (FunctionDesc metric : metrics) {
+            int idx = getIndexOf(metric);
+            if (idx < 0) {
+                continue;
+            }
+            result.set(idx);
+        }
+        return new ImmutableBitSet(result);
+    }
+
+    @Override
     public String[] makeAggrFuncs(Collection<FunctionDesc> metrics) {
         List<FunctionDesc> metricList = Lists.newArrayListWithCapacity(metrics.size());
         metrics.stream().forEach(m -> {
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 8b45837..3d0c7e5 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
@@ -568,10 +568,10 @@
                     .append(JsonUtil.writeValueAsString(this.modelName)).append("|")//
                     .append(JsonUtil.writeValueAsString(this.nullStrings)).append("|")//
                     .append(JsonUtil.writeValueAsString(this.dimensions)).append("|")//
-                    .append(JsonUtil.writeValueAsString(this.measures)).append("|")//
+                    //.append(JsonUtil.writeValueAsString(this.measures)).append("|")//
                     .append(JsonUtil.writeValueAsString(this.rowkey)).append("|")//
                     .append(JsonUtil.writeValueAsString(this.aggregationGroups)).append("|")//
-                    .append(JsonUtil.writeValueAsString(this.hbaseMapping)).append("|")//
+                    //.append(JsonUtil.writeValueAsString(this.hbaseMapping)).append("|")//
                     .append(JsonUtil.writeValueAsString(this.storageType)).append("|");
 
             if (mandatoryDimensionSetList != null && !mandatoryDimensionSetList.isEmpty()) {
diff --git a/core-cube/src/main/java/org/apache/kylin/gridtable/GTInfo.java b/core-cube/src/main/java/org/apache/kylin/gridtable/GTInfo.java
index 739adf8..e85724f 100644
--- a/core-cube/src/main/java/org/apache/kylin/gridtable/GTInfo.java
+++ b/core-cube/src/main/java/org/apache/kylin/gridtable/GTInfo.java
@@ -205,8 +205,8 @@
         for (int i = 0; i < colBlocks.length; i++) {
             merge = merge.or(colBlocks[i]);
         }
-        if (!merge.equals(colAll))
-            throw new IllegalStateException();
+//        if (!merge.equals(colAll))
+//            throw new IllegalStateException();
 
         // primary key must be the first column block
         if (!primaryKey.equals(colBlocks[0]))
diff --git a/server-base/src/main/java/org/apache/kylin/rest/controller/CubeController.java b/server-base/src/main/java/org/apache/kylin/rest/controller/CubeController.java
index c3f45a6..8b92c00 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/controller/CubeController.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/controller/CubeController.java
@@ -44,6 +44,7 @@
 import org.apache.kylin.cube.model.CubeJoinedFlatTableDesc;
 import org.apache.kylin.cube.model.HBaseColumnDesc;
 import org.apache.kylin.cube.model.HBaseColumnFamilyDesc;
+import org.apache.kylin.cube.model.HBaseMappingDesc;
 import org.apache.kylin.cube.model.RowKeyColDesc;
 import org.apache.kylin.dimension.DimensionEncodingFactory;
 import org.apache.kylin.engine.mr.common.CuboidStatsReaderUtil;
@@ -55,6 +56,7 @@
 import org.apache.kylin.metadata.model.MeasureDesc;
 import org.apache.kylin.metadata.model.SegmentRange;
 import org.apache.kylin.metadata.model.SegmentRange.TSRange;
+import org.apache.kylin.metadata.model.SegmentStatusEnum;
 import org.apache.kylin.metadata.project.ProjectInstance;
 import org.apache.kylin.metadata.realization.RealizationStatusEnum;
 import org.apache.kylin.rest.exception.BadRequestException;
@@ -77,6 +79,7 @@
 import org.apache.kylin.rest.response.ResponseCode;
 import org.apache.kylin.rest.service.CubeService;
 import org.apache.kylin.rest.service.JobService;
+import org.apache.kylin.rest.service.MeasureService;
 import org.apache.kylin.rest.service.ProjectService;
 import org.apache.kylin.rest.util.ValidateUtil;
 import org.apache.kylin.source.kafka.util.KafkaClient;
@@ -121,6 +124,10 @@
     @Qualifier("projectService")
     private ProjectService projectService;
 
+    @Autowired
+    @Qualifier("measureMgmtService")
+    public MeasureService measureService;
+
     @RequestMapping(value = "/validate/{cubeName}", method = RequestMethod.GET, produces = { "application/json" })
     @ResponseBody
     public EnvelopeResponse<Boolean> validateModelName(@PathVariable String cubeName) {
@@ -653,6 +660,12 @@
                 updateRequest(cubeRequest, false, error);
                 return cubeRequest;
             }
+            Pair<List<String>, List<String>> updateMeasures = cubeService.getUpdateMeasures(desc, cubeService.getCubeDescManager().getCubeDesc(desc.getName()));
+            if (cube.getSegments().size() > 0 && updateMeasures.getFirst().size() > 0) {
+                // add dynamic measure to HBase column family mapping
+                HBaseMappingDesc newHBaseMapping = cubeService.getAutoHBaseMapping(desc, updateMeasures.getFirst());
+                desc.setHbaseMapping(newHBaseMapping);
+            }
 
             validateColumnFamily(desc);
 
@@ -735,6 +748,13 @@
                 hr.setSourceOffsetStart((Long) segment.getSegRange().start.v);
                 hr.setSourceOffsetEnd((Long) segment.getSegRange().end.v);
             }
+            // add info about Measure on this segment
+            if (hr.getSegmentStatus().equals(SegmentStatusEnum.READY.toString())) {
+                List<String> measuresOnSegment = measureService.getMeasuresOnSegment(cubeName, hr.getSegmentName());
+                hr.setMeasuresOnSegment(measuresOnSegment);
+            } else {
+                hr.setMeasuresOnSegment(Collections.EMPTY_LIST);
+            }
             hbase.add(hr);
         }
 
diff --git a/server-base/src/main/java/org/apache/kylin/rest/controller/MeasureController.java b/server-base/src/main/java/org/apache/kylin/rest/controller/MeasureController.java
new file mode 100644
index 0000000..a50f165
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/controller/MeasureController.java
@@ -0,0 +1,113 @@
+/*
+ * 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.rest.controller;
+
+import com.google.common.collect.Maps;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.metadata.model.DateTimeRange;
+import org.apache.kylin.rest.response.SegmentResponse;
+import org.apache.kylin.rest.service.MeasureService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Controller
+@RequestMapping(value = "/measure")
+public class MeasureController extends BasicController{
+    private static final Logger LOG = LoggerFactory.getLogger(MeasureController.class);
+
+    @Autowired
+    @Qualifier("measureMgmtService")
+    public MeasureService measureService;
+
+
+    @GetMapping(value = "/segment/{cubeName}/{measureName}", produces = { "application/json" })
+    @ResponseBody
+    public SegmentResponse getSegmentRanges(@PathVariable String cubeName,
+                                               @PathVariable String measureName) {
+        if (!getConfig().isEditableMetricCube()) {
+            return new SegmentResponse();
+        }
+        List<DateTimeRange> dtrs = measureService.getSegmentRanges(cubeName, measureName);
+        int segCnt = measureService.getSegmentCountOf(cubeName, measureName);
+        SegmentResponse ret = new SegmentResponse();
+        ret.setSegmentCount(segCnt);
+        ret.setTimeRanges(dtrs.stream().map(dr -> dr.toString()).collect(Collectors.toList()));
+        return ret;
+    }
+
+    @GetMapping(value = "/segment/{cubeName}", produces = { "application/json" })
+    @ResponseBody
+    public Map<String, SegmentResponse> getSegmentRanges(@PathVariable String cubeName) {
+        if (!getConfig().isEditableMetricCube()) {
+            return Collections.EMPTY_MAP;
+        }
+        Map<String, List<DateTimeRange>> measureVsegment = measureService.getMeasureSegmentsPair(cubeName);
+        Map<String, SegmentResponse> ret = Maps.newHashMapWithExpectedSize(measureVsegment.size());
+        for (Map.Entry<String, List<DateTimeRange>> entry : measureVsegment.entrySet()) {
+            SegmentResponse sr = new SegmentResponse();
+            sr.setTimeRanges(entry.getValue().stream().map(dr -> dr.toString()).collect(Collectors.toList()));
+            sr.setSegmentCount(measureService.getSegmentCountOf(cubeName, entry.getKey()));
+            ret.put(entry.getKey(), sr);
+        }
+        return ret;
+    }
+
+
+    @GetMapping(value = "/{cubeName}", produces = { "application/json" })
+    @ResponseBody
+    public Map<String, List<String>> getMeasuresOnSegment(@PathVariable String cubeName) {
+        if (!getConfig().isEditableMetricCube()) {
+            return Collections.EMPTY_MAP;
+        }
+        Map<String, List<String>> segmentMeasurePair = measureService.getMeasuresOnSegment(cubeName);
+        return segmentMeasurePair;
+    }
+
+    @GetMapping(value = "/{cubeName}/{segmentName}", produces = { "application/json" })
+    @ResponseBody
+    public List<String> getMeasuresOnSegment(@PathVariable String cubeName,
+                                             @PathVariable String segmentName) {
+        if (!getConfig().isEditableMetricCube()) {
+            return Collections.EMPTY_LIST;
+        }
+        List<String> measureNames = measureService.getMeasuresOnSegment(cubeName, segmentName);
+        return measureNames;
+    }
+
+    public KylinConfig getConfig() {
+        KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
+        if (kylinConfig == null) {
+            throw new IllegalArgumentException("Failed to load kylin config instance");
+        }
+        return kylinConfig;
+    }
+
+}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/response/CubeInstanceResponse.java b/server-base/src/main/java/org/apache/kylin/rest/response/CubeInstanceResponse.java
index f6f88bd..22b8464 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/response/CubeInstanceResponse.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/response/CubeInstanceResponse.java
@@ -22,6 +22,7 @@
 import org.apache.kylin.metadata.model.ISourceAware;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.kylin.metadata.realization.RealizationStatusEnum;
 
 /**
  */
@@ -46,6 +47,8 @@
     private long inputRecordCnt;
     @JsonProperty("input_records_size")
     private long inputRecordSizeBytes;
+    @JsonProperty("can_add_measure")
+    private boolean canAddMeasure;
 
     public CubeInstanceResponse(CubeInstance cube, String project) {
 
@@ -81,6 +84,7 @@
         initSizeKB();
         initInputRecordCount();
         initInputRecordSizeBytes();
+        this.canAddMeasure = checkCanAddMeasure(cube);
     }
 
     protected void setModel(String model) {
@@ -99,4 +103,16 @@
         this.inputRecordSizeBytes = super.getInputRecordSizeBytes();
     }
 
+    private boolean checkCanAddMeasure(CubeInstance cube) {
+        if (!cube.getConfig().isEditableMetricCube()) {
+            return false;
+        }
+        if (!cube.getStatus().equals(RealizationStatusEnum.DISABLED)) {
+            return false;
+        }
+        if (cube.getSegments().size() == 0) {
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/server-base/src/main/java/org/apache/kylin/rest/response/HBaseResponse.java b/server-base/src/main/java/org/apache/kylin/rest/response/HBaseResponse.java
index 6d17a53..e365827 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/response/HBaseResponse.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/response/HBaseResponse.java
@@ -19,6 +19,7 @@
 package org.apache.kylin.rest.response;
 
 import java.io.Serializable;
+import java.util.List;
 
 public class HBaseResponse implements Serializable {
     private static final long serialVersionUID = 7263557115683263492L;
@@ -33,6 +34,7 @@
     private long sourceOffsetStart;
     private long sourceOffsetEnd;
     private long sourceCount;
+    private List<String> measuresOnSegment;
 
     public HBaseResponse() {
     }
@@ -159,4 +161,12 @@
     public void setSourceCount(long sourceCount) {
         this.sourceCount = sourceCount;
     }
+
+    public List<String> getMeasuresOnSegment() {
+        return measuresOnSegment;
+    }
+
+    public void setMeasuresOnSegment(List<String> measuresOnSegment) {
+        this.measuresOnSegment = measuresOnSegment;
+    }
 }
diff --git a/server-base/src/main/java/org/apache/kylin/rest/response/SQLResponse.java b/server-base/src/main/java/org/apache/kylin/rest/response/SQLResponse.java
index 1721efe..40da475 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/response/SQLResponse.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/response/SQLResponse.java
@@ -83,6 +83,9 @@
     // indicating the lazy query start time, -1 indicating not enabled
     protected long lazyQueryStartTime = -1L;
 
+    // show the missing Measure within the query scope
+    protected List<String> missMeasureMessage;
+
     public SQLResponse() {
     }
 
@@ -257,4 +260,12 @@
             this.queryStatistics = null;
         }
     }
+
+    public List<String> getMissMeasureMessage() {
+        return missMeasureMessage;
+    }
+
+    public void setMissMeasureMessage(List<String> missMeasureMessage) {
+        this.missMeasureMessage = missMeasureMessage;
+    }
 }
diff --git a/server-base/src/main/java/org/apache/kylin/rest/response/SegmentResponse.java b/server-base/src/main/java/org/apache/kylin/rest/response/SegmentResponse.java
new file mode 100644
index 0000000..ba2d428
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/response/SegmentResponse.java
@@ -0,0 +1,47 @@
+/*
+ * 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.rest.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.List;
+
+public class SegmentResponse {
+
+    @JsonProperty("segment_count")
+    private int segmentCount;
+    @JsonProperty("time_ranges")
+    private List<String> timeRanges;
+
+    public int getSegmentCount() {
+        return segmentCount;
+    }
+
+    public void setSegmentCount(int segmentCount) {
+        this.segmentCount = segmentCount;
+    }
+
+    public List<String> getTimeRanges() {
+        return timeRanges;
+    }
+
+    public void setTimeRanges(List<String> timeRanges) {
+        this.timeRanges = timeRanges;
+    }
+}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java b/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
index 9ac2602..8dc3379 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
@@ -24,6 +24,7 @@
 import org.apache.kylin.cube.CubeDescManager;
 import org.apache.kylin.cube.CubeManager;
 import org.apache.kylin.job.execution.ExecutableManager;
+import org.apache.kylin.measure.MeasureManager;
 import org.apache.kylin.metadata.TableMetadataManager;
 import org.apache.kylin.metadata.acl.TableACLManager;
 import org.apache.kylin.metadata.badquery.BadQueryHistoryManager;
@@ -98,4 +99,8 @@
     public MetricsManager getMetricsManager() {
         return MetricsManager.getInstance();
     }
+
+    public MeasureManager getMeasureManager() {
+        return MeasureManager.getInstance(getConfig());
+    }
 }
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java b/server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java
index 7378165..8cd3bee 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/CubeService.java
@@ -20,6 +20,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.EnumSet;
@@ -27,6 +28,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.hadoop.hbase.client.HBaseAdmin;
@@ -39,10 +41,15 @@
 import org.apache.kylin.cube.CubeManager;
 import org.apache.kylin.cube.CubeSegment;
 import org.apache.kylin.cube.CubeUpdate;
+import org.apache.kylin.cube.adapter.AbstractHBaseMappingAdapter;
+import org.apache.kylin.cube.adapter.IWiseHBaseMapper;
 import org.apache.kylin.cube.cuboid.Cuboid;
 import org.apache.kylin.cube.cuboid.CuboidCLI;
 import org.apache.kylin.cube.cuboid.CuboidScheduler;
 import org.apache.kylin.cube.model.CubeDesc;
+import org.apache.kylin.cube.model.HBaseColumnDesc;
+import org.apache.kylin.cube.model.HBaseColumnFamilyDesc;
+import org.apache.kylin.cube.model.HBaseMappingDesc;
 import org.apache.kylin.engine.EngineFactory;
 import org.apache.kylin.engine.mr.CubingJob;
 import org.apache.kylin.engine.mr.JobBuilderSupport;
@@ -393,6 +400,12 @@
         }
 
         this.releaseAllSegments(cube);
+        // All measures in a column family are stored in one column will reduce the
+        // complexity in Web UI
+        if (cube.getSegments().size() == 0) {
+            mergeMeasureIntoOneCF(cube.getDescriptor());
+        }
+
         return cube;
     }
 
@@ -606,7 +619,9 @@
         CubeInstance cubeInstance = CubeManager.getInstance(getConfig()).updateCubeDropSegments(cube, toDelete);
 
         cleanSegmentStorage(Collections.singletonList(toDelete));
-
+        if (cube.getSegments().size() == 0) {
+            mergeMeasureIntoOneCF(cube.getDescriptor());
+        }
         return cubeInstance;
     }
 
@@ -1079,4 +1094,39 @@
             throw new InternalErrorException("Failed to perform one-click migrating", e);
         }
     }
+
+    public Pair<List<String>, List<String>> getUpdateMeasures(CubeDesc newDesc, CubeDesc oldDesc) {
+        List<String> oldMeasures = oldDesc.getMeasures().stream().map(MeasureDesc::getName).collect(Collectors.toList());
+        List<String> newMeasures = newDesc.getMeasures().stream().map(MeasureDesc::getName).collect(Collectors.toList());
+
+        List<String> needAdd = Lists.newArrayList(newMeasures);
+        needAdd.removeAll(oldMeasures);
+
+        List<String> needDrop = Lists.newArrayList(oldMeasures);
+        needDrop.removeAll(newMeasures);
+        Pair<List<String>, List<String>> ret = new Pair<>(needAdd, needDrop);
+        return ret;
+    }
+
+    public HBaseMappingDesc getAutoHBaseMapping(CubeDesc desc, List<String> needAddMeasure) {
+        IWiseHBaseMapper mapper = AbstractHBaseMappingAdapter.getHBaseAdapter(getConfig());
+        return mapper.addMeasure(desc, needAddMeasure);
+    }
+
+    private HBaseMappingDesc mergeMeasureIntoOneCF(CubeDesc desc) {
+        HBaseMappingDesc hBaseMappingDesc = desc.getHbaseMapping();
+        for (HBaseColumnFamilyDesc cfDesc : hBaseMappingDesc.getColumnFamily()) {
+            if (cfDesc.getColumns().length < 2) {
+                continue;
+            }
+            List<String> measuresInCF = Lists.newArrayList();
+            for (HBaseColumnDesc colDesc : cfDesc.getColumns()) {
+                measuresInCF.addAll(Arrays.asList(colDesc.getMeasureRefs()));
+            }
+            cfDesc.setColumns(new HBaseColumnDesc[]{cfDesc.getColumns()[0]});
+            cfDesc.getColumns()[0].setMeasureRefs(measuresInCF.toArray(new String[0]));
+        }
+
+        return hBaseMappingDesc;
+    }
 }
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/MeasureService.java b/server-base/src/main/java/org/apache/kylin/rest/service/MeasureService.java
new file mode 100644
index 0000000..07b3dcf
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/MeasureService.java
@@ -0,0 +1,125 @@
+/*
+ * 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.rest.service;
+
+import com.google.common.collect.Maps;
+import org.apache.kylin.cube.CubeInstance;
+import org.apache.kylin.cube.model.CubeDesc;
+import org.apache.kylin.measure.MeasureInstance;
+import org.apache.kylin.metadata.model.DateTimeRange;
+import org.apache.kylin.metadata.model.ISegment;
+import org.apache.kylin.metadata.model.MeasureDesc;
+import org.apache.kylin.metadata.model.SegmentStatusEnum;
+import org.apache.kylin.rest.exception.BadRequestException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+
+@Service("measureMgmtService")
+public class MeasureService extends BasicService{
+    private final static Logger LOG = LoggerFactory.getLogger(MeasureService.class);
+
+    public List<DateTimeRange> getSegmentRanges(String cubeName, String measureName) {
+        CubeDesc cube = getCubeDesc(cubeName);
+        if (cube == null) {
+            return Collections.EMPTY_LIST;
+        }
+        return getSegmentRanges(cube, measureName);
+    }
+
+    public Map<String, List<DateTimeRange>> getMeasureSegmentsPair(String cubeName) {
+        Map<String, List<DateTimeRange>> ret = Maps.newHashMap();
+        CubeDesc cube = getCubeDesc(cubeName);
+        if (cube == null) {
+            return Collections.EMPTY_MAP;
+        }
+        List<MeasureInstance> measuresInCache = getMeasureManager().getMeasuresInCube(cubeName);
+        List<MeasureDesc> measuresInCube = cube.getMeasures();
+        if (measuresInCache.size() != measuresInCube.size()) {
+            throw new IllegalStateException(String.format(Locale.ROOT, "The size of measures not equal. in cache: [%s], in cube: [%s]",
+                    measuresInCache.stream().map(m -> m.getName()).collect(Collectors.joining(", ")),
+                    measuresInCube.stream().map(m -> m.getName()).collect(Collectors.joining(", "))));
+        }
+        for (MeasureDesc measureDesc : measuresInCube) {
+            ret.put(measureDesc.getName(), getSegmentRanges(cube, measureDesc.getName()));
+        }
+        return ret;
+    }
+
+    public int getSegmentCountOf(String cubeName, String measureName) {
+        MeasureInstance measure = getMeasureManager().getMeasure(cubeName, measureName);
+        return null == measure ? 0 : measure.getSegments().size();
+    }
+
+    public List<DateTimeRange> getSegmentRanges(CubeDesc cube, String measureName) {
+        MeasureDesc measure = findMeasureByName(cube, measureName);
+        MeasureInstance measureInstance = getMeasureManager().getMeasure(cube.getName(), measure.getName());
+        if (measureInstance == null) {
+            return Collections.EMPTY_LIST;
+        }
+        return measureInstance.getDateTimeRanges();
+    }
+
+    public List<String> getMeasuresOnSegment(String cubeName, String segmentName) {
+        return getMeasureManager()
+                .getMeasuresOnSegment(cubeName, segmentName)
+                .stream()
+                .map(MeasureInstance::getName)
+                .collect(Collectors.toList());
+    }
+
+    public Map<String, List<String>> getMeasuresOnSegment(String cubeName) {
+        CubeDesc cubeDesc = getCubeDesc(cubeName);
+        if (null == cubeDesc) {
+            return Collections.EMPTY_MAP;
+        }
+        CubeInstance cubeInstance = getCubeManager().getCube(cubeDesc.getName());
+        Map<String, List<String>> ret = Maps.newHashMap();
+        for (ISegment seg : cubeInstance.getSegments(SegmentStatusEnum.READY)) {
+            ret.put(seg.getName(), getMeasuresOnSegment(cubeName, seg.getName()));
+        }
+        return ret;
+    }
+
+    private CubeDesc getCubeDesc(String cubeName) {
+        CubeDesc cube = getCubeDescManager().getCubeDesc(cubeName);
+        if (null == cube) {
+            LOG.warn("Can't find cube {}, may be it's designning?", cubeName);
+            return null;
+        }
+        return cube;
+    }
+
+    private MeasureDesc findMeasureByName(CubeDesc cube, String measureName) {
+        for (MeasureDesc measureDesc : cube.getMeasures()) {
+            if (measureName.equals(measureDesc.getName())) {
+                return measureDesc;
+            }
+        }
+        throw new BadRequestException(format(Locale.ROOT, "Can't find measure[%s] in cube[%s]", measureName, cube.getName()));
+    }
+}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java b/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java
index 59b2b61..b1ed290 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java
@@ -45,6 +45,7 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import javax.annotation.PostConstruct;
 
@@ -64,6 +65,7 @@
 import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
 import org.apache.kylin.cache.cachemanager.MemcachedCacheManager;
 import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.MissingMeasureSegment;
 import org.apache.kylin.common.QueryContext;
 import org.apache.kylin.common.QueryContextFacade;
 import org.apache.kylin.common.debug.BackdoorToggles;
@@ -440,7 +442,12 @@
             }
             if (sqlResponse.getIsException())
                 throw new InternalErrorException(sqlResponse.getExceptionMessage());
-
+            // add hint about the missing measure within the query
+            List<MissingMeasureSegment> missingMeasureSegmentList = queryContext.getMissingMeasureSegments();
+            List<String> missMeasureMsgs = missingMeasureSegmentList.stream()
+                    .map(mms -> mms.getMissingMsg())
+                    .collect(Collectors.toList());
+            sqlResponse.setMissMeasureMessage(missMeasureMsgs);
             return sqlResponse;
 
         } finally {
diff --git a/webapp/app/js/controllers/cubeAdvanceSetting.js b/webapp/app/js/controllers/cubeAdvanceSetting.js
index 08cd044..958c65a 100755
--- a/webapp/app/js/controllers/cubeAdvanceSetting.js
+++ b/webapp/app/js/controllers/cubeAdvanceSetting.js
@@ -330,9 +330,11 @@
   $scope.getAssignedMeasureNames = function () {
     var assignedMeasures = [];
     angular.forEach($scope.cubeMetaFrame.hbase_mapping.column_family, function (colFamily, index) {
-      angular.forEach(colFamily.columns[0].measure_refs, function (measure, index) {
-        assignedMeasures.push(measure);
-      });
+      angular.forEach(colFamily.columns, function (column, index) {
+        angular.forEach(column.measure_refs, function (measure, index) {
+            assignedMeasures.push(measure);
+         });
+      })
     });
     return assignedMeasures;
   };
@@ -342,14 +344,20 @@
     var tmpColumnFamily = $scope.cubeMetaFrame.hbase_mapping.column_family;
 
     for(var j=0;j<$scope.cubeMetaFrame.hbase_mapping.column_family.length; j++) {
-      for (var i=0;i<$scope.cubeMetaFrame.hbase_mapping.column_family[j].columns[0].measure_refs.length; i++){
-        var allIndex = allMeasureNames.indexOf($scope.cubeMetaFrame.hbase_mapping.column_family[j].columns[0].measure_refs[i]);
-        if (allIndex == -1) {
-          tmpColumnFamily[j].columns[0].measure_refs.splice(i, 1);
-          i--
+      for (var k=0; k < $scope.cubeMetaFrame.hbase_mapping.column_family[j].columns.length; k++) {
+        for (var i=0;i<$scope.cubeMetaFrame.hbase_mapping.column_family[j].columns[k].measure_refs.length; i++){
+          var allIndex = allMeasureNames.indexOf($scope.cubeMetaFrame.hbase_mapping.column_family[j].columns[0].measure_refs[i]);
+          if (allIndex == -1) {
+            tmpColumnFamily[j].columns[0].measure_refs.splice(i, 1);
+            i--
+          }
+        }
+        if (tmpColumnFamily[j].columns[k].measure_refs.length == 0) {
+          tmpColumnFamily[j].columns.splice(k, 1);
+          k--
         }
       }
-      if (tmpColumnFamily[j].columns[0].measure_refs.length == 0) {
+      if (tmpColumnFamily[j].columns.length == 0) {
         tmpColumnFamily.splice(j, 1);
         j--
       }
@@ -524,4 +532,12 @@
   };
 
   $scope.cubeLookups = $scope.getCubeLookups();
+
+  $scope.getCFDisplayString = function (columnFamily) {
+    var columnArr = new Array();
+    for (i = 0; i < columnFamily.columns.length; i++) {
+      columnArr[i] = columnFamily.columns[i].qualifier + ":[" + columnFamily.columns[i].measure_refs + "]"
+    }
+    return columnArr.join(", ");
+  };
 });
diff --git a/webapp/app/js/controllers/cubeEdit.js b/webapp/app/js/controllers/cubeEdit.js
index eeed96a..6201eca 100755
--- a/webapp/app/js/controllers/cubeEdit.js
+++ b/webapp/app/js/controllers/cubeEdit.js
@@ -28,6 +28,14 @@
   var absUrl = $location.absUrl();
   $scope.cubeMode = absUrl.indexOf("/cubes/add") != -1 ? 'addNewCube' : absUrl.indexOf("/cubes/edit") != -1 ? 'editExistCube' : 'default';
 
+  $scope.cubeMode2 = 'default';
+  $scope.isMeasureEdit = false;
+  if ($scope.cubeMode === 'default' && absUrl.indexOf("/cubes/measure") != -1) {
+    $scope.cubeMode2 = 'measure';
+    $scope.isMeasureEdit = true;
+    $scope.cubeMode = 'editExistCube';
+  }
+
   if ($scope.cubeMode == "addNewCube" &&ProjectModel.selectedProject==null) {
     SweetAlert.swal('Oops...', 'Please select your project first.', 'warning');
     $location.path("/models");
diff --git a/webapp/app/js/controllers/cubeMeasures.js b/webapp/app/js/controllers/cubeMeasures.js
index 79d21e5..5313ed0 100644
--- a/webapp/app/js/controllers/cubeMeasures.js
+++ b/webapp/app/js/controllers/cubeMeasures.js
@@ -18,10 +18,11 @@
 
 'use strict';
 
-KylinApp.controller('CubeMeasuresCtrl', function ($scope, $modal,MetaModel,cubesManager,CubeDescModel,SweetAlert,VdmUtil,TableModel,cubeConfig,modelsManager,kylinConfig) {
+KylinApp.controller('CubeMeasuresCtrl', function ($scope, $modal,MetaModel,cubesManager,CubeDescModel,SweetAlert,VdmUtil,TableModel,cubeConfig,modelsManager,kylinConfig,MeasureService) {
   $scope.num=0;
   $scope.convertedColumns=[];
   $scope.groupby=[];
+  $scope.newMeasures=[];
   $scope.initUpdateMeasureStatus = function(){
     $scope.updateMeasureStatus = {
       isEdit:false,
@@ -250,9 +251,16 @@
 
     if($scope.updateMeasureStatus.isEdit == true){
       $scope.cubeMetaFrame.measures[$scope.updateMeasureStatus.editIndex] = $scope.newMeasure;
+      if ($scope.isMeasureEdit) {
+        $scope.removeElement($scope.newMeasures, $scope.cubeMetaFrame.measures[$scope.updateMeasureStatus.editIndex]);
+        $scope.newMeasures.push($scope.newMeasure);
+      }
     }
     else {
       $scope.cubeMetaFrame.measures.push($scope.newMeasure);
+      if ($scope.isMeasureEdit) {
+        $scope.newMeasures.push($scope.newMeasure);
+      }
     }
 
     $scope.newMeasure = null;
@@ -504,6 +512,43 @@
     }
   }
 
+  $scope.isNewMeasure = function(measure) {
+    for (var i = 0; i < $scope.newMeasures.length; i++) {
+      if ($scope.newMeasures[i].name === measure.name) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  $scope.measureTimeRange = {};
+  $scope.$watch('cubeMetaFrame.name', function(newValue, oldValue) {
+    if (newValue) {
+      MeasureService.get({segment: "segment", cube: $scope.cubeMetaFrame.name}, {}, function (result) {
+          $scope.measureTimeRange = result;
+        }, function(e) {
+            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.getTimeRange = function(measureName) {
+    var tr = $scope.measureTimeRange[measureName];
+    if (tr) {
+      return "<div style='text-align:left'>"
+        + "Segment Count: " + tr.segment_count + "<br/>"
+        + "Time Range: <br/>" + tr.time_ranges.join("<br/>")
+        + "</div>";
+    } else {
+      return "empty";
+    }
+  }
 });
 
 var NextParameterModalCtrl = function ($scope, scope,para,$modalInstance,cubeConfig, CubeService, MessageService, $location, SweetAlert,ProjectModel, loadingRequest,ModelService) {
diff --git a/webapp/app/js/controllers/cubeSchema.js b/webapp/app/js/controllers/cubeSchema.js
index 31a5afe..b77b5b8 100755
--- a/webapp/app/js/controllers/cubeSchema.js
+++ b/webapp/app/js/controllers/cubeSchema.js
@@ -37,6 +37,20 @@
 
     $scope.curStep = $scope.wizardSteps[0];
 
+  $scope.findMeasuresStepIndex = function() {
+    for (var i = 0; i < $scope.wizardSteps.length; i++) {
+      if ($scope.wizardSteps[i].title === 'Measures') {
+        return i;
+      }
+    }
+    return 0;
+  }
+
+  if ($scope.isMeasureEdit) {
+    var editMeasureStepIdx = $scope.findMeasuresStepIndex();
+    $scope.curStep = $scope.wizardSteps[editMeasureStepIdx];
+  }
+
   $scope.getTypeVersion=function(typename){
     var searchResult=/\[v(\d+)\]/.exec(typename);
     if(searchResult&&searchResult.length){
@@ -388,12 +402,13 @@
         }
 
         var cfMeasures = [];
-        angular.forEach($scope.cubeMetaFrame.hbase_mapping.column_family,function(cf){
-          angular.forEach(cf.columns[0].measure_refs, function (measure, index) {
-            cfMeasures.push(measure);
+        angular.forEach($scope.cubeMetaFrame.hbase_mapping.column_family, function (colFamily, index) {
+          angular.forEach(colFamily.columns, function (column, index) {
+            angular.forEach(column.measure_refs, function(measure) {
+              cfMeasures.push(measure);
+            });
           });
         });
-
         var uniqCfMeasures = _.uniq(cfMeasures);
         if(uniqCfMeasures.length != $scope.cubeMetaFrame.measures.length) {
           errors.push("All measures need to be assigned to column family");
@@ -497,4 +512,8 @@
             });
         });
     }
+
+    $scope.cancel = function () {
+      $location.path("models");
+    }
 });
diff --git a/webapp/app/js/controllers/cubes.js b/webapp/app/js/controllers/cubes.js
index d4c67bb..f8eee6b 100644
--- a/webapp/app/js/controllers/cubes.js
+++ b/webapp/app/js/controllers/cubes.js
@@ -606,6 +606,10 @@
       });
     };
 
+    $scope.addMeasure = function (cube) {
+      $location.path("cubes/measure/" + cube.name);
+    };
+
     $scope.listCubeAccess = function (cube) {
       //check project auth for user
       $scope.cubeProjectEntity = _.find($scope.projectModel.projects, function(project) {return project.name == $scope.projectModel.selectedProject;});
diff --git a/webapp/app/js/controllers/query.js b/webapp/app/js/controllers/query.js
index 5be4cd7..cb5af5a 100644
--- a/webapp/app/js/controllers/query.js
+++ b/webapp/app/js/controllers/query.js
@@ -569,4 +569,19 @@
                 $scope.chart.data = [];
             }
         }
+
+        $scope.getMissInfo = function(query) {
+          if (query.status != 'success') {
+            return '';
+          }
+          var missMeasureInfo = query.result.missMeasureMessage;
+          if (missMeasureInfo) {
+            var retMsg = "";
+            for (var i = 0; i < missMeasureInfo.length; i++) {
+              retMsg += (i+1) + ". " + missMeasureInfo[i] + "\n";
+            }
+            return retMsg;
+          }
+          return "empty";
+        }
     });
diff --git a/webapp/app/js/services/measure.js b/webapp/app/js/services/measure.js
new file mode 100644
index 0000000..5429852
--- /dev/null
+++ b/webapp/app/js/services/measure.js
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+KylinApp.factory('MeasureService', ['$resource', function ($resource, config) {
+  return $resource(Config.service.url + 'measure/:segment/:cube/:segment_name', {}, {
+    get: {method: 'GET', params: {}, isArray: false}
+  });
+}])
diff --git a/webapp/app/partials/cubeDesigner/advanced_settings.html b/webapp/app/partials/cubeDesigner/advanced_settings.html
index 5b063ef..0eae9c9 100755
--- a/webapp/app/partials/cubeDesigner/advanced_settings.html
+++ b/webapp/app/partials/cubeDesigner/advanced_settings.html
@@ -607,7 +607,7 @@
            <div style="margin-left:42px">
              <div class="box-body">
                <!-- VIEW MODE -->
-               <div class="row"  ng-if="state.mode=='view'&& cubeMetaFrame.hbase_mapping.column_family.length > 0">
+               <div class="row"  ng-if="state.mode=='view' || instance.segments.length > 0 && cubeMetaFrame.hbase_mapping.column_family.length > 0">
                  <table class="table table-striped table-hover">
                    <thead>
                    <tr>
@@ -623,7 +623,7 @@
                      </td>
                      <!--Name -->
                      <td class="col-xs-11">
-                       <span>{{colFamily.columns[0].measure_refs}}</span>
+                       <span>{{getCFDisplayString(colFamily)}}</span>
                      </td>
                    </tr>
                    </tbody>
@@ -631,7 +631,7 @@
                </div>
 
                <!-- EDIT MODE -->
-               <div  ng-if="state.mode=='edit'" class="form-group " style="width: 100%">
+               <div  ng-if="state.mode=='edit' && instance.segments.length == 0" class="form-group " style="width: 100%">
                  <table ng-if="cubeMetaFrame.hbase_mapping.column_family.length > 0"
                         class="table table-hover">
 
@@ -673,7 +673,7 @@
                </div>
 
                <div class="form-group" >
-                 <button class="btn btn-sm btn-info" ng-click="addColumnFamily()" ng-show="state.mode=='edit'">
+                 <button class="btn btn-sm btn-info" ng-click="addColumnFamily()" ng-show="state.mode=='edit' && instance.segments.length == 0">
                    <i class="fa fa-plus"></i> ColumnFamily
                  </button>
                </div>
diff --git a/webapp/app/partials/cubeDesigner/measures.html b/webapp/app/partials/cubeDesigner/measures.html
index 2e11fe3..c9397df 100755
--- a/webapp/app/partials/cubeDesigner/measures.html
+++ b/webapp/app/partials/cubeDesigner/measures.html
@@ -41,7 +41,7 @@
                       <tr ng-repeat="measure in cubeMetaFrame.measures | filter: state.measureFilter track by $index">
                           <td>
                               <!--Name -->
-                              <span tooltip="measure name..">{{measure.name}}</span>
+                              <span tooltip-html-unsafe="{{getTimeRange(measure.name)}}">{{measure.name}}</span>
                           </td>
                           <td>
                               <!--Expression -->
@@ -63,11 +63,13 @@
                           </td>
                           <td ng-if="state.mode=='edit'">
                               <!--Edit Button -->
-                              <button class="btn btn-xs btn-info" ng-click="addNewMeasure(measure, $index)" ng-disabled="instance.status=='READY'">
+                            <!-- In cubeMode 'editExistCube', user shouldn't edit or remove measure if there still have segments under cube -->
+                            <!-- User can only add new measures by click Add Measure in cube list -->
+                              <button class="btn btn-xs btn-info" ng-click="addNewMeasure(measure, $index)" ng-disabled="instance.status=='READY' || (!isMeasureEdit && instance.segments.length > 0)" ng-if="!isMeasureEdit || isNewMeasure(measure)">
                                   <i class="fa fa-pencil"></i>
                               </button>
                               <!--Remove Button -->
-                              <button class="btn btn-xs  btn-danger" ng-click="removeElement(cubeMetaFrame.measures, measure)" ng-disabled="instance.status=='READY'">
+                              <button class="btn btn-xs  btn-danger" ng-click="removeElement(cubeMetaFrame.measures, measure);removeElement(newMeasures, measure)" ng-disabled="instance.status=='READY' || (!isMeasureEdit && instance.segments.length > 0)" ng-if="!isMeasureEdit || isNewMeasure(measure)">
                                   <i class="fa fa-trash-o"></i>
                               </button>
                           </td>
@@ -78,7 +80,7 @@
   </ng-form>
   <!--Add Measures Button-->
   <div class="form-group">
-      <button class="btn btn-sm btn-info" ng-click="addNewMeasure()" ng-show="state.mode=='edit' && !newMeasure" ng-disabled="instance.status=='READY'">
+      <button class="btn btn-sm btn-info" ng-click="addNewMeasure()" ng-show="state.mode=='edit' && !newMeasure" ng-disabled="instance.status=='READY' || (!isMeasureEdit && instance.segments.length > 0)">
           <i class="fa fa-plus"></i> Measure
       </button>
   </div>
diff --git a/webapp/app/partials/cubes/cube_detail.html b/webapp/app/partials/cubes/cube_detail.html
index 8099845..dae5502 100755
--- a/webapp/app/partials/cubes/cube_detail.html
+++ b/webapp/app/partials/cubes/cube_detail.html
@@ -107,7 +107,7 @@
               <h5><b>Segment Number:</b> <span class="red">{{cube.hbase.length}}</span> <b>Total Size:</b> <span class="red">{{cube.totalSize | bytes}}</span></h5>
             </div>
             <div ng-repeat="table in cube.hbase">
-                <h5><b>Segment:</b> {{table.segmentName}}</h5>
+                <h5><b>Segment:</b> <span tooltip-html-unsafe="<div style='text-align:left'>Measure Count: {{table.measuresOnSegment.length}}<br/>Measures:<br/>{{table.measuresOnSegment.join('<br/>')}}</div>">{{table.segmentName}}</span></h5>
                 <ul>
                     <li ng-if="cube.streaming">Status: <span class="red">{{table.segmentStatus}}</span></li>
                     <li ng-if="cube.model.partition_desc.partition_date_column">Start Time: <span class="red">{{table.dateRangeStart | reverseToGMT0}}</span></li>
@@ -293,4 +293,4 @@
             <div class="col-md-7">{{receiverStats.consume_lag}}</div>
         </div>
     </div>
-</script>
\ No newline at end of file
+</script>
diff --git a/webapp/app/partials/cubes/cube_schema.html b/webapp/app/partials/cubes/cube_schema.html
index 5cf39dc..0285819 100644
--- a/webapp/app/partials/cubes/cube_schema.html
+++ b/webapp/app/partials/cubes/cube_schema.html
@@ -18,10 +18,11 @@
 
 <div class="box box-primary box-2px">
     <div class="box-header widget-header-blue widget-header-flat">
-        <h4 class="box-title text-info">Cube Designer</h4>
+      <h4 class="box-title text-info" ng-if="!isMeasureEdit">Cube Designer</h4>
+      <h4 class="box-title text-info" ng-if="isMeasureEdit">Measure Editor</h4>
     </div>
     <div class="box-body">
-            <div>
+            <div ng-if="!isMeasureEdit">
                 <ul class="wizard-steps">
                     <li ng-repeat="step in wizardSteps"
                         class="{{step==curStep?'active':''}} {{step.isComplete?'complete':''}}">
@@ -42,17 +43,21 @@
                         </div>
                     </div>
                     <div class="col-xs-4">
-                        <button class="btn btn-prev" ng-click="preView()" ng-show="curStep.title!='Cube Info'">
+                        <button class="btn btn-prev" ng-click="preView()" ng-show="!isMeasureEdit && curStep.title!='Cube Info'">
                             <i class="ace-icon fa fa-arrow-left"></i>
                             Prev
                         </button>
+                        <button class="btn btn-prev" ng-click="cancel()" ng-if="isMeasureEdit">
+                          <i class="ace-icon fa fa-arrow-left"></i>
+                          Cancel
+                        </button>
                         <button id="nextButton" class="btn btn-success btn-next"  ng-click="checkCubeForm($index)?nextView():''" ng-disabled="forms[curStep.form].$invalid"
-                                ng-show="curStep.title!='Overview'">
+                                ng-show="!isMeasureEdit && curStep.title!='Overview'">
                             Next
                             <i class="ace-icon fa fa-arrow-right icon-on-right"></i>
                         </button>
                         <button class="btn btn-primary" ng-click="prepareCube();saveCube()" ng-disabled="design_form.$invalid"
-                                ng-if="curStep.title=='Overview' && state.mode=='edit'">
+                                ng-if="(isMeasureEdit || curStep.title=='Overview') && state.mode=='edit'">
                             Save
                         </button>
                     </div>
diff --git a/webapp/app/partials/cubes/cubes.html b/webapp/app/partials/cubes/cubes.html
index fd176f7..798ae67 100644
--- a/webapp/app/partials/cubes/cubes.html
+++ b/webapp/app/partials/cubes/cubes.html
@@ -103,7 +103,7 @@
                         <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' && isAutoMigrateCubeEnabled() && (userService.hasRole('ROLE_ADMIN') || hasPermission('cube',cube, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask)) "><a ng-click="migrateCube(cube)">Migrate</a></li>
-
+                        <li ng-if="cube.can_add_measure && (userService.hasRole('ROLE_ADMIN') || hasPermission('cube',cube, permissions.ADMINISTRATION.mask, permissions.MANAGEMENT.mask))"><a ng-click="addMeasure(cube)">Add Measure</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;">
                         <li ng-if="cube.status!='DISABLED' && cube.consumeState=='RUNNING'"><a ng-click="pauseCube(cube, $index);">Pause</a></li>
diff --git a/webapp/app/partials/query/query_detail.html b/webapp/app/partials/query/query_detail.html
index 63cf5ac..9add54c 100644
--- a/webapp/app/partials/query/query_detail.html
+++ b/webapp/app/partials/query/query_detail.html
@@ -59,14 +59,18 @@
             <span class="label label-lg label-info" ng-if="curQuery.status=='executing'">
                 <i class="fa fa-cog fa-spin"></i> Executing...</span>
         </li>
-        <li class="col-md-5 " style="display: inline">
+        <li class="col-md-2 " style="display: inline">
             <label>Project: &nbsp;</label>
             <span>{{curQuery.project}}</span>
         </li>
-        <li class="col-md-5 " style="display: inline">
+        <li class="col-md-4 " style="display: inline">
             <label>Cubes: &nbsp;</label>
             <span>{{curQuery.result.cube | limitTo:30}}<span ng-if="curQuery.result.cube.length > 30">...  <i class="fa fa-list text-aqua" style="cursor: pointer;" popover-placement="left" popover="{{curQuery.result.cube | formatCubeName}}" popover-title="Cube Info Details"></i></span></span>
         </li>
+        <li class="col-md-4 " style="display: inline">
+        <label>Miss: &nbsp;</label>
+        <span>{{getMissInfo(curQuery) | limitTo:30}}<span ng-if="getMissInfo(curQuery).length > 0">...  <i class="fa fa-list text-aqua" style="cursor: pointer;" popover-placement="left" popover="{{getMissInfo(curQuery)}}" popover-title="Miss Info Details"></i></span></span>
+      </li>
     </ul>
 </div>
 
diff --git a/webapp/app/routes.json b/webapp/app/routes.json
index 210fda2..2dcddcc 100644
--- a/webapp/app/routes.json
+++ b/webapp/app/routes.json
@@ -48,6 +48,14 @@
     }
   },
   {
+    "url": "/cubes/measure/:cubeName",
+    "params": {
+      "templateUrl": "partials/cubes/cube_edit.html",
+      "tab": "models",
+      "controller": "CubeEditCtrl"
+    }
+  },
+  {
     "url": "/sourceMeta",
     "params": {
       "templateUrl": "partials/tables/source_metadata.html",