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/19 23:59:05 UTC

[incubator-pinot] branch master updated: [TE] frontend - harleyjj/anomalies - make anomalies filterable by subscription group (#4131)

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 d403a28  [TE] frontend - harleyjj/anomalies - make anomalies filterable by subscription group (#4131)
d403a28 is described below

commit d403a285a8b33b73640a8969d11aa10eb66955bd
Author: Harley Jackson <ha...@gmail.com>
AuthorDate: Fri Apr 19 16:59:00 2019 -0700

    [TE] frontend - harleyjj/anomalies - make anomalies filterable by subscription group (#4131)
    
    Makes anomalies on Anomalies Route filterable by subscription group
    Adds "Clear All" button for filters
---
 .../app/pods/anomalies/controller.js               | 80 +++++++++++++++++++---
 .../thirdeye-frontend/app/pods/anomalies/route.js  | 26 ++++++-
 .../app/pods/anomalies/template.hbs                |  2 +
 .../app/pods/components/entity-filter/component.js |  9 +++
 .../app/pods/components/entity-filter/template.hbs |  8 ++-
 5 files changed, 112 insertions(+), 13 deletions(-)

diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js b/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
index 5e8124d..13be4dd 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
@@ -12,9 +12,12 @@ import {
   setProperties,
   observer
 } from '@ember/object';
-import { isPresent } from '@ember/utils';
+import { inject as service } from '@ember/service';
+import { isPresent, isEmpty } from '@ember/utils';
 import Controller from '@ember/controller';
+import yamljs from 'yamljs';
 import { reads } from '@ember/object/computed';
+import { toastOptions } from 'thirdeye-frontend/utils/constants';
 import { setUpTimeRangeOptions, powerSort } from 'thirdeye-frontend/utils/manage-alert-utils';
 import {  anomalyResponseObjNew } from 'thirdeye-frontend/utils/anomaly';
 import moment from 'moment';
@@ -28,6 +31,9 @@ const TIME_RANGE_OPTIONS = ['1d', '1w', '1m', '3m'];
 export default Controller.extend({
 
   queryParams: ['testMode'],
+  store: service('store'),
+
+  notifications: service('toast'),
 
   /**
    * One-way CP to store all sub groups
@@ -167,15 +173,13 @@ export default Controller.extend({
         // no filter applied, just return all
         return anomalyIds;
       }
-      let selectedAnomalies = [];
+      let selectedAnomalies = anomalyIds;
       filterMaps.forEach(map => {
-        if (anomalyFilters[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
+        if (!isEmpty(selectedFilters)) {
           // a filter is selected, grab relevant anomalyIds
-          if (selectedAnomalies.length === 0) {
-            selectedAnomalies = this._unionOfArrays(anomaliesById, map, anomalyFilters[map]);
-          } else {
-            selectedAnomalies = this._intersectOfArrays(selectedAnomalies, this._unionOfArrays(anomaliesById, map, anomalyFilters[map]));
-          }
+          selectedAnomalies = this._intersectOfArrays(selectedAnomalies, this._unionOfArrays(anomaliesById, map, anomalyFilters[map]));
         }
       });
       return selectedAnomalies;
@@ -303,6 +307,14 @@ export default Controller.extend({
           filterKeys = [...filterKeys, ...group];
         });
         Object.assign(filter, { filterKeys });
+      } else if (filter.name === "subscriptionFilterMap"){
+        const 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"){
         let anomalyPropertyArray = Object.keys(anomaliesById.searchFilters[filter.name]);
         anomalyPropertyArray = anomalyPropertyArray.map(prop => {
@@ -355,7 +367,10 @@ export default Controller.extend({
     } else {
       let addedIds = [];
       selectedFilters.forEach(filter => {
-        addedIds = [...addedIds, ...anomaliesById.searchFilters[filterType][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;
     }
@@ -367,10 +382,57 @@ export default Controller.extend({
     return existingArray.filter(anomalyId => incomingArray.includes(anomalyId));
   },
 
+  /**
+   * This will retrieve the subscription groups from Ember Data and extract yaml configs
+   * The yaml configs are used to extract alert names and apply them as filters
+   * @method _subscriptionGroupFilter
+   * @param {Object} filterObj
+   * @returns {Object}
+   * @private
+   */
+  _subscriptionGroupFilter(filterObj) {
+    // get selected subscription groups, if any
+    const notifications = get(this, 'notifications');
+    const selectedSubGroups = filterObj['subscriptionFilterMap'];
+    if (Array.isArray(selectedSubGroups) && selectedSubGroups.length > 0) {
+      // extract selected subscription groups from Ember Data
+      const selectedSubGroupObjects = this.get('store')
+        .peekAll('subscription-groups')
+        .filter(group => {
+          return selectedSubGroups.includes(group.get('name'));
+        });
+      let additionalAlertNames = [];
+      // for each group, grab yaml, extract alert names for adding to filterObj
+      selectedSubGroupObjects.forEach(group => {
+        try {
+          const yamlAsObject = yamljs.parse(group.get('yaml'));
+          if (Array.isArray(yamlAsObject.subscribedDetections)) {
+            additionalAlertNames = [ ...additionalAlertNames, ...yamlAsObject.subscribedDetections];
+          }
+        }
+        catch(error){
+          notifications.error(`Failed to retrieve alert names for subscription group: ${group.get('name')}`, 'Error', toastOptions);
+        }
+      });
+      // add the alert names extracted from groups to any that are already present
+      let updatedFunctionFilterMap = Array.isArray(filterObj['functionFilterMap']) ? [ ...filterObj['functionFilterMap'], ...additionalAlertNames] : additionalAlertNames;
+      updatedFunctionFilterMap = [ ...new Set(powerSort(updatedFunctionFilterMap, null))];
+      set(filterObj, 'functionFilterMap', updatedFunctionFilterMap);
+    }
+    return filterObj;
+  },
+
   actions: {
+    // Clears all selected filters at once
+    clearFilters() {
+      this._resetLocalFilters();
+    },
+
     // Handles filter selections (receives array of filter options)
     userDidSelectFilter(filterObj) {
       const filterBlocksLocal = get(this, 'filterBlocksLocal');
+      // handle special case of subscription groups
+      filterObj = this._subscriptionGroupFilter(filterObj);
       filterBlocksLocal.forEach(block => {
         block.selected = filterObj[block.name];
       });
diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js b/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
index 4a26050..9fb9583 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
@@ -12,12 +12,17 @@ export default Route.extend({
 
   // Make duration service accessible
   durationCache: service('services/duration'),
+  anomaliesApiService: service('services/api/anomalies'),
   session: service(),
+  store: service('store'),
 
-  model() {
+  async model() {
+    const anomaliesById = await getAnomalyIdsByTimeRange(start, end);
+    const subscriptionGroups = await this.get('anomaliesApiService').querySubscriptionGroups(); // Get all subscription groups available
     return hash({
       updateAnomalies:  getAnomalyIdsByTimeRange,
-      anomaliesById: getAnomalyIdsByTimeRange(start, end)
+      anomaliesById,
+      subscriptionGroups
     });
   },
 
@@ -56,6 +61,12 @@ export default Route.extend({
         type: 'select',
         matchWidth: true,
         filterKeys: []
+      },
+      {
+        name: 'subscriptionFilterMap',
+        title: 'Subscription Groups',
+        type: 'select',
+        filterKeys: []
       }
     ];
 
@@ -81,6 +92,14 @@ export default Route.extend({
         const filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
         // Add filterKeys prop to each facet or filter block
         Object.assign(filter, { filterKeys });
+      } else if (filter.name === "subscriptionFilterMap"){
+        const 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))];
@@ -101,7 +120,8 @@ export default Route.extend({
       updateAnomalies: model.updateAnomalies,  //requires start and end time in epoch ex updateAnomalies(start, end)
       filterBlocksLocal,
       anomalyIds: model.anomaliesById.anomalyIds,
-      anomaliesRange: [start, end]
+      anomaliesRange: [start, end],
+      subscriptionGroups: model.subscriptionGroups
     });
   },
 
diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/anomalies/template.hbs
index 2655dee..fc1bdf5 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/template.hbs
@@ -8,6 +8,8 @@
         resetFilters=resetFiltersLocal
         filterBlocks=filterBlocksLocal
         onSelectFilter=(action "userDidSelectFilter")
+        canClear=true
+        onClearFilters=(action "clearFilters")
       }}
     </div>
   </aside>
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/component.js b/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/component.js
index bfb43a6..80d5781 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/component.js
@@ -153,6 +153,15 @@ export default Component.extend({
     toggleDisplay(clickedBlock) {
       // Note: toggleProperty will not be able to find 'filterBlocks', as it is not an observed property
       set(clickedBlock, 'isHidden', !clickedBlock.isHidden);
+    },
+
+    /**
+     * Bubbles clear filters action up to parent
+     * @method clearFilters
+     * @return {undefined}
+     */
+    clearFilters() {
+      this.get('onClearFilters')();
     }
   }
 });
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/template.hbs
index 076ba60..40b551b 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/template.hbs
@@ -1,5 +1,11 @@
 <header class="entity-filter__head">
-  <div class="manage-alert-container__title">{{title}}</div>
+  <div class="manage-alert-container__title">{{title}}
+    {{#if canClear}}
+    <a href="#" {{action "clearFilters"}} >
+      <span class="pull-right">Clear All</span>
+    </a>
+    {{/if}}
+  </div>
 </header>
 
 {{#each filterBlocks as |block|}}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org