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 2020/08/06 18:00:06 UTC

[incubator-pinot] branch new-anomalies-page-ui updated (20a747f -> 56b86f5)

This is an automated email from the ASF dual-hosted git repository.

jihao pushed a change to branch new-anomalies-page-ui
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git.


 discard 20a747f  ui for new anomalies page
     add 17a3873  Improve performance of DistinctCountThetaSketch by eliminating empty sketches and unions. (#5798)
     add 43b01ef  Fixing codecov (#5806)
     add 336131a  Fix theta-sketch missing break in switch statement (#5811)
     add bf928e9  [TE] Fix issue of not loading RCA template when metricid is not specified (#5799)
     add 71c77a5  Create reader context only once in ColumnValueReader (#5813)
     add 23889f0  Message from exception in Schema add/validate (#5815)
     add 4153958  Enhancing the segment replacement api (#5782)
     add ffa9541  Pre-generate aggregation functions in QueryContext (#5805)
     add f68b82e  Enhance VarByteChunkSVForwardIndexReader to directly read from data buffer for uncompressed data (#5816)
     add 2e08602  Pradeep/sr ssl fix (#5758)
     new 56b86f5  ui for new anomalies page

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (20a747f)
            \
             N -- N -- N   refs/heads/new-anomalies-page-ui (56b86f5)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/pinot_tests.yml                  |  14 +-
 docs/pluggable_streams.rst                         |  56 ++++++++
 .../BrokerUserDefinedMessageHandlerFactory.java    |  27 ++++
 ...essage.java => RoutingTableRebuildMessage.java} |  32 ++---
 pinot-common/src/test/resources/pql_queries.list   |   8 +-
 pinot-common/src/test/resources/sql_queries.list   |   8 +-
 .../api/resources/PinotSchemaRestletResource.java  |  14 +-
 .../helix/core/PinotHelixResourceManager.java      |  84 +++++++++++-
 .../helix/core/PinotHelixResourceManagerTest.java  |   2 +-
 .../org/apache/pinot/core/common/DataFetcher.java  |  13 +-
 .../core/common/datatable/DataTableUtils.java      |   7 +-
 .../operator/combine/GroupByCombineOperator.java   |   4 +-
 .../combine/GroupByOrderByCombineOperator.java     |   4 +-
 .../plan/AggregationGroupByOrderByPlanNode.java    |   3 +-
 .../core/plan/AggregationGroupByPlanNode.java      |   3 +-
 .../pinot/core/plan/AggregationPlanNode.java       |   3 +-
 .../plan/DictionaryBasedAggregationPlanNode.java   |   4 +-
 .../plan/MetadataBasedAggregationPlanNode.java     |   4 +-
 .../aggregation/function/AggregationFunction.java  |   9 +-
 .../function/AggregationFunctionUtils.java         |   6 +-
 .../function/AggregationFunctionVisitorBase.java   | 114 ----------------
 .../function/AvgAggregationFunction.java           |   5 -
 .../function/AvgMVAggregationFunction.java         |   5 -
 .../function/CountAggregationFunction.java         |   5 -
 .../function/CountMVAggregationFunction.java       |   5 -
 .../function/DistinctAggregationFunction.java      |   5 -
 .../function/DistinctCountAggregationFunction.java | 114 ++++++++++------
 .../DistinctCountBitmapAggregationFunction.java    | 140 ++++++++++++-------
 .../DistinctCountBitmapMVAggregationFunction.java  |  47 +++----
 .../DistinctCountHLLAggregationFunction.java       |   5 -
 .../DistinctCountHLLMVAggregationFunction.java     |   5 -
 .../DistinctCountMVAggregationFunction.java        |  32 ++---
 .../DistinctCountRawHLLAggregationFunction.java    |   5 -
 ...inctCountRawThetaSketchAggregationFunction.java |   5 -
 ...istinctCountThetaSketchAggregationFunction.java |  65 +++++----
 .../function/FastHLLAggregationFunction.java       |   5 -
 .../function/MaxAggregationFunction.java           |   5 -
 .../function/MaxMVAggregationFunction.java         |   5 -
 .../function/MinAggregationFunction.java           |   5 -
 .../function/MinMVAggregationFunction.java         |   5 -
 .../function/MinMaxRangeAggregationFunction.java   |   5 -
 .../function/MinMaxRangeMVAggregationFunction.java |   5 -
 .../function/PercentileAggregationFunction.java    |   5 -
 .../function/PercentileEstAggregationFunction.java |   5 -
 .../PercentileEstMVAggregationFunction.java        |   5 -
 .../function/PercentileMVAggregationFunction.java  |   5 -
 .../PercentileTDigestAggregationFunction.java      |   5 -
 .../PercentileTDigestMVAggregationFunction.java    |   5 -
 ...artitionedDistinctCountAggregationFunction.java |   5 -
 .../function/StUnionAggregationFunction.java       |   5 -
 .../function/SumAggregationFunction.java           |   5 -
 .../function/SumMVAggregationFunction.java         |   5 -
 .../core/query/reduce/ResultReducerFactory.java    |   6 +-
 .../core/query/request/context/QueryContext.java   |  27 +++-
 .../request/context/utils/QueryContextUtils.java   |  19 +--
 .../forward/BaseChunkSVForwardIndexReader.java     |  17 +--
 .../FixedByteChunkSVForwardIndexReader.java        |   4 +
 .../forward/VarByteChunkSVForwardIndexReader.java  | 152 ++++++++++++++++-----
 .../pinot/core/startree/v2/BaseStarTreeV2Test.java |   3 +-
 .../DefaultAggregationExecutorTest.java            |   4 +-
 .../apache/pinot/perf/BenchmarkCombineGroupBy.java |   4 +-
 .../apache/pinot/perf/BenchmarkIndexedTable.java   |   4 +-
 ...aConfluentSchemaRegistryAvroMessageDecoder.java |  49 ++++++-
 .../thirdeye-frontend/app/pods/rootcause/route.js  |  70 ++++++----
 64 files changed, 714 insertions(+), 592 deletions(-)
 copy pinot-common/src/main/java/org/apache/pinot/common/messages/{SegmentRefreshMessage.java => RoutingTableRebuildMessage.java} (63%)
 delete mode 100644 pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionVisitorBase.java


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


[incubator-pinot] 01/01: ui for new anomalies page

Posted by ji...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jihao pushed a commit to branch new-anomalies-page-ui
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git

commit 56b86f503de7adce72dc5c8f88803dda064676df
Author: Jihao Zhang <ji...@linkedin.com>
AuthorDate: Wed Aug 5 11:11:17 2020 -0700

    ui for new anomalies page
---
 .../app/pods/anomalies/controller.js               | 446 +++++----------------
 .../thirdeye-frontend/app/pods/anomalies/route.js  | 144 ++-----
 .../app/pods/anomalies/template.hbs                |   2 +-
 .../pods/components/anomaly-summary/component.js   |  41 +-
 .../pods/components/anomaly-summary/template.hbs   |  14 +-
 .../app/pods/components/entity-filter/component.js |   9 +-
 thirdeye/thirdeye-frontend/app/utils/anomaly.js    |  54 ++-
 7 files changed, 239 insertions(+), 471 deletions(-)

diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js b/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
index 8011f91..05fe75e 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/controller.js
@@ -3,22 +3,13 @@
  * @module manage/alerts/controller
  * @exports alerts controller
  */
-import _ from 'lodash';
-import {
-  set,
-  get,
-  computed,
-  getProperties,
-  setProperties
-} from '@ember/object';
-import { inject as service } from '@ember/service';
-import { isPresent, isEmpty } from '@ember/utils';
+import {computed, get, getProperties, set, setProperties} from '@ember/object';
+import {inject as service} from '@ember/service';
+import {isPresent} from '@ember/utils';
 import Controller from '@ember/controller';
-import { redundantParse } from 'thirdeye-frontend/utils/yaml-tools';
-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 {reads} from '@ember/object/computed';
+import {setUpTimeRangeOptions} from 'thirdeye-frontend/utils/manage-alert-utils';
+import {searchAnomalyWithFilters} from 'thirdeye-frontend/utils/anomaly';
 import moment from 'moment';
 
 const TIME_PICKER_INCREMENT = 5; // tells date picker hours field how granularly to display time
@@ -29,8 +20,7 @@ const TIME_RANGE_OPTIONS = ['1d', '1w', '1m', '3m'];
 
 export default Controller.extend({
 
-  queryParams: ['testMode'],
-  store: service('store'),
+  queryParams: ['testMode'], store: service('store'),
 
   notifications: service('toast'),
 
@@ -69,9 +59,7 @@ export default Controller.extend({
   /**
    * Filter settings
    */
-  anomalyFilters: {},
-  resetFiltersLocal: null,
-  alertFoundByName: null,
+  anomalyFilters: {}, resetFiltersLocal: null, alertFoundByName: null,
 
   /**
    * The first and broadest entity search property
@@ -85,354 +73,139 @@ export default Controller.extend({
   pageSize: 10,
 
   // Number of pages to display
-  paginationSize: computed(
-    'pagesNum',
-    'pageSize',
-    function() {
-      const pagesNum = this.get('pagesNum');
-      const pageSize = this.get('pageSize');
-
-      return Math.min(pagesNum, pageSize/2);
-    }
-  ),
+  paginationSize: computed('pagesNum', 'pageSize', function () {
+    const pagesNum = this.get('pagesNum');
+    const pageSize = this.get('pageSize');
+
+    return Math.min(pagesNum, pageSize / 2);
+  }),
 
   // Total Number of pages to display
-  pagesNum: computed(
-    'totalAnomalies',
-    'pageSize',
-    function() {
-      const { pageSize, totalAnomalies } = getProperties(this, 'pageSize', 'totalAnomalies');
-      return Math.ceil(totalAnomalies/pageSize);
-    }
-  ),
+  pagesNum: computed('totalAnomalies', 'pageSize', function () {
+    const {pageSize, totalAnomalies} = getProperties(this, 'pageSize', 'totalAnomalies');
+    return Math.ceil(totalAnomalies / pageSize);
+  }),
 
   // creates the page Array for view
-  viewPages: computed(
-    'pages',
-    'currentPage',
-    'paginationSize',
-    'pageNums',
-    function() {
-      const size = this.get('paginationSize');
-      const currentPage = this.get('currentPage');
-      const max = this.get('pagesNum');
-      const step = Math.floor(size / 2);
-
-      if (max === 1) { return; }
-
-      const startingNumber = ((max - currentPage) < step)
-        ? Math.max(max - size + 1, 1)
-        : Math.max(currentPage - step, 1);
-
-      return [...new Array(size)].map((page, index) =>  startingNumber + index);
-    }
-  ),
-
-  // return list of anomalyIds according to filter(s) applied
-  selectedAnomalies: computed(
-    'anomalyFilters',
-    'anomalyIdList',
-    'activeFiltersString',
-    function() {
-      const {
-        anomalyIdList,
-        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 anomalyIdList;
-      }
-      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
-        if (!isEmpty(selectedFilters)) {
-          // a filter is selected, grab relevant anomalyIds
-          selectedAnomalies = this._intersectOfArrays(selectedAnomalies, this._unionOfArrays(anomaliesById, map, anomalyFilters[map]));
-        }
-      });
-      return selectedAnomalies;
+  viewPages: computed('pages', 'currentPage', 'paginationSize', 'pageNums', function () {
+    const size = this.get('paginationSize');
+    const currentPage = this.get('currentPage');
+    const max = this.get('pagesNum');
+    const step = Math.floor(size / 2);
+
+    if (max === 1) {
+      return;
     }
-  ),
 
-  totalAnomalies: computed(
-    'selectedAnomalies',
-    function() {
-      return get(this, 'selectedAnomalies').length;
-    }
-  ),
+    const startingNumber = ((max - currentPage) < step) ? Math.max(max - size + 1, 1) : Math.max(currentPage - step, 1);
 
-  noAnomalies: computed(
-    'totalAnomalies',
-    function() {
-      return (get(this, 'totalAnomalies') === 0);
-    }
-  ),
-
-  paginatedSelectedAnomalies: computed(
-    'selectedAnomalies.@each',
-    'filtersTriggered',
-    'pageSize',
-    'currentPage',
-    function() {
-      const {
-        pageSize,
-        currentPage
-      } = getProperties(this, 'pageSize', 'currentPage');
-      // Initial set of anomalies
-      let anomalies = this.get('selectedAnomalies');
-      // Return one page of sorted anomalies
-      return anomalies.slice((currentPage - 1) * pageSize, currentPage * pageSize);
-    }
-  ),
+    return [...new Array(size)].map((page, index) => startingNumber + index);
+  }),
+
+  totalAnomalies: computed('searchResult', function () {
+    return get(this, 'searchResult').count;
+  }),
+
+  noAnomalies: computed('totalAnomalies', function () {
+    return (get(this, 'totalAnomalies') === 0);
+  }),
+
+  paginatedSelectedAnomalies: computed('searchResult', function () {
+    // Return one page of sorted anomalies
+    return get(this, 'searchResult').elements;
+  }),
 
   /**
    * Date types to display in the pills
    * @type {Object[]} - array of objects, each of which represents each date pill
    */
-  pill: computed(
-    'anomaliesRange', 'startDate', 'endDate', 'duration',
-    function() {
-      const anomaliesRange = get(this, 'anomaliesRange');
-      const startDate = Number(anomaliesRange[0]);
-      const endDate = Number(anomaliesRange[1]);
-      const duration = get(this, 'duration') || DEFAULT_ACTIVE_DURATION;
-      const predefinedRanges = {
-        'Today': [moment().startOf('day'), moment().startOf('day').add(1, 'days')],
-        'Last 24 hours': [moment().subtract(1, 'day'), moment()],
-        'Yesterday': [moment().subtract(1, 'day').startOf('day'), moment().startOf('day')],
-        'Last Week': [moment().subtract(1, 'week').startOf('day'), moment().startOf('day')]
-      };
-
-      return {
-        uiDateFormat: UI_DATE_FORMAT,
-        activeRangeStart: moment(startDate).format(DISPLAY_DATE_FORMAT),
-        activeRangeEnd: moment(endDate).format(DISPLAY_DATE_FORMAT),
-        timeRangeOptions: setUpTimeRangeOptions(TIME_RANGE_OPTIONS, duration),
-        timePickerIncrement: TIME_PICKER_INCREMENT,
-        predefinedRanges
-      };
-    }
-  ),
+  pill: computed('anomaliesRange', 'startDate', 'endDate', 'duration', function () {
+    const anomaliesRange = get(this, 'anomaliesRange');
+    const startDate = Number(anomaliesRange[0]);
+    const endDate = Number(anomaliesRange[1]);
+    const duration = get(this, 'duration') || DEFAULT_ACTIVE_DURATION;
+    const predefinedRanges = {
+      'Today': [moment().startOf('day'), moment().startOf('day').add(1, 'days')],
+      'Last 24 hours': [moment().subtract(1, 'day'), moment()],
+      'Yesterday': [moment().subtract(1, 'day').startOf('day'), moment().startOf('day')],
+      'Last Week': [moment().subtract(1, 'week').startOf('day'), moment().startOf('day')]
+    };
+
+    return {
+      uiDateFormat: UI_DATE_FORMAT,
+      activeRangeStart: moment(startDate).format(DISPLAY_DATE_FORMAT),
+      activeRangeEnd: moment(endDate).format(DISPLAY_DATE_FORMAT),
+      timeRangeOptions: setUpTimeRangeOptions(TIME_RANGE_OPTIONS, duration),
+      timePickerIncrement: TIME_PICKER_INCREMENT,
+      predefinedRanges
+    };
+  }),
 
   // String containing all selected filters for display
-  activeFiltersString: computed(
-    'anomalyFilters',
-    'filtersTriggered',
-    function() {
-      const anomalyFilters = get(this, 'anomalyFilters');
-      const filterAbbrevMap = {
-        functionFilterMap: 'function',
-        datasetFilterMap: 'dataset',
-        statusFilterMap: 'status',
-        metricFilterMap: 'metric',
-        dimensionFilterMap: 'dimension'
-      };
-      let filterStr = 'All Anomalies';
-      if (isPresent(anomalyFilters)) {
-        let filterArr = [get(this, 'primaryFilterVal')];
-        Object.keys(anomalyFilters).forEach((filterKey) => {
-          const value = anomalyFilters[filterKey];
-          const isStatusAll = filterKey === 'status' && Array.isArray(value) && value.length > 1;
-          // Only display valid search filters
-          if (filterKey !== 'triggerType' && value !== null && value.length && !isStatusAll) {
-            let concatVal = filterKey === 'status' && !value.length ? 'Active' : value.join(', ');
-            let abbrevKey = filterAbbrevMap[filterKey] || filterKey;
-            filterArr.push(`${abbrevKey}: ${concatVal}`);
-          }
-        });
-        filterStr = filterArr.join(' | ');
-      }
-      return filterStr;
+  activeFiltersString: computed('anomalyFilters', 'filtersTriggered', function () {
+    const anomalyFilters = get(this, 'anomalyFilters');
+    const filterAbbrevMap = {
+      functionFilterMap: 'function',
+      datasetFilterMap: 'dataset',
+      statusFilterMap: 'status',
+      metricFilterMap: 'metric',
+      dimensionFilterMap: 'dimension'
+    };
+    let filterStr = 'All Anomalies';
+    if (isPresent(anomalyFilters)) {
+      let filterArr = [get(this, 'primaryFilterVal')];
+      Object.keys(anomalyFilters).forEach((filterKey) => {
+        const value = anomalyFilters[filterKey];
+        const isStatusAll = filterKey === 'status' && Array.isArray(value) && value.length > 1;
+        // Only display valid search filters
+        if (filterKey !== 'triggerType' && value !== null && value.length && !isStatusAll) {
+          let concatVal = filterKey === 'status' && !value.length ? 'Active' : value.join(', ');
+          let abbrevKey = filterAbbrevMap[filterKey] || filterKey;
+          filterArr.push(`${abbrevKey}: ${concatVal}`);
+        }
+      });
+      filterStr = filterArr.join(' | ');
     }
-  ),
+    return filterStr;
+  }),
 
   // 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.
-   * @method _resetFilters
-   * @param {Boolean} isSelectDisabled
-   * @returns {undefined}
-   * @private
-   */
-  _resetLocalFilters() {
-    let anomalyFilters = {};
-    const newFilterBlocksLocal = _.cloneDeep(get(this, 'initialFiltersLocal'));
-    const anomaliesById = get(this, 'anomaliesById');
-
-    // Fill in select options for these filters ('filterKeys') based on alert properties from model.alerts
-    newFilterBlocksLocal.forEach((filter) => {
-      let filterKeys = [];
-      if (filter.name === "dimensionFilterMap" && isPresent(anomaliesById.searchFilters[filter.name])) {
-        const anomalyPropertyArray = Object.keys(anomaliesById.searchFilters[filter.name]);
-        anomalyPropertyArray.forEach(dimensionType => {
-          let group = Object.keys(anomaliesById.searchFilters[filter.name][dimensionType]);
-          group = group.map(dim => `${dimensionType}::${dim}`);
-          filterKeys = [...filterKeys, ...group];
-        });
-      } else if (filter.name === "subscriptionFilterMap"){
-        filterKeys = this.get('store')
-          .peekAll('subscription-groups')
-          .sortBy('name')
-          .filter(group => (group.get('active') && group.get('yaml')))
-          .map(group => group.get('name'));
-      } 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
-          const mapping = anomalyResponseObjNew.filter(e => (e.status === prop));
-          // map the status to name
-          return mapping.length > 0 ? mapping[0].name : prop;
-        });
-        filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
-      } else {
-        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,
-      resetFiltersLocal: moment().valueOf(),
-      anomalyFilters
-    });
-  },
-
-  // method to union anomalyId arrays for filters applied of same type
-  _unionOfArrays(anomaliesById, filterType, selectedFilters) {
-    //handle dimensions separately, since they are nested
-    let addedIds = [];
-    if (filterType === 'dimensionFilterMap' && isPresent(anomaliesById.searchFilters[filterType])) {
-      selectedFilters.forEach(filter => {
-        const [type, dimension] = filter.split('::');
-        addedIds = [...addedIds, ...anomaliesById.searchFilters.dimensionFilterMap[type][dimension]];
-      });
-    } 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));
-        // map the name to status
-        return mapping.length > 0 ? mapping[0].status : f;
-      });
-      translatedFilters.forEach(filter => {
-        addedIds = [...addedIds, ...anomaliesById.searchFilters[filterType][filter]];
-      });
-    } else {
-      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;
-  },
+      anomaliesRange, anomalyIds, pageSize, currentPage, anomalyFilters
+    } = this.getProperties('anomaliesRange', 'anomalyIds', 'pageSize', 'currentPage', 'anomalyFilters');
 
-  // method to intersect anomalyId arrays for filters applied of different types
-  // i.e. we want anomalies that have both characteristics when filter type is different
-  _intersectOfArrays(existingArray, incomingArray) {
-    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'));
+    set(this, 'isLoading', true);
+    const [start, end] = anomaliesRange;
+    searchAnomalyWithFilters(pageSize * (currentPage - 1), pageSize, anomalyIds ? null : start, anomalyIds ? null : end,
+      anomalyFilters.feedbackStatus, anomalyFilters.subscription, anomalyFilters.alertName, anomalyFilters.metric,
+      anomalyFilters.dataset, anomalyIds)
+      .then(res => {
+        this.setProperties({
+          searchResult: res
         });
-      let additionalAlertNames = [];
-      // for each group, grab yaml, extract alert names for adding to filterObj
-      selectedSubGroupObjects.forEach(group => {
-        let yamlAsObject;
-        try {
-          yamlAsObject = redundantParse(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);
-        }
+        set(this, 'isLoading', false);
+      })
+      .catch(() => {
+        set(this, 'isLoading', false);
       });
-      // 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();
+      set(this, 'anomalyFilters', {});
+      this._updateVisuals();
     },
 
     // 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];
-      });
       setProperties(this, {
-        filtersTriggered: true,
-        allowFilterSummary: true,
-        anomalyFilters: filterObj
+        filtersTriggered: true, allowFilterSummary: true, anomalyFilters: filterObj
       });
       // Reset current page
       set(this, 'currentPage', 1);
+      this._updateVisuals();
     },
 
     /**
@@ -442,9 +215,7 @@ export default Controller.extend({
      */
     onRangeSelection(timeRangeOptions) {
       const {
-        start,
-        end,
-        value: duration
+        start, end, value: duration
       } = timeRangeOptions;
 
       const startDate = moment(start).valueOf();
@@ -452,6 +223,8 @@ export default Controller.extend({
       //Update the time range option selected
       set(this, 'anomaliesRange', [startDate, endDate]);
       set(this, 'duration', duration);
+      set(this, 'anomalyIds', null);
+      set(this, 'currentPage', 1)
       this._updateVisuals();
     },
 
@@ -486,6 +259,7 @@ export default Controller.extend({
       }
 
       this.set('currentPage', newPage);
+      this._updateVisuals();
     }
   }
 });
diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js b/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
index e2f3c6a..9c4bf8f 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/route.js
@@ -1,22 +1,17 @@
-import { hash } from 'rsvp';
+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 {
-  getAnomalyFiltersByTimeRange,
-  getAnomalyFiltersByAnomalyId,
-  anomalyResponseObjNew } from 'thirdeye-frontend/utils/anomaly';
+import {inject as service} from '@ember/service';
+import {anomalyResponseObj, searchAnomaly} from 'thirdeye-frontend/utils/anomaly';
 import _ from 'lodash';
 import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
 
 const start = moment().subtract(1, 'day').valueOf();
 const end = moment().valueOf();
+const pagesize = 10;
 
 const queryParamsConfig = {
-  refreshModel: true,
-  replace: false
+  refreshModel: true, replace: false
 };
 
 export default Route.extend(AuthenticatedRouteMixin, {
@@ -33,14 +28,15 @@ export default Route.extend(AuthenticatedRouteMixin, {
 
   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
+    let {anomalyIds} = params;
+    let searchResult;
+    if (anomalyIds) {
+      anomalyIds = anomalyIds.split(",");
+    }
+    // query anomalies
+    searchResult = searchAnomaly(0, pagesize, anomalyIds ? null : start, anomalyIds ? null : end, anomalyIds);
     return hash({
-      updateAnomalies:  getAnomalyFiltersByTimeRange,
-      anomaliesById,
-      subscriptionGroups,
-      anomalyIds
+      updateAnomalies: searchAnomaly, anomalyIds, searchResult
     });
   },
 
@@ -51,87 +47,28 @@ export default Route.extend(AuthenticatedRouteMixin, {
     const defaultParams = {
       anomalyIds
     };
-    Object.assign(model, { ...defaultParams});
+    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"
-    const filterBlocksLocal = [
-      {
-        name: 'statusFilterMap',
-        title: 'Feedback Status',
-        type: 'select',
-        matchWidth: true,
-        filterKeys: []
-      },
-      {
-        name: 'functionFilterMap',
-        title: 'Alert Names',
-        type: 'select',
-        filterKeys: []
-      },
-      {
-        name: 'datasetFilterMap',
-        title: 'Dataset',
-        type: 'select',
-        filterKeys: []
-      },
-      {
-        name: 'metricFilterMap',
-        title: 'Metric',
-        type: 'select',
-        filterKeys: []
-      },
-      {
-        name: 'dimensionFilterMap',
-        title: 'Dimension',
-        type: 'select',
-        matchWidth: true,
-        filterKeys: []
-      },
-      {
-        name: 'subscriptionFilterMap',
-        title: 'Subscription Groups',
-        type: 'select',
-        filterKeys: []
-      }
-    ];
-
-    // Fill in select options for these filters ('filterKeys') based on alert properties from model.alerts
-    filterBlocksLocal.forEach((filter) => {
-      let filterKeys = [];
-      if (filter.name === "dimensionFilterMap" && isPresent(model.anomaliesById.searchFilters[filter.name])) {
-        const anomalyPropertyArray = Object.keys(model.anomaliesById.searchFilters[filter.name]);
-        anomalyPropertyArray.forEach(dimensionType => {
-          let group = Object.keys(model.anomaliesById.searchFilters[filter.name][dimensionType]);
-          group = group.map(dim => `${dimensionType}::${dim}`);
-          filterKeys = [...filterKeys, ...group];
-        });
-      } 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
-          const mapping = anomalyResponseObjNew.filter(e => (e.status === prop));
-          // map the status to name
-          return mapping.length > 0 ? mapping[0].name : prop;
-        });
-        filterKeys = [ ...new Set(powerSort(anomalyPropertyArray, null))];
-      } else if (filter.name === "subscriptionFilterMap"){
-        filterKeys = this.get('store')
-          .peekAll('subscription-groups')
-          .sortBy('name')
-          .filter(group => (group.get('active') && group.get('yaml')))
-          .map(group => group.get('name'));
-      } else {
-        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 });
-    });
+    const filterBlocksLocal = [{
+      name: 'alertName', title: 'Alert Names', type: 'search', filterKeys: []
+    }, {
+      name: 'dataset', title: 'Datasets', type: 'search', filterKeys: []
+    }, {
+      name: 'metric', title: 'Metrics', type: 'search', filterKeys: []
+    }, {
+      name: 'feedbackStatus',
+      title: 'Feedback Status',
+      type: 'select',
+      matchWidth: true,
+      filterKeys: anomalyResponseObj.map(f => f.name)
+    }, {
+      name: 'subscription', title: 'Subscription Groups', hasNullOption: true, // allow searches for 'none'
+      type: 'search', filterKeys: []
+    }];
 
     // Keep an initial copy of the secondary filter blocks in memory
     Object.assign(model, {
@@ -139,15 +76,9 @@ export default Route.extend(AuthenticatedRouteMixin, {
     });
     // Send filters to controller
     controller.setProperties({
-      model,
-      anomaliesById: model.anomaliesById,
-      resultsActive: true,
-      updateAnomalies: model.updateAnomalies,  //requires start and end time in epoch ex updateAnomalies(start, end)
-      filterBlocksLocal,
-      anomalyIdList: model.anomaliesById.anomalyIds,
-      anomaliesRange: [start, end],
-      subscriptionGroups: model.subscriptionGroups,
-      anomalyIds: this.get('anomalyIds')
+      model, resultsActive: true, updateAnomalies: model.updateAnomalies,  //requires start and end time in epoch ex updateAnomalies(start, end)
+      filterBlocksLocal, anomaliesRange: [start, end], anomalyIds: this.get('anomalyIds'), // url params
+      searchResult: model.searchResult
     });
   },
 
@@ -164,8 +95,7 @@ export default Route.extend(AuthenticatedRouteMixin, {
       if (transition.intent.name && transition.intent.name !== 'logout') {
         this.set('session.store.fromUrl', {lastIntentTransition: transition});
       }
-    },
-    error() {
+    }, error() {
       // The `error` hook is also provided the failed
       // `transition`, which can be stored and later
       // `.retry()`d if desired.
@@ -180,9 +110,9 @@ export default Route.extend(AuthenticatedRouteMixin, {
     },
 
     /**
-    * Refresh route's model.
-    * @method refreshModel
-    */
+     * Refresh route's model.
+     * @method refreshModel
+     */
     refreshModel() {
       this.refresh();
     }
diff --git a/thirdeye/thirdeye-frontend/app/pods/anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/anomalies/template.hbs
index fc1bdf5..96ac454 100644
--- a/thirdeye/thirdeye-frontend/app/pods/anomalies/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/anomalies/template.hbs
@@ -63,7 +63,7 @@
           {{#each paginatedSelectedAnomalies as |anomaly|}}
             <section class="te-search-results">
               {{anomaly-summary
-                anomalyId=anomaly
+                anomalyData = anomaly
               }}
             </section>
           {{/each}}
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 0be588a..8adf8dc 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/component.js
@@ -16,7 +16,8 @@ import { getFormattedDuration,
   verifyAnomalyFeedback,
   anomalyResponseObj,
   anomalyResponseObjNew,
-  updateAnomalyFeedback
+  updateAnomalyFeedback,
+  anomalyTypeMapping
 } from 'thirdeye-frontend/utils/anomaly';
 import RSVP from "rsvp";
 import fetch from 'fetch';
@@ -211,7 +212,8 @@ export default Component.extend({
           shownChangeRate: humanizeFloat(change),
           anomalyFeedback: a.feedback ? a.feedback.feedbackType : "NONE",
           showResponseSaved: (labelResponse.anomalyId === a.id) ? labelResponse.showResponseSaved : false,
-          showResponseFailed: (labelResponse.anomalyId === a.id) ? labelResponse.showResponseFailed: false
+          showResponseFailed: (labelResponse.anomalyId === a.id) ? labelResponse.showResponseFailed: false,
+          type: anomalyTypeMapping[a.type]
         };
       }
       return tableAnomaly;
@@ -230,34 +232,29 @@ export default Component.extend({
   ),
 
   _fetchAnomalyData() {
-    const anomalyId = get(this, 'anomalyId');
-    const anomalyUrl = `/dashboard/anomalies/view/${anomalyId}`;
+    const anomalyData = get(this, 'anomalyData');
+    const anomalyId = anomalyData.id;
 
+    set(this, 'anomalyId', anomalyId);
     set(this, 'isLoading', true);
 
-    fetch(anomalyUrl)
-      .then(checkStatus)
-      .then(res => {
-        set(this, 'anomalyData', res);
-        const predictedUrl = `/detection/predicted-baseline/${anomalyId}?start=${res.startTime}&end=${res.endTime}&padding=true`;
-        const timeseriesHash = {
-          predicted: fetch(predictedUrl).then(res => checkStatus(res, 'get', true))
-        };
-        return RSVP.hash(timeseriesHash);
-      })
-      .then((res) => {
-        if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
-          set(this, 'current', res.predicted);
-          set(this, 'predicted', res.predicted);
-          set(this, 'isLoading', false);
-        }
-      })
+    const predictedUrl = `/detection/predicted-baseline/${anomalyId}?start=${anomalyData.startTime}&end=${anomalyData.endTime}&padding=true`;
+    const timeseriesHash = {
+      predicted: fetch(predictedUrl).then(res => checkStatus(res, 'get', true))
+    };
+    RSVP.hash(timeseriesHash).then((res) => {
+      if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
+        set(this, 'current', res.predicted);
+        set(this, 'predicted', res.predicted);
+        set(this, 'isLoading', false);
+      }
+    })
       .catch(() => {
         if (!(this.get('isDestroyed') || this.get('isDestroying'))) {
           set(this, 'isLoading', false);
         }
       });
-  },
+    },
 
   _formatAnomaly(anomaly) {
     return `${moment(anomaly.startTime).format(TABLE_DATE_FORMAT)}`;
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 3303c1e..d8b47ae 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/anomaly-summary/template.hbs
@@ -44,6 +44,11 @@
           </th>
           <th class="te-anomaly-table__cell-head">
             <a class="te-anomaly-table__cell-link">
+              Anomaly Type
+            </a>
+          </th>
+          <th class="te-anomaly-table__cell-head">
+            <a class="te-anomaly-table__cell-link">
               Feedback
             </a>
           </th>
@@ -74,7 +79,14 @@
               </li>
             </ul>
            </td>
-           <td class="te-anomaly-table__cell">
+          <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">
+                {{anomaly.type}}
+              </li>
+            </ul>
+          </td>
+          <td class="te-anomaly-table__cell">
               {{#if renderStatusIcon}}
                 {{#if anomaly.showResponseSaved}}
                   <i class="te-anomaly-table__icon--status glyphicon glyphicon-ok-circle"></i>
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 2f88193..b641a5c 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/entity-filter/component.js
@@ -121,7 +121,7 @@ export default Component.extend({
       case 'metric': {
         return fetch(autocompleteAPI.metric(text))
           .then(checkStatus)
-          .then(metrics => metrics.map(m => m.name));
+          .then(metrics => [...new Set(metrics.map(m => m.name))]);
       }
       case 'application': {
         return fetch(autocompleteAPI.application(text))
@@ -140,7 +140,12 @@ export default Component.extend({
       case 'dataset': {
         return fetch(autocompleteAPI.dataset(text))
           .then(checkStatus)
-          .then(datasets => datasets.map(d => d.name));
+          .then(datasets =>  [...new Set(datasets.map(d => d.name))]);
+      }
+      case 'alertName': {
+        return fetch(autocompleteAPI.alertByName(text))
+          .then(checkStatus)
+          .then(detections => detections.map(d => d.name));
       }
     }
   }),
diff --git a/thirdeye/thirdeye-frontend/app/utils/anomaly.js b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
index 3bbb2b8..3a2542b 100644
--- a/thirdeye/thirdeye-frontend/app/utils/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
@@ -79,6 +79,10 @@ export const anomalyResponseObjNew = [
   }
 ];
 
+export const anomalyTypeMapping = {
+  "DEVIATION": "Metric Deviation", "TREND_CHANGE": "Trend Change", "DATA_SLA": "SLA Violation"
+}
+
 /**
  * Mapping for anomalyResponseObj 'status' to 'name' for easy lookup
  */
@@ -95,7 +99,6 @@ anomalyResponseObjNew.forEach((obj) => {
   anomalyResponseMapNew[obj.value] = obj.name;
 });
 
-
 /**
  * Update feedback status on any anomaly
  * @method updateAnomalyFeedback
@@ -243,6 +246,50 @@ export function pluralizeTime(time, unit) {
   return time ? time + ' ' + unitStr : '';
 }
 
+export function searchAnomaly(offset, limit, startTime, endTime, anomalyIds) {
+  return searchAnomalyWithFilters(offset, limit, startTime, endTime, [], [], [], [], [], anomalyIds)
+}
+
+export function searchAnomalyWithFilters(offset, limit, startTime, endTime, feedbackStatuses, subscriptionGroups,
+  detectionNames, metrics, datasets, anomalyIds) {
+  let url = `/anomaly-search?offset=${offset}&limit=${limit}`;
+  if (startTime) {
+    url = url.concat(`&startTime=${startTime}`);
+  }
+  if (endTime) {
+    url = url.concat(`&endTime=${endTime}`);
+  }
+  feedbackStatuses = feedbackStatuses || [];
+  for (const feedbackStatus of feedbackStatuses) {
+    const feedback = anomalyResponseObj.find(feedback => feedback.name === feedbackStatus)
+    if (feedback) {
+      url = url.concat(`&feedbackStatus=${feedback.value}`);
+    }
+  }
+  subscriptionGroups = subscriptionGroups || [];
+  for (const subscriptionGroup of subscriptionGroups) {
+    url = url.concat(`&subscriptionGroup=${subscriptionGroup}`);
+  }
+  detectionNames = detectionNames || [];
+  for (const detectionName of detectionNames) {
+    url = url.concat(`&detectionName=${detectionName}`);
+  }
+  metrics = metrics || [];
+  for (const metric of metrics) {
+    url = url.concat(`&metric=${metric}`);
+  }
+  datasets = datasets || [];
+  for (const dataset of datasets) {
+    url = url.concat(`&dataset=${dataset}`);
+  }
+  anomalyIds = anomalyIds || [];
+  for (const anomalyId of anomalyIds) {
+    url = url.concat(`&anomalyId=${anomalyId}`);
+  }
+  return fetch(url).then(checkStatus);
+}
+
+
 export default {
   anomalyResponseObj,
   anomalyResponseMap,
@@ -255,5 +302,8 @@ export default {
   putAlertActiveStatus,
   getYamlPreviewAnomalies,
   getAnomaliesByAlertId,
-  getBounds
+  getBounds,
+  searchAnomaly,
+  searchAnomalyWithFilters,
+  anomalyTypeMapping
 };


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