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