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

[incubator-pinot] branch master updated: [TE] frontend - harleyjj/detection-health - UI for model performance (#4413)

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 e041ec5  [TE] frontend - harleyjj/detection-health - UI for model performance (#4413)
e041ec5 is described below

commit e041ec5439735de81d3f57af91db5b380b9f8062
Author: Harley Jackson <hj...@linkedin.com>
AuthorDate: Wed Jul 10 14:19:37 2019 -0700

    [TE] frontend - harleyjj/detection-health - UI for model performance (#4413)
    
    Implements new detection health component for alert overview and preview with alert id
    Corrects some comment and naming errors
    Adds login redirect to manage/explore and manage/yaml
    Removes [Beta] from preview label
---
 .../app/pods/components/alert-details/component.js |   2 +
 .../app/pods/components/alert-details/template.hbs |  80 +++++++--
 .../pods/components/detection-health/component.js  | 194 +++++++++++++++++++++
 .../pods/components/detection-health/template.hbs  |  84 +++++++++
 .../app/pods/components/stats-cards/component.js   |  42 +++--
 .../app/pods/components/stats-cards/template.hbs   |  61 +++++--
 .../app/pods/components/yaml-editor/template.hbs   |   2 +-
 .../app/pods/manage/explore/route.js               |  37 +++-
 .../app/pods/manage/explore/template.hbs           |   1 +
 .../app/pods/manage/yaml/route.js                  |  46 ++++-
 .../app/pods/manage/yaml/template.hbs              |   3 +-
 .../app/pods/self-serve/create-alert/template.hbs  |   2 +-
 .../app/styles/shared/_styles.scss                 | 118 +++++++++++++
 13 files changed, 608 insertions(+), 64 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 4047113..528db3d 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
@@ -93,6 +93,7 @@ export default Component.extend({
   granularity: null,
   alertYaml: null,
   dimensionExploration: null,
+  detectionHealth: null, // result of call to detection/health/{id}, passed in by parent
 
 
 
@@ -615,6 +616,7 @@ export default Component.extend({
     }
   ),
 
+
   /**
    * Date types to display in the pills
    * @type {Object[]} - array of objects, each of which represents each date pill
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 66f9a03..a2f1ea0 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
@@ -54,26 +54,72 @@
             predefinedRanges=pill.predefinedRanges
             selectAction=(action "onRangeSelection")
           }}
-          <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
-                <span>
-                  <i class="glyphicon glyphicon-question-sign"></i>
-                  {{#tooltip-on-element class="te-tooltip"}}
-                    All estimated performance numbers are based on reviewed anomalies.
-                  {{/tooltip-on-element}}
-                </span>
-              </label>
-            </h4>
-            {{!-- Alert anomaly stats cards --}}
-            {{stats-cards stats=stats}}
 
-            {{#if repRunStatus}}
-              <p class="te-self-serve__block-subtext te-self-serve__block-subtext--normal">Replay in progress. Please check back later...</p>
-            {{/if}}
+
+          {{#if alertId}}
+
+          <div class="te-content-block__performance-health-wrapper">
+            <!-- Anomalies, Response Rate, Precision, Recall -->
+            <div class="te-horizontal-cards te-content-block__alert-performance">
+              <h4 class="te-self-serve__block-title">
+                <label for="select-dimension" class="control-label te-label">
+                  Alert Performance
+                  <span>
+                    <i class="glyphicon glyphicon-question-sign"></i>
+                    {{#tooltip-on-element class="te-tooltip"}}
+                      All estimated performance numbers are based on reviewed anomalies.
+                    {{/tooltip-on-element}}
+                  </span>
+                </label>
+              </h4>
+              {{!-- Alert anomaly stats cards --}}
+              {{stats-cards stats=stats}}
+            </div>
+
+            <!-- Detection Health -->
+            <div class="te-horizontal-cards te-content-block__detection-health">
+              <h4 class="te-self-serve__block-title">
+                <label for="select-dimension" class="control-label te-label">
+                  Detection health
+                  <span>
+                    <i class="glyphicon glyphicon-question-sign"></i>
+                    {{#tooltip-on-element class="te-tooltip"}}
+                      See how your detection configuration is performing in detail
+                      by clicking 'view details' below{{#if showRules}},
+                      and select different rules above the graph
+                      to see their respective regression errors{{/if}}.
+                    {{/tooltip-on-element}}
+                  </span>
+                </label>
+              </h4>
+              {{detection-health
+                health=detectionHealth
+                selectedRule=selectedRule
+              }}
+            </div>
           </div>
 
+          {{else}}
+
+            <!-- Anomalies, Response Rate, Precision, Recall -->
+            <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
+                  <span>
+                    <i class="glyphicon glyphicon-question-sign"></i>
+                    {{#tooltip-on-element class="te-tooltip"}}
+                      All estimated performance numbers are based on reviewed anomalies.
+                    {{/tooltip-on-element}}
+                  </span>
+                </label>
+              </h4>
+              {{!-- Alert anomaly stats cards --}}
+              {{stats-cards stats=stats}}
+            </div>
+
+          {{/if}}
+
           {{#if isReportSuccess}}
             {{#bs-alert type="success" class="te-form__banner te-form__banner--success"}}
               <strong>Success:</strong> Anomaly reported for dates <strong>{{reportedRange}}</strong>. Refresh page to see new anomalies...
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/detection-health/component.js b/thirdeye/thirdeye-frontend/app/pods/components/detection-health/component.js
new file mode 100644
index 0000000..82c73aa
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/detection-health/component.js
@@ -0,0 +1,194 @@
+/**
+ * Detection-Health Component
+ * Displays the health of a given detection, including a tooltip with further details about health
+ * @property {Object} details  - [required] details of health status retrieved from endpoint
+ * @example
+ * {{detection-health
+ *   health=detectionHealth
+ *   selectedRule=selectedRule
+ * }}
+ * @exports detection-health
+ */
+import Component from '@ember/component';
+import { get, computed } from '@ember/object';
+import floatToPercent from 'thirdeye-frontend/utils/float-to-percent';
+import moment from 'moment';
+
+const EXECUTION_TIME_FORMAT = 'MMM DD, YYYY - HH:mm:ss';
+
+export default Component.extend({
+  selectedRule: null, // passed in by parent when relevant
+  classNames: ['te-horizontal-cards__container'],
+  labelMap: {
+    GOOD: '--good',
+    MODERATE: '--average',
+    BAD: '--poor'
+  },
+  statusMap: {
+    GOOD: 'Good',
+    MODERATE: 'Average',
+    BAD: 'Poor',
+    UNKNOWN: 'Unknown'
+  },
+
+  /**
+   * Changes the color of text in Detection Health
+   * @type {String}
+   */
+  tasks: computed(
+    'health',
+    function() {
+      const health = get(this, 'health');
+      const tasks = [];
+      const taskTypes = ['not run', 'succeeded', 'failed', 'timeout'];
+      const numTasks = [0, 0, 0, 0];
+      if (health && health.detectionTaskStatus && typeof health.detectionTaskStatus === 'object') {
+        if (typeof health.detectionTaskStatus.taskCounts === 'object') {
+          const taskCounts = health.detectionTaskStatus.taskCounts;
+          const keys = ['WAITING', 'COMPLETED', 'FAILED', 'TIMEOUT'];
+          for (let i = 0; i < keys.length; i++) {
+            numTasks[i] = taskCounts[keys[i]];
+          }
+        }
+      }
+      const taskLabels = [null, '--good', '--poor', '--average'];
+      for (let i = 0; i < taskTypes.length; i++) {
+        const task = {
+          title: taskTypes[i],
+          number: numTasks[i],
+          label: taskLabels[i]
+        };
+        tasks.push(task);
+      }
+      return tasks;
+    }
+  ),
+
+  /**
+   * Maps backend values for anomaly coverage status to UI values
+   * @type {String}
+   */
+  anomalyCoverage: computed(
+    'health',
+    function() {
+      const {
+        health,
+        statusMap,
+        labelMap
+      } = this.getProperties('health', 'statusMap', 'labelMap');
+      const info = {};
+      if (health && health.anomalyCoverageStatus && typeof health.anomalyCoverageStatus === 'object') {
+        info.ratio = floatToPercent(health.anomalyCoverageStatus.anomalyCoverageRatio);
+        info.status = statusMap[health.anomalyCoverageStatus.healthStatus];
+        info.label = labelMap[health.anomalyCoverageStatus.healthStatus];
+      }
+      return info;
+    }
+  ),
+
+  /**
+   * Maps backend values for detection status to UI values
+   * @type {String}
+   */
+  detection: computed(
+    'health',
+    function() {
+      const {
+        health,
+        statusMap,
+        labelMap
+      } = this.getProperties('health', 'statusMap', 'labelMap');
+      const info = {};
+      if (health && health.detectionTaskStatus && typeof health.detectionTaskStatus === 'object') {
+        info.status = statusMap[health.detectionTaskStatus.healthStatus];
+        info.label = labelMap[health.detectionTaskStatus.healthStatus];
+      }
+      return info;
+    }
+  ),
+
+  /**
+   * Formats execution time for UI display
+   * @type {String}
+   */
+  executionTime: computed(
+    'health',
+    function() {
+      const health = get(this, 'health');
+      return `${moment(health.executionTime).format(EXECUTION_TIME_FORMAT)}`;
+    }
+  ),
+
+  /**
+   * Maps backend values for overall status to UI values
+   * @type {String}
+   */
+  overall: computed(
+    'health',
+    function() {
+      const {
+        health,
+        statusMap,
+        labelMap
+      } = this.getProperties('health', 'statusMap', 'labelMap');
+      const info = {};
+      if (health && typeof health === 'object') {
+        info.status = statusMap[health.overallHealth];
+        info.label = labelMap[health.overallHealth];
+      }
+      return info;
+    }
+  ),
+
+  /**
+   * generates regression details for rule selected or first rule if no rule selected
+   * @type {Object}
+   */
+  regressionInfo: computed(
+    'health',
+    'selectedRule',
+    function() {
+      const {
+        health,
+        labelMap,
+        selectedRule,
+        statusMap
+      } = this.getProperties('health', 'labelMap', 'selectedRule', 'statusMap');
+      const info = {};
+      info.mape = floatToPercent(NaN); // set default to Nan
+      let rule = selectedRule ? selectedRule.detectorName : null;
+      // 3 possibilities: selectedRule, no selectedRule and rules available, no rules available
+      if (health && health.regressionStatus && typeof health.regressionStatus === 'object') {
+        const regressionStatus = health.regressionStatus;
+        info.status = statusMap[regressionStatus.healthStatus]; // default status will be overall regression status
+        info.label = labelMap[regressionStatus.healthStatus];
+        if (typeof regressionStatus.detectorMapes === 'object' && typeof regressionStatus.detectorHealthStatus === 'object') {
+          // There is a selectedRule
+          if (rule) {
+            info.mape = floatToPercent(regressionStatus.detectorMapes[rule]);
+            info.status = statusMap[regressionStatus.detectorHealthStatus[rule]];
+            info.label = labelMap[regressionStatus.detectorHealthStatus[rule]];
+            info.rule = selectedRule.name;
+          } else { // There is no selectedRule
+            let mapes = [];
+            let ruleHealth = [];
+            if (typeof regressionStatus.detectorMapes === 'object') {
+              mapes = Object.keys(regressionStatus.detectorMapes);
+            }
+            if (typeof regressionStatus.detectorHealthStatus === 'object') {
+              ruleHealth = Object.keys(regressionStatus.detectorHealthStatus);
+            }
+            // There are rules available
+            if (mapes.length > 0 && ruleHealth.length > 0) {
+              info.mape = floatToPercent(regressionStatus.detectorMapes[mapes[0]]);
+              info.status = statusMap[regressionStatus.detectorHealthStatus[mapes[0]]];
+              info.label = labelMap[regressionStatus.detectorHealthStatus[mapes[0]]];
+              info.rule = mapes[0].split(':')[0];
+            }
+          }
+        }
+      }
+      return info;
+    }
+  )
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/detection-health/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/detection-health/template.hbs
new file mode 100644
index 0000000..d221bb0
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/detection-health/template.hbs
@@ -0,0 +1,84 @@
+<ul class="te-horizontal-cards__single-card">
+  <li class="te-horizontal-cards__card-title">
+    <label for="select-dimension" class="control-label te-label--no-bottom">
+      30-day status
+      <span>
+        <i class="glyphicon glyphicon-question-sign"></i>
+        {{#tooltip-on-element class="te-tooltip"}}
+          The overall health status of your detection configuration is based on
+          the most recent 30 days.
+        {{/tooltip-on-element}}
+      </span>
+    </label>
+  </li>
+  <li class="te-horizontal-cards__card-number{{overall.label}}">
+    {{overall.status}}
+  </li>
+  <a role="button" tabindex="0" class=".te-button--link">
+    view details
+    {{#bs-popover placement="bottom" title="Detection health"}}
+      <p>Overall health of the metrics that monitor detection performance.</p>
+
+      <div class="te-horizontal-metrics__container">
+        <ul class="te-horizontal-metrics__metric">
+          <li class="te-horizontal-metrics__number">{{executionTime}}</li>
+          <li class="te-horizontal-metrics__title">Execution Time</li>
+        </ul>
+      </div>
+
+      <label class="control-label te-label">
+        Detection task status
+      </label>
+
+      <div class="te-horizontal-metrics__container">
+        <ul class="te-horizontal-metrics__metric">
+          <li class="te-horizontal-metrics__number{{detection.label}}">{{detection.status}}</li>
+          <li class="te-horizontal-metrics__title">Status</li>
+        </ul>
+
+        {{#each tasks as |task|}}
+          <ul class="te-horizontal-metrics__metric">
+            <li class="te-horizontal-metrics__number{{task.label}}">{{task.number}}</li>
+            <li class="te-horizontal-metrics__title">{{task.title}}</li>
+          </ul>
+        {{/each}}
+      </div>
+
+      <label class="control-label te-label">
+        Anomaly coverage ratio
+      </label>
+
+      <div class="te-horizontal-metrics__container">
+        <ul class="te-horizontal-metrics__metric">
+          <li class="te-horizontal-metrics__number{{anomalyCoverage.label}}">{{anomalyCoverage.status}}</li>
+          <li class="te-horizontal-metrics__title">Status</li>
+        </ul>
+
+        <ul class="te-horizontal-metrics__metric">
+          <li class="te-horizontal-metrics__number">{{anomalyCoverage.ratio}}%</li>
+          <li class="te-horizontal-metrics__title">Last 30 days</li>
+        </ul>
+      </div>
+
+      <p>Adjust alert sensitivity using alert preview.</p>
+
+      <label class="control-label te-label">
+        Regression error{{#if regressionInfo.rule}} for {{regressionInfo.rule}}{{/if}}
+      </label>
+
+      <div class="te-horizontal-metrics__container">
+        <ul class="te-horizontal-metrics__metric">
+          <li class="te-horizontal-metrics__number{{regressionInfo.label}}">{{regressionInfo.status}}</li>
+          <li class="te-horizontal-metrics__title">Status</li>
+        </ul>
+
+        <ul class="te-horizontal-metrics__metric">
+          <li class="te-horizontal-metrics__number">{{regressionInfo.mape}}%</li>
+          <li class="te-horizontal-metrics__title">Percentage of anomalies covered</li>
+        </ul>
+      </div>
+
+      <p>Tune the alert using preview.</p>
+    {{/bs-popover}}
+  </a>
+</ul>
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/component.js b/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/component.js
index 0d0ffed..c6f213d 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/component.js
@@ -11,10 +11,18 @@
  * @exports stats-cards
  */
 import Component from '@ember/component';
-import { set, get } from '@ember/object';
+import { set, get, computed } from '@ember/object';
 
 export default Component.extend({
   classNames: ['te-horizontal-cards__container'],
+  statsTransformed: [],
+
+  oneCardOnly: computed(
+    'statsTransformed',
+    function() {
+      return (this.get('statsTransformed').length < 2);
+    }
+  ),
 
   /**
    * Transform the stats array passed to the component
@@ -31,22 +39,22 @@ export default Component.extend({
    * @param {Array.Array<String>} - entries of each stats card (i.e. [['entry1', ...], ['entry2', ...], ['entry3], ...])
    * @return {Object[]} - array of objects, each of which represents a stats card
    * @example
-   * [{
-   *    title: 'title',
-   *    description: 'description',
-   *    value: 7,
-   *    unit: 'digit'
-   *  }, {
-   *    title: 'title',
-   *    description: 'description',
-   *    value: 87.1,
-   *    unit: 'percent'
-   *  }, {
-   *    title: 'title',
-   *    description: 'description',
-   *    value: 87.1,
-   *    unit: 'percent'
-   * }];
+   * [[
+   *    'title',
+   *    'description',
+   *    7,
+   *    'digit'
+   *  ], [
+   *    'title',
+   *    'description',
+   *    87.1,
+   *    'percent'
+   *  ], [
+   *    'title',
+   *    'description',
+   *    87.1,
+   *    'percent'
+   * ]];
    */
   statsBuilder(statsArray) {
     const props = ['title', 'description', 'value', 'unit'];
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/template.hbs
index f1d0de4..da781ff 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/stats-cards/template.hbs
@@ -1,18 +1,43 @@
-{{#each statsTransformed as |card|}}
-  <ul class="te-horizontal-cards__card">
-    <li class="te-horizontal-cards__card-title">
-      <label for="select-dimension" class="control-label te-label">
-        {{card.title}}
-        <span>
-          <i class="glyphicon glyphicon-question-sign"></i>
-          {{#tooltip-on-element class="te-tooltip"}}
-            {{card.description}}
-          {{/tooltip-on-element}}
-        </span>
-      </label>
-    </li>
-    <li class="te-horizontal-cards__card-number">
-      {{card.value}}{{#if (eq card.unit 'percent')}}%{{/if}}
-    </li>
-  </ul>
-{{/each}}
+{{#if oneCardOnly}}
+
+  {{#each statsTransformed as |card|}}
+    <ul class="te-horizontal-cards__single-card">
+      <li class="te-horizontal-cards__card-title">
+        <label for="select-dimension" class="control-label te-label">
+          {{card.title}}
+          <span>
+            <i class="glyphicon glyphicon-question-sign"></i>
+            {{#tooltip-on-element class="te-tooltip"}}
+              {{card.description}}
+            {{/tooltip-on-element}}
+          </span>
+        </label>
+      </li>
+      <li class="te-horizontal-cards__card-number">
+        {{card.value}}{{#if (eq card.unit 'percent')}}%{{/if}}
+      </li>
+    </ul>
+  {{/each}}
+
+{{else}}
+
+  {{#each statsTransformed as |card|}}
+    <ul class="te-horizontal-cards__card">
+      <li class="te-horizontal-cards__card-title">
+        <label for="select-dimension" class="control-label te-label">
+          {{card.title}}
+          <span>
+            <i class="glyphicon glyphicon-question-sign"></i>
+            {{#tooltip-on-element class="te-tooltip"}}
+              {{card.description}}
+            {{/tooltip-on-element}}
+          </span>
+        </label>
+      </li>
+      <li class="te-horizontal-cards__card-number">
+        {{card.value}}{{#if (eq card.unit 'percent')}}%{{/if}}
+      </li>
+    </ul>
+  {{/each}}
+
+{{/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 26487c2..174f690 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/yaml-editor/template.hbs
@@ -61,7 +61,7 @@
     {{#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 [Beta] {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
+          <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}}
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js b/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
index 89f00f5..9dc770e 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
@@ -11,13 +11,15 @@ import { toastOptions } from 'thirdeye-frontend/utils/constants';
 import { formatYamlFilter } from 'thirdeye-frontend/utils/utils';
 import yamljs from 'yamljs';
 import moment from 'moment';
+import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
 
-export default Route.extend({
+export default Route.extend(AuthenticatedRouteMixin, {
   notifications: service('toast'),
+  analysisRange: [moment().add(1, 'day').subtract(30, 'day').startOf('day').valueOf(), moment().add(1, 'day').startOf('day').valueOf()],
 
   async model(params) {
     const alertId = params.alert_id;
-    // makes sense to rename this getProps since we are using the get method
+    const analysisRange = get(this, 'analysisRange');
     const getProps = {
       method: 'get',
       headers: { 'content-type': 'application/json' }
@@ -73,6 +75,23 @@ export default Route.extend({
       notifications.error('Retrieving alert yaml failed.', error, toastOptions);
     }
 
+    //detection health fetch
+    const executionTime = moment().valueOf(); // grab execution time
+    const healthUrl = `/detection/health/${alertId}?start=${analysisRange[0]}&end=${analysisRange[1]}`;
+    try {
+      const health_result = await fetch(healthUrl, getProps);
+      const health_status  = get(health_result, 'status');
+      const health_json = await health_result.json();
+      if (health_status !== 200) {
+        notifications.error('Retrieval of detection health failed.', 'Error', toastOptions);
+      } else {
+        health_json.executionTime = executionTime; // attach execution time for display
+        set(this, 'detectionHealth', health_json);
+      }
+    } catch (error) {
+      notifications.error('Retrieval of detection health failed.', 'Error', toastOptions);
+    }
+
     //subscription group fetch
     const subUrl = `/detection/subscription-groups/${alertId}`;//dropdown of subscription groups
     try {
@@ -107,10 +126,24 @@ export default Route.extend({
       alertId,
       alertData: get(this, 'detectionInfo'),
       detectionYaml: get(this, 'rawDetectionYaml'),
+      detectionHealth: get(this, 'detectionHealth'),
       subscribedGroups,
       metricUrn: get(this, 'metricUrn'),
       metricUrnList: get(this, 'metricUrnList') ? get(this, 'metricUrnList') : [],
       granularity
     });
+  },
+
+  actions: {
+    /**
+     * save session url for transition on login
+     * @method willTransition
+     */
+    willTransition(transition) {
+      //saving session url - TODO: add a util or service - lohuynh
+      if (transition.intent.name && transition.intent.name !== 'logout') {
+        this.set('session.store.fromUrl', {lastIntentTransition: transition});
+      }
+    }
   }
 });
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs b/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
index fa2e091..5425e24 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
@@ -34,6 +34,7 @@
         metricUrnList=model.metricUrnList
         granularity=model.granularity
         dimensionExploration=model.alertData.dimensionExploration
+        detectionHealth=model.detectionHealth
       }}
     {{/if}}
   </div>
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
index dd99e62..b990418 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/route.js
@@ -11,16 +11,19 @@ import yamljs from 'yamljs';
 import moment from 'moment';
 import { yamlAlertSettings, toastOptions } from 'thirdeye-frontend/utils/constants';
 import { formatYamlFilter } from 'thirdeye-frontend/utils/utils';
+import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
 
 const CREATE_GROUP_TEXT = 'Create a new subscription group';
 
-export default Route.extend({
+export default Route.extend(AuthenticatedRouteMixin, {
   anomaliesApiService: service('services/api/anomalies'),
   notifications: service('toast'),
+  analysisRange: [moment().add(1, 'day').subtract(30, 'day').startOf('day').valueOf(), moment().add(1, 'day').startOf('day').valueOf()],
 
   async model(params) {
     const alertId = params.alert_id;
-    const postProps = {
+    const analysisRange = get(this, 'analysisRange');
+    const getProps = {
       method: 'get',
       headers: { 'content-type': 'application/json' }
     };
@@ -29,7 +32,7 @@ export default Route.extend({
     //detection alert fetch
     const detectionUrl = `/detection/${alertId}`;
     try {
-      const detection_result = await fetch(detectionUrl, postProps);
+      const detection_result = await fetch(detectionUrl, getProps);
       const detection_status  = get(detection_result, 'status');
       const detection_json = await detection_result.json();
       if (detection_status !== 200) {
@@ -59,10 +62,27 @@ export default Route.extend({
       notifications.error('Retrieving alert yaml failed.', error, toastOptions);
     }
 
+    //detection health fetch
+    const executionTime = moment().valueOf(); // grab execution time
+    const healthUrl = `/detection/health/${alertId}?start=${analysisRange[0]}&end=${analysisRange[1]}`;
+    try {
+      const health_result = await fetch(healthUrl, getProps);
+      const health_status  = get(health_result, 'status');
+      const health_json = await health_result.json();
+      if (health_status !== 200) {
+        notifications.error('Retrieval of detection health failed.', 'Error', toastOptions);
+      } else {
+        health_json.executionTime = executionTime; // attach execution time for display
+        set(this, 'detectionHealth', health_json);
+      }
+    } catch (error) {
+      notifications.error('Retrieval of detection health failed.', 'Error', toastOptions);
+    }
+
     //subscription group fetch
     const subUrl = `/detection/subscription-groups/${alertId}`;//dropdown of subscription groups
     try {
-      const settings_result = await fetch(subUrl, postProps);
+      const settings_result = await fetch(subUrl, getProps);
       const settings_status  = get(settings_result, 'status');
       const settings_json = await settings_result.json();
       if (settings_status !== 200) {
@@ -71,7 +91,7 @@ export default Route.extend({
         set(this, 'subscriptionGroups', settings_json);
       }
     } catch (error) {
-      notifications.error('Retrieving subscription groups failed.', error, toastOptions);
+      notifications.error('Retrieving subscription groups failed.', 'Error', toastOptions);
     }
 
     let subscribedGroups = "";
@@ -95,6 +115,7 @@ export default Route.extend({
       alertId,
       alertData: get(this, 'detectionInfo'),
       detectionYaml: get (this, 'rawDetectionYaml'),
+      detectionHealth: get (this, 'detectionHealth'),
       subscriptionGroups: get(this, 'subscriptionGroups'),
       subscribedGroups,
       subscriptionGroupNames // all subscription groups as Ember data
@@ -140,7 +161,18 @@ export default Route.extend({
       model,
       createGroup
     });
-  }
-
+  },
 
+  actions: {
+    /**
+     * save session url for transition on login
+     * @method willTransition
+     */
+    willTransition(transition) {
+      //saving session url - TODO: add a util or service - lohuynh
+      if (transition.intent.name && transition.intent.name !== 'logout') {
+        this.set('session.store.fromUrl', {lastIntentTransition: transition});
+      }
+    }
+  }
 });
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
index 224269a..90aca1c 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/yaml/template.hbs
@@ -38,7 +38,7 @@
           {{#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 [Beta] {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
+                <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}}
@@ -48,6 +48,7 @@
                   alertId=model.alertId
                   alertYaml=detectionYaml
                   dataIsCurrent=alertDataIsCurrent
+                  detectionHealth=model.detectionHealth
                 }}
                   {{yield}}
                 {{/alert-details}}
diff --git a/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/template.hbs b/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/template.hbs
index 98b8072..7a68854 100644
--- a/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/self-serve/create-alert/template.hbs
@@ -439,7 +439,7 @@
         {{#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 [Beta] {{if toggleCollapsed "/ Enter YAML configuration to preview alert." ""}}
+              <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}}
diff --git a/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss b/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
index c39370c..8ff6579 100644
--- a/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
@@ -6,6 +6,12 @@ body {
   background-color: $te-main-background;
   margin: 0;
 }
+.popover-title {
+  font-size: 18px;
+  font-weight: normal;
+  color: app-shade(black, 9);
+  background-color: white;
+}
 
 .smaller {
   font-size: 19px;
@@ -191,6 +197,37 @@ body {
   margin: 10px 0 20px 0;
   background: white;
   padding: 24px;
+
+  &__alert-performance {
+    display: inline-block;
+    width: calc(80% - 20px);
+    margin-right: 10px;
+    font-family: "Source Sans Pro", sans-serif;
+    background: white;
+    padding: 24px;
+    padding-bottom: 100%;
+    margin-bottom: -100%;
+    border-right: 2px solid app-shade(black, 1);
+  }
+
+  &__detection-health {
+    width: 20%;
+    display: inline-block;
+    float: right;
+    margin-left: 10px;
+    font-family: "Source Sans Pro", sans-serif;
+    background: white;
+    padding: 24px;
+    padding-bottom: 100%;
+    margin-bottom: -100%;
+  }
+
+  &__performance-health-wrapper {
+    overflow: hidden;
+    border-radius: 5px;
+    border: 2px solid app-shade(black, 1);
+    background: white;
+  }
 }
 
 .te-hide {
@@ -206,6 +243,12 @@ body {
     margin-bottom: 0;
   }
 
+  &--no-bottom {
+    font-weight: normal;
+    color: app-shade(black, 9);
+    margin-bottom: 0;
+  }
+
   &--small {
     margin-bottom: 2px;
   }
@@ -335,6 +378,46 @@ body {
   }
 }
 
+.popover {
+  max-width: none;
+}
+
+.te-horizontal-metrics {
+  &__container {
+    display: flex;
+    clear: both;
+  }
+
+  &__metric {
+    list-style: none;
+    display: flex;
+    align-items: stretch;
+    flex-direction: column;
+
+    &:last-child {
+      margin-right: 0;
+    }
+  }
+
+  &__number {
+    color: black;
+    &--good {
+      color: #4B9D27;
+      font-weight: normal;
+    }
+
+    &--average {
+      color: #F5A623;
+      font-weight: normal;
+    }
+
+    &--poor {
+      color: #FF4B51;
+      font-weight: normal;
+    }
+  }
+}
+
 .te-horizontal-cards {
   &--hidden {
     visibility: hidden;
@@ -364,6 +447,23 @@ body {
     }
   }
 
+  &__single-card {
+    list-style: none;
+    background-color: white;
+    margin-right: 12px;
+    border-radius: 5px;
+    border: 2px solid app-shade(black, 1);
+    padding: 13px 15px 6px 17px;
+    display: flex;
+    align-items: stretch;
+    flex-direction: column;
+    justify-content: space-between;
+
+    &:last-child {
+      margin-right: 0;
+    }
+  }
+
   &__card-title {
     font-weight: 600;
     font-size: 16px;
@@ -427,6 +527,24 @@ body {
   &__card-number {
     font-size: 28px;
     font-weight: normal;
+
+    &--good {
+      color: #4B9D27;
+      font-size: 28px;
+      font-weight: normal;
+    }
+
+    &--average {
+      color: #F5A623;
+      font-size: 28px;
+      font-weight: normal;
+    }
+
+    &--poor {
+      color: #FF4B51;
+      font-size: 28px;
+      font-weight: normal;
+    }
   }
 
   &__card-units {


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