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