You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by xh...@apache.org on 2019/04/17 17:19:51 UTC
[incubator-pinot] branch master updated: [TE] frontend -
harleyjj/home - get all anomalies by subscription group (#4118)
This is an automated email from the ASF dual-hosted git repository.
xhsun 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 20c2b15 [TE] frontend - harleyjj/home - get all anomalies by subscription group (#4118)
20c2b15 is described below
commit 20c2b150bd861d30fcb8303042357cf31e75bcce
Author: Harley Jackson <ha...@gmail.com>
AuthorDate: Wed Apr 17 10:19:45 2019 -0700
[TE] frontend - harleyjj/home - get all anomalies by subscription group (#4118)
---
.../app/adapters/subscription-groups.js | 10 +++
.../app/models/subscription-groups.js | 10 +++
.../app/pods/home/index/controller.js | 62 +++++++++++++++++-
.../thirdeye-frontend/app/pods/home/index/route.js | 45 +++++++++----
.../app/pods/home/index/template.hbs | 53 ++++++++++++----
.../app/pods/services/api/anomalies/service.js | 73 ++++++++++++++++++++--
.../app/styles/pods/home/index/dashboard.scss | 10 ++-
7 files changed, 228 insertions(+), 35 deletions(-)
diff --git a/thirdeye/thirdeye-frontend/app/adapters/subscription-groups.js b/thirdeye/thirdeye-frontend/app/adapters/subscription-groups.js
new file mode 100644
index 0000000..82df566
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/adapters/subscription-groups.js
@@ -0,0 +1,10 @@
+import BaseAdapter from './base';
+
+export default BaseAdapter.extend({
+ //for api call - /detection/subscription-groups
+ namespace: '/detection',
+ pathForType(type) {
+ // path customization is needed here since ember data will convert to camel case by default
+ return type.dasherize();
+ }
+});
diff --git a/thirdeye/thirdeye-frontend/app/models/subscription-groups.js b/thirdeye/thirdeye-frontend/app/models/subscription-groups.js
new file mode 100644
index 0000000..c7cd9a3
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/models/subscription-groups.js
@@ -0,0 +1,10 @@
+import DS from 'ember-data';
+
+export default DS.Model.extend({
+ name: DS.attr(),
+ active: DS.attr(),
+ createdBy: DS.attr(),
+ updatedBy: DS.attr(),
+ properties: DS.attr(),
+ yaml: DS.attr()
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/home/index/controller.js b/thirdeye/thirdeye-frontend/app/pods/home/index/controller.js
index ec1543e..c1d5716 100644
--- a/thirdeye/thirdeye-frontend/app/pods/home/index/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/home/index/controller.js
@@ -18,6 +18,8 @@ export default Controller.extend({
toggleCollapsed: false, /* hide/show accordians */
isReportAnomalyEnabled: false,
store: service('store'),
+ anomaliesByOptions: ['Application', 'Subscription Group'],
+ anomaliesBySelected: 'Application',
/**
* Overrides ember-models-table's css classes
@@ -41,6 +43,22 @@ export default Controller.extend({
});
},
+ /**
+ * Flag for showing appropriate dropdown
+ * @type {Boolean}
+ */
+ byApplication: computed(
+ 'anomaliesBySelected',
+ function() {
+ return (get(this, 'anomaliesBySelected') === 'Application');
+ }
+ ),
+
+ /**
+ * Grabs Ember Data objects from the application collection - peekAll will look at what's in the store without requesting from the backend
+ * Sorts the array of application objects by the field "application" and returns it
+ * @type {Array}
+ */
sortedApplications: computed(
'model.applications',
function() {
@@ -50,6 +68,22 @@ export default Controller.extend({
}
),
+ /**
+ * Grabs Ember Data objects from the subscription-groups collection - peekAll will look at what's in the store without requesting from the backend
+ * Sorts the array of application objects by the field "application", filters objects that are inactive or lack yaml fields and returns it
+ * @type {Array}
+ */
+ sortedSubscriptionGroups: computed(
+ 'subscriptionGroups',
+ function() {
+ // Iterate through each anomaly
+ let groups = this.get('store').peekAll('subscription-groups')
+ .sortBy('name')
+ .filter(group => (group.get('active') && group.get('yaml')));
+ return groups;
+ }
+ ),
+
filteredAnomalyMapping: computed(
'model.{anomalyMapping,feedbackType}',
function() {
@@ -217,11 +251,28 @@ export default Controller.extend({
/**
* Sets the selected application property based on user selection
+ * Sets subGroup to null since these two have a NAND relationship in the UI
* @param {Object} selectedApplication - object that represents selected application
* @return {undefined}
*/
selectApplication(selectedApplication) {
- set(this, 'appName', selectedApplication.get('application'));
+ this.setProperties({
+ appName: selectedApplication.get('application'),
+ subGroup: null
+ });
+ },
+
+ /**
+ * Sets the selected subscription group property based on user selection
+ * Sets appName to null since these two have a NAND relationship in the UI
+ * @param {Object} selectedSubGroup - object that represents selected subscription group
+ * @return {undefined}
+ */
+ selectSubscriptionGroup(selectedSubGroup) {
+ this.setProperties({
+ subGroup: selectedSubGroup.get('name'),
+ appName: null
+ });
},
/**
@@ -253,6 +304,15 @@ export default Controller.extend({
onFilterBy(feedbackType, selected) {
const feedbackItem = this._checkFeedback(selected);
set(this, 'feedbackType', feedbackItem.name);
+ },
+
+ /**
+ * Switch between getting anomalies by Application or Subscription Group
+ * @method onAnomaliesBy
+ * @param {String} selected - the selection item
+ */
+ onAnomaliesBy(selected) {
+ set(this, 'anomaliesBySelected', selected);
}
}
});
diff --git a/thirdeye/thirdeye-frontend/app/pods/home/index/route.js b/thirdeye/thirdeye-frontend/app/pods/home/index/route.js
index 86e70e2..5682f54 100644
--- a/thirdeye/thirdeye-frontend/app/pods/home/index/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/home/index/route.js
@@ -21,14 +21,16 @@ export default Route.extend(AuthenticatedRouteMixin, {
startDate: queryParamsConfig,
endDate: queryParamsConfig,
duration: queryParamsConfig,
- feedbackType: queryParamsConfig
+ feedbackType: queryParamsConfig,
+ subGroup: queryParamsConfig
},
- applicationAnomalies: null,
+ anomalies: null,
appName: null,
startDate: moment().startOf('day').utc().valueOf(), //set default to 0:00 for data consistency
endDate: moment().startOf('day').add(1, 'day').utc().valueOf(), //set default to 0:00 for data consistency
duration: 'today', //set default to today
feedbackType: 'All Resolutions',
+ subGroup: null,
/**
* Returns a mapping of anomalies by metric and functionName (aka alert), performance stats for anomalies by
@@ -47,8 +49,9 @@ export default Route.extend(AuthenticatedRouteMixin, {
* }
*/
async model(params) {
- const { appName, startDate, endDate, duration, feedbackType } = params;//check params
+ const { appName, startDate, endDate, duration, feedbackType, subGroup } = params;//check params
const applications = await this.get('anomaliesApiService').queryApplications(appName, startDate);// Get all applicatons available
+ const subscriptionGroups = await this.get('anomaliesApiService').querySubscriptionGroups(); // Get all subscription groups available
return hash({
appName,
@@ -56,7 +59,9 @@ export default Route.extend(AuthenticatedRouteMixin, {
endDate,
duration,
applications,
- feedbackType
+ subscriptionGroups,
+ feedbackType,
+ subGroup
});
},
@@ -67,19 +72,21 @@ export default Route.extend(AuthenticatedRouteMixin, {
const endDate = Number(model.endDate) || this.get('endDate');
const duration = model.duration || this.get('duration');
const feedbackType = model.feedbackType || this.get('feedbackType');
+ const subGroup = model.subGroup || null;
// Update props
this.setProperties({
appName,
startDate,
endDate,
duration,
- feedbackType
+ feedbackType,
+ subGroup
});
return new RSVP.Promise(async (resolve, reject) => {
try {
- const anomalyMapping = appName ? await this.get('_getAnomalyMapping').perform(model) : [];
- const alertsByMetric = appName ? this.getAlertsByMetric() : [];
+ const anomalyMapping = (appName || subGroup) ? await this.get('_getAnomalyMapping').perform(model) : [];
+ const alertsByMetric = (appName || subGroup) ? this.getAlertsByMetric() : [];
const defaultParams = {
anomalyMapping,
appName,
@@ -87,7 +94,8 @@ export default Route.extend(AuthenticatedRouteMixin, {
endDate,
duration,
feedbackType,
- alertsByMetric
+ alertsByMetric,
+ subGroup
};
// Update model
resolve(Object.assign(model, { ...defaultParams }));
@@ -99,16 +107,24 @@ export default Route.extend(AuthenticatedRouteMixin, {
_getAnomalyMapping: task (function * () {//TODO: need to add to anomaly util - LH
let anomalyMapping = {};
+ let anomalies;
//fetch the anomalies from the onion wrapper cache.
- const applicationAnomalies = yield this.get('anomaliesApiService').queryAnomaliesByAppName(this.get('appName'), this.get('startDate'), this.get('endDate'));
+ if (this.get('appName') && this.get('subGroup')) {
+ //this functionality is not provided in the UI, but the user can manually type the params into URL simultaneously
+ anomalies = yield this.get('anomaliesApiService').queryAnomaliesByJoin(this.get('appName'), this.get('subGroup'), this.get('startDate'), this.get('endDate'));
+ } else if (this.get('appName')) {
+ anomalies = yield this.get('anomaliesApiService').queryAnomaliesByAppName(this.get('appName'), this.get('startDate'), this.get('endDate'));
+ } else {
+ anomalies = yield this.get('anomaliesApiService').queryAnomaliesBySubGroup(this.get('subGroup'), this.get('startDate'), this.get('endDate'));
+ }
const humanizedObject = {
queryDuration: this.get('duration'),
queryStart: this.get('startDate'),
queryEnd: this.get('endDate')
};
- this.set('applicationAnomalies', applicationAnomalies);
+ this.set('anomalies', anomalies);
- applicationAnomalies.forEach(anomaly => {
+ anomalies.forEach(anomaly => {
const metricName = anomaly.get('metricName');
//Grouping the anomalies of the same metric name
if (!anomalyMapping[metricName]) {
@@ -128,7 +144,7 @@ export default Route.extend(AuthenticatedRouteMixin, {
*/
getAlertsByMetric() {
const metricsObj = {};
- this.get('applicationAnomalies').forEach(anomaly => {
+ this.get('anomalies').forEach(anomaly => {
let functionName = anomaly.get('functionName');
let functionId = anomaly.get('functionId');
let metricName = anomaly.get('metricName');
@@ -154,7 +170,10 @@ export default Route.extend(AuthenticatedRouteMixin, {
columns,
appNameSelected: model.applications.findBy('application', this.get('appName')),
appName: this.get('appName'),
- anomaliesCount: this.get('applicationAnomalies.content') ? this.get('applicationAnomalies.content').length : 0
+ anomaliesCount: this.get('anomalies.content') ? this.get('anomalies.content').length : 0,
+ subGroupSelected: model.subscriptionGroups.findBy('name', this.get('subGroup')),
+ subGroup: this.get('subGroup'),
+ subscriptionGroups: model.subscriptionGroups
});
},
diff --git a/thirdeye/thirdeye-frontend/app/pods/home/index/template.hbs b/thirdeye/thirdeye-frontend/app/pods/home/index/template.hbs
index fbe5c12..05ec59e 100644
--- a/thirdeye/thirdeye-frontend/app/pods/home/index/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/home/index/template.hbs
@@ -19,19 +19,46 @@
<article class="dashboard-container__body">
<section class="dashboard-container__application-header">
- <h2 class="dashboard-container__title">Application:<span>{{appName}}</span></h2>
- <div class="dashboard-container__application-header-dropdown">
- {{#power-select
- options=sortedApplications
- selected=appNameSelected
- searchField="application"
- searchEnabled=true
- placeholder="Please pick an application"
- onchange=(action "selectApplication")
- as |app|}}
- {{app.application}}
- {{/power-select}}
- </div>
+ <h3 class="dashboard-container__title">Anomalies Filtered by: </h3>
+ {{#power-select
+ triggerId="dash-anomalies-by"
+ triggerClass="te-anomaly-table__select te-anomaly-table__select--margin-left"
+ options=anomaliesByOptions
+ searchEnabled=false
+ selected=anomaliesBySelected
+ onchange=(action "onAnomaliesBy")
+ as |getBy|
+ }}
+ {{getBy}}
+ {{/power-select}}
+
+ {{#if byApplication}}
+ <div class="dashboard-container__application-header-dropdown">
+ {{#power-select
+ options=sortedApplications
+ selected=appNameSelected
+ searchField="application"
+ searchEnabled=true
+ placeholder="Please pick an application"
+ onchange=(action "selectApplication")
+ as |app|}}
+ {{app.application}}
+ {{/power-select}}
+ </div>
+ {{else}}
+ <div class="dashboard-container__application-header-dropdown">
+ {{#power-select
+ options=sortedSubscriptionGroups
+ selected=subGroupSelected
+ searchField="name"
+ searchEnabled=true
+ placeholder="Please pick a subscription group"
+ onchange=(action "selectSubscriptionGroup")
+ as |subGroup|}}
+ {{subGroup.name}}
+ {{/power-select}}
+ </div>
+ {{/if}}
</section>
{{#if (gt anomaliesCount 0)}}
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 585ee42..b76be93 100644
--- a/thirdeye/thirdeye-frontend/app/pods/services/api/anomalies/service.js
+++ b/thirdeye/thirdeye-frontend/app/pods/services/api/anomalies/service.js
@@ -126,22 +126,85 @@ export default Service.extend({
},
/**
- * @summary Fetch all anomalies by application name and start time
+ * @summary Fetch all subscription group names. We can use it to list in the select box.
+ * @method querySubscriptionGroups
+ * @return {Ember.RSVP.Promise}
+ * @example: /detection/subscription-groups
+ usage: `this.get('anomaliesApiService').querySubscriptionGroups();`
+ */
+ async querySubscriptionGroups() {
+ const queryCache = this.get('queryCache');
+ const modelName = 'subscription-groups';
+ const cacheKey = queryCache.urlForQueryKey(modelName, {});
+ const groups = await queryCache.query(modelName, {}, { reload: false, cacheKey });
+ return groups;
+ },
+
+ /**
+ * @summary Fetch all anomalies by application name and time range
* @method queryAnomaliesByAppName
* @param {String} appName - the application name
- * @param {Number} startStamp - the anomaly iso start time
+ * @param {Number} start - the anomaly iso start time
+ * @param {Number} end - the anomaly iso end time
* @return {Ember.RSVP.Promise}
* @example: for call `/userdashboard/anomalies?application={someAppName}&start={1508472800000}`
- usage: `this.get('anomaliesApiService').queryAnomaliesByAppName(this.get('appName'), this.get('startDate'));`
+ usage: `this.get('anomaliesApiService').queryAnomaliesByAppName(this.get('appName'), this.get('startDate'), this.get('endDate'));`
*/
async queryAnomaliesByAppName(appName, start, end) {
- assert('you must pass appName param as an required argument.', appName);
- assert('you must pass start param as an required argument.', start);
+ assert('you must pass appName param as a required argument.', appName);
+ assert('you must pass start param as a required argument.', start);
+ assert('you must pass end param as a required argument.', end);
const queryCache = this.get('queryCache');
const modelName = 'anomalies';
const query = { application: appName, start, end };
const anomalies = await queryCache.query(modelName, query, { reload: false, cacheKey: queryCache.urlForQueryKey(modelName, query) });
return anomalies;
+ },
+
+ /**
+ * @summary Fetch all anomalies by subscription group name and time range
+ * @method queryAnomaliesBySubGroup
+ * @param {String} subGroup - the subscription group name
+ * @param {Number} start - the anomaly iso start time
+ * @param {Number} end - the anomaly iso end time
+ * @return {Ember.RSVP.Promise}
+ * @example: for call `/userdashboard/anomalies?group={someSubGroup}&start={1508472800000}&end={1508472800000}`
+ usage: `this.get('anomaliesApiService').queryAnomaliesBySubGroup(this.get('subGroup'), this.get('startDate'), this.get('endDate'));`
+ */
+ async queryAnomaliesBySubGroup(subGroup, start, end) {
+ assert('you must pass subGroup param as a required argument.', subGroup);
+ assert('you must pass start param as a required argument.', start);
+ assert('you must pass end param as a required argument.', end);
+
+ const queryCache = this.get('queryCache');
+ const modelName = 'anomalies';
+ const query = { group: subGroup, start, end };
+ const anomalies = await queryCache.query(modelName, query, { reload: false, cacheKey: queryCache.urlForQueryKey(modelName, query) });
+ return anomalies;
+ },
+
+ /**
+ * @summary Fetch all anomalies by application name, subscription group name, and time range
+ * @method queryAnomaliesByJoin
+ * @param {String} appName - the application name
+ * @param {String} subGroup - the subscription group name
+ * @param {Number} start - the anomaly iso start time
+ * @param {Number} end - the anomaly iso end time
+ * @return {Ember.RSVP.Promise}
+ * @example: for call `/userdashboard/anomalies?appName={someAppName}group={someSubGroup}&start={1508472800000}&end={1508472800000}`
+ usage: `this.get('anomaliesApiService').queryAnomaliesByIntersection(this.get('appName'), this.get('subGroup'), this.get('startDate'), this.get('endDate'));`
+ */
+ async queryAnomaliesByJoin(appName, subGroup, start, end) {
+ assert('you must pass appName param as a required argument.', appName);
+ assert('you must pass subGroup param as a required argument.', subGroup);
+ assert('you must pass start param as a required argument.', start);
+ assert('you must pass end param as a required argument.', end);
+
+ const queryCache = this.get('queryCache');
+ const modelName = 'anomalies';
+ const query = { application: appName, group: subGroup, start, end };
+ const anomalies = await queryCache.query(modelName, query, { reload: false, cacheKey: queryCache.urlForQueryKey(modelName, query) });
+ return anomalies;
}
});
diff --git a/thirdeye/thirdeye-frontend/app/styles/pods/home/index/dashboard.scss b/thirdeye/thirdeye-frontend/app/styles/pods/home/index/dashboard.scss
index e6c730c..113b4ec 100644
--- a/thirdeye/thirdeye-frontend/app/styles/pods/home/index/dashboard.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/pods/home/index/dashboard.scss
@@ -45,12 +45,16 @@
}
&__application-header {
- display: flex;
- justify-content: space-between;
+ display: inline-flex;
align-items: baseline;
+ width: 100%;
+ position: relative;
&-dropdown {
- width: 200px;
+ width: 225px;
+ position: absolute;
+ right: 0px;
+ top: 20px;
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org