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

jihao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new a7a1745  [TE] detection - yaml validation (#3597)
a7a1745 is described below

commit a7a1745b6845252410fe7f0f4bff602d474df227
Author: Jihao Zhang <[email protected]>
AuthorDate: Mon Dec 10 14:05:38 2018 -0800

    [TE] detection - yaml validation (#3597)
    
    - Detection yaml config validation, both syntactically and semantically
    - Yaml edit endpoint
    - Use wow baseline if baseline provider not specified.
---
 .../java/com/linkedin/thirdeye/api/Constants.java  |   1 +
 .../bao/jdbc/DetectionConfigManagerImpl.java       |  35 +++++
 .../detection/DetectionMigrationResource.java      |  11 +-
 .../thirdeye/detection/DetectionPipeline.java      |   3 +-
 .../SitewideImpactRuleAnomalyFilter.java           |   3 +
 .../spec/AbsoluteChangeRuleAnomalyFilterSpec.java  |   2 +-
 .../spec/AbsoluteChangeRuleDetectorSpec.java       |   2 +-
 .../PercentageChangeRuleAnomalyFilterSpec.java     |   2 +-
 .../spec/PercentageChangeRuleDetectorSpec.java     |   2 +-
 .../spec/SitewideImpactRuleAnomalyFilterSpec.java  |   2 +-
 .../wrapper/BaselineFillingMergeWrapper.java       |  23 +++-
 .../wrapper/ChildKeepingMergeWrapper.java          |   7 +-
 .../yaml/CompositePipelineConfigTranslator.java    |  71 +++++++----
 .../yaml/YamlDetectionConfigTranslator.java        |   1 -
 .../yaml/YamlDetectionTranslatorLoader.java        |   2 +
 .../thirdeye/detection/yaml/YamlResource.java      | 141 ++++++++++++++++-----
 .../stage/AnomalyDetectionStageWrapperTest.java    |   2 +-
 .../stage/BaselineRuleDetectionStageTest.java      |   2 +-
 .../wrapper/AnomalyDetectorWrapperTest.java        |   1 +
 .../wrapper/BaselineFillingMergeWrapperTest.java   |   6 +-
 .../compositePipelineTranslatorTestResult-1.json   |  40 +++---
 .../compositePipelineTranslatorTestResult-2.json   |  12 +-
 .../thirdeye/detection/yaml/pipeline-config-1.yaml |  11 +-
 .../thirdeye/detection/yaml/pipeline-config-2.yaml |   4 +-
 24 files changed, 270 insertions(+), 116 deletions(-)

diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/api/Constants.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/api/Constants.java
index bbaac7f..b19913f 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/api/Constants.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/api/Constants.java
@@ -26,4 +26,5 @@ public class Constants {
   public static final String RCA_TAG = "Rootcause";
   public static final String DASHBOARD_TAG = "Dashboard";
   public static final String ONBOARD_TAG = "Onboard";
+  public static final String YAML_TAG = "Yaml";
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/datalayer/bao/jdbc/DetectionConfigManagerImpl.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/datalayer/bao/jdbc/DetectionConfigManagerImpl.java
index 946c9c7..06ad3ca 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/datalayer/bao/jdbc/DetectionConfigManagerImpl.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/datalayer/bao/jdbc/DetectionConfigManagerImpl.java
@@ -19,7 +19,10 @@ package com.linkedin.thirdeye.datalayer.bao.jdbc;
 import com.google.inject.Singleton;
 import com.linkedin.thirdeye.datalayer.bao.DetectionConfigManager;
 import com.linkedin.thirdeye.datalayer.dto.DetectionConfigDTO;
+import com.linkedin.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
 import com.linkedin.thirdeye.datalayer.pojo.DetectionConfigBean;
+import com.linkedin.thirdeye.datalayer.pojo.MergedAnomalyResultBean;
+import java.util.Collections;
 
 
 @Singleton
@@ -28,5 +31,37 @@ public class DetectionConfigManagerImpl extends 
AbstractManagerImpl<DetectionCon
     super(DetectionConfigDTO.class, DetectionConfigBean.class);
   }
 
+  @Override
+  public int update(DetectionConfigDTO detectionConfigDTO) {
+    if (detectionConfigDTO.getId() == null) {
+      Long id = save(detectionConfigDTO);
+      if (id > 0) {
+        return 1;
+      } else {
+        return 0;
+      }
+    } else {
+      DetectionConfigBean detectionConfigBean = 
convertDetectionConfigDTO2Bean(detectionConfigDTO);
+      return genericPojoDao.update(detectionConfigBean);
+    }
+  }
+
+  @Override
+  public Long save(DetectionConfigDTO detectionConfigDTO) {
+    if (detectionConfigDTO.getId() != null) {
+      //TODO: throw exception and force the caller to call update instead
+      update(detectionConfigDTO);
+      return detectionConfigDTO.getId();
+    }
+    DetectionConfigBean detectionConfigBean = 
convertDetectionConfigDTO2Bean(detectionConfigDTO);
+    Long id = genericPojoDao.put(detectionConfigBean);
+    detectionConfigDTO.setId(id);
+    return id;
+  }
 
+  DetectionConfigBean convertDetectionConfigDTO2Bean(DetectionConfigDTO 
detectionConfigDTO){
+    detectionConfigDTO.setComponents(Collections.emptyMap());
+    DetectionConfigBean bean = convertDTO2Bean(detectionConfigDTO, 
DetectionConfigBean.class);
+    return bean;
+  }
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/DetectionMigrationResource.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/DetectionMigrationResource.java
index c53af49..eee8f0c 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/DetectionMigrationResource.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/DetectionMigrationResource.java
@@ -115,21 +115,20 @@ public class DetectionMigrationResource {
     }
 
     Map<String, Object> ruleYaml = new LinkedHashMap<>();
-    ruleYaml.put("name", "myRule");
 
     // detection
     if (anomalyFunctionDTO.getType().equals("WEEK_OVER_WEEK_RULE")){
       // wo1w change detector
-      ruleYaml.put("detection", 
Collections.singletonList(ImmutableMap.of("type", "PERCENTAGE_RULE",
+      ruleYaml.put("detection", 
Collections.singletonList(ImmutableMap.of("name", "detection_rule1", "type", 
"PERCENTAGE_RULE",
           "params", 
getPercentageChangeRuleDetectorParams(anomalyFunctionDTO))));
     } else if (anomalyFunctionDTO.getType().equals("MIN_MAX_THRESHOLD")){
       // threshold detector
-      ruleYaml.put("detection", 
Collections.singletonList(ImmutableMap.of("type", "THRESHOLD",
+      ruleYaml.put("detection", 
Collections.singletonList(ImmutableMap.of("name", "detection_rule1", "type", 
"THRESHOLD",
           "params", 
getMinMaxThresholdRuleDetectorParams(anomalyFunctionDTO))));
     } else{
       // algorithm detector
       ruleYaml.put("detection", Collections.singletonList(
-          ImmutableMap.of("type", "ALGORITHM", "params", 
getAlgorithmDetectorParams(anomalyFunctionDTO),
+          ImmutableMap.of("name", "detection_rule1", "type", "ALGORITHM", 
"params", getAlgorithmDetectorParams(anomalyFunctionDTO),
               PROP_WINDOW_SIZE, anomalyFunctionDTO.getWindowSize(),
               PROP_WINDOW_UNIT, 
anomalyFunctionDTO.getWindowUnit().toString())));
     }
@@ -141,16 +140,18 @@ public class DetectionMigrationResource {
       Map<String, Object> filterYaml = new LinkedHashMap<>();
       if (!alertFilter.containsKey("thresholdField")) {
         // algorithm alert filter
-        filterYaml = ImmutableMap.of("type", "ALGORITHM_FILTER", "params", 
getAlertFilterParams(anomalyFunctionDTO));
+        filterYaml = ImmutableMap.of("name", "filter_rule1", "type", 
"ALGORITHM_FILTER", "params", getAlertFilterParams(anomalyFunctionDTO));
       } else {
         // threshold filter migrate to rule filters
         // site wide impact filter migrate to rule based swi filter
         if 
(anomalyFunctionDTO.getAlertFilter().get("thresholdField").equals("impactToGlobal")){
           filterYaml.put("type", "SITEWIDE_IMPACT_FILTER");
+          filterYaml.put("name", "filter_rule1");
           filterYaml.put("params", 
getSiteWideImpactFilterParams(anomalyFunctionDTO));
         }
         // weight filter migrate to rule based percentage change filter
         if 
(anomalyFunctionDTO.getAlertFilter().get("thresholdField").equals("weight")){
+          filterYaml.put("name", "filter_rule1");
           filterYaml.put("type", "PERCENTAGE_CHANGE_FILTER");
           filterYaml.put("params", 
getPercentageChangeFilterParams(anomalyFunctionDTO));
         }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/DetectionPipeline.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/DetectionPipeline.java
index 776b427..b07e4e9 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/DetectionPipeline.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/DetectionPipeline.java
@@ -67,7 +67,8 @@ public abstract class DetectionPipeline {
     try {
       this.initComponents();
     } catch (Exception e) {
-      LOG.error("initialize components failed", e);
+      LOG.error("Initialize components failed", e);
+      throw new IllegalArgumentException("Initialize components failed. Please 
check rule parameters. " + e.getMessage());
     }
   }
 
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/components/SitewideImpactRuleAnomalyFilter.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/components/SitewideImpactRuleAnomalyFilter.java
index e5800b6..610ac39 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/components/SitewideImpactRuleAnomalyFilter.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/components/SitewideImpactRuleAnomalyFilter.java
@@ -1,5 +1,6 @@
 package com.linkedin.thirdeye.detection.components;
 
+import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
@@ -92,6 +93,8 @@ public class SitewideImpactRuleAnomalyFilter implements 
AnomalyFilter<SitewideIm
   public void init(SitewideImpactRuleAnomalyFilterSpec spec, InputDataFetcher 
dataFetcher) {
     this.dataFetcher = dataFetcher;
     this.threshold = spec.getThreshold();
+    Preconditions.checkArgument(Math.abs(this.threshold) <= 1, "Site wide 
impact threshold should be less or equal than 1");
+
     this.pattern = Pattern.valueOf(spec.getPattern().toUpperCase());
 
     // customize baseline offset
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/AbsoluteChangeRuleAnomalyFilterSpec.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/AbsoluteChangeRuleAnomalyFilterSpec.java
index 138ca50..a304d68 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/AbsoluteChangeRuleAnomalyFilterSpec.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/AbsoluteChangeRuleAnomalyFilterSpec.java
@@ -20,7 +20,7 @@ public class AbsoluteChangeRuleAnomalyFilterSpec extends 
AbstractSpec {
   private String timezone = "UTC";
   private double threshold = Double.NaN;
   private String offset;
-  private String pattern;
+  private String pattern = "UP_OR_DOWN";
 
   public String getTimezone() {
     return timezone;
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/AbsoluteChangeRuleDetectorSpec.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/AbsoluteChangeRuleDetectorSpec.java
index 26f5d66..e7b573c 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/AbsoluteChangeRuleDetectorSpec.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/AbsoluteChangeRuleDetectorSpec.java
@@ -20,7 +20,7 @@ public class AbsoluteChangeRuleDetectorSpec extends 
AbstractSpec {
   private double absoluteChange = Double.NaN;
   private String offset = "wo1w";
   private String timezone = "UTC";
-  private String pattern;
+  private String pattern = "UP_OR_DOWN";
 
   public double getAbsoluteChange() {
     return absoluteChange;
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/PercentageChangeRuleAnomalyFilterSpec.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/PercentageChangeRuleAnomalyFilterSpec.java
index 3717bb6..fbb7885 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/PercentageChangeRuleAnomalyFilterSpec.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/PercentageChangeRuleAnomalyFilterSpec.java
@@ -20,7 +20,7 @@ public class PercentageChangeRuleAnomalyFilterSpec extends 
AbstractSpec {
   private String timezone = "UTC";
   private double threshold = Double.NaN;
   private String offset;
-  private String pattern;
+  private String pattern= "UP_OR_DOWN";
 
   public String getTimezone() {
     return timezone;
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/PercentageChangeRuleDetectorSpec.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/PercentageChangeRuleDetectorSpec.java
index d58533e..e59d14c 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/PercentageChangeRuleDetectorSpec.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/PercentageChangeRuleDetectorSpec.java
@@ -20,7 +20,7 @@ public class PercentageChangeRuleDetectorSpec extends 
AbstractSpec {
   private double percentageChange = Double.NaN;
   private String offset = "wo1w";
   private String timezone = "UTC";
-  private String pattern;
+  private String pattern = "UP_OR_DOWN";
 
   public String getPattern() {
     return pattern;
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/SitewideImpactRuleAnomalyFilterSpec.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/SitewideImpactRuleAnomalyFilterSpec.java
index c0090fa..5d12317 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/SitewideImpactRuleAnomalyFilterSpec.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/spec/SitewideImpactRuleAnomalyFilterSpec.java
@@ -9,7 +9,7 @@ public class SitewideImpactRuleAnomalyFilterSpec extends 
AbstractSpec {
   private String timezone = "UTC";
   private double threshold = Double.NaN;
   private String offset;
-  private String pattern;
+  private String pattern = "UP_OR_DOWN";
   private String sitewideMetricName;
   private String sitewideCollection;
   private Map<String, Collection<String>> filters = new HashMap<>();
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/wrapper/BaselineFillingMergeWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/wrapper/BaselineFillingMergeWrapper.java
index df08645..a3a560f 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/wrapper/BaselineFillingMergeWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/wrapper/BaselineFillingMergeWrapper.java
@@ -40,7 +40,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 import org.apache.commons.collections.MapUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -51,7 +50,7 @@ import org.slf4j.LoggerFactory;
  * of filling baseline & current values and inject detector, metric urn. Each 
detector has a separate baseline filling merge wrapper
  */
 public class BaselineFillingMergeWrapper extends MergeWrapper {
-  private static final Logger LOG = 
LoggerFactory.getLogger(MergeWrapper.class);
+  private static final Logger LOG = 
LoggerFactory.getLogger(BaselineFillingMergeWrapper.class);
 
   private static final String PROP_BASELINE_PROVIDER = "baselineValueProvider";
   private static final String PROP_CURRENT_PROVIDER = "currentValueProvider";
@@ -59,10 +58,11 @@ public class BaselineFillingMergeWrapper extends 
MergeWrapper {
   private static final String PROP_BASELINE_PROVIDER_COMPONENT_NAME = 
"baselineProviderComponentName";
   private static final String PROP_DETECTOR = "detector";
   private static final String PROP_DETECTOR_COMPONENT_NAME = 
"detectorComponentName";
+  private static final String DEFAULT_WOW_BASELINE_PROVIDER_NAME = 
"DEFAULT_WOW";
 
   private BaselineProvider baselineValueProvider; // optionally configure a 
baseline value loader
   private BaselineProvider currentValueProvider;
-  private String baselineProviderComponentKey;
+  private String baselineProviderComponentName;
   private String detectorComponentName;
   private String metricUrn;
 
@@ -71,10 +71,19 @@ public class BaselineFillingMergeWrapper extends 
MergeWrapper {
     super(provider, config, startTime, endTime);
 
     if (config.getProperties().containsKey(PROP_BASELINE_PROVIDER)) {
-      this.baselineProviderComponentKey = 
DetectionUtils.getComponentName(MapUtils.getString(config.getProperties(), 
PROP_BASELINE_PROVIDER));
-      
Preconditions.checkArgument(this.config.getComponents().containsKey(this.baselineProviderComponentKey));
-      this.baselineValueProvider = (BaselineProvider) 
this.config.getComponents().get(this.baselineProviderComponentKey);
+      this.baselineProviderComponentName = 
DetectionUtils.getComponentName(MapUtils.getString(config.getProperties(), 
PROP_BASELINE_PROVIDER));
+      
Preconditions.checkArgument(this.config.getComponents().containsKey(this.baselineProviderComponentName));
+      this.baselineValueProvider = (BaselineProvider) 
this.config.getComponents().get(this.baselineProviderComponentName);
+    } else {
+      // default baseline provider, use wo1w
+      this.baselineValueProvider = new RuleBaselineProvider();
+      this.baselineProviderComponentName = DEFAULT_WOW_BASELINE_PROVIDER_NAME;
+      RuleBaselineProviderSpec spec = new RuleBaselineProviderSpec();
+      spec.setOffset("wo1w");
+      InputDataFetcher dataFetcher = new 
DefaultInputDataFetcher(this.provider, this.config.getId());
+      this.baselineValueProvider.init(spec, dataFetcher);
     }
+
     if (config.getProperties().containsKey(PROP_CURRENT_PROVIDER)) {
       String detectorReferenceKey = 
DetectionUtils.getComponentName(MapUtils.getString(config.getProperties(), 
currentValueProvider));
       
Preconditions.checkArgument(this.config.getComponents().containsKey(detectorReferenceKey));
@@ -158,7 +167,7 @@ public class BaselineFillingMergeWrapper extends 
MergeWrapper {
         
anomaly.setAvgCurrentVal(this.currentValueProvider.computePredictedAggregates(slice,
 aggregationFunction));
         if (this.baselineValueProvider != null) {
           
anomaly.setAvgBaselineVal(this.baselineValueProvider.computePredictedAggregates(slice,
 aggregationFunction));
-          anomaly.getProperties().put(PROP_BASELINE_PROVIDER_COMPONENT_NAME, 
this.baselineProviderComponentKey);
+          anomaly.getProperties().put(PROP_BASELINE_PROVIDER_COMPONENT_NAME, 
this.baselineProviderComponentName);
           anomaly.setWeight(calculateWeight(anomaly));
         }
       } catch (Exception e) {
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/wrapper/ChildKeepingMergeWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/wrapper/ChildKeepingMergeWrapper.java
index 9ffc5c2..9bba54f 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/wrapper/ChildKeepingMergeWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/wrapper/ChildKeepingMergeWrapper.java
@@ -16,6 +16,7 @@
 
 package com.linkedin.thirdeye.detection.wrapper;
 
+import com.google.common.collect.Collections2;
 import com.linkedin.thirdeye.datalayer.dto.DetectionConfigDTO;
 import com.linkedin.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
 import com.linkedin.thirdeye.detection.DataProvider;
@@ -87,7 +88,11 @@ public class ChildKeepingMergeWrapper extends 
BaselineFillingMergeWrapper {
       }
     }
 
-    return super.fillCurrentAndBaselineValue(output);
+    // refill current and baseline values for parent anomalies
+    Collection<MergedAnomalyResultDTO> parentAnomalies =
+        Collections2.filter(output, mergedAnomaly -> mergedAnomaly != null && 
!mergedAnomaly.getChildren().isEmpty());
+    super.fillCurrentAndBaselineValue(new ArrayList<>(parentAnomalies));
+    return output;
   }
 
   private MergedAnomalyResultDTO copyAnomalyInfo(MergedAnomalyResultDTO 
anomaly, MergedAnomalyResultDTO newAnomaly) {
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/CompositePipelineConfigTranslator.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/CompositePipelineConfigTranslator.java
index b9f6710..2288c4b 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/CompositePipelineConfigTranslator.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/CompositePipelineConfigTranslator.java
@@ -129,7 +129,6 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
   private static final String PROP_RULES = "rules";
   private static final String PROP_NESTED = "nested";
   private static final String PROP_BASELINE_PROVIDER = "baselineValueProvider";
-  private static final String PROP_NAME = "name";
   private static final String PROP_DETECTOR = "detector";
   private static final String PROP_MOVING_WINDOW_DETECTION = 
"isMovingWindowDetection";
   private static final String PROP_WINDOW_DELAY = "windowDelay";
@@ -139,6 +138,8 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
   private static final String PROP_FREQUENCY = "frequency";
   private static final String PROP_MERGER = "merger";
   private static final String PROP_TIMEZONE = "timezone";
+  private static final String PROP_NAME = "name";
+  private static final String DEFAULT_BASELINE_PROVIDER_YAML_TYPE = 
"RULE_BASELINE";
 
   private static final DetectionRegistry DETECTION_REGISTRY = 
DetectionRegistry.getInstance();
   private static final Map<String, String> DETECTOR_TO_BASELINE = 
ImmutableMap.of("ALGORITHM", "ALGORITHM_BASELINE");
@@ -149,10 +150,11 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
   private DatasetConfigDTO datasetConfig;
   private String metricUrn;
   private Map<String, Object> mergerProperties = new HashMap<>();
-  private Map<String, Collection<String>> filterMaps;
+  protected final org.yaml.snakeyaml.Yaml yaml;
 
   public CompositePipelineConfigTranslator(Map<String, Object> yamlConfig, 
DataProvider provider) {
     super(yamlConfig, provider);
+    this.yaml = new org.yaml.snakeyaml.Yaml();
   }
 
   @Override
@@ -175,17 +177,16 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
     List<Map<String, Object>> ruleYamls = getList(yamlConfig.get(PROP_RULES));
     List<Map<String, Object>> nestedPipelines = new ArrayList<>();
     for (Map<String, Object> ruleYaml : ruleYamls) {
-      String ruleName = MapUtils.getString(ruleYaml, PROP_NAME);
       List<Map<String, Object>> filterYamls = 
ConfigUtils.getList(ruleYaml.get(PROP_FILTER));
       List<Map<String, Object>> detectionYamls = 
ConfigUtils.getList(ruleYaml.get(PROP_DETECTION));
-      List<Map<String, Object>> detectionProperties = 
buildListOfMergeWrapperProperties(ruleName, detectionYamls);
+      List<Map<String, Object>> detectionProperties = 
buildListOfMergeWrapperProperties(detectionYamls);
       if (filterYamls == null || filterYamls.isEmpty()) {
         nestedPipelines.addAll(detectionProperties);
       } else {
         List<Map<String, Object>> filterNestedProperties = detectionProperties;
         for (Map<String, Object> filterProperties : filterYamls) {
           filterNestedProperties = 
buildFilterWrapperProperties(AnomalyFilterWrapper.class.getName(), 
filterProperties,
-              filterNestedProperties, ruleName);
+              filterNestedProperties);
         }
         nestedPipelines.addAll(filterNestedProperties);
       }
@@ -213,21 +214,21 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
     return dimensionWrapperProperties;
   }
 
-  private List<Map<String, Object>> buildListOfMergeWrapperProperties(String 
ruleName,
+  private List<Map<String, Object>> buildListOfMergeWrapperProperties(
       List<Map<String, Object>> yamlConfigs) {
     List<Map<String, Object>> properties = new ArrayList<>();
     for (Map<String, Object> yamlConfig : yamlConfigs) {
-      properties.add(buildMergeWrapperProperties(ruleName, yamlConfig));
+      properties.add(buildMergeWrapperProperties(yamlConfig));
     }
     return properties;
   }
 
-  private Map<String, Object> buildMergeWrapperProperties(String ruleName, 
Map<String, Object> yamlConfig) {
+  private Map<String, Object> buildMergeWrapperProperties(Map<String, Object> 
yamlConfig) {
     String detectorType = MapUtils.getString(yamlConfig, PROP_TYPE);
-    long id = MapUtils.getLong(yamlConfig, "id", 0L);
+    String name = MapUtils.getString(yamlConfig, PROP_NAME);
     Map<String, Object> nestedProperties = new HashMap<>();
     nestedProperties.put(PROP_CLASS_NAME, 
AnomalyDetectorWrapper.class.getName());
-    String detectorKey = makeComponentKey(ruleName, detectorType, id);
+    String detectorKey = makeComponentKey(detectorType, name);
 
     fillInWindowSizeAndUnit(nestedProperties, yamlConfig, detectorType);
 
@@ -236,11 +237,11 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
     Map<String, Object> properties = new HashMap<>();
     properties.put(PROP_CLASS_NAME, 
BaselineFillingMergeWrapper.class.getName());
     properties.put(PROP_NESTED, Collections.singletonList(nestedProperties));
-    String baselineProviderType =  "RULE_BASELINE";
+    String baselineProviderType = DEFAULT_BASELINE_PROVIDER_YAML_TYPE;
     if (DETECTOR_TO_BASELINE.containsKey(detectorType)) {
       baselineProviderType = DETECTOR_TO_BASELINE.get(detectorType);
     }
-    String baselineProviderKey = makeComponentKey(ruleName, 
baselineProviderType, id);
+    String baselineProviderKey = makeComponentKey(baselineProviderType, name);
     properties.put(PROP_BASELINE_PROVIDER, baselineProviderKey);
     properties.put(PROP_DETECTOR, detectorKey);
     buildComponentSpec(yamlConfig, baselineProviderType, baselineProviderKey);
@@ -291,7 +292,7 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
   }
 
   private List<Map<String, Object>> buildFilterWrapperProperties(String 
wrapperClassName,
-      Map<String, Object> yamlConfig, List<Map<String, Object>> 
nestedProperties, String ruleName) {
+      Map<String, Object> yamlConfig, List<Map<String, Object>> 
nestedProperties) {
     if (yamlConfig == null || yamlConfig.isEmpty()) {
       return nestedProperties;
     }
@@ -299,9 +300,9 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
     if (wrapperProperties.isEmpty()) {
       return Collections.emptyList();
     }
-    long id = MapUtils.getLong(yamlConfig, "id", 0L);
+    String name = MapUtils.getString(yamlConfig, PROP_NAME);
     String filterType = MapUtils.getString(yamlConfig, PROP_TYPE);
-    String filterKey = makeComponentKey(ruleName, filterType, id);
+    String filterKey = makeComponentKey(filterType, name);
     wrapperProperties.put(PROP_FILTER, filterKey);
     buildComponentSpec(yamlConfig, filterType, filterKey);
 
@@ -361,7 +362,10 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
     String componentClassName = DETECTION_REGISTRY.lookup(type);
     Map<String, Object> componentSpecs = new HashMap<>();
     componentSpecs.put(PROP_CLASS_NAME, componentClassName);
-    Map<String, Object> params = MapUtils.getMap(yamlConfig, PROP_PARAMS);
+    Map<String, Object> params = new HashMap<>();
+    if (yamlConfig.containsKey(PROP_PARAMS)){
+      params = MapUtils.getMap(yamlConfig, PROP_PARAMS);
+    }
 
     if (DETECTION_REGISTRY.isTunable(componentClassName)) {
       try {
@@ -399,8 +403,8 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
     return tunable;
   }
 
-  private String makeComponentKey(String name, String type, long id) {
-    return "$" + name + ":" + type + ":" + id;
+  private String makeComponentKey(String type, String name) {
+    return "$" + name + ":" + type;
   }
 
   @Override
@@ -409,26 +413,41 @@ public class CompositePipelineConfigTranslator extends 
YamlDetectionConfigTransl
     Preconditions.checkArgument(yamlConfig.containsKey(PROP_METRIC), "Property 
missing " + PROP_METRIC);
     Preconditions.checkArgument(yamlConfig.containsKey(PROP_DATASET), 
"Property missing " + PROP_DATASET);
     Preconditions.checkArgument(yamlConfig.containsKey(PROP_RULES), "Property 
missing " + PROP_RULES);
+    if (existingConfig != null) {
+      Map<String, Object> existingYamlConfig = (Map<String, Object>) 
this.yaml.load(existingConfig.getYaml());
+      Preconditions.checkArgument(MapUtils.getString(yamlConfig, 
PROP_METRIC).equals(MapUtils.getString(existingYamlConfig, PROP_METRIC)), 
"metric name cannot be modified");
+      Preconditions.checkArgument(MapUtils.getString(yamlConfig, 
PROP_DATASET).equals(MapUtils.getString(existingYamlConfig, PROP_DATASET)), 
"dataset name cannot be modified");
+    }
     Set<String> names = new HashSet<>();
     List<Map<String, Object>> ruleYamls = getList(yamlConfig.get(PROP_RULES));
     for (int i = 0; i < ruleYamls.size(); i++) {
       Map<String, Object> ruleYaml = ruleYamls.get(i);
-      Preconditions.checkArgument(ruleYaml.containsKey(PROP_NAME), "In rule 
No." + (i + 1) + ", rule name property missing. ");
-      String name = MapUtils.getString(ruleYaml, PROP_NAME);
-      Preconditions.checkArgument(!names.contains(name), "In rule No." + (i + 
1) + ", found duplicated rule name: " + name, ". Rule name must be unique.");
-      names.add(name);
       Preconditions.checkArgument(ruleYaml.containsKey(PROP_DETECTION),
-          "In rule No." + (i + 1) + ", detection stage property missing. ");
+          "In rule No." + (i + 1) + ", detection rule is missing. ");
       List<Map<String, Object>> detectionStageYamls = 
ConfigUtils.getList(ruleYaml.get(PROP_DETECTION));
+      // check each detection rule
       for (Map<String, Object> detectionStageYaml : detectionStageYamls) {
         Preconditions.checkArgument(detectionStageYaml.containsKey(PROP_TYPE),
-            "In rule No." + (i + 1) + ", detection stage type missing. ");
+            "In rule No." + (i + 1) + ", a detection rule type is missing. ");
+        String type = MapUtils.getString(detectionStageYaml, PROP_TYPE);
+        String name = MapUtils.getString(detectionStageYaml, PROP_NAME);
+        Preconditions.checkNotNull(name, "In rule No." + (i + 1) + ", a 
detection rule name for type " +  type + " is missing");
+        Preconditions.checkArgument(!names.contains(name), "In rule No." + (i 
+ 1) +
+            ", found duplicate rule name, rule name must be unique." );
+        Preconditions.checkArgument(!name.contains(":"), "Sorry, rule name 
cannot contain \':\'");
       }
       if (ruleYaml.containsKey(PROP_FILTER)) {
-        List<Map<String, Object>> filterStageYamls = 
ConfigUtils.getList(MapUtils.getMap(ruleYaml, PROP_FILTER));
+        List<Map<String, Object>> filterStageYamls = 
ConfigUtils.getList(ruleYaml.get(PROP_FILTER));
+        // check each filter rule
         for (Map<String, Object> filterStageYaml : filterStageYamls) {
           Preconditions.checkArgument(filterStageYaml.containsKey(PROP_TYPE),
-              "In rule No." + (i + 1) + ", filter stage type missing. ");
+              "In rule No." + (i + 1) + ", a filter rule type is missing. ");
+          String type = MapUtils.getString(filterStageYaml, PROP_TYPE);
+          String name = MapUtils.getString(filterStageYaml, PROP_NAME);
+          Preconditions.checkNotNull(name, "In rule No." + (i + 1) + ", a 
filter rule name for type " + type + " is missing");
+          Preconditions.checkArgument(!names.contains(name), "In rule No." + 
(i + 1) +
+              ", found duplicate rule name, rule name must be unique." );
+          Preconditions.checkArgument(!name.contains(":"), "Sorry, rule name 
cannot contain \':\'");
         }
       }
     }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlDetectionConfigTranslator.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlDetectionConfigTranslator.java
index 4e748b5..29257dc 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlDetectionConfigTranslator.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlDetectionConfigTranslator.java
@@ -3,7 +3,6 @@ package com.linkedin.thirdeye.detection.yaml;
 import com.google.common.base.Preconditions;
 import com.linkedin.thirdeye.datalayer.dto.DetectionConfigDTO;
 import com.linkedin.thirdeye.detection.DataProvider;
-import com.linkedin.thirdeye.detection.DetectionPipeline;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.commons.collections.MapUtils;
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlDetectionTranslatorLoader.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlDetectionTranslatorLoader.java
index acea6a9..ca2e87f 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlDetectionTranslatorLoader.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlDetectionTranslatorLoader.java
@@ -1,5 +1,6 @@
 package com.linkedin.thirdeye.detection.yaml;
 
+import com.google.common.base.Preconditions;
 import com.linkedin.thirdeye.detection.DataProvider;
 import com.linkedin.thirdeye.detection.annotation.registry.DetectionRegistry;
 import java.lang.reflect.Constructor;
@@ -13,6 +14,7 @@ public class YamlDetectionTranslatorLoader {
   private static DetectionRegistry DETECTION_REGISTRY = 
DetectionRegistry.getInstance();
 
   public YamlDetectionConfigTranslator from(Map<String, Object> yamlConfig, 
DataProvider provider) throws Exception {
+    Preconditions.checkArgument(yamlConfig.containsKey(PROP_PIPELINE_TYPE), 
"Pipeline type is missing.");
     String className = 
DETECTION_REGISTRY.lookupYamlConverter(yamlConfig.get(PROP_PIPELINE_TYPE).toString().toUpperCase());
     Constructor<?> constructor = 
Class.forName(className).getConstructor(Map.class, DataProvider.class);
     return (YamlDetectionConfigTranslator) constructor.newInstance(yamlConfig, 
provider);
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlResource.java
 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlResource.java
index 5ae893a..6ffd36a 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlResource.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/com/linkedin/thirdeye/detection/yaml/YamlResource.java
@@ -1,8 +1,8 @@
 package com.linkedin.thirdeye.detection.yaml;
 
 import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
+import com.linkedin.thirdeye.api.Constants;
 import com.linkedin.thirdeye.datalayer.bao.DatasetConfigManager;
 import com.linkedin.thirdeye.datalayer.bao.DetectionAlertConfigManager;
 import com.linkedin.thirdeye.datalayer.bao.DetectionConfigManager;
@@ -22,7 +22,10 @@ import com.linkedin.thirdeye.detection.ConfigUtils;
 import com.linkedin.thirdeye.detection.DataProvider;
 import com.linkedin.thirdeye.detection.DefaultDataProvider;
 import com.linkedin.thirdeye.detection.DetectionPipelineLoader;
+import com.wordnik.swagger.annotations.Api;
+import com.wordnik.swagger.annotations.ApiOperation;
 import com.wordnik.swagger.annotations.ApiParam;
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -33,18 +36,22 @@ import java.util.Set;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.yaml.snakeyaml.Yaml;
 
 
 @Path("/yaml")
+@Api(tags = {Constants.YAML_TAG})
 public class YamlResource {
   protected static final Logger LOG = 
LoggerFactory.getLogger(YamlResource.class);
 
@@ -52,7 +59,6 @@ public class YamlResource {
   private static final String PROP_TYPE = "type";
   private static final String PROP_DETECTION_CONFIG_ID = "detectionConfigIds";
 
-  private static final Yaml YAML = new Yaml();
 
   private final DetectionConfigManager detectionConfigDAO;
   private final DetectionAlertConfigManager detectionAlertConfigDAO;
@@ -64,6 +70,7 @@ public class YamlResource {
   private final EventManager eventDAO;
   private final MergedAnomalyResultManager anomalyDAO;
   private final DetectionPipelineLoader loader;
+  private final Yaml yaml;
 
   public YamlResource() {
     this.detectionConfigDAO = 
DAORegistry.getInstance().getDetectionConfigManager();
@@ -74,6 +81,7 @@ public class YamlResource {
     this.datasetDAO = DAORegistry.getInstance().getDatasetConfigDAO();
     this.eventDAO = DAORegistry.getInstance().getEventDAO();
     this.anomalyDAO = DAORegistry.getInstance().getMergedAnomalyResultDAO();
+    this.yaml = new Yaml();
 
     TimeSeriesLoader timeseriesLoader =
         new DefaultTimeSeriesLoader(metricDAO, datasetDAO, 
ThirdEyeCacheRegistry.getInstance().getQueryCache());
@@ -90,48 +98,111 @@ public class YamlResource {
   /**
    Set up a detection pipeline using a YAML config
    @param payload YAML config string
-   @return a message contains the saved detection config id & detection alert 
id
+   @param startTime tuning window start time for tunable components
+   @param endTime tuning window end time for tunable components
+   @return a message contains the saved detection config id
    */
   @POST
   @Produces(MediaType.APPLICATION_JSON)
   @Consumes(MediaType.TEXT_PLAIN)
-  public Response setUpDetectionPipeline(@ApiParam("payload") String payload,
-      @QueryParam("startTime") long startTime, @QueryParam("endTime") long 
endTime) throws Exception {
-    if (Strings.isNullOrEmpty(payload)) {
-      throw new IllegalArgumentException("Empty Payload");
-    }
-    Map<String, Object> yamlConfig = (Map<String, Object>) 
this.YAML.load(payload);
-
-    Preconditions.checkArgument(yamlConfig.containsKey(PROP_NAME), "missing " 
+ PROP_NAME);
-    // retrieve id if detection config already exists
-    List<DetectionConfigDTO> detectionConfigDTOs =
-        this.detectionConfigDAO.findByPredicate(Predicate.EQ("name", 
MapUtils.getString(yamlConfig, PROP_NAME)));
-    DetectionConfigDTO existingDetectionConfig = null;
-    if (!detectionConfigDTOs.isEmpty()) {
-      existingDetectionConfig = detectionConfigDTOs.get(0);
-    }
+  @ApiOperation("Set up a detection pipeline using a YAML config")
+  public Response setUpDetectionPipeline(
+      @ApiParam("yaml config") String payload,
+      @ApiParam("tuning window start time for tunable components") 
@QueryParam("startTime") long startTime,
+      @ApiParam("tuning window end time for tunable components") 
@QueryParam("endTime") long endTime) {
+    String errorMessage;
+    try {
+      Preconditions.checkArgument(StringUtils.isNotBlank(payload), "Empty 
payload");
+      Map<String, Object> yamlConfig = (Map<String, Object>) 
this.yaml.load(payload);
+
+      // retrieve id if detection config already exists
+      List<DetectionConfigDTO> detectionConfigDTOs =
+          this.detectionConfigDAO.findByPredicate(Predicate.EQ("name", 
MapUtils.getString(yamlConfig, PROP_NAME)));
+      DetectionConfigDTO existingDetectionConfig = null;
+      if (!detectionConfigDTOs.isEmpty()) {
+        existingDetectionConfig = detectionConfigDTOs.get(0);
+      }
+
+      YamlDetectionConfigTranslator translator = 
this.translatorLoader.from(yamlConfig, this.provider);
+      DetectionConfigDTO detectionConfig = 
translator.withTrainingWindow(startTime, endTime)
+          .withExistingDetectionConfig(existingDetectionConfig)
+          .generateDetectionConfig();
+      detectionConfig.setYaml(payload);
+      validatePipeline(detectionConfig);
+      Long detectionConfigId = this.detectionConfigDAO.save(detectionConfig);
+      Preconditions.checkNotNull(detectionConfigId, "Save detection config 
failed");
 
-    YamlDetectionConfigTranslator translator = 
this.translatorLoader.from(yamlConfig, this.provider);
-    DetectionConfigDTO detectionConfig;
-    try{
-      detectionConfig = translator.withTrainingWindow(startTime, 
endTime).withExistingDetectionConfig(existingDetectionConfig).generateDetectionConfig();
+      return Response.ok(detectionConfig).build();
+    } catch (InvocationTargetException e){
+      // exception thrown in validate pipeline via reflection
+      LOG.error("Validate pipeline error", e);
+      errorMessage = e.getCause().getMessage();
     } catch (Exception e) {
       LOG.error("yaml translation error", e);
-      return Response.status(400).entity(ImmutableMap.of("status", "400", 
"message", e.getMessage())).build();
+      errorMessage = e.getMessage();
     }
-    detectionConfig.setYaml(payload);
-    Long detectionConfigId = this.detectionConfigDAO.save(detectionConfig);
-    Preconditions.checkNotNull(detectionConfigId, "Save detection config 
failed");
-
-    // optionally set up an alerter for the detection pipeline
-    if (yamlConfig.containsKey("alert")) {
-      Map<String, Object> alertYaml = MapUtils.getMap(yamlConfig, "alert");
-      DetectionAlertConfigDTO alertConfigDTO = 
getDetectionAlertConfig(alertYaml, detectionConfigId);
-      Long detectionAlertConfigId = 
this.detectionAlertConfigDAO.save(alertConfigDTO);
-      Preconditions.checkNotNull(detectionAlertConfigId, "Save detection 
alerter config failed");
+    return Response.status(400).entity(ImmutableMap.of("status", "400", 
"message", errorMessage)).build();
+  }
+
+  /**
+   Edit a detection pipeline using a YAML config
+   @param payload YAML config string
+   @param id the detection config id to edit
+   @param startTime tuning window start time for tunable components
+   @param endTime tuning window end time for tunable components
+   @return a message contains the saved detection config id
+   */
+  @PUT
+  @Path("/{id}")
+  @Produces(MediaType.APPLICATION_JSON)
+  @Consumes(MediaType.TEXT_PLAIN)
+  @ApiOperation("Edit a detection pipeline using a YAML config")
+  public Response editDetectionPipeline(
+      @ApiParam("yaml config") String payload,
+      @ApiParam("the detection config id to edit") @PathParam("id") long id,
+      @ApiParam("tuning window start time for tunable components")  
@QueryParam("startTime") long startTime,
+      @ApiParam("tuning window end time for tunable components") 
@QueryParam("endTime") long endTime) {
+    String errorMessage;
+    try {
+      Preconditions.checkArgument(StringUtils.isNotBlank(payload), "Empty 
payload");
+      Map<String, Object> yamlConfig = (Map<String, Object>) 
this.yaml.load(payload);
+
+      DetectionConfigDTO existingDetectionConfig = 
this.detectionConfigDAO.findById(id);
+      Preconditions.checkArgument(existingDetectionConfig != null, "Existing 
detection config " + id + " not found");
+
+      YamlDetectionConfigTranslator translator = 
this.translatorLoader.from(yamlConfig, this.provider);
+      DetectionConfigDTO detectionConfig = 
translator.withTrainingWindow(startTime, endTime)
+          .withExistingDetectionConfig(existingDetectionConfig)
+          .generateDetectionConfig();
+      detectionConfig.setYaml(payload);
+      validatePipeline(detectionConfig);
+      Long detectionConfigId = this.detectionConfigDAO.save(detectionConfig);
+      Preconditions.checkNotNull(detectionConfigId, "Save detection config 
failed");
+
+      return Response.ok(detectionConfig).build();
+    } catch (InvocationTargetException e){
+      // exception thrown in validate pipeline via reflection
+      LOG.error("Validate pipeline error", e);
+      errorMessage = e.getCause().getMessage();
+    } catch (Exception e) {
+      LOG.error("yaml translation error", e);
+      errorMessage = e.getMessage();
     }
+    return Response.status(400).entity(ImmutableMap.of("status", "400", 
"message", errorMessage)).build();
+  }
 
-    return Response.ok(detectionConfig).build();
+
+  /*
+   * Init the pipeline to check if detection pipeline property is valid 
semantically.
+   */
+  private void validatePipeline(DetectionConfigDTO detectionConfig) throws 
Exception {
+    Long id = detectionConfig.getId();
+    // swap out id
+    detectionConfig.setId(-1L);
+    // try to load the detection pipeline and init all the components
+    this.loader.from(provider, detectionConfig, 0, 0);
+    // set id back
+    detectionConfig.setId(id);
   }
 
   /**
@@ -195,7 +266,7 @@ public class YamlResource {
     for (DetectionConfigDTO detectionConfigDTO : detectionConfigDTOs) {
       if (detectionConfigDTO.getYaml() != null) {
         Map<String, Object> yamlObject = new HashMap<>();
-        yamlObject.putAll((Map<? extends String, ?>) 
this.YAML.load(detectionConfigDTO.getYaml()));
+        yamlObject.putAll((Map<? extends String, ?>) 
this.yaml.load(detectionConfigDTO.getYaml()));
         yamlObject.put("id", detectionConfigDTO.getId());
         yamlObject.put("isActive", detectionConfigDTO.isActive());
         yamlObject.put("createdBy", detectionConfigDTO.getCreatedBy());
diff --git 
a/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/algorithm/stage/AnomalyDetectionStageWrapperTest.java
 
b/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/algorithm/stage/AnomalyDetectionStageWrapperTest.java
index 0e04d11..6f634c9 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/algorithm/stage/AnomalyDetectionStageWrapperTest.java
+++ 
b/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/algorithm/stage/AnomalyDetectionStageWrapperTest.java
@@ -49,7 +49,7 @@ public class AnomalyDetectionStageWrapperTest {
     this.properties.put(PROP_METRIC_URN, "thirdeye:metric:1");
     this.properties.put("specs", this.stageSpecs);
     this.config = new DetectionConfigDTO();
-
+    this.config.setId(-1L);
     this.config.setProperties(properties);
 
     this.provider = new MockDataProvider();
diff --git 
a/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/algorithm/stage/BaselineRuleDetectionStageTest.java
 
b/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/algorithm/stage/BaselineRuleDetectionStageTest.java
index 12cb7c4..72acffa 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/algorithm/stage/BaselineRuleDetectionStageTest.java
+++ 
b/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/algorithm/stage/BaselineRuleDetectionStageTest.java
@@ -79,7 +79,7 @@ public class BaselineRuleDetectionStageTest {
 
     this.config = new DetectionConfigDTO();
     this.config.setProperties(properties);
-
+    this.config.setId(-1L);
     this.provider = new MockDataProvider()
         .setTimeseries(timeseries)
         .setMetrics(Collections.singletonList(metricConfigDTO));
diff --git 
a/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/wrapper/AnomalyDetectorWrapperTest.java
 
b/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/wrapper/AnomalyDetectorWrapperTest.java
index 0d6891e..960d4a8 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/wrapper/AnomalyDetectorWrapperTest.java
+++ 
b/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/wrapper/AnomalyDetectorWrapperTest.java
@@ -54,6 +54,7 @@ public class AnomalyDetectorWrapperTest {
     this.config = new DetectionConfigDTO();
     this.config.setComponents(ImmutableMap.of("testDetector", new 
ThresholdRuleDetector()));
     this.config.setProperties(properties);
+    this.config.setId(-1L);
 
     this.provider = new MockDataProvider();
     MetricConfigDTO metric = new MetricConfigDTO();
diff --git 
a/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/wrapper/BaselineFillingMergeWrapperTest.java
 
b/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/wrapper/BaselineFillingMergeWrapperTest.java
index 481c6f0..ac73ad4 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/wrapper/BaselineFillingMergeWrapperTest.java
+++ 
b/thirdeye/thirdeye-pinot/src/test/java/com/linkedin/thirdeye/detection/wrapper/BaselineFillingMergeWrapperTest.java
@@ -97,7 +97,9 @@ public class BaselineFillingMergeWrapperTest {
   @Test
   public void testMergerCurrentAndBaselineLoading() throws Exception {
     MergedAnomalyResultDTO anomaly = makeAnomaly(3000, 3600);
-    anomaly.setProperties(ImmutableMap.of("detectorComponentName", 
"testDetector"));
+    Map<String, String> anomalyProperties = new HashMap<>();
+    anomalyProperties.put("detectorComponentName", "testDetector");
+    anomaly.setProperties(anomalyProperties);
     anomaly.setMetricUrn("thirdeye:metric:1");
 
     Map<MetricSlice, DataFrame> aggregates = new HashMap<>();
@@ -127,6 +129,8 @@ public class BaselineFillingMergeWrapperTest {
     Assert.assertTrue(anomalyResults.contains(anomaly));
     Assert.assertEquals(anomalyResults.get(0).getAvgBaselineVal(), 100.0);
     Assert.assertEquals(anomalyResults.get(0).getAvgCurrentVal(), 100.0);
+    
Assert.assertEquals(anomalyResults.get(0).getProperties().get("detectorComponentName"),
 "testDetector");
+    
Assert.assertEquals(anomalyResults.get(0).getProperties().get("baselineProviderComponentName"),
 "baseline");
   }
 
 }
\ No newline at end of file
diff --git 
a/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/compositePipelineTranslatorTestResult-1.json
 
b/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/compositePipelineTranslatorTestResult-1.json
index 65fb1b5..75335eb 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/compositePipelineTranslatorTestResult-1.json
+++ 
b/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/compositePipelineTranslatorTestResult-1.json
@@ -7,33 +7,33 @@
       "className" : 
"com.linkedin.thirdeye.detection.algorithm.DimensionWrapper",
       "metricUrn" : "thirdeye:metric:1:D1%3Dv1:D1%3Dv2:D2%3Dv3",
       "nested" : [ {
-        "filter" : "$rule1:THRESHOLD_RULE_FILTER:1",
+        "filter" : "$thresholdFilter_2:THRESHOLD_RULE_FILTER",
         "className" : 
"com.linkedin.thirdeye.detection.wrapper.AnomalyFilterWrapper",
         "nested" : [ {
-          "filter" : "$rule1:THRESHOLD_RULE_FILTER:0",
+          "filter" : "$thresholdFilter_1:THRESHOLD_RULE_FILTER",
           "className" : 
"com.linkedin.thirdeye.detection.wrapper.AnomalyFilterWrapper",
           "nested" : [ {
-            "baselineValueProvider" : "$rule1:RULE_BASELINE:0",
+            "baselineValueProvider" : "$maxThreshold_1:RULE_BASELINE",
             "className" : 
"com.linkedin.thirdeye.detection.wrapper.BaselineFillingMergeWrapper",
             "maxGap" : 0,
             "nested" : [ {
               "className" : 
"com.linkedin.thirdeye.detection.wrapper.AnomalyDetectorWrapper"
             } ],
-            "detector" : "$rule1:THRESHOLD:0",
+            "detector" : "$maxThreshold_1:THRESHOLD",
             "maxDuration" : 100
           } ]
         } ]
       }, {
-        "filter" : "$rule2:THRESHOLD_RULE_FILTER:0",
+        "filter" : "$thresholdFilter_3:THRESHOLD_RULE_FILTER",
         "className" : 
"com.linkedin.thirdeye.detection.wrapper.AnomalyFilterWrapper",
         "nested" : [ {
-          "baselineValueProvider" : "$rule2:RULE_BASELINE:0",
+          "baselineValueProvider" : "$maxThreshold_2:RULE_BASELINE",
           "className" : 
"com.linkedin.thirdeye.detection.wrapper.BaselineFillingMergeWrapper",
           "maxGap" : 0,
           "nested" : [ {
             "className" : 
"com.linkedin.thirdeye.detection.wrapper.AnomalyDetectorWrapper"
           } ],
-          "detector" : "$rule2:THRESHOLD:0",
+          "detector" : "$maxThreshold_2:THRESHOLD",
           "maxDuration" : 100
         } ]
       } ],
@@ -43,33 +43,33 @@
     "maxDuration" : 100
   },
   "components" : {
-    "rule1:RULE_BASELINE:0" : {
+    "maxThreshold_2:THRESHOLD" : {
+      "max" : 100,
+      "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleDetector"
+    },
+    "maxThreshold_1:THRESHOLD" : {
+      "max" : 100,
+      "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleDetector"
+    },
+    "maxThreshold_2:RULE_BASELINE" : {
       "max" : 100,
       "className" : 
"com.linkedin.thirdeye.detection.components.RuleBaselineProvider"
     },
-    "rule1:THRESHOLD_RULE_FILTER:0" : {
+    "thresholdFilter_1:THRESHOLD_RULE_FILTER" : {
       "min" : 50,
       "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleAnomalyFilter"
     },
-    "rule1:THRESHOLD_RULE_FILTER:1" : {
+    "thresholdFilter_2:THRESHOLD_RULE_FILTER" : {
       "min" : 50,
       "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleAnomalyFilter"
     },
-    "rule2:THRESHOLD_RULE_FILTER:0" : {
+    "thresholdFilter_3:THRESHOLD_RULE_FILTER" : {
       "min" : 50,
       "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleAnomalyFilter"
     },
-    "rule2:THRESHOLD:0" : {
-      "max" : 100,
-      "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleDetector"
-    },
-    "rule2:RULE_BASELINE:0" : {
+    "maxThreshold_1:RULE_BASELINE" : {
       "max" : 100,
       "className" : 
"com.linkedin.thirdeye.detection.components.RuleBaselineProvider"
-    },
-    "rule1:THRESHOLD:0" : {
-      "max" : 100,
-      "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleDetector"
     }
   },
   "cron" : "0 0 14 * * ? *"
diff --git 
a/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/compositePipelineTranslatorTestResult-2.json
 
b/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/compositePipelineTranslatorTestResult-2.json
index bd43106..91c176d 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/compositePipelineTranslatorTestResult-2.json
+++ 
b/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/compositePipelineTranslatorTestResult-2.json
@@ -6,25 +6,25 @@
       "className" : 
"com.linkedin.thirdeye.detection.algorithm.DimensionWrapper",
       "metricUrn" : "thirdeye:metric:1:D1%3Dv1:D1%3Dv2:D2%3Dv3",
       "nested" : [ {
-        "baselineValueProvider" : "$rule1:RULE_BASELINE:0",
+        "baselineValueProvider" : "$rule1:RULE_BASELINE",
         "className" : 
"com.linkedin.thirdeye.detection.wrapper.BaselineFillingMergeWrapper",
         "nested" : [ {
           "className" : 
"com.linkedin.thirdeye.detection.wrapper.AnomalyDetectorWrapper"
         } ],
-        "detector" : "$rule1:THRESHOLD:0"
+        "detector" : "$rule1:THRESHOLD"
       } ],
       "minContribution" : 0.05,
       "dimensions" : [ "D1", "D2" ]
     } ]
   },
   "components" : {
-    "rule1:RULE_BASELINE:0" : {
+    "rule1:THRESHOLD" : {
       "max" : 100,
-      "className" : 
"com.linkedin.thirdeye.detection.components.RuleBaselineProvider"
+      "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleDetector"
     },
-    "rule1:THRESHOLD:0" : {
+    "rule1:RULE_BASELINE" : {
       "max" : 100,
-      "className" : 
"com.linkedin.thirdeye.detection.components.ThresholdRuleDetector"
+      "className" : 
"com.linkedin.thirdeye.detection.components.RuleBaselineProvider"
     }
   },
   "cron" : "0 0 14 * * ? *"
diff --git 
a/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/pipeline-config-1.yaml
 
b/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/pipeline-config-1.yaml
index 49c6463..911d0d9 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/pipeline-config-1.yaml
+++ 
b/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/pipeline-config-1.yaml
@@ -14,27 +14,30 @@ dimensionExploration:
   - D2
   minContribution: 0.05
 rules:
-- name: rule1
-  detection:
+- detection:
   - type: THRESHOLD
+    name: maxThreshold_1
     params:
       max: 100
   filter:
   - type: THRESHOLD_RULE_FILTER
-    id: 0
+    name: thresholdFilter_1
     params:
       min: 50
   - type: THRESHOLD_RULE_FILTER
-    id: 1
+    name: thresholdFilter_2
     params:
       min: 50
+
 - name: rule2
   detection:
   - type: THRESHOLD
+    name: maxThreshold_2
     params:
       max: 100
   filter:
   - type: THRESHOLD_RULE_FILTER
+    name: thresholdFilter_3
     params:
       min: 50
 merger:
diff --git 
a/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/pipeline-config-2.yaml
 
b/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/pipeline-config-2.yaml
index 815a77f..323325e 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/pipeline-config-2.yaml
+++ 
b/thirdeye/thirdeye-pinot/src/test/resources/com/linkedin/thirdeye/detection/yaml/pipeline-config-2.yaml
@@ -14,9 +14,9 @@ dimensionExploration:
   - D2
   minContribution: 0.05
 rules:
-- name: rule1
-  detection:
+- detection:
   - type: THRESHOLD
+    name: rule1
     params:
       max: 100
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to