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 <hjack...@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-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org