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 008bb5c  [TE] frontend - harleyjj/alert-overview - implements alert 
overview for new pipeline using alert-details component (#3843)
008bb5c is described below

commit 008bb5c1909f592c2dfdd9138bae7dc8d965c824
Author: Harley Jackson <[email protected]>
AuthorDate: Fri Feb 15 10:06:19 2019 -0800

    [TE] frontend - harleyjj/alert-overview - implements alert overview for new 
pipeline using alert-details component (#3843)
    
    1. Moves some API calls to utils and removes some unused code
    2. Sets default in the preview to be 1 week
    3. Adds logic to the alert-details component to make it deal with 
alert-overview and preview
    4. Corrects spelling of 'getFormatedDuration'
    5. Adjusts stats boxes to new anomaly response style
    6. Implements manage/explore-new route using alert-details component
    7. Annotates preview as being in Beta
---
 .../app/pods/components/alert-details/component.js | 138 +++++++++++----------
 .../app/pods/components/alert-details/template.hbs |  41 +++---
 .../app/pods/components/yaml-editor/template.hbs   |   5 +-
 .../app/pods/manage/explore-new/controller.js      |   9 ++
 .../app/pods/manage/explore-new/route.js           | 120 ++++++++++++++++++
 .../pods/manage/{yaml => explore-new}/template.hbs |  18 +--
 .../app/pods/manage/yaml/template.hbs              |   5 +
 .../app/pods/services/api/anomalies/service.js     |   4 +-
 thirdeye/thirdeye-frontend/app/router.js           |   1 +
 thirdeye/thirdeye-frontend/app/utils/anomaly.js    |  72 +++++------
 .../thirdeye-frontend/app/utils/api/anomaly.js     |  33 +++--
 .../app/utils/manage-alert-utils.js                |   4 +-
 thirdeye/thirdeye-frontend/app/utils/utils.js      |  21 +++-
 .../tests/unit/utils/anomaly-test.js               |   6 +-
 14 files changed, 318 insertions(+), 159 deletions(-)

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 9b5d3d4..d2deb78 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
@@ -18,7 +18,7 @@ import { computed, observer, set, get, getProperties } from 
'@ember/object';
 import { later } from '@ember/runloop';
 import { checkStatus, humanizeFloat } from 'thirdeye-frontend/utils/utils';
 import { colorMapping, toColor, makeTime } from 
'thirdeye-frontend/utils/rca-utils';
-import { getFormatedDuration } from 'thirdeye-frontend/utils/anomaly';
+import { getYamlPreviewAnomalies, getAnomaliesByAlertId, getFormattedDuration 
} from 'thirdeye-frontend/utils/anomaly';
 import { inject as service } from '@ember/service';
 import { task } from 'ember-concurrency';
 import floatToPercent from 'thirdeye-frontend/utils/float-to-percent';
@@ -40,7 +40,7 @@ export default Component.extend({
   anomalyMapping: {},
   timeseries: null,
   isLoading: false,
-  analysisRange: [moment().subtract(1, 'month').startOf('hour').valueOf(), 
moment().startOf('hour').valueOf()],
+  analysisRange: [moment().subtract(1, 'week').startOf('hour').valueOf(), 
moment().startOf('hour').valueOf()],
   isPendingData: false,
   colorMapping: colorMapping,
   zoom: {
@@ -58,7 +58,7 @@ export default Component.extend({
   compareMode: 'wo1w',
   baseline: null,
   errorAnomalies: null,
-  showPreview: false,
+  showDetails: false,
   componentId: 'timeseries-chart',
   anomalies: null,
   baselineOptions: [
@@ -75,28 +75,21 @@ export default Component.extend({
   sortColumnStartUp: false,
   sortColumnChangeUp: false,
   sortColumnNumberUp: true,
-  sortColumnResolutionUp: false,
+  sortColumnFeedbackUp: false,
   selectedSortMode: '',
   selectedBaseline: 'wo1w',
   pageSize: 10,
   currentPage: 1,
+  isPreviewMode: false,
+  alertId: null,
 
 
-  // alertYamlChanged: observer('alertYaml', 'analysisRange', 
'disableYamlSave', async function() {
-  //   set(this, 'isPendingData', true);
-  //   // deal with the change
-  //   const alertYaml = get(this, 'alertYaml');
-  //   if(alertYaml) {
-  //     try {
-  //       const anomalyMapping = await 
this.get('_getAnomalyMapping').perform(alertYaml);
-  //       set(this, 'isPendingData', false);
-  //       set(this, 'anomalyMapping', anomalyMapping);
-  //       debugger;
-  //     } catch (error) {
-  //       throw new Error(`Unable to retrieve anomaly data. ${error}`);
-  //     }
-  //   }
-  // }),
+  updateVisuals: observer('analysisRange', function() {
+    const isPreviewMode = get(this, 'isPreviewMode');
+    if(!isPreviewMode) {
+      this._fetchAnomalies();
+    }
+  }),
 
   /**
    * Table pagination: number of pages to display
@@ -294,9 +287,10 @@ export default Component.extend({
       anomalies.forEach(a => {
         let tableRow = {
           number: i,
+          anomalyId: a.id,
           start: a.startTime,
           startDateStr: this._formatAnomaly(a),
-          durationStr: getFormatedDuration(a.startTime, a.endTime),
+          durationStr: getFormattedDuration(a.startTime, a.endTime),
           shownCurrent: humanizeFloat(a.avgCurrentVal),
           shownBaseline: humanizeFloat(a.avgBaselineVal),
           change: ((a.avgCurrentVal/a.avgBaselineVal - 1.0) * 100.0),
@@ -316,7 +310,10 @@ export default Component.extend({
   stats: computed(
     'anomalyMapping',
     function() {
-      const anomalyMapping = get(this, 'anomalyMapping');
+      const {
+        anomalyMapping,
+        isPreviewMode
+      } = this.getProperties('anomalyMapping', 'isPreviewMode');
       if (!anomalyMapping) {
         return {};
       }
@@ -325,19 +322,18 @@ export default Component.extend({
       let falsePositives = 0;
       let falseNegatives = 0;
       let numberOfAnomalies = 0;
-
       Object.keys(anomalyMapping).forEach(function (key) {
         anomalyMapping[key].forEach(function (attr) {
           numberOfAnomalies++;
-          if(attr.anomaly && attr.anomaly.data) {
-            const classification = attr.anomaly.data.classification;
-            if (classification != 'NONE') {
+          if(attr.anomaly && attr.anomaly.statusClassification) {
+            const classification = attr.anomaly.statusClassification;
+            if (classification !== 'NONE') {
               respondedAnomaliesCount++;
-              if (classification == 'TRUE_POSITIVE') {
+              if (classification === 'TRUE_POSITIVE') {
                 truePositives++;
-              } else if (classification == 'FALSE_POSITIVE') {
+              } else if (classification === 'FALSE_POSITIVE') {
                 falsePositives++;
-              } else if (classification == 'FALSE_NEGATIVE') {
+              } else if (classification === 'FALSE_NEGATIVE') {
                 falseNegatives++;
               }
             }
@@ -348,7 +344,7 @@ export default Component.extend({
       const totalAnomaliesCount = numberOfAnomalies;
       const totalAlertsDescription = 'Total number of anomalies that occured 
over a period of time';
       let statsArray = [];
-      if(respondedAnomaliesCount > 0) {
+      if(!isPreviewMode) {
         const responseRate = respondedAnomaliesCount / totalAnomaliesCount;
         const precision = truePositives / (truePositives + falsePositives);
         const recall = truePositives / (truePositives + falseNegatives);
@@ -401,49 +397,46 @@ export default Component.extend({
 
   _getAnomalyMapping: task (function * (alertYaml) {//TODO: need to add to 
anomaly util - LH
     let anomalyMapping = {};
-    const analysisRange = get(this, 'analysisRange');
-    const postProps = {
-      method: 'POST',
-      body: alertYaml,
-      headers: { 'content-type': 'text/plain' }
-    };
-    const notifications = get(this, 'notifications');
+    const {
+      analysisRange,
+      notifications,
+      isPreviewMode,
+      alertId,
+      metricUrn,
+    } = this.getProperties('analysisRange', 'notifications', 'isPreviewMode', 
'alertId', 'metricUrn');
 
     //detection alert fetch
     const start = analysisRange[0];
     const end = analysisRange[1];
-    const alertUrl = 
`/yaml/preview?start=${start}&end=${end}&tuningStart=0&tuningEnd=0`;
     let anomalies;
+    let applicationAnomalies;
     try {
-      const alert_result = yield fetch(alertUrl, postProps);
-      const alert_status  = get(alert_result, 'status');
-      const applicationAnomalies = yield alert_result.json();
-
-      if (alert_status !== 200 && applicationAnomalies.message) {
-        notifications.error(applicationAnomalies.message, 'Preview alert 
failed');
-      } else {
-        anomalies = applicationAnomalies.anomalies;
+      if(isPreviewMode){
+        applicationAnomalies = yield getYamlPreviewAnomalies(alertYaml, start, 
end);
         set(this, 'metricUrn', 
Object.keys(applicationAnomalies.diagnostics['0'])[0]);
+        anomalies = applicationAnomalies.anomalies;
+      } else {
+        applicationAnomalies = yield getAnomaliesByAlertId(alertId, start, 
end);
+        anomalies = applicationAnomalies;
+      }
 
-        if (anomalies && anomalies.length > 0) {
-          const humanizedObject = {
-            queryDuration: '1m',
-            queryStart: start,
-            queryEnd: end
-          };
-          this.set('applicationAnomalies', anomalies);
-
-          anomalies.forEach(anomaly => {
-            const metricName = anomaly.metric;
-            //Grouping the anomalies of the same metric name
-            if (!anomalyMapping[metricName]) {
-              anomalyMapping[metricName] = [];
-            }
+      if (anomalies && anomalies.length > 0) {
+        const humanizedObject = {
+          queryDuration: '1m',
+          queryStart: start,
+          queryEnd: end
+        };
 
-            // Group anomalies by metricName and function name (alertName) and 
wrap it into the Humanized cache. Each `anomaly` is the raw data from ember 
data cache.
-            
anomalyMapping[metricName].push(this.get('anomaliesApiService').getHumanizedEntity(anomaly,
 humanizedObject));
-          });
-        }
+        anomalies.forEach(anomaly => {
+          const metricName = anomaly.metric;
+          //Grouping the anomalies of the same metric name
+          if (!anomalyMapping[metricName]) {
+            anomalyMapping[metricName] = [];
+          }
+
+          // Group anomalies by metricName and function name (alertName) and 
wrap it into the Humanized cache. Each `anomaly` is the raw data from ember 
data cache.
+          
anomalyMapping[metricName].push(this.get('anomaliesApiService').getHumanizedEntity(anomaly,
 humanizedObject));
+        });
       }
     } catch (error) {
       notifications.error('Preview alert failed', error);
@@ -455,6 +448,15 @@ export default Component.extend({
     };
   }).drop(),
 
+  init() {
+    this._super(...arguments);
+    const isPreviewMode = get(this, 'isPreviewMode');
+    if (!isPreviewMode) {
+      set(this, 'analysisRange', [moment().subtract(1, 
'month').startOf('hour').valueOf(), moment().startOf('hour').valueOf()])
+      this._fetchAnomalies();
+    }
+  },
+
   didRender(){
     this._super(...arguments);
 
@@ -544,7 +546,11 @@ export default Component.extend({
           anomalies: results.anomalies,
           isLoading: false
         });
-        this._fetchTimeseries();
+        if (get(this, 'metricUrn')) {
+          this._fetchTimeseries();
+        } else {
+          throw new Error('Unable to get MetricUrn from response');
+        }
       });
     } catch (error) {
       set(this, 'isLoading', false);
@@ -605,8 +611,8 @@ export default Component.extend({
     getPreview() {
       this.setProperties({
         isLoading: true,
-        showPreview: true,
-        disableYamlSave: true
+        showDetails: true,
+        dataIsCurrent: true
       });
       this._fetchAnomalies();
     },
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 1a323b3..7b7ebcb 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
@@ -1,21 +1,24 @@
 <div class="alert-details">
 
   <div class="pull-right">
-    {{bs-button
-      defaultText=(if showPreview "Refresh Preview" "Preview Alert")
-      disabled=disablePreviewButton
-      type="outline-primary"
-      buttonType="refresh"
-      onClick=(action "getPreview")
-      class="te-button te-button--cancel"
-    }}
+    {{#if isPreviewMode}}
+      {{bs-button
+        defaultText=(if showDetails "Refresh Preview" "Preview Alert")
+        disabled=disablePreviewButton
+        type="outline-primary"
+        buttonType="refresh"
+        onClick=(action "getPreview")
+        class="te-button te-button--cancel"
+      }}
+    {{/if}}
   </div>
   {{#unless errorAnomalies}}
-    {{#if showPreview}}
+    {{#if showDetails}}
       {{#if isLoading}}
         {{ember-spinner scale=0.5 rotate=10 speed='1.1' 
color='#3498DB'}}Please wait while we compile the data.
       {{else}}
-        {{#if disableYamlSave}}
+        {{#if dataIsCurrent}}
+         <div class="te-content-block">
           {{range-pill-selectors
             title="Showing"
             uiDateFormat=pill.uiDateFormat
@@ -26,7 +29,7 @@
             predefinedRanges=pill.predefinedRanges
             selectAction=(action "onRangeSelection")
           }}
-          <div class="te-horizontal-cards">
+          <div class="te-horizontal-cards te-content-block">
             <h4 class="te-self-serve__block-title">
               <label for="select-dimension" class="control-label te-label">
                 Alert Performance
@@ -89,9 +92,9 @@
                       </th>
                       {{#if notPreview}}
                         <th class="te-anomaly-table__cell-head">
-                          <a class="te-anomaly-table__cell-link" {{action 
"toggleSortDirection" "resolution"}}>
-                            Resolution
-                            <i class="te-anomaly-table__icon glyphicon {{if 
sortColumnResolutionUp "glyphicon-menu-up" "glyphicon-menu-down"}}"></i>
+                          <a class="te-anomaly-table__cell-link" {{action 
"toggleSortDirection" "feedback"}}>
+                            Feedback
+                            <i class="te-anomaly-table__icon glyphicon {{if 
sortColumnFeedbackUp "glyphicon-menu-up" "glyphicon-menu-down"}}"></i>
                           </a>
                         </th>
                         <th class="te-anomaly-table__cell-head"></th>
@@ -106,12 +109,12 @@
                        <td class="te-anomaly-table__cell">
                         <ul class="te-anomaly-table__list 
te-anomaly-table__list--left">
                           <li class="te-anomaly-table__list-item 
te-anomaly-table__list-item--stronger">
-                            {{#if notPreview}}
+                            {{#if isPreviewMode}}
+                              {{anomaly.startDateStr}}
+                            {{else}}
                               <a target="_blank" 
class="te-anomaly-table__link" 
href="/app/#/rootcause?anomalyId={{anomaly.anomalyId}}">
                                 {{anomaly.startDateStr}}
                               </a>
-                            {{else}}
-                              {{anomaly.startDateStr}}
                             {{/if}}
                           </li>
                           <li class="te-anomaly-table__list-item 
te-anomaly-table__list-item--lighter">{{anomaly.durationStr}}</li>
@@ -219,8 +222,10 @@
                 </ul>
               </nav>
             {{/if}}
-
           {{/if}}
+
+
+              </div>
         {{else}}
         <div class="yaml-editor-msg">Alert configuration has changed.</div>
         {{/if}}
diff --git 
a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs 
b/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
index 1a8a5f6..3f46c86 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
@@ -49,14 +49,15 @@
     {{#bs-accordion onChange=(action "changeAccordion") as |acc|}}
       {{#acc.item value=preview as |aitem|}}
         {{#aitem.title}}
-          <section class="dashboard-container__title">Preview alert {{if 
toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
+          <section class="dashboard-container__title">Preview alert [Beta] 
{{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=alertYaml
-            disableYamlSave=disableYamlSave
+            dataIsCurrent=disableYamlSave
           }}
             {{yield}}
           {{/alert-details}}
diff --git 
a/thirdeye/thirdeye-frontend/app/pods/manage/explore-new/controller.js 
b/thirdeye/thirdeye-frontend/app/pods/manage/explore-new/controller.js
new file mode 100644
index 0000000..96ef85f
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore-new/controller.js
@@ -0,0 +1,9 @@
+/**
+ * Controller for Alert Landing and Details Page
+ * @module manage/alert
+ * @exports manage/alert
+ */
+import Controller from '@ember/controller';
+
+export default Controller.extend({
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore-new/route.js 
b/thirdeye/thirdeye-frontend/app/pods/manage/explore-new/route.js
new file mode 100644
index 0000000..b554704
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore-new/route.js
@@ -0,0 +1,120 @@
+/**
+ * Handles the 'alert details' route.
+ * @module manage/alert/route
+ * @exports manage alert model
+ */
+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';
+
+export default Route.extend({
+  notifications: service('toast'),
+
+  async model(params) {
+    const alertId = params.alert_id;
+    const postProps = {
+      method: 'get',
+      headers: { 'content-type': 'application/json' }
+    };
+    const notifications = get(this, 'notifications');
+
+    //detection alert fetch
+    const alertUrl = `/detection/${alertId}`;
+    try {
+      const alert_result = await fetch(alertUrl, postProps);
+      const alert_status  = get(alert_result, 'status');
+      const alert_json = await alert_result.json();
+      if (alert_status !== 200) {
+        notifications.error('Retrieval of alert yaml failed.', 'Error');
+      } else {
+        if (alert_json.yaml) {
+          const yaml = yamljs.parse(alert_json.yaml);
+          Object.assign(yaml, {
+            application: alert_json.name,
+            isActive: alert_json.active,
+            createdBy: alert_json.createdBy,
+            updatedBy: alert_json.updatedBy,
+            functionName: yaml.detectionName,
+            collection: yaml.dataset,
+            type: alert_json.pipelineType,
+            exploreDimensions: alert_json.dimensions,
+            filters: this._formatYamlFilter(yaml.filters),
+            dimensionExploration: 
this._formatYamlFilter(yaml.dimensionExploration),
+            yaml: alert_json.yaml
+          });
+          this.setProperties({
+            detectionYaml: yaml,
+            alertId: alertId,
+            metricUrn: alert_json.properties.nested[0].nestedMetricUrns[0]
+          });
+        }
+      }
+    } catch (error) {
+      notifications.error('Retrieving alert yaml failed.', error);
+    }
+
+    //subscription group fetch
+    const subUrl = `/detection/subscription-groups/${alertId}`;//dropdown of 
subscription groups
+    try {
+      const settings_result = await fetch(subUrl, postProps);
+      const settings_status  = get(settings_result, 'status');
+      const settings_json = await settings_result.json();
+      if (settings_status !== 200) {
+        notifications.error('Retrieving subscription groups failed.', 'Error');
+      } else {
+        set(this, 'subscriptionGroups', settings_json);
+      }
+    } catch (error) {
+      notifications.error('Retrieving subscription groups failed.', error);
+    }
+
+    const subscriptionGroupYamlDisplay = typeof get(this, 
'subscriptionGroups') === 'object' && get(this, 'subscriptionGroups').length > 
0 ? get(this, 'subscriptionGroups')[0].yaml : get(this, 
'subscriptionGroups').yaml;
+    const subscriptionGroupId = typeof get(this, 'subscriptionGroups') === 
'object' && get(this, 'subscriptionGroups').length > 0 ? get(this, 
'subscriptionGroups')[0].id : get(this, 'subscriptionGroups').id;
+    const metricUrn = get(this, 'metricUrn');
+    return RSVP.hash({
+      alertId,
+      subscriptionGroupId,
+      alertData: get(this, 'detectionYaml'),
+      detectionYaml: get(this, 'detectionYaml').yaml,
+      subscriptionGroups: get(this, 'subscriptionGroups'),
+      subscriptionGroupYamlDisplay,
+      metricUrn: get(this, 'metricUrn')
+    });
+  },
+
+  /**
+   * 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
+   */
+  _formatYamlFilter(filters) {
+    if (filters){
+      const filterStrings = [];
+      Object.keys(filters).forEach(
+        function(filterKey) {
+          const filter = filters[filterKey];
+          if (typeof filter === 'object') {
+            filter.forEach(
+              function (filterValue) {
+                filterStrings.push(filterKey + '=' + filterValue);
+              }
+            );
+          } else {
+            filterStrings.push(filterKey + '=' + filter);
+          }
+        }
+      );
+      return filterStrings.join(';');
+    }
+    return '';
+  }
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs 
b/thirdeye/thirdeye-frontend/app/pods/manage/explore-new/template.hbs
similarity index 71%
copy from thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
copy to thirdeye/thirdeye-frontend/app/pods/manage/explore-new/template.hbs
index b32d9cf..e86a26e 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore-new/template.hbs
@@ -4,6 +4,11 @@
       alertData=model.alertData
       isLoadError=isLoadError
     }}
+      <div class="te-search-results__cta">
+        {{#link-to "manage.yaml" model.alertId}}
+          <button class="te-button te-button--outline">Edit</button>
+        {{/link-to}}
+      </div>
     {{/self-serve-alert-yaml-details}}
   </div>
 </section>
@@ -17,14 +22,13 @@
         <p class="te-alert-page-pending__text">{{errorText}}</p>
       </div>
     {{else}}
-      {{yaml-editor
-        alertId=model.alertId
-        subscriptionGroupId=model.subscriptionGroupId
-        isEditMode=true
-        showSettings=true
-        subscriptionGroupNames=model.subscriptionGroups
+      {{alert-details
+        isPreviewMode=false
         alertYaml=model.detectionYaml
-        detectionSettingsYaml=model.subscriptionGroupYamlDisplay
+        showDetails=true
+        dataIsCurrent=true
+        alertId=model.alertId
+        metricUrn=model.metricUrn
       }}
     {{/if}}
   </div>
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs 
b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
index b32d9cf..999f91d 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
@@ -4,6 +4,11 @@
       alertData=model.alertData
       isLoadError=isLoadError
     }}
+    <div class="te-search-results__cta">
+      {{#link-to "manage.explore-new" model.alertId}}
+        <button class="te-button te-button--outline">Alert Overview</button>
+      {{/link-to}}
+    </div>
     {{/self-serve-alert-yaml-details}}
   </div>
 </section>
diff --git 
a/thirdeye/thirdeye-frontend/app/pods/services/api/anomalies/service.js 
b/thirdeye/thirdeye-frontend/app/pods/services/api/anomalies/service.js
index 2305a60..585ee42 100644
--- a/thirdeye/thirdeye-frontend/app/pods/services/api/anomalies/service.js
+++ b/thirdeye/thirdeye-frontend/app/pods/services/api/anomalies/service.js
@@ -5,7 +5,7 @@ import EmberObject, { computed, get } from '@ember/object';
 import { humanizeFloat, humanizeChange } from 'thirdeye-frontend/utils/utils';
 import floatToPercent from 'thirdeye-frontend/utils/float-to-percent';
 import {
-  getFormatedDuration,
+  getFormattedDuration,
   anomalyResponseObj
 } from 'thirdeye-frontend/utils/anomaly';
 
@@ -23,7 +23,7 @@ const HumanizedAnomaly = EmberObject.extend({// ex: 
record.humanizedChangeDispla
     return humanizeChange(get(this, '_changeFloat'));
   }),
   duration: computed('anomaly.{start,end}', function() {
-    return getFormatedDuration(get(this, 'anomaly.start'), get(this, 
'anomaly.end'));
+    return getFormattedDuration(get(this, 'anomaly.start'), get(this, 
'anomaly.end'));
   }),
   current: computed('anomaly.current', function() {
     return humanizeFloat(get(this, 'anomaly.current'));
diff --git a/thirdeye/thirdeye-frontend/app/router.js 
b/thirdeye/thirdeye-frontend/app/router.js
index b84491b..59b15b8 100644
--- a/thirdeye/thirdeye-frontend/app/router.js
+++ b/thirdeye/thirdeye-frontend/app/router.js
@@ -27,6 +27,7 @@ Router.map(function() {
     this.route('alerts', function() {
       this.route('performance');
     });
+    this.route('explore-new', { path: 'explore-new/:alert_id'});
     this.route('yaml', { path: 'yaml/:alert_id' });
   });
 
diff --git a/thirdeye/thirdeye-frontend/app/utils/anomaly.js 
b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
index 84e0935..98032c4 100644
--- a/thirdeye/thirdeye-frontend/app/utils/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
@@ -3,10 +3,15 @@ import moment from 'moment';
 import _ from 'lodash';
 import {
   checkStatus,
-  postProps
+  postProps,
+  postYamlProps
 } from 'thirdeye-frontend/utils/utils';
 import fetch from 'fetch';
-import { anomalyApiUrls } from 'thirdeye-frontend/utils/api/anomaly';
+import {
+  anomalyApiUrls,
+  getAnomaliesForYamlPreviewUrl,
+  getAnomaliesByAlertIdUrl
+} from 'thirdeye-frontend/utils/api/anomaly';
 
 /**
  * Response type options for anomalies.
@@ -59,53 +64,42 @@ export function updateAnomalyFeedback(anomalyId, 
feedbackType) {
 }
 
 /**
- * Fetch a single anomaly record for verification
- * @method verifyAnomalyFeedback
- * @param {Number} anomalyId
+ * Post Yaml to preview anomalies detected with given configuration
+ * @method getYamlPreviewAnomalies
+ * @param {String} yamlString - the alert configuration in Yaml
+ * @param {Number} startTime - start time of analysis range
+ * @param {Number} endTime - end time of analysis range
  * @return {Ember.RSVP.Promise}
  */
-export function verifyAnomalyFeedback(anomalyId) {
-  const url = `${anomalyApiUrls.getAnomalyDataUrl()}${anomalyId}`;
-  return fetch(url).then(checkStatus);
+export function getYamlPreviewAnomalies(yamlString, startTime, endTime) {
+  const url = getAnomaliesForYamlPreviewUrl(startTime, endTime);
+  return fetch(url, postYamlProps(yamlString)).then((res) => checkStatus(res, 
'post', false, true));
 }
 
 /**
- * Fetch all anomalies by application name and start time
- * @method getAnomaliesByAppName
- * @param {String} appName - the application name
- * @param {Number} startStamp - the anomaly iso start time
+ * Get anomalies for a given detection id over a specified time range
+ * @method getAnomaliesByAlertId
+ * @param {Number} alertId - the alert id aka detection config id
+ * @param {Number} startTime - start time of analysis range
+ * @param {Number} endTime - end time of analysis range
  * @return {Ember.RSVP.Promise}
- * @example: 
/userdashboard/anomalies?application=someAppName&start=1508472800000
  */
-export function getAnomaliesByAppName(appName, startTime) {
-  if (!appName) {
-    return Promise.reject(new Error('appName param is required.'));
-  }
-  if (!startTime) {
-    return Promise.reject(new Error('startTime param is required.'));
-  }
-  const url = anomalyApiUrls.getAnomaliesByAppNameUrl(appName, startTime);
-  return fetch(url).then(checkStatus).catch(() => {});
+export function getAnomaliesByAlertId(alertId, startTime, endTime) {
+  const url = getAnomaliesByAlertIdUrl(alertId, startTime, endTime);
+  return fetch(url).then(checkStatus);
 }
 
 /**
- * Fetch the application performance details
- * @method getPerformanceByAppNameUrl
- * @param {String} appName - the application name
- * @param {Number} startStamp - the anomaly iso start time
- * @param {Number} endStamp - the anomaly iso end time
+ * Fetch a single anomaly record for verification
+ * @method verifyAnomalyFeedback
+ * @param {Number} anomalyId
  * @return {Ember.RSVP.Promise}
- * @example: 
/detection-job/eval/application/lms-ads?start=2017-09-01T00:00:00Z&end=2018-04-01T00:00:00Z
  */
-export function getPerformanceByAppNameUrl(appName, startTime, endTime) {
-  if (!appName || !startTime || !endTime) {
-    return Promise.reject(new Error('param required.'));
-  }
-  const url = anomalyApiUrls.getPerformanceByAppNameUrl(appName, startTime, 
endTime) ;
-  return fetch(url).then(checkStatus).catch(() => {});
+export function verifyAnomalyFeedback(anomalyId) {
+  const url = `${anomalyApiUrls.getAnomalyDataUrl()}${anomalyId}`;
+  return fetch(url).then(checkStatus);
 }
 
-
 /**
  * Formats anomaly duration property for display on the table
  * @param {Number} anomalyStart - the anomaly start time
@@ -113,7 +107,7 @@ export function getPerformanceByAppNameUrl(appName, 
startTime, endTime) {
  * @returns {String} the fomatted duration time
  * @example getFormatDuration(1491804013000, 1491890413000) // yields => 'Apr 
9, 11:00 PM'
  */
-export function getFormatedDuration(anomalyStart, anomalyEnd) {
+export function getFormattedDuration(anomalyStart, anomalyEnd) {
   const startMoment = moment(anomalyStart);
   const endMoment = moment(anomalyEnd);
   const anomalyDuration = moment.duration(endMoment.diff(startMoment));
@@ -144,9 +138,9 @@ export default {
   anomalyResponseObj,
   anomalyResponseMap,
   updateAnomalyFeedback,
-  getFormatedDuration,
+  getFormattedDuration,
   verifyAnomalyFeedback,
   pluralizeTime,
-  getAnomaliesByAppName,
-  getPerformanceByAppNameUrl
+  getYamlPreviewAnomalies,
+  getAnomaliesByAlertId
 };
diff --git a/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js 
b/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
index 5b77887..77e7905 100644
--- a/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
@@ -10,32 +10,31 @@ export function getAnomalyDataUrl(startStamp = 0, endStamp 
= 0) {
 }
 
 /**
- * Returns the application performance details
-  * @param {String} appName - the application name
-  * @param {Number} startStamp - the anomaly iso start time
-  * @param {Number} endStamp - the anomaly iso end time
- * @returns {String} the complete Anomalies By AppName url
- * @example getPerformanceByAppNameUrl('someAppName', 1508472800000) // yields 
=> 
/detection-job/eval/application/lms-ads?start=2017-09-01T00:00:00Z&end=2018-04-01T00:00:00Z
+ * Returns url for getting the Anomalies for a given YAML configuration so 
user can preview
+ * @param {Number} startTime - the anomaly start time
+ * @param {Number} endTime - the anomaly end time
+ * @returns {String} the complete yaml/preview url
+ * @example getAnomaliesForYamlPreview(1508472700000, 1508472800000) // yields 
=> /yaml/preview?start=1508472700000&end=1508472800000&tuningStart=0&tuningEnd=0
  */
-export function getPerformanceByAppNameUrl(appName, startTime, endTime) {
-  return 
`/detection-job/eval/application/${appName}?start=${startTime}&end=${endTime}`;
+export function getAnomaliesForYamlPreviewUrl(startTime, endTime) {
+  return 
`/yaml/preview?start=${startTime}&end=${endTime}&tuningStart=0&tuningEnd=0`;
 }
 
 /**
- * Returns the Anomalies By AppName url
-  * @param {String} appName - the application name
- * @param {Number} startStamp - the anomaly start time
- * @returns {String} the complete Anomalies By AppName url
- * @example getAnomaliesByAppNameUrl('someAppName', 1508472800000) // yields 
=> /userdashboard/anomalies?application=someAppName&start=1508472800000
+ * Returns the url for getting Anomalies for a given detection id over the 
specified time range
+ * @param {Number} alertId - the alert id aka detection config id
+ * @param {Number} startTime - the anomaly start time
+ * @param {Number} endTime - the anomaly end time
+ * @example getAnomaliesByAlertId(99999999,1508472700000, 1508472800000) // 
yields => /detection/99999999/anomalies?start=1508472700000&end=1508472800000
  */
-export function getAnomaliesByAppNameUrl(appName, startTime) {
-  return `/userdashboard/anomalies?application=${appName}&start=${startTime}`;
+export function getAnomaliesByAlertIdUrl(alertId, startTime, endTime) {
+  return `/detection/${alertId}/anomalies?start=${startTime}&end=${endTime}`;
 }
 
 export const anomalyApiUrls = {
   getAnomalyDataUrl,
-  getAnomaliesByAppNameUrl,
-  getPerformanceByAppNameUrl
+  getAnomaliesForYamlPreviewUrl,
+  getAnomaliesByAlertIdUrl
 };
 
 export default {
diff --git a/thirdeye/thirdeye-frontend/app/utils/manage-alert-utils.js 
b/thirdeye/thirdeye-frontend/app/utils/manage-alert-utils.js
index 64f892a..c32f178 100644
--- a/thirdeye/thirdeye-frontend/app/utils/manage-alert-utils.js
+++ b/thirdeye/thirdeye-frontend/app/utils/manage-alert-utils.js
@@ -2,7 +2,7 @@ import moment from 'moment';
 import { isPresent, isBlank } from "@ember/utils";
 import { getWithDefault } from '@ember/object';
 import { buildDateEod } from 'thirdeye-frontend/utils/utils';
-import { getFormatedDuration } from 'thirdeye-frontend/utils/anomaly';
+import { getFormattedDuration } from 'thirdeye-frontend/utils/anomaly';
 import floatToPercent from 'thirdeye-frontend/utils/float-to-percent';
 
 /**
@@ -105,7 +105,7 @@ export function enhanceAnomalies(rawAnomalies, 
severityScores) {
       shownChangeRate: changeRate,
       isUserReported: anomaly.anomalyResultSource === 'USER_LABELED_ANOMALY',
       startDateStr: moment(anomaly.anomalyStart).format('MMM D, hh:mm A'),
-      durationStr: getFormatedDuration(anomaly.anomalyStart, 
anomaly.anomalyEnd),
+      durationStr: getFormattedDuration(anomaly.anomalyStart, 
anomaly.anomalyEnd),
       severityScore: score && !isNaN(score) ? score.toFixed(2) : 'N/A',
       shownCurrent: Number(anomaly.current) > 0 ? anomaly.current : 'N/A',
       shownBaseline: Number(anomaly.baseline) > 0 ? anomaly.baseline : 'N/A',
diff --git a/thirdeye/thirdeye-frontend/app/utils/utils.js 
b/thirdeye/thirdeye-frontend/app/utils/utils.js
index 8a0bbae..9be8a95 100644
--- a/thirdeye/thirdeye-frontend/app/utils/utils.js
+++ b/thirdeye/thirdeye-frontend/app/utils/utils.js
@@ -9,9 +9,10 @@ import { splitFilterFragment, toFilterMap, makeTime } from 
'thirdeye-frontend/ut
  * @param {Object} response - the response object from a fetch call
  * @param {String} mode - the request type: 'post', 'get'
  * @param {Boolean} recoverBlank - whether silent failure is allowed
+ * @param {Boolean} isYamlPreview - handle response differently for post of 
yaml preview
  * @return {Object} either json-formatted payload or error object
  */
-export function checkStatus(response, mode = 'get', recoverBlank = false) {
+export function checkStatus(response, mode = 'get', recoverBlank = false, 
isYamlPreview = false) {
   if (response.status === 401) {
     // We want to throw a 401 error up the error substate(s) to handle it
     throw new Error('401');
@@ -20,7 +21,7 @@ export function checkStatus(response, mode = 'get', 
recoverBlank = false) {
     if (response.status === 204) {
       return '';
     } else {
-      return (mode === 'get') ? response.json() : 
JSON.parse(JSON.stringify(response));
+      return (mode === 'get' || isYamlPreview) ? response.json() : 
JSON.parse(JSON.stringify(response));
     }
   } else {
     const error = new Error(response.statusText);
@@ -147,6 +148,19 @@ export function postProps(postData) {
 }
 
 /**
+ * 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
  */
@@ -162,5 +176,6 @@ export default {
   makeFilterString,
   parseProps,
   postProps,
-  toIso
+  toIso,
+  postYamlProps
 };
diff --git a/thirdeye/thirdeye-frontend/tests/unit/utils/anomaly-test.js 
b/thirdeye/thirdeye-frontend/tests/unit/utils/anomaly-test.js
index 587f8da..b550b8d 100644
--- a/thirdeye/thirdeye-frontend/tests/unit/utils/anomaly-test.js
+++ b/thirdeye/thirdeye-frontend/tests/unit/utils/anomaly-test.js
@@ -1,13 +1,13 @@
 import { module, test } from 'qunit';
-import { getFormatedDuration } from 'thirdeye-frontend/utils/anomaly';
+import { getFormattedDuration } from 'thirdeye-frontend/utils/anomaly';
 
 module('Unit | Utility | anomaly', function() {
   test('it returns a formated duration time correctly', function(assert) {
-    assert.equal(getFormatedDuration(1491804013000, 1491890413000), '1 day', 
'it returns correct duration ok');
+    assert.equal(getFormattedDuration(1491804013000, 1491890413000), '1 day', 
'it returns correct duration ok');
   });
 
   test('it returns a non-zero duration time correctly', function(assert) {
     //We want to display only non-zero duration values in our table
-    assert.equal(getFormatedDuration(1491804013000, 1491804013000), '', 'it 
filters out non-zero duration ok');
+    assert.equal(getFormattedDuration(1491804013000, 1491804013000), '', 'it 
filters out non-zero duration ok');
   });
 });


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

Reply via email to