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/04/26 00:55:58 UTC
[incubator-pinot] branch master updated: [TE] frontend -
harleyjj/anomalies - enable anomalyIds param for email linking to new
anomalies route (#4164)
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 b7b6ba3 [TE] frontend - harleyjj/anomalies - enable anomalyIds param for email linking to new anomalies route (#4164)
b7b6ba3 is described below
commit b7b6ba35dc6b5c39e5f4527e063e021c112a877c
Author: Harley Jackson <hj...@linkedin.com>
AuthorDate: Thu Apr 25 17:55:53 2019 -0700
[TE] frontend - harleyjj/anomalies - enable anomalyIds param for email linking to new anomalies route (#4164)
---
.../app/pods/anomalies/controller.js | 121 ++++++++++-----------
.../thirdeye-frontend/app/pods/anomalies/route.js | 69 ++++++++----
.../pods/components/anomaly-summary/component.js | 8 +-
.../pods/components/anomaly-summary/template.hbs | 1 +
thirdeye/thirdeye-frontend/app/utils/anomaly.js | 24 +++-
.../thirdeye-frontend/app/utils/api/anomaly.js | 20 +++-
6 files changed, 147 insertions(+), 96 deletions(-)
diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js b/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
index 13be4dd..f2bc02b 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
@@ -9,8 +9,7 @@ import {
get,
computed,
getProperties,
- setProperties,
- observer
+ setProperties
} from '@ember/object';
import { inject as service } from '@ember/service';
import { isPresent, isEmpty } from '@ember/utils';
@@ -97,33 +96,6 @@ export default Controller.extend({
}
),
- // When the user changes the time range, this will fetch the anomaly ids
- updateVisuals: observer(
- 'anomaliesRange',
- function() {
- const {
- anomaliesRange,
- updateAnomalies
- } = this.getProperties('anomaliesRange', 'updateAnomalies');
- set(this, 'isLoading', true);
- const [ start, end ] = anomaliesRange;
- updateAnomalies(start, end)
- .then(res => {
- this.setProperties({
- anomaliesById: res,
- anomalyIds: res.anomalyIds
- });
- this._resetLocalFilters();
- set(this, 'isLoading', false);
- })
- .catch(() => {
- this._resetLocalFilters();
- set(this, 'isLoading', false);
- });
-
- }
- ),
-
// Total Number of pages to display
pagesNum: computed(
'totalAnomalies',
@@ -159,21 +131,21 @@ export default Controller.extend({
// return list of anomalyIds according to filter(s) applied
selectedAnomalies: computed(
'anomalyFilters',
- 'anomalyIds',
+ 'anomalyIdList',
'activeFiltersString',
function() {
const {
- anomalyIds,
+ anomalyIdList,
anomalyFilters,
anomaliesById,
activeFiltersString
- } = this.getProperties('anomalyIds', 'anomalyFilters', 'anomaliesById', 'activeFiltersString');
+ } = this.getProperties('anomalyIdList', 'anomalyFilters', 'anomaliesById', 'activeFiltersString');
const filterMaps = ['statusFilterMap', 'functionFilterMap', 'datasetFilterMap', 'metricFilterMap', 'dimensionFilterMap'];
if (activeFiltersString === 'All Anomalies') {
// no filter applied, just return all
- return anomalyIds;
+ return anomalyIdList;
}
- let selectedAnomalies = anomalyIds;
+ let selectedAnomalies = anomalyIdList;
filterMaps.forEach(map => {
const selectedFilters = anomalyFilters[map];
// When a filter gets deleted, it leaves an empty array behind. We need to treat null and empty array the same here
@@ -283,6 +255,34 @@ export default Controller.extend({
}
),
+ // When the user changes the time range, this will fetch the anomaly ids
+ _updateVisuals() {
+ const {
+ anomaliesRange,
+ updateAnomalies,
+ anomalyIds
+ } = this.getProperties('anomaliesRange', 'updateAnomalies', 'anomalyIds');
+ set(this, 'isLoading', true);
+ const [ start, end ] = anomaliesRange;
+ if (anomalyIds) {
+ set(this, 'anomalyIds', null);
+ } else {
+ updateAnomalies(start, end)
+ .then(res => {
+ this.setProperties({
+ anomaliesById: res,
+ anomalyIdList: res.anomalyIds
+ });
+ this._resetLocalFilters();
+ set(this, 'isLoading', false);
+ })
+ .catch(() => {
+ this._resetLocalFilters();
+ set(this, 'isLoading', false);
+ });
+ }
+ },
+
/**
* When user chooses to either find an alert by name, or use a global filter,
* we should re-set all local filters.
@@ -298,24 +298,21 @@ export default Controller.extend({
// Fill in select options for these filters ('filterKeys') based on alert properties from model.alerts
newFilterBlocksLocal.forEach((filter) => {
- if (filter.name === "dimensionFilterMap") {
+ let filterKeys = [];
+ if (filter.name === "dimensionFilterMap" && isPresent(anomaliesById.searchFilters[filter.name])) {
const anomalyPropertyArray = Object.keys(anomaliesById.searchFilters[filter.name]);
- let filterKeys = [];
anomalyPropertyArray.forEach(dimensionType => {
let group = Object.keys(anomaliesById.searchFilters[filter.name][dimensionType]);
group = group.map(dim => `${dimensionType}::${dim}`);
filterKeys = [...filterKeys, ...group];
});
- Object.assign(filter, { filterKeys });
} else if (filter.name === "subscriptionFilterMap"){
- const filterKeys = this.get('store')
+ filterKeys = this.get('store')
.peekAll('subscription-groups')
.sortBy('name')
.filter(group => (group.get('active') && group.get('yaml')))
.map(group => group.get('name'));
- // Add filterKeys prop to each facet or filter block
- Object.assign(filter, { filterKeys });
- } else if (filter.name === "statusFilterMap"){
+ } else if (filter.name === "statusFilterMap" && isPresent(anomaliesById.searchFilters[filter.name])){
let anomalyPropertyArray = Object.keys(anomaliesById.searchFilters[filter.name]);
anomalyPropertyArray = anomalyPropertyArray.map(prop => {
// get the right object
@@ -323,17 +320,16 @@ export default Controller.extend({
// map the status to name
return mapping.length > 0 ? mapping[0].name : prop;
});
- const filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
- // Add filterKeys prop to each facet or filter block
- Object.assign(filter, { filterKeys });
+ filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
} else {
- const anomalyPropertyArray = Object.keys(anomaliesById.searchFilters[filter.name]);
- const filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
- // Add filterKeys prop to each facet or filter block
- Object.assign(filter, { filterKeys });
+ if (isPresent(anomaliesById.searchFilters[filter.name])) {
+ const anomalyPropertyArray = Object.keys(anomaliesById.searchFilters[filter.name]);
+ filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
+ }
}
+ // Add filterKeys prop to each facet or filter block
+ Object.assign(filter, { filterKeys });
});
-
// Reset local (secondary) filters, and set select fields to 'disabled'
setProperties(this, {
filterBlocksLocal: newFilterBlocksLocal,
@@ -345,15 +341,13 @@ export default Controller.extend({
// method to union anomalyId arrays for filters applied of same type
_unionOfArrays(anomaliesById, filterType, selectedFilters) {
//handle dimensions separately, since they are nested
- if (filterType === 'dimensionFilterMap') {
- let addedIds = [];
+ let addedIds = [];
+ if (filterType === 'dimensionFilterMap' && isPresent(anomaliesById.searchFilters[filterType])) {
selectedFilters.forEach(filter => {
const [type, dimension] = filter.split('::');
addedIds = [...addedIds, ...anomaliesById.searchFilters.dimensionFilterMap[type][dimension]];
});
- return addedIds;
- } else if (filterType === 'statusFilterMap'){
- let addedIds = [];
+ } else if (filterType === 'statusFilterMap' && isPresent(anomaliesById.searchFilters[filterType])){
const translatedFilters = selectedFilters.map(f => {
// get the right object
const mapping = anomalyResponseObjNew.filter(e => (e.name === f));
@@ -363,17 +357,17 @@ export default Controller.extend({
translatedFilters.forEach(filter => {
addedIds = [...addedIds, ...anomaliesById.searchFilters[filterType][filter]];
});
- return addedIds;
} else {
- let addedIds = [];
- selectedFilters.forEach(filter => {
- // If there are no anomalies from the time range with these filters, then the result will be null, so we handle that here
- // It can happen for functionFilterMap only, because we are using subscription groups to map to alert names (function filters)
- const anomalyIdsInResponse = anomaliesById.searchFilters[filterType][filter];
- addedIds = anomalyIdsInResponse ? [...addedIds, ...anomaliesById.searchFilters[filterType][filter]] : addedIds;
- });
- return addedIds;
+ if (isPresent(anomaliesById.searchFilters[filterType])) {
+ selectedFilters.forEach(filter => {
+ // If there are no anomalies from the time range with these filters, then the result will be null, so we handle that here
+ // It can happen for functionFilterMap only, because we are using subscription groups to map to alert names (function filters)
+ const anomalyIdsInResponse = anomaliesById.searchFilters[filterType][filter];
+ addedIds = anomalyIdsInResponse ? [...addedIds, ...anomaliesById.searchFilters[filterType][filter]] : addedIds;
+ });
+ }
}
+ return addedIds;
},
// method to intersect anomalyId arrays for filters applied of different types
@@ -462,6 +456,7 @@ export default Controller.extend({
//Update the time range option selected
set(this, 'anomaliesRange', [startDate, endDate]);
set(this, 'duration', duration);
+ this._updateVisuals();
},
// Handles UI sort change
diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js b/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
index 9fb9583..9701ca5 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
@@ -2,12 +2,22 @@ import { hash } from 'rsvp';
import Route from '@ember/routing/route';
import moment from 'moment';
import { inject as service } from '@ember/service';
+import { isPresent } from '@ember/utils';
import { powerSort } from 'thirdeye-frontend/utils/manage-alert-utils';
-import { getAnomalyIdsByTimeRange, anomalyResponseObjNew } from 'thirdeye-frontend/utils/anomaly';
+import {
+ getAnomalyFiltersByTimeRange,
+ getAnomalyFiltersByAnomalyId,
+ anomalyResponseObjNew } from 'thirdeye-frontend/utils/anomaly';
+import _ from 'lodash';
const start = moment().subtract(1, 'day').valueOf();
const end = moment().valueOf();
+const queryParamsConfig = {
+ refreshModel: true,
+ replace: false
+};
+
export default Route.extend({
// Make duration service accessible
@@ -15,17 +25,35 @@ export default Route.extend({
anomaliesApiService: service('services/api/anomalies'),
session: service(),
store: service('store'),
+ queryParams: {
+ anomalyIds: queryParamsConfig
+ },
+ anomalyIds: null,
- async model() {
- const anomaliesById = await getAnomalyIdsByTimeRange(start, end);
+ async model(params) {
+ // anomalyIds param allows for clicking into the route from email and listing a specific set of anomalyIds
+ let { anomalyIds } = params;
+ const anomaliesById = anomalyIds ? await getAnomalyFiltersByAnomalyId(start, end, anomalyIds) : await getAnomalyFiltersByTimeRange(start, end);
const subscriptionGroups = await this.get('anomaliesApiService').querySubscriptionGroups(); // Get all subscription groups available
return hash({
- updateAnomalies: getAnomalyIdsByTimeRange,
+ updateAnomalies: getAnomalyFiltersByTimeRange,
anomaliesById,
- subscriptionGroups
+ subscriptionGroups,
+ anomalyIds
});
},
+ afterModel(model) {
+ // If we set anomalyIds to null in the controller, the route will refresh and clear the params from url
+ const anomalyIds = model.anomalyIds || null;
+ this.set('anomalyIds', anomalyIds);
+ const defaultParams = {
+ anomalyIds
+ };
+ Object.assign(model, { ...defaultParams});
+ return model;
+ },
+
setupController(controller, model) {
// This filter category is "secondary". To add more, add an entry here and edit the controller's "filterToPropertyMap"
@@ -72,16 +100,15 @@ export default Route.extend({
// Fill in select options for these filters ('filterKeys') based on alert properties from model.alerts
filterBlocksLocal.forEach((filter) => {
- if (filter.name === "dimensionFilterMap") {
+ let filterKeys = [];
+ if (filter.name === "dimensionFilterMap" && isPresent(model.anomaliesById.searchFilters[filter.name])) {
const anomalyPropertyArray = Object.keys(model.anomaliesById.searchFilters[filter.name]);
- let filterKeys = [];
anomalyPropertyArray.forEach(dimensionType => {
let group = Object.keys(model.anomaliesById.searchFilters[filter.name][dimensionType]);
group = group.map(dim => `${dimensionType}::${dim}`);
filterKeys = [...filterKeys, ...group];
});
- Object.assign(filter, { filterKeys });
- } else if (filter.name === "statusFilterMap"){
+ } else if (filter.name === "statusFilterMap" && isPresent(model.anomaliesById.searchFilters[filter.name])){
let anomalyPropertyArray = Object.keys(model.anomaliesById.searchFilters[filter.name]);
anomalyPropertyArray = anomalyPropertyArray.map(prop => {
// get the right object
@@ -89,28 +116,25 @@ export default Route.extend({
// map the status to name
return mapping.length > 0 ? mapping[0].name : prop;
});
- const filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
- // Add filterKeys prop to each facet or filter block
- Object.assign(filter, { filterKeys });
+ filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
} else if (filter.name === "subscriptionFilterMap"){
- const filterKeys = this.get('store')
+ filterKeys = this.get('store')
.peekAll('subscription-groups')
.sortBy('name')
.filter(group => (group.get('active') && group.get('yaml')))
.map(group => group.get('name'));
- // Add filterKeys prop to each facet or filter block
- Object.assign(filter, { filterKeys });
} else {
- const anomalyPropertyArray = Object.keys(model.anomaliesById.searchFilters[filter.name]);
- const filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
- // Add filterKeys prop to each facet or filter block
- Object.assign(filter, { filterKeys });
+ if (isPresent(model.anomaliesById.searchFilters[filter.name])) {
+ const anomalyPropertyArray = Object.keys(model.anomaliesById.searchFilters[filter.name]);
+ filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
+ }
}
+ Object.assign(filter, { filterKeys });
});
// Keep an initial copy of the secondary filter blocks in memory
Object.assign(model, {
- initialFiltersLocal: filterBlocksLocal
+ initialFiltersLocal: _.cloneDeep(filterBlocksLocal)
});
// Send filters to controller
controller.setProperties({
@@ -119,9 +143,10 @@ export default Route.extend({
resultsActive: true,
updateAnomalies: model.updateAnomalies, //requires start and end time in epoch ex updateAnomalies(start, end)
filterBlocksLocal,
- anomalyIds: model.anomaliesById.anomalyIds,
+ anomalyIdList: model.anomaliesById.anomalyIds,
anomaliesRange: [start, end],
- subscriptionGroups: model.subscriptionGroups
+ subscriptionGroups: model.subscriptionGroups,
+ anomalyIds: this.get('anomalyIds')
});
},
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 0f3e4fb..6e90868 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/component.js
@@ -10,7 +10,7 @@ import {
computed,
getProperties
} from '@ember/object';
-import { colorMapping, toColor, makeTime } from 'thirdeye-frontend/utils/rca-utils';
+import { colorMapping, makeTime } from 'thirdeye-frontend/utils/rca-utils';
import { getFormattedDuration,
anomalyResponseMapNew,
verifyAnomalyFeedback,
@@ -61,10 +61,14 @@ export default Component.extend({
rescale: true
},
+ // legend and point are for the graph
legend: {
show: true,
position: 'right'
},
+ point: {
+ show: false
+ },
isLoading: false,
feedbackOptions: ['Not reviewed yet', 'Yes - unexpected', 'Expected temporary change', 'Expected permanent change', 'No change observed'],
labelMap: anomalyResponseMapNew,
@@ -216,7 +220,7 @@ export default Component.extend({
set(this, 'predicted', res.predicted);
set(this, 'isLoading', false);
})
- .catch(err => {
+ .catch(() => {
set(this, 'isLoading', false);
});
},
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 f8f3bfe..b0bbdb2 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/template.hbs
@@ -16,6 +16,7 @@
zoom=zoom
subchart=subchart
legend=legend
+ point=point
}}
</div>
{{!-- Alert anomaly table --}}
diff --git a/thirdeye/thirdeye-frontend/app/utils/anomaly.js b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
index b7ec6f4..fe16601 100644
--- a/thirdeye/thirdeye-frontend/app/utils/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
@@ -11,7 +11,8 @@ import {
anomalyApiUrls,
getAnomaliesForYamlPreviewUrl,
getAnomaliesByAlertIdUrl,
- getAnomalyIdsByTimeRangeUrl
+ getAnomalyFiltersByTimeRangeUrl,
+ getAnomalyFiltersByAnomalyIdUrl
} from 'thirdeye-frontend/utils/api/anomaly';
/**
@@ -122,14 +123,27 @@ export function getAnomaliesByAlertId(alertId, startTime, endTime) {
}
/**
- * Get anomaly ids over a specified time range
- * @method getAnomalyIdsByTimeRange
+ * Get anomaly filters over a specified time range
+ * @method getAnomalyFiltersByTimeRange
* @param {Number} startTime - start time of query range
* @param {Number} endTime - end time of query range
* @return {Ember.RSVP.Promise}
*/
-export function getAnomalyIdsByTimeRange(startTime, endTime) {
- const url = getAnomalyIdsByTimeRangeUrl(startTime, endTime);
+export function getAnomalyFiltersByTimeRange(startTime, endTime) {
+ const url = getAnomalyFiltersByTimeRangeUrl(startTime, endTime);
+ return fetch(url).then(checkStatus);
+}
+
+/**
+ * Get anomaly filters for specified anomaly ids
+ * @method getAnomalyFiltersByAnomalyId
+ * @param {Number} startTime - start time of query range
+ * @param {Number} endTime - end time of query range
+ * @param {String} anomalyIds - string of comma delimited anomaly ids
+ * @return {Ember.RSVP.Promise}
+ */
+export function getAnomalyFiltersByAnomalyId(startTime, endTime, anomalyIds) {
+ const url = getAnomalyFiltersByAnomalyIdUrl(startTime, endTime, anomalyIds);
return fetch(url).then(checkStatus);
}
diff --git a/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js b/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
index d17c1d1..5d4d077 100644
--- a/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/api/anomaly.js
@@ -32,20 +32,32 @@ export function getAnomaliesByAlertIdUrl(alertId, startTime, endTime) {
}
/**
- * Returns the url for getting anomaly ids of all anomalies over the specified time range
+ * Returns the url for getting anomaly filters of all anomalies over the specified time range
* @param {Number} startTime - beginning of time range of interest
* @param {Number} endTime - end of time range of interest
- * @example getAnomalyIdsByTimeRangeUrl(1508472700000, 1508472800000) // yields => /anomalies/search/time/1508472700000/1508472800000/1?filterOnly=true
+ * @example getAnomalyFiltersByTimeRangeUrl(1508472700000, 1508472800000) // yields => /anomalies/search/time/1508472700000/1508472800000/1?filterOnly=true
*/
-export function getAnomalyIdsByTimeRangeUrl(startTime, endTime) {
+export function getAnomalyFiltersByTimeRangeUrl(startTime, endTime) {
return `/anomalies/search/time/${startTime}/${endTime}/1?filterOnly=true`;
}
+/**
+ * Returns the url for getting anomaly filters of anomalies with given id's
+ * @param {Number} startTime - beginning of time range of interest
+ * @param {Number} endTime - end of time range of interest
+ * @param {String} anomalyIds - string of comma delimitedanomaly ids
+ * @example getAnomalyFiltersByAnomalyIdUrl(1508472700000, 1508472800000, anomalyIds) // yields => /anomalies/search/anomalyIds/1508472700000/1508472800000/1?anomalyIds={anomalyIds}
+ */
+export function getAnomalyFiltersByAnomalyIdUrl(startTime, endTime, anomalyIds) {
+ return `/anomalies/search/anomalyIds/${startTime}/${endTime}/1?anomalyIds=${encodeURIComponent(anomalyIds)}`;
+}
+
export const anomalyApiUrls = {
getAnomalyDataUrl,
getAnomaliesForYamlPreviewUrl,
getAnomaliesByAlertIdUrl,
- getAnomalyIdsByTimeRangeUrl
+ getAnomalyFiltersByTimeRangeUrl,
+ getAnomalyFiltersByAnomalyIdUrl
};
export default {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org