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/05/22 22:42:22 UTC

[incubator-pinot] branch master updated: Bounds (#4231)

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 91e4679  Bounds (#4231)
91e4679 is described below

commit 91e4679f7b9fa338f6060ee019da22f3029e8b33
Author: Harley Jackson <hj...@linkedin.com>
AuthorDate: Wed May 22 15:42:16 2019 -0700

    Bounds (#4231)
    
    Adds confidence bounds to graph in Preview when Predicted baseline is selected
    Adds spinner to graph when updating baseline only (Preview and Alert Overview)
    Makes time range selection automatically call preview - necessary since the current time series now comes from the preview endpoint
    Fixes legend to not show when there are more than 20 anomalies to display in the graph
    Enables filtering by both rule and dimension in preview
---
 .../app/pods/components/alert-details/component.js | 341 +++++++++++++++------
 .../app/pods/components/alert-details/template.hbs | 100 ++++--
 .../pods/components/timeseries-chart/component.js  |  64 +++-
 thirdeye/thirdeye-frontend/app/styles/app.scss     |   1 +
 .../app/styles/components/timeseries-chart.scss    |  28 ++
 .../app/styles/shared/_styles.scss                 |  12 +
 thirdeye/thirdeye-frontend/app/utils/rca-utils.js  |   3 +-
 thirdeye/thirdeye-frontend/app/utils/utils.js      |  11 +
 8 files changed, 441 insertions(+), 119 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 52067a8..3a0c3bb 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
@@ -14,11 +14,11 @@
  */
 
 import Component from '@ember/component';
-import { computed, observer, set, get, getProperties } from '@ember/object';
+import { computed, set, get, getProperties } from '@ember/object';
 import { later } from '@ember/runloop';
-import { checkStatus, humanizeFloat, postProps } from 'thirdeye-frontend/utils/utils';
+import { checkStatus, humanizeFloat, postProps, stripNonFiniteValues } from 'thirdeye-frontend/utils/utils';
 import { toastOptions } from 'thirdeye-frontend/utils/constants';
-import { colorMapping, toColor, makeTime, toMetricLabel, extractTail } from 'thirdeye-frontend/utils/rca-utils';
+import { colorMapping, makeTime, toMetricLabel, extractTail } from 'thirdeye-frontend/utils/rca-utils';
 import { getYamlPreviewAnomalies,
   getAnomaliesByAlertId,
   getFormattedDuration,
@@ -56,10 +56,8 @@ export default Component.extend({
     enabled: true,
     rescale: true
   },
-
-  legend: {
-    show: true,
-    position: 'right'
+  point: {
+    show: false
   },
   errorTimeseries: null,
   metricUrn: null,
@@ -71,22 +69,11 @@ export default Component.extend({
   showDetails: false,
   componentId: 'timeseries-chart',
   anomalies: null,
-  baselineOptions: [
-    { name: 'wo1w', isActive: true},
-    { name: 'wo2w', isActive: false},
-    { name: 'wo3w', isActive: false},
-    { name: 'wo4w', isActive: false},
-    { name: 'mean4w', isActive: false},
-    { name: 'median4w', isActive: false},
-    { name: 'min4w', isActive: false},
-    { name: 'max4w', isActive: false},
-    { name: 'none', isActive: false}
-  ],
   sortColumnStartUp: true,
   sortColumnChangeUp: false,
   sortColumnFeedbackUp: false,
   selectedSortMode: 'start:down',
-  selectedBaseline: 'wo1w',
+  selectedBaseline: null,
   pageSize: 10,
   currentPage: 1,
   isPreviewMode: false,
@@ -100,26 +87,49 @@ export default Component.extend({
   isReportFailure: false,
   openReportModal: false,
   missingAnomalyProps: {},
+  uniqueTimeSeries: null,
+  selectedRule: null,
+  isLoadingTimeSeries: false,
 
 
 
-  updateVisuals: observer(
-    'analysisRange',
-    'metricUrn',
+  /**
+   * This needs to be a computed variable until there is an endpoint for showing predicted with any metricurn
+   * @type {Array}
+   */
+  baselineOptions: computed(
+    'isPreviewMode',
     function() {
-      const {
-        isPreviewMode,
-        metricUrn
-      } = this.getProperties('isPreviewMode', 'metricUrn');
-      if(metricUrn) {
-        if(!isPreviewMode) {
-          this._fetchAnomalies();
-        } else {
-          this._fetchTimeseries();
-        }
+      let options;
+      if (get(this, 'isPreviewMode')) {
+        options = [
+          { name: 'predicted', isActive: true},
+          { name: 'wo1w', isActive: false},
+          { name: 'wo2w', isActive: false},
+          { name: 'wo3w', isActive: false},
+          { name: 'wo4w', isActive: false},
+          { name: 'mean4w', isActive: false},
+          { name: 'median4w', isActive: false},
+          { name: 'min4w', isActive: false},
+          { name: 'max4w', isActive: false},
+          { name: 'none', isActive: false}
+        ];
+      } else {
+        options = [
+          { name: 'wo1w', isActive: true},
+          { name: 'wo2w', isActive: false},
+          { name: 'wo3w', isActive: false},
+          { name: 'wo4w', isActive: false},
+          { name: 'mean4w', isActive: false},
+          { name: 'median4w', isActive: false},
+          { name: 'min4w', isActive: false},
+          { name: 'max4w', isActive: false},
+          { name: 'none', isActive: false}
+        ];
       }
-
-    }),
+      return options;
+    }
+  ),
 
   /**
    * Separate time range for anomalies in preview mode
@@ -133,15 +143,42 @@ export default Component.extend({
       range.push(analysisRange[0]);
       // set end to now if the end time is in the future
       const end = Math.min(moment().valueOf(), analysisRange[1]);
-      range.push(end)
+      range.push(end);
       return range;
     }
   ),
 
   /**
-   * Whether the alert has multiple dimensions
+   * Rules to display in rules dropdown
+   * @type {Array}
+   */
+  ruleOptions: computed(
+    'uniqueTimeSeries',
+    function() {
+      const uniqueTimeSeries = get(this, 'uniqueTimeSeries');
+      if (uniqueTimeSeries) {
+        return [...new Set(uniqueTimeSeries.map(series => series.detectorName))];
+      }
+      return [];
+    }
+  ),
+
+  /**
+   * flag to differentiate preview loading and graph loading
    * @type {Boolean}
    */
+  isPreviewLoading: computed(
+    'isPreviewMode',
+    'isLoading',
+    function() {
+      return (get(this, 'isPreviewMode') && get(this, 'isLoading'));
+    }
+  ),
+
+  /**
+   * dimensions to display in dimensions dropdown
+   * @type {Array}
+   */
   dimensionOptions: computed(
     'metricUrnList',
     function() {
@@ -310,34 +347,74 @@ export default Component.extend({
     }
   ),
 
-  series: computed(
+  currentAnomalies: computed(
     'anomalies',
+    'metricUrn',
+    'selectedRule',
+    'selectedDimension',
+    function() {
+      let currentAnomalies = [];
+      const {
+        metricUrn, anomalies, selectedRule
+      } = getProperties(this, 'metricUrn', 'anomalies', 'selectedRule');
+      if (!_.isEmpty(anomalies)) {
+
+        currentAnomalies = anomalies.filter(anomaly => {
+          if (anomaly.metricUrn === metricUrn) {
+            if(get(this, 'isPreviewMode') && anomaly.properties && typeof anomaly.properties === 'object') {
+              return (anomaly.properties.detectorComponentName.includes(selectedRule));
+            } else if (!get(this, 'isPreviewMode')) {
+              // This is necessary until we surface rule selector in Alert Overview
+              return true;
+            }
+          }
+          return false;
+        });
+      }
+      return currentAnomalies;
+    }
+  ),
+
+  legend: computed(
+    'numCurrentAnomalies',
+    function() {
+      if (get(this, 'numCurrentAnomalies') > ANOMALY_LEGEND_THRESHOLD) {
+        return {
+          show: false,
+          position: 'right'
+        };
+      }
+      return {
+        show: true,
+        position: 'right'
+      };
+    }
+  ),
+
+  numCurrentAnomalies: computed(
+    'currentAnomalies.@each',
+    function() {
+      const currentAnomalies = get(this, 'currentAnomalies');
+      return currentAnomalies.length;
+    }
+  ),
+
+  series: computed(
+    'currentAnomalies.@each',
     'timeseries',
     'baseline',
     'analysisRange',
+    'selectedRule',
+    'metricUrn',
     function () {
       const {
-        metricUrn, anomalies, timeseries, baseline
-      } = getProperties(this, 'metricUrn', 'anomalies', 'timeseries',
-        'baseline');
+        currentAnomalies, timeseries, baseline
+      } = getProperties(this, 'currentAnomalies', 'timeseries', 'baseline');
 
       const series = {};
+      if (!_.isEmpty(currentAnomalies)) {
 
-      if (!_.isEmpty(anomalies)) {
-
-        const anomaliesInGraph = anomalies.filter(anomaly => anomaly.metricUrn === metricUrn);
-        if (anomaliesInGraph.length > ANOMALY_LEGEND_THRESHOLD) {
-          set(this, 'legend', {
-            show: false,
-            position: 'right'
-          });
-        } else {
-          set(this, 'legend', {
-            show: true,
-            position: 'right'
-          });
-        }
-        anomaliesInGraph.forEach(anomaly => {
+        currentAnomalies.forEach(anomaly => {
           const key = this._formatAnomaly(anomaly);
           series[key] = {
             timestamps: [anomaly.startTime, anomaly.endTime],
@@ -353,21 +430,51 @@ export default Component.extend({
         });
       }
 
-      if (timeseries && !_.isEmpty(timeseries.value)) {
-        series['current'] = {
-          timestamps: timeseries.timestamp,
-          values: timeseries.value,
-          type: 'line',
-          color: toColor(metricUrn)
-        };
+      // The current time series has a different naming convention in Preview
+      if (get(this, 'isPreviewMode')) {
+        if (timeseries && !_.isEmpty(timeseries.current)) {
+          series['current'] = {
+            timestamps: timeseries.timestamp,
+            values: stripNonFiniteValues(timeseries.current),
+            type: 'line',
+            color: 'grey'
+          };
+        }
+      } else {
+        if (timeseries && !_.isEmpty(timeseries.value)) {
+          series['current'] = {
+            timestamps: timeseries.timestamp,
+            values: stripNonFiniteValues(timeseries.value),
+            type: 'line',
+            color: 'grey'
+          };
+        }
       }
 
       if (baseline && !_.isEmpty(baseline.value)) {
         series['baseline'] = {
           timestamps: baseline.timestamp,
-          values: baseline.value,
+          values: stripNonFiniteValues(baseline.value),
+          type: 'line',
+          color: 'blue'
+        };
+      }
+
+      if (baseline && !_.isEmpty(baseline.upper_bound)) {
+        series['upperBound'] = {
+          timestamps: baseline.timestamp,
+          values: stripNonFiniteValues(baseline.upper_bound),
           type: 'line',
-          color: 'light-' + toColor(metricUrn)
+          color: 'confidence-bounds-blue'
+        };
+      }
+
+      if (baseline && !_.isEmpty(baseline.lower_bound)) {
+        series['lowerBound'] = {
+          timestamps: baseline.timestamp,
+          values: stripNonFiniteValues(baseline.lower_bound),
+          type: 'line',
+          color: 'confidence-bounds-blue'
         };
       }
       return series;
@@ -521,6 +628,7 @@ export default Component.extend({
     const startAnomalies = anomaliesRange[0];
     const endAnomalies = anomaliesRange[1];
     let anomalies;
+    let uniqueTimeSeries;
     let applicationAnomalies;
     let metricUrnList;
     try {
@@ -530,9 +638,13 @@ export default Component.extend({
           metricUrnList = Object.keys(applicationAnomalies.diagnostics['0']);
           set(this, 'metricUrnList', metricUrnList);
           set(this, 'selectedDimension', toMetricLabel(extractTail(decodeURIComponent(metricUrnList[0]))));
+          if (applicationAnomalies.predictions && Array.isArray(applicationAnomalies.predictions) && (typeof applicationAnomalies.predictions[0] === 'object')){
+            set(this, 'selectedRule', applicationAnomalies.predictions[0].detectorName);
+          }
           set(this, 'metricUrn', metricUrnList[0]);
         }
         anomalies = applicationAnomalies.anomalies;
+        uniqueTimeSeries = applicationAnomalies.predictions;
       } else {
         applicationAnomalies = yield getAnomaliesByAlertId(alertId, start, end);
         const metricUrnObj = {};
@@ -543,6 +655,8 @@ export default Component.extend({
           metricUrnList = Object.keys(metricUrnObj);
           if (metricUrnList.length > 0) {
             set(this, 'metricUrnList', metricUrnList);
+            set(this, 'selectedDimension', toMetricLabel(extractTail(decodeURIComponent(metricUrnList[0]))));
+            set(this, 'metricUrn', metricUrnList[0]);
           }
         }
         anomalies = applicationAnomalies;
@@ -567,12 +681,15 @@ export default Component.extend({
         });
       }
     } catch (error) {
-      notifications.error(error.body.message, 'Error', toastOptions);
+      if (error.body) {
+        notifications.error(error.body.message, 'Error', toastOptions);
+      }
     }
 
     return {
       anomalyMapping,
-      anomalies
+      anomalies,
+      uniqueTimeSeries
     };
   }).drop(),
 
@@ -580,12 +697,18 @@ export default Component.extend({
     this._super(...arguments);
     const isPreviewMode = get(this, 'isPreviewMode');
     if (!isPreviewMode) {
-      set(this, 'analysisRange', [moment().add(1, 'day').subtract(1, 'month').startOf('day').valueOf(), moment().add(1, 'day').startOf('day').valueOf()]);
-      set(this, 'duration', '1m');
-      set(this, 'selectedDimension', 'Choose a dimension');
+      this.setProperties({
+        analysisRange: [moment().add(1, 'day').subtract(1, 'month').startOf('day').valueOf(), moment().add(1, 'day').startOf('day').valueOf()],
+        duration: '1m',
+        selectedDimension: 'Choose a dimension',
+        selectedBaseline: 'wo1w'
+      });
       this._fetchAnomalies();
     } else {
-      set(this, 'duration', '1d');
+      this.setProperties({
+        duration: '1d',
+        selectedBaseline: 'predicted'
+      });
     }
   },
 
@@ -641,32 +764,65 @@ export default Component.extend({
     const {
       metricUrn,
       analysisRange,
-      selectedBaseline
-    } = this.getProperties('metricUrn', 'analysisRange', 'selectedBaseline');
+      selectedBaseline,
+      isPreviewMode,
+      selectedRule,
+      uniqueTimeSeries
+    } = this.getProperties('metricUrn', 'analysisRange', 'selectedBaseline', 'isPreviewMode', 'selectedRule', 'uniqueTimeSeries');
+    const timeZone = 'America/Los_Angeles';
 
-    set(this, 'errorTimeseries', null);
+    this.setProperties({
+      errorTimeseries: null,
+      isLoadingTimeSeries: true
+    });
 
-    const timeZone = 'America/Los_Angeles';
-    const urlCurrent = `/rootcause/metric/timeseries?urn=${metricUrn}&start=${analysisRange[0]}&end=${analysisRange[1]}&offset=current&timezone=${timeZone}`;
-    fetch(urlCurrent)
-      .then(checkStatus)
-      .then(res => {
+    if (isPreviewMode) {
+      const seriesSet = uniqueTimeSeries.find(series => {
+        if (series.detectorName === selectedRule && series.metricUrn === metricUrn) {
+          return series;
+        }
+      });
+      if (selectedBaseline === 'predicted') {
         this.setProperties({
-          timeseries: res,
-          isLoading: false
+          timeseries: seriesSet.predictedTimeSeries,
+          baseline: seriesSet.predictedTimeSeries,
+          isLoadingTimeSeries: false
         });
-      });
-
+      } else {
+        const urlBaseline = `/rootcause/metric/timeseries?urn=${metricUrn}&start=${analysisRange[0]}&end=${analysisRange[1]}&offset=${selectedBaseline}&timezone=${timeZone}`;
+        fetch(urlBaseline)
+          .then(checkStatus)
+          .then(res => {
+            this.setProperties({
+              timeseries: seriesSet.predictedTimeSeries,
+              baseline: res,
+              isLoadingTimeSeries: false
+            });
+          });
+      }
+    } else {
+      const urlCurrent = `/rootcause/metric/timeseries?urn=${metricUrn}&start=${analysisRange[0]}&end=${analysisRange[1]}&offset=current&timezone=${timeZone}`;
+      fetch(urlCurrent)
+        .then(checkStatus)
+        .then(res => {
+          this.setProperties({
+            timeseries: res,
+            isLoadingTimeSeries: false
+          });
+        });
+      const urlBaseline = `/rootcause/metric/timeseries?urn=${metricUrn}&start=${analysisRange[0]}&end=${analysisRange[1]}&offset=${selectedBaseline}&timezone=${timeZone}`;
+      fetch(urlBaseline)
+        .then(checkStatus)
+        .then(res => set(this, 'baseline', res));
+    }
     set(this, 'errorBaseline', null);
-
-    const urlBaseline = `/rootcause/metric/timeseries?urn=${metricUrn}&start=${analysisRange[0]}&end=${analysisRange[1]}&offset=${selectedBaseline}&timezone=${timeZone}`;
-    fetch(urlBaseline)
-      .then(checkStatus)
-      .then(res => set(this, 'baseline', res));
   },
 
   _fetchAnomalies() {
-    set(this, 'errorAnomalies', null);
+    this.setProperties({
+      errorAnomalies: null,
+      isLoading: true
+    });
 
     try {
       const content = get(this, 'alertYaml');
@@ -675,6 +831,7 @@ export default Component.extend({
           this.setProperties({
             anomalyMapping: results.anomalyMapping,
             anomalies: results.anomalies,
+            uniqueTimeSeries: results.uniqueTimeSeries,
             isLoading: false
           });
           if (get(this, 'metricUrn')) {
@@ -850,6 +1007,11 @@ export default Component.extend({
         });
     },
 
+    onSelectRule(selected) {
+      set(this, 'selectedRule', selected);
+      this._fetchTimeseries();
+    },
+
     onSelectDimension(selected) {
       const metricUrnList = get(this, 'metricUrnList');
       const newMetricUrn = metricUrnList.find(urn => {
@@ -861,6 +1023,7 @@ export default Component.extend({
         metricUrn: newMetricUrn,
         selectedDimension: toMetricLabel(extractTail(decodeURIComponent(newMetricUrn)))
       });
+      this._fetchTimeseries();
     },
 
     /**
@@ -907,6 +1070,7 @@ export default Component.extend({
       //Update the time range option selected
       set(this, 'analysisRange', [startDate, endDate]);
       set(this, 'duration', duration);
+      this._fetchAnomalies();
     },
 
     /**
@@ -914,7 +1078,6 @@ export default Component.extend({
     */
     getPreview() {
       this.setProperties({
-        isLoading: true,
         showDetails: true,
         dataIsCurrent: true
       });
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 cc54ad4..431e077 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/template.hbs
@@ -2,19 +2,21 @@
 
   <div class="pull-right">
     {{#if isPreviewMode}}
-      {{bs-button
-        defaultText=(if showDetails "Rerun Preview" "Preview Alert")
-        disabled=disablePreviewButton
-        type="outline-primary"
-        buttonType="refresh"
-        onClick=(action "getPreview")
-        class="te-button te-button--cancel"
-      }}
+      {{#unless dataIsCurrent}}
+        {{bs-button
+          defaultText=(if showDetails "Rerun Preview" "Preview Alert")
+          disabled=disablePreviewButton
+          type="outline-primary"
+          buttonType="refresh"
+          onClick=(action "getPreview")
+          class="te-button te-button--cancel"
+        }}
+      {{/unless}}
     {{/if}}
   </div>
   {{#unless errorAnomalies}}
     {{#if showDetails}}
-      {{#if isLoading}}
+      {{#if isPreviewLoading}}
         {{ember-spinner scale=0.5 rotate=10 speed='1.1' color='#3498DB'}}
         <p>Running detection between <strong>{{pill.activeRangeStart}}</strong> and <strong>{{pill.activeRangeEnd}}</strong>.</p>
       {{else}}
@@ -23,7 +25,11 @@
             <h4><i class="glyphicon glyphicon-exclamation-sign"></i> Use with caution!</h4>
             <p>Preview can take a long time to run. We recommend previewing for less than a week on
               daily metrics and less than a day on hourly/minutely metrics.</p>
-            <p>Click on the "Rerun Preview" option if you updated the time window.</p>
+            {{#if dataIsCurrent}}
+              <p>Preview will be rerun automatically when you update the time window.</p>
+            {{else}}
+              <p>Click "Rerun Preview" to get the preview for your updated configuration</p>
+            {{/if}}
           </div>
         {{/if}}
         {{#if dataIsCurrent}}
@@ -70,35 +76,73 @@
         {{/if}}
 
         <div class="te-content-block">
-          {{#if alertHasDimensions}}
-            <h4 class="te-self-serve__block-title">Anomalies over time for dimension {{selectedDimension}}</h4>
+          {{#if isPreviewMode}}
+            <h4 class="te-self-serve__block-title">{{selectedRule}} {{#if alertHasDimensions}}/ {{selectedDimension}}{{/if}} anomalies over time ({{numCurrentAnomalies}})</h4>
           {{else}}
-            <h4 class="te-self-serve__block-title">Anomalies over time </h4>
+            <h4 class="te-self-serve__block-title">{{#if alertHasDimensions}}{{selectedDimension}} a{{else}}A{{/if}}nomalies over time ({{numCurrentAnomalies}})</h4>
           {{/if}}
 
           {{#unless isPreviewMode}}
             <a class="te-self-serve__side-link te-self-serve__side-link--high" {{action "onClickReportAnomaly" this}}>Report missing anomaly</a>
           {{/unless}}
 
-          {{!-- Dimension selector --}}
-          {{#if alertHasDimensions}}
-            <div class="te-form__select te-form__select--wider col-md-3">
+          <div class="te-form__select te-form__select--same-line col-md-3">
+            {{!-- Rule selector --}}
+            {{#if isPreviewMode}}
               {{#power-select
-                triggerId="select-dimension"
+                triggerId="select-rule"
                 triggerClass="te-form__select"
-                options=dimensionOptions
+                options=ruleOptions
                 searchEnabled=true
                 searchPlaceholder="Type to filter..."
-                matchTriggerWidth=true
+                matchTriggerWidth=false
                 matchContentWidth=true
-                selected=selectedDimension
-                onchange=(action "onSelectDimension")
-                as |dimension|
+                selected=selectedRule
+                onchange=(action "onSelectRule")
+                as |rule|
               }}
-                {{dimension}}
+                {{rule}}
               {{/power-select}}
-            </div>
-          {{/if}}
+
+              {{!-- Dimension selector --}}
+              {{#if alertHasDimensions}}
+                <div class="te-form__select te-form__select--margin-left">
+                  {{#power-select
+                    triggerId="select-dimension"
+                    triggerClass="te-form__select"
+                    options=dimensionOptions
+                    searchEnabled=true
+                    searchPlaceholder="Type to filter..."
+                    matchTriggerWidth=false
+                    matchContentWidth=true
+                    selected=selectedDimension
+                    onchange=(action "onSelectDimension")
+                    as |dimension|
+                  }}
+                    {{dimension}}
+                  {{/power-select}}
+                </div>
+              {{/if}}
+            {{else}}
+              {{!-- Dimension selector --}}
+              {{#if alertHasDimensions}}
+                {{#power-select
+                  triggerId="select-dimension"
+                  triggerClass="te-form__select"
+                  options=dimensionOptions
+                  searchEnabled=true
+                  searchPlaceholder="Type to filter..."
+                  matchTriggerWidth=false
+                  matchContentWidth=true
+                  selected=selectedDimension
+                  onchange=(action "onSelectDimension")
+                  as |dimension|
+                }}
+                  {{dimension}}
+                {{/power-select}}
+              {{/if}}
+            {{/if}}
+          </div>
 
           {{!-- Missing anomaly modal --}}
           {{#te-modal
@@ -132,12 +176,18 @@
           {{/te-modal}}
 
           <div class="col-xs-12 te-graph-container">
+            {{#if isLoadingTimeSeries}}
+              <div class="spinner-wrapper spinner-wrapper--card">
+                {{ember-spinner}}
+              </div>
+            {{/if}}
             {{timeseries-chart
               series=series
               colorMapping=colorMapping
               axis=axis
               zoom=zoom
               legend=legend
+              point=point
             }}
             {{#unless isPreviewMode}}
               <div class="te-form__note">
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/timeseries-chart/component.js b/thirdeye/thirdeye-frontend/app/pods/components/timeseries-chart/component.js
index dacb827..da45cd1 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/timeseries-chart/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/timeseries-chart/component.js
@@ -40,7 +40,7 @@ export default Component.extend({
   tooltip: {
     format: {
       title: (d) => moment(d).format('MM/DD hh:mm a'),
-      value: (val, ratio, id) => d3.format('.3s')(val)
+      value: (val) => d3.format('.3s')(val)
     }
   },
 
@@ -81,7 +81,8 @@ export default Component.extend({
   },
 
   zoom: { // on init only
-    enabled: true
+    enabled: true,
+    onzoom: null
   },
 
   point: { // on init only
@@ -190,7 +191,6 @@ export default Component.extend({
   _updateChart() {
     const diffConfig = this._makeDiffConfig();
     const chart = this.get('_chart');
-    diffConfig.legend.show ? chart.legend.show() : chart.legend.hide();
     chart.regions(diffConfig.regions);
     chart.axis.range(this._makeAxisRange(diffConfig.axis));
     chart.unzoom();
@@ -198,6 +198,59 @@ export default Component.extend({
     this._updateCache();
   },
 
+  _shadeBounds(){
+    d3.select(".confidence-bounds").remove();
+    d3.select(".sub-confidence-bounds").remove();
+    const chart = this.api;
+    if (chart && chart.legend && chart.internal && chart.internal.data && chart.internal.data.targets) {
+      if (chart.internal.data.targets.length > 24) {
+        chart.legend.hide();
+      }
+    }
+    if(chart && chart.internal && chart.internal.data && chart.internal.data.xs && Array.isArray(chart.internal.data.xs.upperBound)) {
+      const indices = d3.range(chart.internal.data.xs.upperBound.length);
+      const yscale = chart.internal.y;
+      const xscale = chart.internal.x;
+      const yscaleSub = chart.internal.subY;
+      const xscaleSub = chart.internal.subX;
+      const xVals = chart.internal.data.xs.upperBound;
+      let upperBoundVals = chart.internal.data.targets.find(target => {
+        return target.id === 'upperBound';
+      });
+      let lowerBoundVals = chart.internal.data.targets.find(target => {
+        return target.id === 'lowerBound';
+      });
+
+      if (upperBoundVals && lowerBoundVals) {
+        upperBoundVals = upperBoundVals.values.map(e => e.value);
+        lowerBoundVals = lowerBoundVals.values.map(e => e.value);
+
+        const area_main = d3.svg.area()
+          .interpolate('linear')
+          .x(d => xscale(xVals[d]))
+          .y0(d => yscale(lowerBoundVals[d]))
+          .y1(d => yscale(upperBoundVals[d]));
+
+        const area_sub = d3.svg.area()
+          .interpolate('linear')
+          .x(d => xscaleSub(xVals[d]))
+          .y0(d => yscaleSub(lowerBoundVals[d]))
+          .y1(d => yscaleSub(upperBoundVals[d]));
+
+        d3.select(".c3-chart").append('path')
+          .datum(indices)
+          .attr('class', 'confidence-bounds')
+          .attr('d', area_main);
+
+        d3.select(".c3-brush").append('path')
+          .datum(indices)
+          .attr('class', 'sub-confidence-bounds')
+          .attr('d', area_sub);
+      }
+    }
+
+  },
+
   didUpdateAttrs() {
     this._super(...arguments);
     const series = this.get('series') || {};
@@ -252,8 +305,11 @@ export default Component.extend({
     config.size = this.get('height');
     config.point = this.get('point');
     config.line = this.get('line');
+    config.onrendered = this.get('_shadeBounds');
+
+    const chart = c3.generate(config);
+    this.set('_chart', chart);
 
-    this.set('_chart', c3.generate(config));
     this._updateCache();
   }
 });
diff --git a/thirdeye/thirdeye-frontend/app/styles/app.scss b/thirdeye/thirdeye-frontend/app/styles/app.scss
index 59940ab..96ee0a6 100644
--- a/thirdeye/thirdeye-frontend/app/styles/app.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/app.scss
@@ -63,6 +63,7 @@ body {
 @import 'components/card-container';
 @import 'components/te-anomaly-table';
 @import 'components/te-toggle';
+@import 'components/timeseries-chart';
 @import 'components/range-pill-selectors';
 @import 'components/shared/common-tabs';
 @import 'components/yaml-editor';
diff --git a/thirdeye/thirdeye-frontend/app/styles/components/timeseries-chart.scss b/thirdeye/thirdeye-frontend/app/styles/components/timeseries-chart.scss
new file mode 100644
index 0000000..8247c05
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/styles/components/timeseries-chart.scss
@@ -0,0 +1,28 @@
+// Overriding default c3
+
+.timeseries-chart {
+  .c3-target-baseline {
+    stroke-dasharray: 2,2;
+  }
+  .c3-target-predicted {
+    stroke-dasharray: 2,2;
+  }
+  .c3-target-upperBound {
+    opacity: 0.5 !important;
+  }
+  .c3-target-lowerBound {
+    opacity: 0.5 !important;
+  }
+  path.confidence-bounds {
+    stroke: #dcf8f3;
+    stroke-opacity: 0.5;
+	  fill: #dcf8f3;
+    opacity: 0.5
+ }
+ path.sub-confidence-bounds {
+   stroke: #dcf8f3;
+   stroke-opacity: 0.5;
+   fill: #dcf8f3;
+   opacity: 0.5
+}
+};
diff --git a/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss b/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
index 6ea9238..c39370c 100644
--- a/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/shared/_styles.scss
@@ -165,6 +165,18 @@ body {
     &--left {
       margin-left: -15px;
     }
+
+    &--same-line {
+      display: inline-flex;
+      align-items: baseline;
+      width: 100%;
+      position: relative;
+      padding: 0px;
+    }
+
+    &--margin-left {
+      margin-left: 20px;
+    }
   }
 
   &__new-sub {
diff --git a/thirdeye/thirdeye-frontend/app/utils/rca-utils.js b/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
index eff8834..f10d6a0 100644
--- a/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
+++ b/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
@@ -22,7 +22,8 @@ export const colorMapping = {
   'light-orange': '#F8C19E',
   'light-teal': '#98DADE',
   'light-pink': '#FFB9E2',
-  'light-grey': '#CFCFCF'
+  'light-grey': '#CFCFCF',
+  'confidence-bounds-blue' : '#dcf8f3'
 };
 
 // TODO load from config
diff --git a/thirdeye/thirdeye-frontend/app/utils/utils.js b/thirdeye/thirdeye-frontend/app/utils/utils.js
index 00f0313..6c4a5d1 100644
--- a/thirdeye/thirdeye-frontend/app/utils/utils.js
+++ b/thirdeye/thirdeye-frontend/app/utils/utils.js
@@ -177,6 +177,16 @@ export function toIso(dateStr) {
 }
 
 /**
+ * Replace all Infinity and NaN with null in array of numbers
+ * @param {Array} timeSeries - time series to modify
+ */
+export function stripNonFiniteValues(timeSeries) {
+  return timeSeries.map(value => {
+    return (isFinite(value) ? value : null);
+  });
+}
+
+/**
  * The yaml filters formatter. Convert filters in the yaml file in to a legacy filters string
  * For example, filters = {
  *   "country": ["us", "cn"],
@@ -219,6 +229,7 @@ export default {
   parseProps,
   postProps,
   toIso,
+  stripNonFiniteValues,
   postYamlProps,
   formatYamlFilter
 };


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