You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by xh...@apache.org on 2019/10/25 19:49:07 UTC

[incubator-pinot] branch master updated: [TE] frontend - harleyjj/alert-details - fix more preview comparison bugs (#4738)

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

xhsun 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 5af88ef  [TE] frontend - harleyjj/alert-details - fix more preview comparison bugs (#4738)
5af88ef is described below

commit 5af88ef909f1c2d71f20ce815372b3eec408ad1b
Author: Harley Jackson <hj...@linkedin.com>
AuthorDate: Fri Oct 25 12:48:58 2019 -0700

    [TE] frontend - harleyjj/alert-details - fix more preview comparison bugs (#4738)
---
 .../app/pods/anomalies/controller.js               |  15 +-
 .../app/pods/components/alert-details/component.js | 154 ++++--
 .../app/pods/components/alert-details/template.hbs |   2 +-
 .../pods/components/detection-yaml/component.js    |  41 +-
 .../pods/components/detection-yaml/template.hbs    |   8 -
 .../pods/components/subscription-yaml/component.js |  24 +-
 .../pods/components/subscription-yaml/template.hbs |   8 -
 .../app/pods/components/yaml-editor/component.js   | 565 ---------------------
 .../app/pods/components/yaml-editor/template.hbs   | 174 -------
 .../app/pods/manage/alerts/index/route.js          |   3 +-
 .../app/pods/manage/explore/route.js               |  13 +-
 .../app/pods/manage/yaml/route.js                  |  20 +-
 .../app/pods/self-serve/create-alert/route.js      |   6 +-
 thirdeye/thirdeye-frontend/app/styles/app.scss     |   3 +-
 .../{yaml-editor.scss => detection-yaml.scss}      |   4 +-
 .../{yaml-editor.scss => subscription-yaml.scss}   |   4 +-
 thirdeye/thirdeye-frontend/app/utils/anomaly.js    |   2 +-
 thirdeye/thirdeye-frontend/app/utils/constants.js  |  74 ---
 thirdeye/thirdeye-frontend/app/utils/utils.js      |  72 +--
 thirdeye/thirdeye-frontend/app/utils/yaml-tools.js | 347 +++++++++++++
 .../acceptance/self-serve-alert-tuning-test.js     |   2 +-
 .../component-test.js                              |  31 +-
 .../components/subscription-yaml/component-test.js |  57 +++
 23 files changed, 578 insertions(+), 1051 deletions(-)

diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js b/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
index 6578247..8011f91 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
@@ -14,8 +14,7 @@ import {
 import { inject as service } from '@ember/service';
 import { isPresent, isEmpty } from '@ember/utils';
 import Controller from '@ember/controller';
-import yamljs from 'yamljs';
-import jsyaml from 'js-yaml';
+import { redundantParse } from 'thirdeye-frontend/utils/yaml-tools';
 import { reads } from '@ember/object/computed';
 import { toastOptions } from 'thirdeye-frontend/utils/constants';
 import { setUpTimeRangeOptions, powerSort } from 'thirdeye-frontend/utils/manage-alert-utils';
@@ -396,21 +395,13 @@ export default Controller.extend({
       selectedSubGroupObjects.forEach(group => {
         let yamlAsObject;
         try {
-          yamlAsObject = yamljs.parse(group.get('yaml'));
+          yamlAsObject = redundantParse(group.get('yaml'));
           if (Array.isArray(yamlAsObject.subscribedDetections)) {
             additionalAlertNames = [ ...additionalAlertNames, ...yamlAsObject.subscribedDetections];
           }
         }
         catch(error){
-          try {
-            // use jsyaml package to try parsing again, since yamljs doesn't parse some edge cases
-            yamlAsObject = jsyaml.safeLoad(group.get('yaml'));
-            if (Array.isArray(yamlAsObject.subscribedDetections)) {
-              additionalAlertNames = [ ...additionalAlertNames, ...yamlAsObject.subscribedDetections];
-            }
-          } catch (error) {
-            notifications.error(`Failed to retrieve alert names for subscription group: ${group.get('name')}`, 'Error', toastOptions);
-          }
+          notifications.error(`Failed to retrieve alert names for subscription group: ${group.get('name')}`, 'Error', toastOptions);
         }
       });
       // add the alert names extracted from groups to any that are already present
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
index e5137b5..64519f5 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
@@ -16,12 +16,17 @@
 import Component from '@ember/component';
 import { computed, set, get, getProperties } from '@ember/object';
 import { later } from '@ember/runloop';
-import { checkStatus, humanizeFloat, postProps, stripNonFiniteValues } from 'thirdeye-frontend/utils/utils';
+import { checkStatus,
+  humanizeFloat,
+  postProps,
+  replaceNonFiniteWithCurrent,
+  stripNonFiniteValues } from 'thirdeye-frontend/utils/utils';
 import { toastOptions } from 'thirdeye-frontend/utils/constants';
 import { colorMapping, makeTime, toMetricLabel, extractTail } from 'thirdeye-frontend/utils/rca-utils';
 import { getYamlPreviewAnomalies,
   getAnomaliesByAlertId,
   getBounds  } from 'thirdeye-frontend/utils/anomaly';
+import { getValueFromYaml } from 'thirdeye-frontend/utils/yaml-tools';
 import { inject as service } from '@ember/service';
 import { task } from 'ember-concurrency';
 import floatToPercent from 'thirdeye-frontend/utils/float-to-percent';
@@ -84,6 +89,8 @@ export default Component.extend({
   granularity: null,
   alertYaml: null,
   dimensionExploration: null,
+  // cachedMetric holds the last metric of anomalies fetched, so that state can be reset for comparison if metric changes
+  cachedMetric: null,
   getAnomaliesError:false, // stops the component from fetching more anomalies until user changes state
   detectionHealth: null, // result of call to detection/health/{id}, passed in by parent
   timeWindowSize: null, // passed in by parent, which retrieves from endpoint.  Do not set
@@ -268,9 +275,9 @@ export default Component.extend({
 
   /**
    * Return state of anomalies and time series for updating state correctly
-   * 1 - set to old (Alert Overview or Create Alert Preview w/o old)
+   * 1 - set to old (Alert Overview or Create Alert Preview)
    * 2 - set to new (Edit Alert Preview with old or Create Alert Preview w/o new)
-   * 3 - shuffle then set to new (Create Alert Preview with 2 sets already)
+   * 3 - shuffle then set to new (Create Alert Preview with 2 sets already) (not used for now)
    * 4 - get alert anomalies only - no time series (Edit Alert Preview w/o any anomalies loaded yet)
    * 5 - error getting anomalies
    * @type {Number}
@@ -520,7 +527,8 @@ export default Component.extend({
       if (baseline && !_.isEmpty(baseline.upper_bound)) {
         series['Upper and lower bound'] = {
           timestamps: baseline.timestamp,
-          values: stripNonFiniteValues(baseline.upper_bound),
+          values: replaceNonFiniteWithCurrent(baseline.upper_bound,
+            showRules ? timeseries.current : timeseries.value),
           type: 'line',
           color: 'screenshot-bounds'
         };
@@ -529,7 +537,8 @@ export default Component.extend({
       if (baseline && !_.isEmpty(baseline.lower_bound)) {
         series['lowerBound'] = {
           timestamps: baseline.timestamp,
-          values: stripNonFiniteValues(baseline.lower_bound),
+          values: replaceNonFiniteWithCurrent(baseline.lower_bound,
+            showRules ? timeseries.current : timeseries.value),
           type: 'line',
           color: 'screenshot-bounds'
         };
@@ -824,10 +833,10 @@ export default Component.extend({
       let falsePositives = 0;
       let falseNegatives = 0;
       let numberOfAnomalies = 0;
-      anomaliesOld.forEach(function (attr) {
+      anomaliesOld.forEach(function (anomaly) {
         numberOfAnomalies++;
-        if(attr.anomaly && attr.anomaly.statusClassification) {
-          const classification = attr.anomaly.statusClassification;
+        if(anomaly && anomaly.statusClassification) {
+          const classification = anomaly.statusClassification;
           if (classification !== 'NONE') {
             respondedAnomaliesCount++;
             if (classification === 'TRUE_POSITIVE') {
@@ -960,8 +969,9 @@ export default Component.extend({
         }
         anomalies = applicationAnomalies;
       }
+      set(this, 'cachedMetric', getValueFromYaml('metric', alertYaml, 'string'));
     } catch (error) {
-      notifications.error(`_getAnomalies failed: ${error}`, 'Error', toastOptions);
+      notifications.error(`_getAnomalies failed: ${error.body.message}`, 'Error', toastOptions);
       this.set('getAnomaliesError', true);
     }
 
@@ -998,36 +1008,63 @@ export default Component.extend({
     }
   },
 
-  _formattedModifiedBy(feedback) {
-    let result;
-    if (feedback && typeof feedback === 'object') {
-      if (feedback.updatedBy && feedback.updatedBy !== 'no-auth-user') {
-        result = feedback.updatedBy.split('@')[0];
-      } else {
-        result = '--';
+  /**
+   * Helper to reset state if the user is previewing a different metric
+   * returns true if the metrics are different
+   * @method checkMetricIfCreateAlertPreview
+   * @return {boolean}
+   */
+  _checkMetricIfCreateAlertPreview() {
+    let isMetricNew = false;
+    const {
+      stateOfAnomaliesAndTimeSeries,
+      cachedMetric,
+      alertYaml
+    } = this.getProperties('stateOfAnomaliesAndTimeSeries', 'cachedMetric', 'alertYaml');
+    if (stateOfAnomaliesAndTimeSeries === 2 || stateOfAnomaliesAndTimeSeries === 3) {
+      if (!this.get('isEditMode')) {
+        // is Create Alert preview
+        isMetricNew = !(cachedMetric === getValueFromYaml('metric', alertYaml, 'string'));
       }
     }
-    return result;
+    return isMetricNew;
   },
 
-  _formattedRule(properties) {
-    let result;
-    if (properties && typeof properties === 'object') {
-      if (properties.detectorComponentName) {
-        result = properties.detectorComponentName.split(':')[0];
-      } else {
-        result = '--';
-      }
-    }
-    return result;
-  },
+  _fetchAnomalies() {
+    set(this, 'getAnomaliesError', false);
 
-  _formatAnomaly(anomaly) {
-    return `${moment(anomaly.startTime).format(TABLE_DATE_FORMAT)}`;
-  },
+    // If the user is running the detection with a new metric, we should reset the state of time series and anomalies for comparison
+    if (this._checkMetricIfCreateAlertPreview()) {
+      this.setProperties({
+        anomaliesOld: [],
+        anomaliesOldSet: false,
+        anomaliesNew: [],
+        anomaliesNewSet: false
+      });
+    }
 
-  _filterAnomalies(rows) {
-    return rows.filter(row => (row.startTime && row.endTime && !row.child));
+    try {
+      // in Edit Alert Preview, we want the original yaml used for comparisons
+      const content = (get(this, 'isEditMode') && !(get(this, 'anomaliesOldSet'))) ? get(this, 'originalYaml') : get(this, 'alertYaml');
+      return this.get('_getAnomalies').perform(content)
+        .then(results => this._setAnomaliesAndTimeSeries(results))
+        .then(() => {
+          if (get(this, 'metricUrn')) {
+            this._fetchTimeseries();
+          } else {
+            throw new Error('Unable to get MetricUrn from response');
+          }
+        })
+        .catch(error => {
+          if (error.name !== 'TaskCancelation') {
+            this.get('notifications').error(error, 'Error', toastOptions);
+            set(this, 'getAnomaliesError', true);
+          }
+        });
+    } catch (error) {
+      this.get('notifications').error(error, 'Error', toastOptions);
+      set(this, 'getAnomaliesError', true);
+    }
   },
 
   _fetchTimeseries() {
@@ -1090,31 +1127,36 @@ export default Component.extend({
     set(this, 'errorBaseline', null);
   },
 
-  _fetchAnomalies() {
-    set(this, 'getAnomaliesError', false);
+  _filterAnomalies(rows) {
+    return rows.filter(row => (row.startTime && row.endTime && !row.child));
+  },
 
-    try {
-      // in Edit Alert Preview, we want the original yaml used for comparisons
-      const content = (get(this, 'isEditMode') && !(get(this, 'anomaliesOldSet'))) ? get(this, 'originalYaml') : get(this, 'alertYaml');
-      return this.get('_getAnomalies').perform(content)
-        .then(results => this._setAnomaliesAndTimeSeries(results))
-        .then(() => {
-          if (get(this, 'metricUrn')) {
-            this._fetchTimeseries();
-          } else {
-            throw new Error('Unable to get MetricUrn from response');
-          }
-        })
-        .catch(error => {
-          if (error.name !== 'TaskCancelation') {
-            this.get('notifications').error(error, 'Error', toastOptions);
-            set(this, 'getAnomaliesError', true);
-          }
-        });
-    } catch (error) {
-      this.get('notifications').error(error, 'Error', toastOptions);
-      set(this, 'getAnomaliesError', true);
+  _formatAnomaly(anomaly) {
+    return `${moment(anomaly.startTime).format(TABLE_DATE_FORMAT)}`;
+  },
+
+  _formattedModifiedBy(feedback) {
+    let result;
+    if (feedback && typeof feedback === 'object') {
+      if (feedback.updatedBy && feedback.updatedBy !== 'no-auth-user') {
+        result = feedback.updatedBy.split('@')[0];
+      } else {
+        result = '--';
+      }
     }
+    return result;
+  },
+
+  _formattedRule(properties) {
+    let result;
+    if (properties && typeof properties === 'object') {
+      if (properties.detectorComponentName) {
+        result = properties.detectorComponentName.split(':')[0];
+      } else {
+        result = '--';
+      }
+    }
+    return result;
   },
 
   /**
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
index ad46a5c..cacda52 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
@@ -269,7 +269,7 @@
             {{/if}}
         </div>
         {{else}}
-        <div class="yaml-editor-msg">Alert configuration has changed.</div>
+        <div class="detection-yaml-msg">Alert configuration has changed.</div>
           {{range-pill-selectors
             title="Show me"
             uiDateFormat=pill.uiDateFormat
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/component.js b/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/component.js
index f23b3a2..e097034 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/component.js
@@ -1,6 +1,6 @@
 /**
  * Component to render the detection configuration yaml editor.
- * @module components/detection-editor
+ * @module components/detection-yaml
  * @property {Number} alertId - alertId needed in edit mode, to submit changes to alert config
  * @property {boolean} isEditMode - to activate the edit mode
  * @property {String} detectionYaml - the detection yaml
@@ -16,11 +16,10 @@
  */
 
 import Component from '@ember/component';
-import {computed, set, get, getProperties, setProperties} from '@ember/object';
-import {checkStatus} from 'thirdeye-frontend/utils/utils';
-import {yamlAlertProps, toastOptions} from 'thirdeye-frontend/utils/constants';
-import yamljs from 'yamljs';
-import jsyaml from 'js-yaml';
+import { set, get, getProperties, setProperties } from '@ember/object';
+import { checkStatus } from 'thirdeye-frontend/utils/utils';
+import { toastOptions } from 'thirdeye-frontend/utils/constants';
+import { defaultDetectionYaml, redundantParse } from 'thirdeye-frontend/utils/yaml-tools';
 import RSVP from "rsvp";
 import fetch from 'fetch';
 import {
@@ -30,32 +29,23 @@ import {inject as service} from '@ember/service';
 import config from 'thirdeye-frontend/config/environment';
 
 export default Component.extend({
-  classNames: ['yaml-editor'],
+  classNames: ['detection-yaml'],
   notifications: service('toast'),
   /**
-   * Properties we expect to receive for the yaml-editor
+   * Properties we expect to receive for detection-yaml
    */
   currentMetric: null,
   isYamlParseable: true,
   alertTitle: 'Define detection configuration',
   isEditMode: false,
   disableYamlSave: true,
-  detectionMsg: '',                   //General alert failures
   detectionYaml: null,                // The YAML for the anomaly detection
-  currentYamlAlertOriginal: yamlAlertProps,
+  currentYamlAlertOriginal: defaultDetectionYaml,
   alertId: null, // only needed in edit mode
   setDetectionYaml: null, // bubble up detectionYaml changes to parent
 
 
 
-  isDetectionMsg: computed(
-    'detectionMsg',
-    function() {
-      const detectionMsg = get(this, 'detectionMsg');
-      return detectionMsg !== '';
-    }
-  ),
-
   init() {
     this._super(...arguments);
     const {
@@ -208,18 +198,11 @@ export default Component.extend({
       } = getProperties(this, 'detectionYaml', 'noResultsArray');
       let yamlAsObject = {};
       try {
-        yamlAsObject = yamljs.parse(detectionYaml);
+        yamlAsObject = redundantParse(detectionYaml);
         set(this, 'isYamlParseable', true);
-      }
-      catch(err){
-        try {
-          // use jsyaml package to try parsing again, since yamljs doesn't parse some edge cases
-          yamlAsObject = jsyaml.safeLoad(detectionYaml);
-          set(this, 'isYamlParseable', true);
-        } catch (error) {
-          set(this, 'isYamlParseable', false);
-          return noResultsArray;
-        }
+      } catch (error) {
+        set(this, 'isYamlParseable', false);
+        return noResultsArray;
       }
       // if editor.metricId field contains a value, metric was just chosen.  Populate caches for filters and dimensions
       if(editor.metricId){
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/template.hbs
index 0cdee45..47ee730 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/detection-yaml/template.hbs
@@ -49,11 +49,3 @@
     </div>
   {{/if}}
 </div>
-<div class="col-xs-12">
-  {{#if isDetectionMsg}}
-    <div class="yaml-editor-msg">
-      <p class="yaml-editor-msg__icon"><i class="yaml-editor-msg__icon--error glyphicon glyphicon-remove-circle"></i>Error in alert yaml</p>
-      <p>Message: {{detectionMsg}}</p>
-    </div>
-  {{/if}}
-</div>
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/subscription-yaml/component.js b/thirdeye/thirdeye-frontend/app/pods/components/subscription-yaml/component.js
index ed49a1f..2fa40dd 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/subscription-yaml/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/subscription-yaml/component.js
@@ -3,8 +3,8 @@
  * @module components/subscription-yaml
  * @property {number} subscriptionGroupId - the subscription group id in edit mode
  * @property {boolean} isEditMode - to activate the edit mode
- * @property {Object} subscriptionGroupNames - the list of subscription groups
- * @property {Object} subscriptionYaml - the subscription group yaml
+ * @property {Array} subscriptionGroupNames - the list of subscription groups
+ * @property {String} subscriptionYaml - the subscription group yaml
  * @property {function} updateSubscriptionYaml - bubble up the subscription group yaml to parent
  * @example
    {{subscription-yaml
@@ -21,16 +21,16 @@
  */
 
 import Component from '@ember/component';
-import {computed, get, set} from '@ember/object';
-import {yamlAlertSettings} from 'thirdeye-frontend/utils/constants';
-import {inject as service} from '@ember/service';
+import { get, set } from '@ember/object';
+import { defaultSubscriptionYaml } from 'thirdeye-frontend/utils/yaml-tools';
+import { inject as service } from '@ember/service';
 import config from 'thirdeye-frontend/config/environment';
 
 export default Component.extend({
-  classNames: ['yaml-editor'],
+  classNames: ['subscription-yaml'],
   notifications: service('toast'),
   /**
-   * Properties we expect to receive for the yaml-editor
+   * Properties we expect to receive for the subscription-yaml
    */
   currentMetric: null,
   isYamlParseable: true,
@@ -40,7 +40,7 @@ export default Component.extend({
   disableSubGroupSave: true,
   subscriptionMsg: '',                //General subscription failures
   subscriptionYaml:  null,            // The YAML for the subscription group
-  currentYamlSettingsOriginal: yamlAlertSettings,
+  currentYamlSettingsOriginal: defaultSubscriptionYaml,
   showAnomalyModal: false,
   showNotificationModal: false,
   setSubscriptionYaml: null, // function passed in from parent
@@ -48,14 +48,6 @@ export default Component.extend({
 
 
 
-  isSubscriptionMsg: computed(
-    'subscriptionMsg',
-    function() {
-      const subscriptionMsg = get(this, 'subscriptionMsg');
-      return subscriptionMsg !== '';
-    }
-  ),
-
   init() {
     this._super(...arguments);
     const {
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/subscription-yaml/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/subscription-yaml/template.hbs
index 732bb5a..32c1b26 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/subscription-yaml/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/subscription-yaml/template.hbs
@@ -46,11 +46,3 @@
     mode="ace/mode/yaml"
   }}
 </div>
-<div class="col-xs-12">
-  {{#if isSubscriptionMsg}}
-    <div class="yaml-editor-msg">
-      <p class="yaml-editor-msg__icon"><i class="yaml-editor-msg__icon--error glyphicon glyphicon-remove-circle"></i>Error in the subscription yaml</p>
-      <p>Message: {{subscriptionMsg}}</p>
-    </div>
-  {{/if}}
-</div>
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/component.js b/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/component.js
deleted file mode 100644
index 7d31990..0000000
--- a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/component.js
+++ /dev/null
@@ -1,565 +0,0 @@
-/**
- * Component to render the alert and subscription group yaml editors.
- * @module components/yaml-editor
- * @property {number} alertId - the alert id
- * @property {number} subscriptionGroupId - the subscription group id
- * @property {boolean} isEditMode - to activate the edit mode
- * @property {boolean} showSettings - to show the subscriber groups yaml editor
- * @property {Object} subscriptionGroupNames - the list of subscription groups
- * @property {Object} detectionYaml - the detection yaml to display
- * @property {Object} subscriptionYaml - the subscription group yaml to display
- * @example
-   {{yaml-editor
-     alertId=model.alertId
-     subscriptionGroupId=model.subscriptionGroupId
-     isEditMode=true
-     showSettings=true
-     subscriptionGroupNames=model.subscriptionGroupNames
-     detectionYaml=model.detectionYaml
-     subscriptionYaml=model.subscriptionYaml
-   }}
- * @author lohuynh
- */
-
-import Component from '@ember/component';
-import {computed, set, get, getProperties, setProperties} from '@ember/object';
-import {checkStatus} from 'thirdeye-frontend/utils/utils';
-import {yamlAlertProps, yamlAlertSettings, toastOptions} from 'thirdeye-frontend/utils/constants';
-import yamljs from 'yamljs';
-import RSVP from "rsvp";
-import fetch from 'fetch';
-import {
-  selfServeApiGraph, selfServeApiCommon
-} from 'thirdeye-frontend/utils/api/self-serve';
-import {inject as service} from '@ember/service';
-import {task} from 'ember-concurrency';
-import config from 'thirdeye-frontend/config/environment';
-
-const CREATE_GROUP_TEXT = 'Create a new subscription group';
-
-export default Component.extend({
-  classNames: ['yaml-editor'],
-  notifications: service('toast'),
-  /**
-   * Properties we expect to receive for the yaml-editor
-   */
-  currentMetric: null,
-  isYamlParseable: true,
-  alertTitle: 'Define detection configuration',
-  alertSettingsTitle: 'Define subscription configuration',
-  isEditMode: false,
-  showSettings: true,
-  disableYamlSave: true,
-  disableSubGroupSave: true,
-  detectionMsg: '',                   //General alert failures
-  subscriptionMsg: '',                //General subscription failures
-  detectionYaml: null,                // The YAML for the anomaly detection
-  subscriptionYaml:  null,            // The YAML for the subscription group
-  currentYamlAlertOriginal: yamlAlertProps,
-  currentYamlSettingsOriginal: yamlAlertSettings,
-  showAnomalyModal: false,
-  showNotificationModal: false,
-  toggleCollapsed: true,
-  alertDataIsCurrent: true,
-
-
-
-  init() {
-    this._super(...arguments);
-    const subscriptionGroupNamesDisplay = get(this, 'subscriptionGroupNamesDisplay');
-    // Checks to make sure there is a subscription group array with at least one subscription group
-    if (subscriptionGroupNamesDisplay && Array.isArray(subscriptionGroupNamesDisplay) && subscriptionGroupNamesDisplay.length > 0) {
-      const firstGroup = subscriptionGroupNamesDisplay[0];
-      set(this, 'subscriptionYaml', firstGroup.yaml);
-      set(this, 'groupName', firstGroup);
-      set(this, 'subscriptionGroupId', firstGroup.id);
-    }
-  },
-
-  /**
-   * populates subscription group dropdown with options from fetch or model
-   * @method subscriptionGroupNamesDisplay
-   * @return {Object}
-   */
-  subscriptionGroupNamesDisplay: computed(
-    'subscriptionGroupNames',
-    async function() {
-      const {
-        isEditMode,
-        subscriptionGroupNames
-      } = this.getProperties('isEditMode', 'subscriptionGroupNames');
-      const createGroup = {
-        name: CREATE_GROUP_TEXT,
-        id: 'n/a',
-        yaml: yamlAlertSettings
-      };
-      const moddedArray = [createGroup];
-      if (isEditMode) {
-        return [...moddedArray, ...subscriptionGroupNames];
-      }
-      const subscriptionGroups = await get(this, '_fetchSubscriptionGroups').perform();
-      return [...moddedArray, ...subscriptionGroups];
-    }
-  ),
-
-  /**
-   * Flag to trigger special case of no existing subscription groups for an alert
-   * @method noExistingSubscriptionGroup
-   * @return {Boolean}
-   */
-  noExistingSubscriptionGroup: computed(
-    'subscriptionGroupNames',
-    function() {
-      const subscriptionGroupNames = get(this, 'subscriptionGroupNames');
-      if (subscriptionGroupNames && Array.isArray(subscriptionGroupNames) && subscriptionGroupNames.length > 0) {
-        return false;
-      }
-      return true;
-    }
-  ),
-
-  /**
-   * Change subscription group button text depending on whether creating or updating
-   * @method subGroupButtonText
-   * @return {String}
-   */
-  subGroupButtonText: computed(
-    'noExistingSubscriptionGroup',
-    'groupName',
-    function() {
-      const {
-        noExistingSubscriptionGroup,
-        groupName
-      } = this.getProperties('noExistingSubscriptionGroup', 'groupName');
-      return (noExistingSubscriptionGroup || !groupName || groupName.name === CREATE_GROUP_TEXT) ? "Create Group" : "Update Group";
-    }
-  ),
-
-  /**
-   * sets Yaml value displayed to contents of detectionYaml or currentYamlAlertOriginal
-   * @method currentYamlAlert
-   * @return {String}
-   */
-  currentYamlAlert: computed(
-    'detectionYaml',
-    function() {
-      const inputYaml = get(this, 'detectionYaml');
-      return inputYaml || get(this, 'currentYamlAlertOriginal');
-    }
-  ),
-
-  /**
-   * sets Yaml value displayed to contents of subscriptionYaml or currentYamlSettingsOriginal
-   * @method currentYamlAlert
-   * @return {String}
-   */
-  currentSubscriptionYaml: computed(
-    'subscriptionYaml',
-    function() {
-      const subscriptionYaml = get(this, 'subscriptionYaml');
-      return subscriptionYaml || get(this, 'currentYamlSettingsOriginal');
-    }
-  ),
-
-
-  isDetectionMsg: computed(
-    'detectionMsg',
-    function() {
-      const detectionMsg = get(this, 'detectionMsg');
-      return detectionMsg !== '';
-    }
-  ),
-
-  isSubscriptionMsg: computed(
-    'subscriptionMsg',
-    function() {
-      const subscriptionMsg = get(this, 'subscriptionMsg');
-      return subscriptionMsg !== '';
-    }
-  ),
-
-  _fetchSubscriptionGroups: task(function* () {
-    //dropdown of subscription groups
-    const url2 = `/detection/subscription-groups`;
-    const postProps2 = {
-      method: 'get',
-      headers: { 'content-type': 'application/json' }
-    };
-    const notifications = get(this, 'notifications');
-
-    try {
-      const response = yield fetch(url2, postProps2);
-      const json = yield response.json();
-      return json.filterBy('yaml');
-    } catch (error) {
-      notifications.error('Failed to retrieve subscription groups.', 'Error', toastOptions);
-    }
-  }).drop(),
-
-  /**
-   * Calls api's for specific metric's autocomplete
-   * @method _loadAutocompleteById
-   * @return Promise
-   */
-  _loadAutocompleteById(metricId) {
-    const promiseHash = {
-      filters: fetch(selfServeApiGraph.metricFilters(metricId)).then(res => checkStatus(res, 'get', true)),
-      dimensions: fetch(selfServeApiGraph.metricDimensions(metricId)).then(res => checkStatus(res, 'get', true))
-    };
-    return RSVP.hash(promiseHash);
-  },
-
-  /**
-   * Get autocomplete suggestions from relevant api
-   * @method _buildYamlSuggestions
-   * @return Promise
-   */
-  _buildYamlSuggestions(currentMetric, yamlAsObject, prefix, noResultsArray,
-    filtersCache, dimensionsCache, position) {
-    // holds default result to return if all checks fail
-    let defaultReturn = Promise.resolve(noResultsArray);
-    // when metric is being autocompleted, entire text field will be replaced and metricId stored in editor
-    if (yamlAsObject.metric === prefix) {
-      return fetch(selfServeApiCommon.metricAutoComplete(prefix))
-        .then(checkStatus)
-        .then(metrics => {
-          if (metrics && metrics.length > 0) {
-            return metrics.map(metric => {
-              const [dataset, metricname] = metric.alias.split('::');
-              return {
-                value: metricname,
-                caption: metric.alias,
-                row: position.row,
-                column: position.column,
-                metricname,
-                dataset,
-                id: metric.id,
-                completer:{
-                  insertMatch: (editor, data) => {
-                    // replace metric row with selected metric
-                    editor.session.replace({
-                      start: { row: data.row, column: 0 },
-                      end: { row: data.row, column: Number.MAX_VALUE }},
-                    `metric: ${data.metricname}`);
-                    // find dataset: field in text
-                    const datasetLocation = editor.find('dataset:');
-                    // if found, replace with dataset
-                    if (datasetLocation) {
-                      editor.session.replace({
-                        start: { row: datasetLocation.start.row, column: 0},
-                        end: { row: datasetLocation.end.row, column: Number.MAX_VALUE }},
-                      `dataset: ${data.dataset}`);
-                      // otherwise, add it to the line below the metric field
-                    } else {
-                      editor.session.insert({
-                        row: data.row + 1, column: 0 },
-                      `dataset: ${data.dataset}\n`);
-                    }
-                    editor.metricId = data.id;
-                  }
-                }};
-            });
-          }
-          return noResultsArray;
-        })
-        .catch(() => {
-          return noResultsArray;
-        });
-    }
-    // if a currentMetric has been stored, we can check autocomplete filters and dimensions
-    if (currentMetric) {
-      const dimensionValues = yamlAsObject.dimensionExploration.dimensions;
-      const filterTypes = typeof yamlAsObject.filters === "object" ? Object.keys(yamlAsObject.filters) : [];
-      if (Array.isArray(dimensionValues) && dimensionValues.includes(prefix)) {
-        if (dimensionsCache.length > 0) {
-          // wraps result in Promise.resolve because return of Promise is expected by yamlSuggestions
-          return Promise.resolve(dimensionsCache.map(dimension => {
-            return {
-              value: dimension
-            };
-          }));
-        }
-      }
-      let filterKey = '';
-      let i = 0;
-      while (i < filterTypes.length) {
-        if (filterTypes[i] === prefix){
-          i = filterTypes.length;
-          // wraps result in Promise.resolve because return of Promise is expected by yamlSuggestions
-          return Promise.resolve(Object.keys(filtersCache).map(filterType => {
-            return {
-              value: `${filterType}:`,
-              caption: `${filterType}:`,
-              snippet: filterType
-            };
-          }));
-        }
-        if (Array.isArray(yamlAsObject.filters[filterTypes[i]]) && yamlAsObject.filters[filterTypes[i]].includes(prefix)) {
-          filterKey = filterTypes[i];
-        }
-        i++;
-      }
-      if (filterKey) {
-        // wraps result in Promise.resolve because return of Promise is expected by yamlSuggestions
-        return Promise.resolve(filtersCache[filterKey].map(filterParam => {
-          return {
-            value: filterParam
-          };
-        }));
-      }
-    }
-    return defaultReturn;
-  },
-
-  // Method for handling subscription group, whether there are any or not
-  async _handleSubscriptionGroup(subscriptionYaml, notifications, subscriptionGroupId) {
-    const {
-      noExistingSubscriptionGroup,
-      groupName
-    } = this.getProperties('noExistingSubscriptionGroup', 'groupName');
-    if (noExistingSubscriptionGroup || !groupName || groupName.name === CREATE_GROUP_TEXT) {
-      //POST settings
-      const setting_url = '/yaml/subscription';
-      const settingsPostProps = {
-        method: 'POST',
-        body: subscriptionYaml,
-        headers: { 'content-type': 'text/plain' }
-      };
-      try {
-        const settings_result = await fetch(setting_url, settingsPostProps);
-        const settings_status  = get(settings_result, 'status');
-        const settings_json = await settings_result.json();
-        if (settings_status !== 200) {
-          set(this, 'errorMsg', get(settings_json, 'message'));
-          notifications.error(`Failed to save the subscription configuration due to: ${settings_json.message}.`, 'Error', toastOptions);
-        } else {
-          notifications.success('Subscription configuration saved successfully', 'Done', toastOptions);
-        }
-      } catch (error) {
-        notifications.error('Error while saving subscription config.', error, toastOptions);
-      }
-    } else {
-      //PUT settings
-      const setting_url = `/yaml/subscription/${subscriptionGroupId}`;
-      const settingsPostProps = {
-        method: 'PUT',
-        body: subscriptionYaml,
-        headers: { 'content-type': 'text/plain' }
-      };
-      try {
-        const settings_result = await fetch(setting_url, settingsPostProps);
-        const settings_status  = get(settings_result, 'status');
-        const settings_json = await settings_result.json();
-        if (settings_status !== 200) {
-          set(this, 'errorMsg', get(settings_json, 'message'));
-          notifications.error(`Failed to save the subscription configuration due to: ${settings_json.message}.`, 'Error', toastOptions);
-        } else {
-          notifications.success('Subscription configuration saved successfully', 'Done', toastOptions);
-        }
-      } catch (error) {
-        notifications.error('Error while saving subscription config.', error, toastOptions);
-      }
-    }
-  },
-
-  actions: {
-    changeAccordion() {
-      set(this, 'toggleCollapsed', !get(this, 'toggleCollapsed'));
-    },
-
-    /**
-     * resets given yaml field to default value for creation mode and server value for edit mode
-     */
-    resetYAML(field) {
-      const isEditMode = get(this, 'isEditMode');
-      if (field === 'anomaly') {
-        if(isEditMode) {
-          set(this, 'detectionYaml', get(this, 'currentYamlAlertOriginal'));
-        } else {
-          const currentYamlAlertOriginal = get(this, 'currentYamlAlertOriginal');
-          set(this, 'detectionYaml', currentYamlAlertOriginal);
-        }
-      } else if (field === 'subscription') {
-        if(isEditMode) {
-          set(this, 'subscriptionYaml', get(this, 'currentYamlSettingsOriginal'));
-        } else {
-          const currentYamlSettingsOriginal = get(this, 'currentYamlSettingsOriginal');
-          set(this, 'subscriptionYaml', currentYamlSettingsOriginal);
-        }
-      }
-    },
-
-    /**
-     * Brings up appropriate modal, based on which yaml field is clicked
-     */
-    triggerDoc(field) {
-      if (field === 'Anomaly') {
-        window.open(config.docs.detectionConfig);
-      } else {
-        window.open(config.docs.subscriptionConfig);
-      }
-    },
-
-    /**
-     * returns array of suggestions for Yaml editor autocompletion
-     */
-    yamlSuggestions(editor, session, position, prefix) {
-      const {
-        detectionYaml,
-        noResultsArray
-      } = getProperties(this, 'detectionYaml', 'noResultsArray');
-      let yamlAsObject = {};
-      try {
-        yamlAsObject = yamljs.parse(detectionYaml);
-        set(this, 'isYamlParseable', true);
-      }
-      catch(err){
-        set(this, 'isYamlParseable', false);
-        return noResultsArray;
-      }
-      // if editor.metricId field contains a value, metric was just chosen.  Populate caches for filters and dimensions
-      if(editor.metricId){
-        const currentMetric = set(this, 'currentMetric', editor.metricId);
-        editor.metricId = '';
-        return get(this, '_loadAutocompleteById')(currentMetric)
-          .then(resultObj => {
-            const { filters, dimensions } = resultObj;
-            setProperties(this, {
-              dimensionsCache: dimensions,
-              filtersCache: filters
-            });
-          })
-          .then(() => {
-            return get(this, '_buildYamlSuggestions')(currentMetric,
-              yamlAsObject, prefix, noResultsArray, get(this, 'filtersCache'),
-              get(this, 'dimensionsCache'), position)
-              .then(results => results);
-          });
-      }
-      const currentMetric = get(this, 'currentMetric');
-      // deals with no metricId, which could be autocomplete for metric or for filters and dimensions already cached
-      return get(this, '_buildYamlSuggestions')(currentMetric, yamlAsObject,
-        prefix, noResultsArray, get(this, 'filtersCache'),
-        get(this, 'dimensionsCache'), position)
-        .then(results => results);
-
-    },
-
-    /**
-     * Activates 'Create changes' button and stores YAML content in detectionYaml
-     */
-    onEditingDetectionYamlAction(value) {
-      setProperties(this, {
-        disableYamlSave: false,
-        detectionYaml: value,
-        detectionMsg: '',
-        subscriptionMsg: '',
-        alertDataIsCurrent: false
-      });
-    },
-
-    /**
-     * Activates 'Create changes' button and stores YAML content in subscriptionYaml
-     */
-    onEditingSubscriptionYamlAction(value) {
-      setProperties(this, {
-        disableSubGroupSave: false,
-        subscriptionYaml: value
-      });
-    },
-
-    /**
-     * Updates the subscription settings yaml with user section
-     */
-    onSubscriptionGroupSelectionAction(value) {
-      if(value.yaml) {
-        set(this, 'subscriptionYaml', value.yaml);
-        set(this, 'groupName', value);
-        set(this, 'subscriptionGroupId', value.id);
-      }
-    },
-
-    /**
-     * Fired by create button in YAML UI
-     * Grabs YAML content and sends it
-     */
-    createAlertYamlAction() {
-      const content = {
-        detection: get(this, 'detectionYaml'),
-        subscription: get(this, 'subscriptionYaml')
-      };
-      const url = '/yaml/create-alert';
-      const postProps = {
-        method: 'post',
-        body: JSON.stringify(content),
-        headers: { 'content-type': 'application/json' }
-      };
-      const notifications = get(this, 'notifications');
-
-      fetch(url, postProps).then((res) => {
-        res.json().then((result) => {
-          if(result){
-            if (result.detectionMsg) {
-              set(this, 'detectionMsg', result.detectionMsg);
-            }
-            if (result.subscriptionMsg) {
-              set(this, 'subscriptionMsg', result.subscriptionMsg);
-            }
-            if (result.detectionAlertConfigId && result.detectionConfigId) {
-              notifications.success('Created alert successfully.', 'Created', toastOptions);
-            }
-          }
-        });
-      }).catch((error) => {
-        notifications.error('Create alert failed.', error, toastOptions);
-      });
-    },
-
-    /**
-     * Fired by alert button in YAML UI in edit mode
-     * Grabs alert yaml and puts it to the backend.
-     */
-    async submitAlertEdit() {
-      const {
-        detectionYaml,
-        notifications,
-        alertId
-      } = getProperties(this, 'detectionYaml', 'notifications', 'alertId');
-
-      //PUT alert
-      const alert_url = `/yaml/${alertId}`;
-      const alertPostProps = {
-        method: 'PUT',
-        body: detectionYaml,
-        headers: { 'content-type': 'text/plain' }
-      };
-      try {
-        const alert_result = await fetch(alert_url, alertPostProps);
-        const alert_status  = get(alert_result, 'status');
-        const alert_json = await alert_result.json();
-        if (alert_status !== 200) {
-          set(this, 'errorMsg', get(alert_json, 'message'));
-          notifications.error(`Failed to save the detection configuration due to: ${alert_json.message}.`, 'Error', toastOptions);
-        } else {
-          notifications.success('Detection configuration saved successfully', 'Done', toastOptions);
-        }
-      } catch (error) {
-        notifications.error('Error while saving detection config.', error, toastOptions);
-      }
-    },
-
-    /**
-     * Fired by subscription group button in YAML UI in edit mode
-     * Grabs subscription group yaml and posts or puts it to the backend.
-     */
-    async submitSubscriptionGroup() {
-      const {
-        subscriptionYaml,
-        notifications,
-        subscriptionGroupId
-      } = getProperties(this, 'subscriptionYaml', 'notifications', 'subscriptionGroupId');
-      // If there is no existing subscription group, this method will handle it
-      this._handleSubscriptionGroup(subscriptionYaml, notifications, subscriptionGroupId);
-    }
-  }
-});
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
deleted file mode 100644
index 174f690..0000000
--- a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
+++ /dev/null
@@ -1,174 +0,0 @@
-<fieldset class="te-form__section te-form__section--first row">
-  <div class="col-xs-12">
-    <legend class="te-form__section-title">{{alertTitle}}</legend>
-  </div>
-  <div class="col-xs-12 {{if isEditMode "bottom-margin"}}">
-    {{#unless isEditMode}}
-      <label for="select-metric" class="control-label te-label te-label--taller required">Can't find your metric?
-        {{#link-to "self-serve.import-metric" class="thirdeye-link-secondary thirdeye-link-secondary--inside"}}
-          Import a Metric From InGraphs
-        {{/link-to}}
-      </label>
-    {{/unless}}
-    <div class="pull-right">
-      {{bs-button
-        defaultText="Reset"
-        type="outline-primary"
-        buttonType="reset"
-        onClick=(action "resetYAML" "anomaly")
-        class="te-button te-button--link"
-      }}
-      {{bs-button
-        defaultText="View Docs & Examples"
-        type="outline-primary"
-        buttonType="link"
-        onClick=(action "triggerDoc" "Anomaly")
-        class="te-button te-button--cancel"
-      }}
-    </div>
-  </div>
-  <div class="col-xs-12 {{if isEditMode "bottom-margin"}}">
-    {{ember-ace
-      lines=35
-      value=currentYamlAlert
-      suggestCompletions=(action 'yamlSuggestions')
-      enableLiveAutocompletion=true
-      update=(action "onEditingDetectionYamlAction")
-      mode="ace/mode/yaml"
-    }}
-    {{#if isEditMode}}
-      <div class="pull-right">
-        {{bs-button
-          defaultText="Update Alert"
-          type="primary"
-          buttonType="submit"
-          disabled=disableYamlSave
-          onClick=(action "submitAlertEdit")
-          class="te-button te-button--submit"
-        }}
-      </div>
-    {{/if}}
-  </div>
-  <div class="col-xs-12">
-    {{#if isDetectionMsg}}
-      <div class="yaml-editor-msg">
-        <p class="yaml-editor-msg__icon"><i class="yaml-editor-msg__icon--error glyphicon glyphicon-remove-circle"></i>Error in alert yaml</p>
-        <p>Message: {{detectionMsg}}</p>
-      </div>
-    {{/if}}
-  </div>
-  <div class="col-xs-12">
-    {{#bs-accordion onChange=(action "changeAccordion") as |acc|}}
-      {{#acc.item value=preview as |aitem|}}
-        {{#aitem.title}}
-          <section class="dashboard-container__title thirdeye-link-secondary">Preview alert {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
-            <span class="pull-right"><i class="glyphicon glyphicon-menu-{{if toggleCollapsed "down" "up"}}"></i></span>
-          </section>
-        {{/aitem.title}}
-        {{#aitem.body}}
-          {{#alert-details
-            isPreviewMode=true
-            alertYaml=detectionYaml
-            alertId=alertId
-            dataIsCurrent=alertDataIsCurrent
-          }}
-            {{yield}}
-          {{/alert-details}}
-        {{/aitem.body}}
-      {{/acc.item}}
-    {{/bs-accordion}}
-  </div>
-
-  <div class="col-xs-12">
-    <hr/>
-  </div>
-  {{#if showSettings}}
-    <div class="col-xs-12">
-      <legend class="te-form__section-title">{{alertSettingsTitle}}</legend>
-    </div>
-    <div class="col-xs-4">
-      {{#if isEditMode}}
-        <label class="te-label te-label--small">
-          {{#if noExistingSubscriptionGroup}}
-            Create a subscription group for this alert
-          {{else}}
-            Edit a subscription group already subscribed to this alert
-          {{/if}}</label>
-      {{else}}
-        <label class="te-label te-label--small">Add this alert to a subscription group</label>
-      {{/if}}
-
-      {{!--  subscription group --}}
-      {{#power-select
-        placeholder="Create a new subscription group"
-        options=subscriptionGroupNamesDisplay
-        selected=groupName
-        searchField="name"
-        onchange=(action 'onSubscriptionGroupSelectionAction')
-        as |groupName|
-      }}
-        {{groupName.name}} ({{groupName.id}})
-      {{/power-select}}
-    </div>
-    <div class="col-xs-12">
-      <label for="select-metric" class="control-label te-label te-label--taller required">Can't find your team? Contact
-        <a class="thirdeye-link-secondary" target="_blank" href="mailto:ask_thirdeye@linkedin.com">ask_thirdeye@linkedin.com</a>
-      </label>
-      <div class="pull-right">
-        {{bs-button
-          defaultText="Reset"
-          type="outline-primary"
-          buttonType="reset"
-          onClick=(action "resetYAML" "subscription")
-          class="te-button te-button--link"
-        }}
-        {{bs-button
-          defaultText="View Docs & Examples"
-          type="outline-primary"
-          buttonType="link"
-          onClick=(action "triggerDoc" "subscription")
-          class="te-button te-button--cancel"
-        }}
-      </div>
-    </div>
-    <div class="col-xs-12">
-      {{!-- subscription settings editor --}}
-      {{ember-ace
-        lines=25
-        value=currentSubscriptionYaml
-        update=(action "onEditingSubscriptionYamlAction")
-        mode="ace/mode/yaml"
-      }}
-    </div>
-    <div class="col-xs-12">
-      {{#if isSubscriptionMsg}}
-        <div class="yaml-editor-msg">
-          <p class="yaml-editor-msg__icon"><i class="yaml-editor-msg__icon--error glyphicon glyphicon-remove-circle"></i>Error in the subscription yaml</p>
-          <p>Message: {{subscriptionMsg}}</p>
-        </div>
-      {{/if}}
-    </div>
-  {{/if}}
-</fieldset>
-
-<fieldset class="te-form__section-submit">
-  {{#if isEditMode}}
-    {{bs-button
-        defaultText=subGroupButtonText
-        type="primary"
-        buttonType="submit"
-        disabled=disableSubGroupSave
-        onClick=(action "submitSubscriptionGroup")
-        class="te-button te-button--submit"
-      }}
-  {{else}}
-    {{bs-button
-      defaultText="Create alert"
-      type="primary"
-      buttonType="submit"
-      disabled=disableYamlSave
-      onClick=(action "createAlertYamlAction")
-      class="te-button te-button--submit"
-    }}
-  {{/if}}
-</fieldset>
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/alerts/index/route.js b/thirdeye/thirdeye-frontend/app/pods/manage/alerts/index/route.js
index 06a731f..a6c2133 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/alerts/index/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/alerts/index/route.js
@@ -3,7 +3,8 @@ import Route from '@ember/routing/route';
 import fetch from 'fetch';
 import { get, getWithDefault } from '@ember/object';
 import { inject as service } from '@ember/service';
-import { checkStatus, formatYamlFilter } from 'thirdeye-frontend/utils/utils';
+import { checkStatus } from 'thirdeye-frontend/utils/utils';
+import { formatYamlFilter} from 'thirdeye-frontend/utils/yaml-tools';
 import { powerSort } from 'thirdeye-frontend/utils/manage-alert-utils';
 import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
 
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js b/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
index 84c2ee5..751a136 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
@@ -8,9 +8,7 @@ import RSVP from 'rsvp';
 import { set, get } from '@ember/object';
 import { inject as service } from '@ember/service';
 import { toastOptions } from 'thirdeye-frontend/utils/constants';
-import { formatYamlFilter } from 'thirdeye-frontend/utils/utils';
-import yamljs from 'yamljs';
-import jsyaml from 'js-yaml';
+import { formatYamlFilter, redundantParse } from 'thirdeye-frontend/utils/yaml-tools';
 import moment from 'moment';
 import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
 
@@ -41,14 +39,9 @@ export default Route.extend(AuthenticatedRouteMixin, {
         if (detection_json.yaml) {
           let detectionInfo;
           try {
-            detectionInfo = yamljs.parse(detection_json.yaml);
+            detectionInfo = redundantParse(detection_json.yaml);
           } catch (error) {
-            try {
-              // use jsyaml package to try parsing again, since yamljs doesn't parse some edge cases
-              detectionInfo = jsyaml.safeLoad(detection_json.yaml);
-            } catch (error) {
-              throw new Error('yaml parsing error');
-            }
+            throw new Error('yaml parsing error');
           }
           const lastDetection = new Date(detection_json.lastTimestamp);
           Object.assign(detectionInfo, {
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
index ac319e9..4c871d0 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
@@ -7,11 +7,10 @@ import Route from '@ember/routing/route';
 import RSVP from 'rsvp';
 import { set, get } from '@ember/object';
 import { inject as service } from '@ember/service';
-import yamljs from 'yamljs';
-import jsyaml from 'js-yaml';
 import moment from 'moment';
-import { yamlAlertSettings, toastOptions } from 'thirdeye-frontend/utils/constants';
-import { formatYamlFilter } from 'thirdeye-frontend/utils/utils';
+import { toastOptions } from 'thirdeye-frontend/utils/constants';
+import { defaultSubscriptionYaml } from 'thirdeye-frontend/utils/yaml-tools';
+import { formatYamlFilter, redundantParse } from 'thirdeye-frontend/utils/yaml-tools';
 import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
 
 const CREATE_GROUP_TEXT = 'Create a new subscription group';
@@ -44,14 +43,9 @@ export default Route.extend(AuthenticatedRouteMixin, {
         if (detection_json.yaml) {
           let detectionInfo;
           try {
-            detectionInfo = yamljs.parse(detection_json.yaml);
+            detectionInfo = redundantParse(detection_json.yaml);
           } catch (error) {
-            try {
-              // use jsyaml package to try parsing again, since yamljs doesn't parse some edge cases
-              detectionInfo = jsyaml.safeLoad(detection_json.yaml);
-            } catch (error) {
-              throw new Error('yaml parsing error');
-            }
+            throw new Error('yaml parsing error');
           }
           const lastDetection = new Date(detection_json.lastTimestamp);
           Object.assign(detectionInfo, {
@@ -144,7 +138,7 @@ export default Route.extend(AuthenticatedRouteMixin, {
     const createGroup = {
       name: CREATE_GROUP_TEXT,
       id: 'n/a',
-      yaml: yamlAlertSettings
+      yaml: defaultSubscriptionYaml
     };
     const moddedArray = [createGroup];
     const subscriptionGroups = this.get('store')
@@ -164,7 +158,7 @@ export default Route.extend(AuthenticatedRouteMixin, {
       { groupName: 'Other Groups', options: subscriptionGroups}
     ];
 
-    let subscriptionYaml = yamlAlertSettings;
+    let subscriptionYaml = defaultSubscriptionYaml;
     let groupName = createGroup;
     let subscriptionGroupId = createGroup.id;
 
diff --git a/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/route.js b/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/route.js
index 24d3a02..ad35170 100644
--- a/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/route.js
@@ -9,7 +9,7 @@ import Route from '@ember/routing/route';
 import { selfServeApiOnboard } from 'thirdeye-frontend/utils/api/self-serve';
 import { postProps, checkStatus } from 'thirdeye-frontend/utils/utils';
 import { inject as service } from '@ember/service';
-import { yamlAlertSettings } from 'thirdeye-frontend/utils/constants';
+import { defaultSubscriptionYaml } from 'thirdeye-frontend/utils/yaml-tools';
 
 const CREATE_GROUP_TEXT = 'Create a new subscription group';
 
@@ -39,7 +39,7 @@ export default Route.extend({
     const createGroup = {
       name: CREATE_GROUP_TEXT,
       id: 'n/a',
-      yaml: yamlAlertSettings
+      yaml: defaultSubscriptionYaml
     };
     const moddedArray = [createGroup];
     const subscriptionGroups = this.get('store')
@@ -54,7 +54,7 @@ export default Route.extend({
         };
       });
     const subscriptionGroupNamesDisplay = [...moddedArray, ...subscriptionGroups];
-    let subscriptionYaml = yamlAlertSettings;
+    let subscriptionYaml = defaultSubscriptionYaml;
     let groupName = createGroup;
     if (subscriptionGroupNamesDisplay && Array.isArray(subscriptionGroupNamesDisplay) && subscriptionGroupNamesDisplay.length > 0) {
       const firstGroup = subscriptionGroupNamesDisplay[0];
diff --git a/thirdeye/thirdeye-frontend/app/styles/app.scss b/thirdeye/thirdeye-frontend/app/styles/app.scss
index 96ee0a6..1fdd6c9 100644
--- a/thirdeye/thirdeye-frontend/app/styles/app.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/app.scss
@@ -66,7 +66,8 @@ body {
 @import 'components/timeseries-chart';
 @import 'components/range-pill-selectors';
 @import 'components/shared/common-tabs';
-@import 'components/yaml-editor';
+@import 'components/detection-yaml';
+@import 'components/subscription-yaml';
 
 // Pod Pages
 @import 'ember-power-select/themes/bootstrap';
diff --git a/thirdeye/thirdeye-frontend/app/styles/components/yaml-editor.scss b/thirdeye/thirdeye-frontend/app/styles/components/detection-yaml.scss
similarity index 87%
copy from thirdeye/thirdeye-frontend/app/styles/components/yaml-editor.scss
copy to thirdeye/thirdeye-frontend/app/styles/components/detection-yaml.scss
index 9268e2e..9da2d9e 100644
--- a/thirdeye/thirdeye-frontend/app/styles/components/yaml-editor.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/components/detection-yaml.scss
@@ -1,5 +1,5 @@
-.yaml-editor {
-  .yaml-editor-msg {
+.detection-yaml {
+  .detection-yaml-msg {
     color: #e75858;
 
     &__icon {
diff --git a/thirdeye/thirdeye-frontend/app/styles/components/yaml-editor.scss b/thirdeye/thirdeye-frontend/app/styles/components/subscription-yaml.scss
similarity index 85%
rename from thirdeye/thirdeye-frontend/app/styles/components/yaml-editor.scss
rename to thirdeye/thirdeye-frontend/app/styles/components/subscription-yaml.scss
index 9268e2e..97e347f 100644
--- a/thirdeye/thirdeye-frontend/app/styles/components/yaml-editor.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/components/subscription-yaml.scss
@@ -1,5 +1,5 @@
-.yaml-editor {
-  .yaml-editor-msg {
+.subscription-yaml {
+  .subscription-yaml-msg {
     color: #e75858;
 
     &__icon {
diff --git a/thirdeye/thirdeye-frontend/app/utils/anomaly.js b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
index 861dc7c..3b7a457 100644
--- a/thirdeye/thirdeye-frontend/app/utils/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
@@ -4,9 +4,9 @@ import _ from 'lodash';
 import {
   checkStatus,
   postProps,
-  postYamlProps,
   getProps
 } from 'thirdeye-frontend/utils/utils';
+import { postYamlProps } from 'thirdeye-frontend/utils/yaml-tools';
 import fetch from 'fetch';
 import {
   anomalyApiUrls,
diff --git a/thirdeye/thirdeye-frontend/app/utils/constants.js b/thirdeye/thirdeye-frontend/app/utils/constants.js
index 4fd9836..261ac9b 100644
--- a/thirdeye/thirdeye-frontend/app/utils/constants.js
+++ b/thirdeye/thirdeye-frontend/app/utils/constants.js
@@ -5,85 +5,11 @@ export const deleteProps = {
   credentials: 'include'
 };
 
-export const yamlAlertProps = `# Below is a sample template. You may refer the documentation for more examples and update the fields accordingly.
-
-# Give a name for this anomaly detection pipeline (should be unique).
-detectionName: name_of_the_detection
-
-# Tell the alert recipients what it means if this alert is fired.
-description: If this alert fires then it means so-and-so and check so-and-so for irregularities
-
-# The metric you want to do anomaly detection on. You may type a few characters and look ahead (ctrl + space) to auto-fill.
-metric: metric_name
-
-# The dataset or UMP table name to which the metric belongs. Look ahead should auto populate this field.
-dataset: dataset_name
-
-rules:                            # Can configure multiple rules with "OR" relationship.
-- detection:
-    - name: detection_rule_1
-      type: ALGORITHM             # Configure the detection type here. See doc for more details.
-      params:                     # The parameters for this rule. Different rules have different params.
-        configuration:
-          bucketPeriod: P1D       # Use PT1H for hourly and PT5M for minute level (ingraph metrics) data.
-          pValueThreshold: 0.05   # Higher value means more sensitive to small changes.
-          mlConfig: true          # The machine learning auto config to select and maintain the configuration with the best performance.
-  filter:                         # Filter out anomalies detected by rules to reduce noise.
-    - name: filter_rule_1
-      type: PERCENTAGE_CHANGE_FILTER
-      params:
-        pattern: UP_OR_DOWN       # Other patterns: "UP","DOWN".
-        threshold: 0.05           # Filter out all changes less than 5% compared to baseline.
-`;
-
-export const yamlAlertSettings = `# Below is a sample subscription group template. You may refer the documentation and update accordingly.
-
-# The name of the subscription group. You may choose an existing or a provide a new subscription group name
-subscriptionGroupName: test_subscription_group
-
-# Every alert in ThirdEye is attached to an application. Please specify the registered application name here. You may request for a new application by dropping an email to ask_thirdeye
-application: thirdeye-internal
-
-# The default notification type. See additional settings for details and exploring other notification types like dimension alerter.
-type: DEFAULT_ALERTER_PIPELINE
-
-# List of detection names that you want to subscribe. Copy-paste the detection name from the above anomaly detection config here.
-subscribedDetections:
-  - name_of_the_detection_above
-
-# Configure how you want to be alerted. You can receive the standard ThirdEye email alert (recommended)
-# or for advanced critical use-cases setup Iris alert by referring to the documentation
-alertSchemes:
-- type: EMAIL
-recipients:
- to:
-  - "me@company.com"          # Specify alert recipient email address here
-  - "me@company.com"
- cc:
-  - "cc_email@company.com"
-fromAddress: thirdeye-dev@linkedin.com
-
-# The frequency at which you want to be notified. Typically you want to be notified immediately after
-# an anomaly is detected. The below cron runs every 5 minutes. Use online cronmaker to compute this.
-cron: "0 0/5 * 1/1 * ? *"
-
-# Enable or disable notification of alert
-active: true
-
-# The below links will appear in the email alerts. This will help alert recipients to quickly refer and act on.
-referenceLinks:
-  "Oncall Runbook": "http://go/oncall"
-  "Thirdeye FAQs": "http://go/thirdeyefaqs"
-
-`;
-
 export const toastOptions = {
   timeOut: 10000
 };
 
 export default {
   deleteProps,
-  yamlAlertProps,
-  yamlAlertSettings,
   toastOptions
 };
diff --git a/thirdeye/thirdeye-frontend/app/utils/utils.js b/thirdeye/thirdeye-frontend/app/utils/utils.js
index f53aa08..127bcc1 100644
--- a/thirdeye/thirdeye-frontend/app/utils/utils.js
+++ b/thirdeye/thirdeye-frontend/app/utils/utils.js
@@ -2,6 +2,7 @@ import d3 from 'd3';
 import { isNone } from '@ember/utils';
 import moment from 'moment';
 import { splitFilterFragment, toFilterMap, makeTime } from 'thirdeye-frontend/utils/rca-utils';
+import _ from 'lodash';
 
 /**
  * The Promise returned from fetch() won't reject on HTTP error status even if the response is an HTTP 404 or 500.
@@ -164,19 +165,6 @@ export function getProps() {
 }
 
 /**
- * Preps post object for Yaml payload
- * @param {string} text to post
- * @returns {Object}
- */
-export function postYamlProps(postData) {
-  return {
-    method: 'post',
-    body: postData,
-    headers: { 'content-type': 'text/plain' }
-  };
-}
-
-/**
  * Format conversion helper
  * @param {String} dateStr - date to convert
  */
@@ -185,6 +173,27 @@ export function toIso(dateStr) {
 }
 
 /**
+ * Replace all Infinity and NaN with value from second time series
+ * @param {Array} series1 - time series to modify
+ * @param {Array} series2 - time series to get replacement values from
+ */
+export function replaceNonFiniteWithCurrent(series1, series2) {
+  if (_.isEmpty(series2)) {
+    return stripNonFiniteValues(series1);
+  }
+  for (let i = 0; i < series1.length; i++) {
+    if (!isFinite(series1[i]) || !series1[i]) {
+      let newValue = null;
+      if (i < series2.length) {
+        newValue = isFinite(series2[i]) ? series2[i] : null;
+      }
+      series1[i] = newValue;
+    }
+  }
+  return series1;
+}
+
+/**
  * Replace all Infinity and NaN with null in array of numbers
  * @param {Array} timeSeries - time series to modify
  */
@@ -194,40 +203,6 @@ export function stripNonFiniteValues(timeSeries) {
   });
 }
 
-/**
- * The yaml filters formatter. Convert filters in the yaml file in to a legacy filters string
- * For example, filters = {
- *   "country": ["us", "cn"],
- *   "browser": ["chrome"]
- * }
- * will be convert into "country=us;country=cn;browser=chrome"
- *
- * @method _formatYamlFilter
- * @param {Map} filters multimap of filters
- * @return {String} - formatted filters string
- */
-export function formatYamlFilter(filters) {
-  if (filters){
-    const filterStrings = [];
-    Object.keys(filters).forEach(
-      function(filterKey) {
-        const filter = filters[filterKey];
-        if (filter && Array.isArray(filter)) {
-          filter.forEach(
-            function (filterValue) {
-              filterStrings.push(filterKey + '=' + filterValue);
-            }
-          );
-        } else {
-          filterStrings.push(filterKey + '=' + filter);
-        }
-      }
-    );
-    return filterStrings.join(';');
-  }
-  return '';
-}
-
 export default {
   checkStatus,
   humanizeFloat,
@@ -237,8 +212,7 @@ export default {
   parseProps,
   postProps,
   toIso,
+  replaceNonFiniteWithCurrent,
   stripNonFiniteValues,
-  postYamlProps,
-  formatYamlFilter,
   getProps
 };
diff --git a/thirdeye/thirdeye-frontend/app/utils/yaml-tools.js b/thirdeye/thirdeye-frontend/app/utils/yaml-tools.js
new file mode 100644
index 0000000..f78549f
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/utils/yaml-tools.js
@@ -0,0 +1,347 @@
+import yamljs from 'yamljs';
+import jsyaml from 'js-yaml';
+
+export const defaultDetectionYaml = `# Below is a sample template. You may refer the documentation for more examples and update the fields accordingly.
+# Give a name for this anomaly detection pipeline (should be unique).
+detectionName: name_of_the_detection
+# Tell the alert recipients what it means if this alert is fired.
+description: If this alert fires then it means so-and-so and check so-and-so for irregularities
+# The metric you want to do anomaly detection on. You may type a few characters and look ahead (ctrl + space) to auto-fill.
+metric: metric_name
+# The dataset or UMP table name to which the metric belongs. Look ahead should auto populate this field.
+dataset: dataset_name
+rules:                            # Can configure multiple rules with "OR" relationship.
+- detection:
+    - name: detection_rule_1
+      type: ALGORITHM             # Configure the detection type here. See doc for more details.
+      params:                     # The parameters for this rule. Different rules have different params.
+        configuration:
+          bucketPeriod: P1D       # Use PT1H for hourly and PT5M for minute level (ingraph metrics) data.
+          pValueThreshold: 0.05   # Higher value means more sensitive to small changes.
+          mlConfig: true          # The machine learning auto config to select and maintain the configuration with the best performance.
+  filter:                         # Filter out anomalies detected by rules to reduce noise.
+    - name: filter_rule_1
+      type: PERCENTAGE_CHANGE_FILTER
+      params:
+        pattern: UP_OR_DOWN       # Other patterns: "UP","DOWN".
+        threshold: 0.05           # Filter out all changes less than 5% compared to baseline.
+`;
+
+export const defaultSubscriptionYaml = `# Below is a sample subscription group template. You may refer the documentation and update accordingly.
+# The name of the subscription group. You may choose an existing or a provide a new subscription group name
+subscriptionGroupName: test_subscription_group
+# Every alert in ThirdEye is attached to an application. Please specify the registered application name here. You may request for a new application by dropping an email to ask_thirdeye
+application: thirdeye-internal
+# List of detection names that you want to subscribe. Copy-paste the detection name from the above anomaly detection config here.
+subscribedDetections:
+  - name_of_the_detection_above
+# Configure how you want to be alerted. You can receive the standard ThirdEye email alert (recommended)
+# or for advanced critical use-cases setup Iris alert by referring to the documentation
+alertSchemes:
+- type: EMAIL
+recipients:
+ to:
+  - "me@company.com"          # Specify alert recipient email address here
+  - "me@company.com"
+ cc:
+  - "cc_email@company.com"
+fromAddress: thirdeye-dev@linkedin.com
+# Enable or disable notification of alert
+active: true
+# The below links will appear in the email alerts. This will help alert recipients to quickly refer and act on.
+referenceLinks:
+  "Oncall Runbook": "http://go/oncall"
+  "Thirdeye FAQs": "http://go/thirdeyefaqs"
+`;
+
+/**
+ * Recursive helper function for merging two javascript objects
+ * @method mergeContents
+ * @param {Object} OR {Array} fieldObject taken from field
+ * @param {Object} OR {Array} yamlObject piece of parsed yaml
+ */
+function mergeContents(fieldObject, yamlObject) {
+  let output = yamlObject; // default to just return original value
+  if (Array.isArray(fieldObject) && Array.isArray(yamlObject)) {
+    // for arrays, we keep old and add new
+    output = [...new Set([...fieldObject, ...yamlObject])];
+  } else if (typeof fieldObject === 'string' && typeof yamlObject === 'string') {
+    // for strings, we replace old with new
+    output = fieldObject;
+  } else if (typeof fieldObject === 'object' && typeof yamlObject === 'object') {
+    // if both are objects, match keys and recurse
+    output = {};
+    const fieldKeys = Object.keys(fieldObject);
+    const yamlKeys = yamlObject ? Object.keys(yamlObject) : [];
+    if (yamlKeys.length > 0) {
+      fieldKeys.forEach(key => {
+        if (yamlKeys.includes(key)) {
+          // merge yaml contents and passed in contents
+          output[key] = mergeContents(fieldObject[key], yamlObject[key]);
+        }
+      });
+    }
+  }
+  return output;
+}
+
+/**
+ * helper for parsing yaml with backup
+ * @method redundantParse
+ * @param {String} yamlString (the yaml string to parse)
+ */
+export function redundantParse(yamlString) {
+  let yamlAsObject = {};
+  try {
+    yamlAsObject = yamljs.parse(yamlString);
+  } catch(error) {
+    try {
+      // use jsyaml package to try parsing again, since yamljs doesn't parse some edge cases
+      yamlAsObject = jsyaml.safeLoad(yamlString);
+    } catch (error) {
+      throw new Error("yaml parsing error");
+    }
+  }
+  return yamlAsObject;
+}
+
+/**
+ * This function will update yaml according to form input arrays and strings from the user
+ * @method fieldsToYaml
+ * @param {Object} fields (assumes keys are same as target fields in yaml)
+ * @param {String} yamlString (the yaml string to be updated)
+ * @return {String} - updatedYaml
+ */
+export function fieldsToYaml(fields, yamlString) {
+  // parse yamlString to JSON
+  let yamlAsObject = {};
+  try {
+    yamlAsObject = redundantParse(yamlString);
+  }
+  catch(err) {
+    return null;
+  }
+  const fieldKeys = Object.keys(fields);
+  const yamlKeys = yamlAsObject ? Object.keys(yamlAsObject) : [];
+  if (yamlKeys.length > 0) {
+    yamlKeys.forEach(key => {
+      if (fieldKeys.includes(key) && fields[key]) {
+        // merge yaml contents and passed in contents, then map stringified version
+        yamlAsObject[key] = mergeContents(fields[key], yamlAsObject[key]);
+      }
+    });
+  }
+  // insert comments back into yaml.
+  yamlString = addBackComments(yamlAsObject, yamlString);
+  return yamlString;
+}
+
+/**
+ * Recursive helper function for getting values from yaml
+ * @method traverseYaml
+ * @param {String} field (field to look for in Yaml)
+ * @param {Object} yamlObject (the yaml object to search in)
+ * @param {String} type (the type of value expected)
+ */
+function traverseYaml(field, yamlObject, type) {
+  const keys = Object.keys(yamlObject);
+  let value = null;
+  for (let i = 0; i < keys.length; i++) {
+    if (keys[i] === field) {
+      value = (typeof yamlObject[keys[i]] === type) ? yamlObject[keys[i]] : null;
+    } else if (yamlObject[keys[i]] && typeof yamlObject[keys[i]] === 'object') {
+      value = traverseYaml(field, yamlObject[keys[i]]);
+    }
+    if (value) {
+      break;
+    }
+  }
+  return value;
+}
+
+
+
+/**
+ * This function will return the value of a given field in a yaml string if it exists
+ * @method getValueFromYaml
+ * @param {String} field (field to look for in Yaml)
+ * @param {String} yamlString (the yaml string to search in)
+ * @param {String} type (the type of value expected)
+ */
+export function getValueFromYaml(field, yamlString, type) {
+  // parse yamlString to JSON
+  let yamlAsObject = {};
+  try {
+    yamlAsObject = redundantParse(yamlString);
+  }
+  catch(err){
+    return null;
+  }
+  return yamlAsObject ? traverseYaml(field, yamlAsObject, type) : null;
+}
+
+/**
+ * This function will preserve comments and blank lines and do a "best effort" to keep in the correct place
+ * @method addBackComments
+ * @param {Object} fields (assumes keys are same as target fields in yaml)
+ * @param {String} yamlString (the yaml string to be updated)
+ * @return {String} - updatedYaml
+ */
+export function addBackComments(yamlAsObject, yamlString) {
+  // stringify new yaml
+  let newYaml;
+  let yamlKeys = Object.keys(yamlAsObject); // this should not change since we only update fields
+  try {
+    newYaml = yamljs.stringify(yamlAsObject, 20, 1);
+  }
+  catch(err) {
+    return null;
+  }
+  // split yamls into array of strings, line by line
+  let oldLines = yamlString.split('\n');
+  let newLines = newYaml.split('\n');
+  const mergedLines = [];
+  // iterate through all keys of yaml, so we can replace all lines allocated to a key
+  let newYamlIndex = 0;
+  let oldYamlIndex = 0; // this will point at the lines of old yaml
+  let keyIndex = 0; // this is for iterating through the keys
+  // iterate through split new yaml
+  while (newYamlIndex < newLines.length && oldYamlIndex < oldLines.length && keyIndex < yamlKeys.length) {
+    let nextKey = ((keyIndex + 1) < yamlKeys.length) ? keyIndex + 1 : keyIndex;
+    if (newLines[newYamlIndex].includes(`${yamlKeys[keyIndex]}:`)) {
+      let foundStart = false;
+      // loop through old yaml string and add comments and/or blank lines as needed
+      while (!foundStart && newYamlIndex < newLines.length && oldYamlIndex < oldLines.length) {
+        if (oldLines[oldYamlIndex].includes(`${yamlKeys[keyIndex]}:`) && oldLines[oldYamlIndex].charAt(0) !== '#') {
+          // start of same key
+          foundStart = true;
+          let foundEndOld = false;
+          let foundEndNew = false;
+          while (!foundEndOld && !foundEndNew && oldYamlIndex < oldLines.length && newYamlIndex < newLines.length) {
+            if (oldLines[oldYamlIndex].charAt(0) !== '#') {
+              const thisLine = oldLines[oldYamlIndex].split('#');
+              if (thisLine.length > 1) {
+                // there are comments, keep them
+                mergedLines.push(newLines[newYamlIndex] + ' #' + thisLine.slice(1).join('#'));
+                newYamlIndex++;
+                oldYamlIndex++;
+              } else {
+                // replace whole line
+                mergedLines.push(newLines[newYamlIndex]);
+                newYamlIndex++;
+                oldYamlIndex++;
+              }
+            } else {
+              // whole line is comment
+              mergedLines.push(oldLines[oldYamlIndex]);
+              oldYamlIndex++;
+            }
+            if (oldYamlIndex < oldLines.length && oldLines[oldYamlIndex].includes(`${yamlKeys[nextKey]}:`) && oldLines[oldYamlIndex].charAt(0) !== '#') {
+              foundEndOld = true;
+            }
+            if (newYamlIndex < newLines.length && newLines[newYamlIndex].includes(`${yamlKeys[nextKey]}:`)) {
+              foundEndNew = true;
+            }
+          }
+          // take care of remaining lines
+          if (!foundEndOld) {
+            while (!foundEndOld && oldYamlIndex < oldLines.length) {
+              if (oldLines[oldYamlIndex].charAt(0) === '#') {
+                // additional comment line, add it
+                mergedLines.push(oldLines[oldYamlIndex]);
+                oldYamlIndex++;
+              } else {
+                const thisLine = oldLines[oldYamlIndex].split('#');
+                if (thisLine.length > 1) {
+                  mergedLines.push(['', thisLine.slice(1).join('#')].join('#'));
+                }
+                oldYamlIndex++;
+              }
+              if (oldYamlIndex < oldLines.length && oldLines[oldYamlIndex].includes(`${yamlKeys[nextKey]}:`) && oldLines[oldYamlIndex].charAt(0) !== '#') {
+                foundEndOld = true;
+              }
+            }
+          }
+          if (!foundEndNew) {
+            while (!foundEndNew && newYamlIndex < newLines.length) {
+              // for new, we just add the lines until the next key or end of array
+              mergedLines.push(newLines[newYamlIndex]);
+              newYamlIndex++;
+              if (newYamlIndex < newLines.length && newLines[newYamlIndex].includes(`${yamlKeys[nextKey]}:`)) {
+                foundEndNew = true;
+              }
+            }
+          }
+        } else {
+          // blank line or comment - add to mergedLines
+          mergedLines.push(oldLines[oldYamlIndex]);
+          oldYamlIndex++;
+        }
+      }
+      keyIndex++;
+    } else {
+      // totally new line, insert to merged result
+      mergedLines.push(newLines[newYamlIndex]);
+      newYamlIndex++;
+    }
+  }
+  yamlString = mergedLines.join('\n');
+  return yamlString;
+}
+
+/**
+ * The yaml filters formatter. Convert filters in the yaml file in to a legacy filters string
+ * For example, filters = {
+ *   "country": ["us", "cn"],
+ *   "browser": ["chrome"]
+ * }
+ * will be convert into "country=us;country=cn;browser=chrome"
+ *
+ * @method formatYamlFilter
+ * @param {Map} filters multimap of filters
+ * @return {String} - formatted filters string
+ */
+export function formatYamlFilter(filters) {
+  if (filters){
+    const filterStrings = [];
+    Object.keys(filters).forEach(
+      function(filterKey) {
+        const filter = filters[filterKey];
+        if (filter && Array.isArray(filter)) {
+          filter.forEach(
+            function (filterValue) {
+              filterStrings.push(filterKey + '=' + filterValue);
+            }
+          );
+        } else {
+          filterStrings.push(filterKey + '=' + filter);
+        }
+      }
+    );
+    return filterStrings.join(';');
+  }
+  return '';
+}
+
+/**
+ * Preps post object for Yaml payload
+ * @param {string} text to post
+ * @returns {Object}
+ */
+export function postYamlProps(postData) {
+  return {
+    method: 'post',
+    body: postData,
+    headers: { 'content-type': 'text/plain' }
+  };
+}
+
+export default {
+  defaultDetectionYaml,
+  defaultSubscriptionYaml,
+  formatYamlFilter,
+  getValueFromYaml,
+  fieldsToYaml,
+  postYamlProps,
+  redundantParse
+};
diff --git a/thirdeye/thirdeye-frontend/tests/acceptance/self-serve-alert-tuning-test.js b/thirdeye/thirdeye-frontend/tests/acceptance/self-serve-alert-tuning-test.js
index 02377e7..1220632 100644
--- a/thirdeye/thirdeye-frontend/tests/acceptance/self-serve-alert-tuning-test.js
+++ b/thirdeye/thirdeye-frontend/tests/acceptance/self-serve-alert-tuning-test.js
@@ -22,7 +22,7 @@ module('Acceptance | tune alert settings', function(hooks) {
     'Subscription Group'
   ];
 
-  test(`visiting alert page to test self-serve tuning flow`, async (assert) => {
+  test(`check whether alerts page shows correct number of alerts`, async (assert) => {
     server.createList('alert', 5);
     await visit(`/manage/alerts`);
 
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/yaml-editor/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/detection-yaml/component-test.js
similarity index 65%
rename from thirdeye/thirdeye-frontend/tests/integration/pods/components/yaml-editor/component-test.js
rename to thirdeye/thirdeye-frontend/tests/integration/pods/components/detection-yaml/component-test.js
index 26e9492..04f76f2 100644
--- a/thirdeye/thirdeye-frontend/tests/integration/pods/components/yaml-editor/component-test.js
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/detection-yaml/component-test.js
@@ -1,17 +1,14 @@
 /**
- * Integration tests for the yaml-editor component.
+ * Integration tests for the detection-yaml component.
  * @property {number} alertId - the alert id
  * @property {Array} subscriptionGroupNames - an array of objects containing subscription group information
  * @property {boolean} isEditMode - to activate the edit mode
  * @property {boolean} showSettings - to show the subscriber groups yaml editor
  * @property {string} detectionYaml - the detection yaml to display
  * @example
-   {{yaml-editor
+   {{detection-yaml
      alertId=1
-     subscriptionGroupId=1
      isEditMode=true
-     showSettings=true
-     subscriptionGroupNames=subscriptionGroupNames
      detectionYaml=detectionYaml
    }}
  * @author hjackson
@@ -22,7 +19,7 @@ import { setupRenderingTest } from 'ember-qunit';
 import { render } from '@ember/test-helpers';
 import hbs from 'htmlbars-inline-precompile';
 
-module('Integration | Component | yaml-editor', function(hooks) {
+module('Integration | Component | detection-yaml', function(hooks) {
   setupRenderingTest(hooks);
   const testText = 'default yaml';
 
@@ -34,11 +31,9 @@ module('Integration | Component | yaml-editor', function(hooks) {
     });
 
     await render(hbs`
-      {{yaml-editor
-        alertId=alertId
+      {{detection-yaml
         isEditMode=true
-        showSettings=true
-        subscriptionGroupNames=subscriptionGroups
+        alertId=alertId
         detectionYaml=detectionYaml
       }}
     `);
@@ -49,25 +44,11 @@ module('Integration | Component | yaml-editor', function(hooks) {
 
     const defaultText = '# Below is a sample template. You may refer the documentation for more examples and update the fields accordingly.';
     await render(hbs`
-      {{yaml-editor
+      {{detection-yaml
         isEditMode=false
-        showSettings=true
       }}
     `);
 
     assert.ok(this.$('.ace_line')[0].children[0].textContent === defaultText);
   });
-
-  test(`displays default yaml file of subscription group in create mode`, async function(assert) {
-
-    const defaultText = '# Below is a sample subscription group template. You may refer the documentation and update accordingly.';
-    await render(hbs`
-      {{yaml-editor
-        isEditMode=false
-        showSettings=true
-      }}
-    `);
-
-    assert.ok(this.$('.ace_line')[30].children[0].textContent === defaultText);
-  });
 });
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/subscription-yaml/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/subscription-yaml/component-test.js
new file mode 100644
index 0000000..ebe4a20
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/subscription-yaml/component-test.js
@@ -0,0 +1,57 @@
+/**
+ * Integration tests for the subscription-yaml component.
+ * @property {Array} subscriptionGroupNames - an array of objects containing subscription group information
+ * @property {boolean} isEditMode - to activate the edit mode
+ * @example
+ {{subscription-yaml
+   isEditMode=true
+   subscriptionYaml=subscriptionYaml
+   subscriptionMsg=""
+   subscriptionGroupNamesDisplay=subscriptionGroupNamesDisplay
+   groupName=groupName
+   createGroup=createGroup
+   }}
+ * @author hjackson
+ */
+
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | subscription-yaml', function(hooks) {
+  setupRenderingTest(hooks);
+  const testText = 'default yaml';
+
+  test(`displays yaml of subscription group in edit mode`, async function(assert) {
+    this.setProperties({
+      subscriptionGroups: [],
+      subscriptionYaml: testText
+    });
+
+    await render(hbs`
+      {{subscription-yaml
+        isEditMode=true
+        subscriptionYaml=subscriptionYaml
+        subscriptionMsg=""
+        subscriptionGroupNamesDisplay=subscriptionGroupNamesDisplay
+        groupName=groupName
+        createGroup=createGroup
+      }}
+    `);
+    assert.ok(this.$('.ace_line')[0].innerText === testText);
+  });
+
+  test(`displays default subscription group yaml in create mode`, async function(assert) {
+
+    const defaultText = '# Below is a sample subscription group template. You may refer the documentation and update accordingly.';
+    await render(hbs`
+      {{subscription-yaml
+        isEditMode=false
+        showSettings=true
+      }}
+    `);
+
+    assert.ok(this.$('.ace_line')[0].children[0].textContent === defaultText);
+  });
+});


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org