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/06/12 23:19:52 UTC

[incubator-pinot] branch master updated: [TE] frontend - harleyjj/screenshot - Adds bounds to Screenshot and Anomalies route (#4300)

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 c7e2481  [TE] frontend - harleyjj/screenshot - Adds bounds to Screenshot and Anomalies route (#4300)
c7e2481 is described below

commit c7e2481b3e8f86fc09307593a3f8378d03152267
Author: Harley Jackson <hj...@linkedin.com>
AuthorDate: Wed Jun 12 16:19:47 2019 -0700

    [TE] frontend - harleyjj/screenshot - Adds bounds to Screenshot and Anomalies route (#4300)
    
    Adds confidence bounds to Screenshot and Anomalies routes
    Implements new color palette on Screenshot, Anomalies, Alert Overview, and Preview graphs
    Changes anomaly indication line from teal to red
    Makes granularity available on Alert Details component for future usage
    Enables _shadeBounds function to handle a dom with multiple graphs
    Draws bounds below the predicted time series line instead of above it
    Makes legend more intuitive and legible
---
 .../app/pods/components/alert-details/component.js | 24 ++++++-----
 .../pods/components/anomaly-summary/component.js   | 50 +++++++++++++++++-----
 .../pods/components/anomaly-summary/template.hbs   |  1 +
 .../pods/components/timeseries-chart/component.js  | 50 +++++++++++++++-------
 .../app/pods/manage/explore/route.js               | 21 ++++++---
 .../app/pods/manage/explore/template.hbs           |  1 +
 .../app/pods/screenshot/controller.js              | 39 ++++++++++++-----
 .../app/styles/components/timeseries-chart.scss    | 22 +++++-----
 thirdeye/thirdeye-frontend/app/utils/rca-utils.js  |  6 ++-
 9 files changed, 149 insertions(+), 65 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 4ec43c4..c29742e 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/alert-details/component.js
@@ -89,6 +89,8 @@ export default Component.extend({
   uniqueTimeSeries: null,
   selectedRule: null,
   isLoadingTimeSeries: false,
+  granularity: null,
+  alertYaml: null,
 
 
 
@@ -427,12 +429,12 @@ export default Component.extend({
             timestamps: [anomaly.startTime, anomaly.endTime],
             values: [1, 1],
             type: 'line',
-            color: 'teal',
+            color: 'red',
             axis: 'y2'
           };
           series[key + '-region'] = Object.assign({}, series[key], {
             type: 'region',
-            color: 'orange'
+            color: 'screenshot-anomaly'
           });
         });
       }
@@ -440,39 +442,39 @@ export default Component.extend({
       // The current time series has a different naming convention in Preview
       if (get(this, 'isPreviewMode')) {
         if (timeseries && !_.isEmpty(timeseries.current)) {
-          series['current'] = {
+          series['Current'] = {
             timestamps: timeseries.timestamp,
             values: stripNonFiniteValues(timeseries.current),
             type: 'line',
-            color: 'grey'
+            color: 'screenshot-current'
           };
         }
       } else {
         if (timeseries && !_.isEmpty(timeseries.value)) {
-          series['current'] = {
+          series['Current'] = {
             timestamps: timeseries.timestamp,
             values: stripNonFiniteValues(timeseries.value),
             type: 'line',
-            color: 'grey'
+            color: 'screenshot-current'
           };
         }
       }
 
       if (baseline && !_.isEmpty(baseline.value)) {
-        series['baseline'] = {
+        series['Baseline'] = {
           timestamps: baseline.timestamp,
           values: stripNonFiniteValues(baseline.value),
           type: 'line',
-          color: 'blue'
+          color: 'screenshot-predicted'
         };
       }
 
       if (baseline && !_.isEmpty(baseline.upper_bound)) {
-        series['upperBound'] = {
+        series['Upper and lower bound'] = {
           timestamps: baseline.timestamp,
           values: stripNonFiniteValues(baseline.upper_bound),
           type: 'line',
-          color: 'confidence-bounds-blue'
+          color: 'screenshot-bounds'
         };
       }
 
@@ -481,7 +483,7 @@ export default Component.extend({
           timestamps: baseline.timestamp,
           values: stripNonFiniteValues(baseline.lower_bound),
           type: 'line',
-          color: 'confidence-bounds-blue'
+          color: 'screenshot-bounds'
         };
       }
       return series;
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/component.js b/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/component.js
index d4d65e9..88b8ad4 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/component.js
@@ -20,7 +20,7 @@ import { getFormattedDuration,
 } from 'thirdeye-frontend/utils/anomaly';
 import RSVP from "rsvp";
 import fetch from 'fetch';
-import { checkStatus, humanizeFloat } from 'thirdeye-frontend/utils/utils';
+import { checkStatus, humanizeFloat, stripNonFiniteValues } from 'thirdeye-frontend/utils/utils';
 import columns from 'thirdeye-frontend/shared/anomaliesTableColumns';
 import moment from 'moment';
 import _ from 'lodash';
@@ -90,9 +90,9 @@ export default Component.extend({
 
       let start = anomalyData.startTime;
       let end = anomalyData.endTime;
-      if (series.current && series.current.timestamps && Array.isArray(series.current.timestamps)) {
-        start = series.current.timestamps[0];
-        end = series.current.timestamps[series.current.timestamps.length - 1];
+      if (series.Current && series.Current.timestamps && Array.isArray(series.Current.timestamps)) {
+        start = series.Current.timestamps[0];
+        end = series.Current.timestamps[series.Current.timestamps.length - 1];
       }
 
       return {
@@ -139,32 +139,51 @@ export default Component.extend({
       const series = {};
 
       if (!_.isEmpty(anomalyData)) {
-        const key = this._formatAnomaly(anomalyData);
+        const key = 'Anomaly';
         series[key] = {
           timestamps: [anomalyData.startTime, anomalyData.endTime],
           values: [1, 1],
           type: 'region',
-          color: 'orange'
+          color: 'screenshot-anomaly'
         };
       }
 
       if (current && !_.isEmpty(current.current)) {
-        series['current'] = {
+        series['Current'] = {
           timestamps: current.timestamp,
           values: current.current,
           type: 'line',
-          color: 'blue'
+          color: 'screenshot-current'
         };
       }
 
       if (predicted && !_.isEmpty(predicted.value)) {
-        series['predicted'] = {
+        series['Predicted'] = {
           timestamps: predicted.timestamp,
           values: predicted.value,
           type: 'line',
-          color: 'orange'
+          color: 'screenshot-predicted'
         };
       }
+
+      if (predicted && !_.isEmpty(predicted.upper_bound)) {
+        series['Upper and lower bound'] = {
+          timestamps: predicted.timestamp,
+          values: stripNonFiniteValues(predicted.upper_bound),
+          type: 'line',
+          color: 'screenshot-bounds'
+        };
+      }
+
+      if (predicted && !_.isEmpty(predicted.lower_bound)) {
+        series['lowerBound'] = {
+          timestamps: predicted.timestamp,
+          values: stripNonFiniteValues(predicted.lower_bound),
+          type: 'line',
+          color: 'screenshot-bounds'
+        };
+      }
+
       return series;
     }
   ),
@@ -207,6 +226,17 @@ export default Component.extend({
     }
   ),
 
+  /**
+   * generates component id using anomalyId
+   */
+  id: computed(
+    'anomalyId',
+    function() {
+      const anomalyId = get(this, 'anomalyId');
+      return `timeseries-chart-anomaly-summary-${anomalyId}`;
+    }
+  ),
+
   _fetchAnomalyData() {
     const anomalyId = get(this, 'anomalyId');
     const anomalyUrl = `/dashboard/anomalies/view/${anomalyId}`;
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/template.hbs
index b0bbdb2..3f73f77 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/template.hbs
@@ -17,6 +17,7 @@
       subchart=subchart
       legend=legend
       point=point
+      id=id
     }}
   </div>
   {{!-- Alert anomaly table --}}
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 e7a3a1a..46fbf60 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/timeseries-chart/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/timeseries-chart/component.js
@@ -40,11 +40,21 @@ export default Component.extend({
   tooltip: {
     format: {
       title: (d) => moment(d).format('MM/DD hh:mm a'),
-      value: (val) => d3.format('.3s')(val)
+      value: (val) => d3.format('.3s')(val),
+      name: (name) => {
+        if (name === 'Upper and lower bound') {
+          return 'Upper bound';
+        } else if (name === 'lowerBound') {
+          return 'Lower bound';
+        }
+        return name;
+      }
     }
   },
 
-  legend: {},
+  legend: {
+    hide: 'lowerBound'
+  },
 
   axis: {
     y: {
@@ -93,6 +103,8 @@ export default Component.extend({
     connectNull: false
   },
 
+  id: 'timeseries-chart',
+
   _makeDiffConfig() {
     const cache = this.get('_seriesCache') || {};
     const series = this.get('series') || {};
@@ -105,6 +117,8 @@ export default Component.extend({
     const changedKeys = seriesKeys.filter(sid => cache[sid] && !_.isEqual(cache[sid], series[sid]));
     const deletedKeys = Object.keys(cache).filter(sid => !series[sid]);
     const regionKeys = seriesKeys.filter(sid => series[sid] && series[sid].type == 'region');
+    // keys containing '-region' should not appear in the graph legend.
+    const noLegendKeys = seriesKeys.filter(sid => (sid.includes('-region')));
 
     const regions = regionKeys.map(sid => {
       const t = series[sid].timestamps;
@@ -117,10 +131,10 @@ export default Component.extend({
       return region;
     });
 
-    const unloadKeys = deletedKeys.concat(regionKeys);
+    const unloadKeys = deletedKeys.concat(noLegendKeys);
     const unload = unloadKeys.concat(unloadKeys.map(sid => `${sid}-timestamps`));
 
-    const loadKeys = addedKeys.concat(changedKeys).filter(sid => !regionKeys.includes(sid));
+    const loadKeys = addedKeys.concat(changedKeys).filter(sid => !noLegendKeys.includes(sid));
     const xs = {};
     loadKeys.forEach(sid => xs[sid] = `${sid}-timestamps`);
 
@@ -138,7 +152,8 @@ export default Component.extend({
 
     const axes = {};
     loadKeys.filter(sid => 'axis' in series[sid]).forEach(sid => axes[sid] = series[sid].axis);
-
+    // keep the lower bound line in graph but remove in from the legend
+    legend.hide = 'lowerBound';
     const config = { unload, xs, columns, types, regions, tooltip, focusedIds, colors, axis, axes, legend };
     return config;
   },
@@ -199,25 +214,27 @@ export default Component.extend({
   },
 
   _shadeBounds(){
-    d3.select(".confidence-bounds").remove();
-    d3.select(".sub-confidence-bounds").remove();
-    d3.select('.timeseries-graph__slider-circle').remove();
-    d3.selectAll('timeseries-graph__slider-line').remove();
+    const parentElement = this.api.internal.config.bindto;
+    d3.select(parentElement).select(".confidence-bounds").remove();
+    d3.select(parentElement).select(".sub-confidence-bounds").remove();
+    d3.select(parentElement).select('.timeseries-graph__slider-circle').remove();
+    d3.select(parentElement).selectAll('timeseries-graph__slider-line').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);
+    // key is 'Upper and lower bound' because we delete the lowerBound key for the legend.
+    if(chart && chart.internal && chart.internal.data && chart.internal.data.xs && Array.isArray(chart.internal.data.xs['Upper and lower bound'])) {
+      const indices = d3.range(chart.internal.data.xs['Upper and lower bound'].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;
+      const xVals = chart.internal.data.xs['Upper and lower bound'];
       let upperBoundVals = chart.internal.data.targets.find(target => {
-        return target.id === 'upperBound';
+        return target.id === 'Upper and lower bound';
       });
       let lowerBoundVals = chart.internal.data.targets.find(target => {
         return target.id === 'lowerBound';
@@ -240,15 +257,15 @@ export default Component.extend({
           .y1(d => yscaleSub(upperBoundVals[d]));
 
         let i = 0;
-        const bothCharts = d3.selectAll('.c3-chart');
+        const bothCharts = d3.select(parentElement).selectAll('.c3-chart-bars');
         bothCharts.each(function() {
           if (i === 0 && this) {
-            d3.select(this).append('path')
+            d3.select(this).insert('path')
               .datum(indices)
               .attr('class', 'confidence-bounds')
               .attr('d', area_main);
           } else if (i === 1 && this) {
-            d3.select(this).append('path')
+            d3.select(this).insert('path')
               .datum(indices)
               .attr('class', 'sub-confidence-bounds')
               .attr('d', area_sub);
@@ -342,6 +359,7 @@ export default Component.extend({
     config.size = this.get('height');
     config.point = this.get('point');
     config.line = this.get('line');
+    config.id = this.get('id');
     config.onrendered = this.get('_shadeBounds');
 
     const chart = c3.generate(config);
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js b/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
index 1f16034..ff8651e 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/route.js
@@ -17,16 +17,18 @@ export default Route.extend({
 
   async model(params) {
     const alertId = params.alert_id;
-    const postProps = {
+    // makes sense to rename this getProps since we are using the get method
+    const getProps = {
       method: 'get',
       headers: { 'content-type': 'application/json' }
     };
     const notifications = get(this, 'notifications');
+    let granularity;
 
     //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) {
@@ -46,12 +48,18 @@ export default Route.extend({
             rawYaml: detection_json.yaml
           });
 
+          try {
+            granularity = detection_json.properties.nested[0].nested[0].nested[0].windowUnit;
+          } catch (error) {
+            granularity = null;
+          }
           this.setProperties({
             alertId: alertId,
             detectionInfo,
-            rawDetectionYaml: get(this, 'detectionInfo') ? get(this, 'detectionInfo').rawYaml : null,
+            rawDetectionYaml: detection_json.yaml,
             metricUrn: detection_json.properties.nested[0].nestedMetricUrns[0],
-            metricUrnList: detection_json.properties.nested[0].nestedMetricUrns
+            metricUrnList: detection_json.properties.nested[0].nestedMetricUrns,
+            granularity
           });
 
         }
@@ -63,7 +71,7 @@ export default Route.extend({
     //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) {
@@ -96,7 +104,8 @@ export default Route.extend({
       detectionYaml: get(this, 'rawDetectionYaml'),
       subscribedGroups,
       metricUrn: get(this, 'metricUrn'),
-      metricUrnList: get(this, 'metricUrnList') ? get(this, 'metricUrnList') : []
+      metricUrnList: get(this, 'metricUrnList') ? get(this, 'metricUrnList') : [],
+      granularity
     });
   }
 });
diff --git a/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs b/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
index c5698d8..8aeef63 100644
--- a/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/manage/explore/template.hbs
@@ -32,6 +32,7 @@
         alertData=model.alertData
         metricUrn=model.metricUrn
         metricUrnList=model.metricUrnList
+        granularity=model.granularity
       }}
     {{/if}}
   </div>
diff --git a/thirdeye/thirdeye-frontend/app/pods/screenshot/controller.js b/thirdeye/thirdeye-frontend/app/pods/screenshot/controller.js
index 245fb7a..85ec1de 100644
--- a/thirdeye/thirdeye-frontend/app/pods/screenshot/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/screenshot/controller.js
@@ -5,7 +5,7 @@ import {
   getProperties
 } from '@ember/object';
 import Controller from '@ember/controller';
-import { humanizeFloat } from 'thirdeye-frontend/utils/utils';
+import { humanizeFloat, stripNonFiniteValues } from 'thirdeye-frontend/utils/utils';
 import moment from 'moment';
 import _ from 'lodash';
 
@@ -63,32 +63,51 @@ export default Controller.extend({
       const series = {};
 
       if (!_.isEmpty(anomalyData)) {
-        const key = this._formatAnomaly(anomalyData);
+        const key = 'Anomaly';
         series[key] = {
           timestamps: [anomalyData.startTime, anomalyData.endTime],
           values: [1, 1],
           type: 'region',
-          color: 'orange'
+          color: 'screenshot-anomaly'
         };
       }
 
       if (current && !_.isEmpty(current.current)) {
-        series['current'] = {
+        series['Current'] = {
           timestamps: current.timestamp,
           values: current.current,
           type: 'line',
-          color: 'blue'
+          color: 'screenshot-current'
         };
       }
 
       if (predicted && !_.isEmpty(predicted.value)) {
-        series['predicted'] = {
+        series['Predicted'] = {
           timestamps: predicted.timestamp,
           values: predicted.value,
           type: 'line',
-          color: 'orange'
+          color: 'screenshot-predicted'
         };
       }
+
+      if (predicted && !_.isEmpty(predicted.upper_bound)) {
+        series['Upper and lower bound'] = {
+          timestamps: predicted.timestamp,
+          values: stripNonFiniteValues(predicted.upper_bound),
+          type: 'line',
+          color: 'screenshot-bounds'
+        };
+      }
+
+      if (predicted && !_.isEmpty(predicted.lower_bound)) {
+        series['lowerBound'] = {
+          timestamps: predicted.timestamp,
+          values: stripNonFiniteValues(predicted.lower_bound),
+          type: 'line',
+          color: 'screenshot-bounds'
+        };
+      }
+
       return series;
     }
   ),
@@ -104,9 +123,9 @@ export default Controller.extend({
 
       let start = anomalyData.startTime;
       let end = anomalyData.endTime;
-      if (series.current && series.current.timestamps && Array.isArray(series.current.timestamps)) {
-        start = series.current.timestamps[0];
-        end = series.current.timestamps[series.current.timestamps.length - 1];
+      if (series.Current && series.Current.timestamps && Array.isArray(series.Current.timestamps)) {
+        start = series.Current.timestamps[0];
+        end = series.Current.timestamps[series.Current.timestamps.length - 1];
       }
 
       return {
diff --git a/thirdeye/thirdeye-frontend/app/styles/components/timeseries-chart.scss b/thirdeye/thirdeye-frontend/app/styles/components/timeseries-chart.scss
index 8247c05..7ea824e 100644
--- a/thirdeye/thirdeye-frontend/app/styles/components/timeseries-chart.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/components/timeseries-chart.scss
@@ -1,28 +1,28 @@
 // Overriding default c3
 
 .timeseries-chart {
-  .c3-target-baseline {
+  .c3-target-Baseline {
     stroke-dasharray: 2,2;
   }
-  .c3-target-predicted {
+  .c3-target-Predicted {
     stroke-dasharray: 2,2;
   }
-  .c3-target-upperBound {
+  .c3-target-Upper-and-lower-bound{
     opacity: 0.5 !important;
   }
   .c3-target-lowerBound {
     opacity: 0.5 !important;
   }
   path.confidence-bounds {
-    stroke: #dcf8f3;
+    stroke: #1CAFED;
     stroke-opacity: 0.5;
-	  fill: #dcf8f3;
+	  fill: #1CAFED;
     opacity: 0.5
  }
- path.sub-confidence-bounds {
-   stroke: #dcf8f3;
-   stroke-opacity: 0.5;
-   fill: #dcf8f3;
-   opacity: 0.5
-}
+   path.sub-confidence-bounds {
+     stroke: #1CAFED;
+     stroke-opacity: 0.5;
+     fill: #1CAFED;
+     opacity: 0.5
+  }
 };
diff --git a/thirdeye/thirdeye-frontend/app/utils/rca-utils.js b/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
index f10d6a0..df263c0 100644
--- a/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
+++ b/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
@@ -23,7 +23,11 @@ export const colorMapping = {
   'light-teal': '#98DADE',
   'light-pink': '#FFB9E2',
   'light-grey': '#CFCFCF',
-  'confidence-bounds-blue' : '#dcf8f3'
+  'confidence-bounds-blue' : '#dcf8f3',
+  'screenshot-current' : '#622570',
+  'screenshot-predicted' : '#EA168E',
+  'screenshot-anomaly' : '#EEF2F5',
+  'screenshot-bounds' : '#1CAFED'
 };
 
 // TODO load from config


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