You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by ap...@apache.org on 2019/01/31 18:59:13 UTC

[incubator-pinot] branch master updated: [TE] rootcause - callgraph gui (#3769)

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

apucher 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 0f4bf85  [TE] rootcause - callgraph gui (#3769)
0f4bf85 is described below

commit 0f4bf857f6df8a707cc9899c22298a2c6d23b126
Author: Alexander Pucher <ap...@linkedin.com>
AuthorDate: Thu Jan 31 10:59:08 2019 -0800

    [TE] rootcause - callgraph gui (#3769)
    
    Adds an additional CallGraph tab to the RCA UI that provides bare-bones visualization of call graph analysis results. This call graph UI integrates directly with data sets and metrics that support page_key and fabric/datacenter filters. The UI does not support time series visualization and selection toggling yet.
---
 .../rootcause-callgraph-table/component.js         | 137 +++++++++++++++++++++
 .../rootcause-callgraph-table/template.hbs         |  33 +++++
 .../pods/partials/rootcause/callgraph/template.hbs |  10 ++
 .../app/pods/rootcause/controller.js               |  35 +++++-
 .../app/pods/rootcause/template.hbs                |   5 +
 .../services/rootcause-callgraph-cache/service.js  | 106 ++++++++++++++++
 .../app/shared/callgraphTableColumns.js            |  28 +++++
 thirdeye/thirdeye-frontend/app/styles/app.scss     |   1 +
 .../app/styles/components/metrics-table.scss       |   7 ++
 .../app/styles/components/rootcause-callgraph.scss |   7 ++
 thirdeye/thirdeye-frontend/app/utils/rca-utils.js  |  37 +++++-
 11 files changed, 396 insertions(+), 10 deletions(-)

diff --git a/thirdeye/thirdeye-frontend/app/pods/components/rootcause-callgraph-table/component.js b/thirdeye/thirdeye-frontend/app/pods/components/rootcause-callgraph-table/component.js
new file mode 100644
index 0000000..0eb8995
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/rootcause-callgraph-table/component.js
@@ -0,0 +1,137 @@
+import { computed, getProperties, set } from '@ember/object';
+import Component from '@ember/component';
+import {
+  toFilters
+} from 'thirdeye-frontend/utils/rca-utils';
+import {
+  humanizeChange,
+  humanizeFloat
+} from 'thirdeye-frontend/utils/utils';
+import CALLGRAPH_TABLE_COLUMNS from 'thirdeye-frontend/shared/callgraphTableColumns';
+import _ from 'lodash';
+
+export default Component.extend({
+  classNames: ['rootcause-metrics'],
+
+  /**
+   * Columns for dimensions table
+   * @type {Array}
+   */
+  callgraphTableColumns: CALLGRAPH_TABLE_COLUMNS,
+
+  //
+  // external properties
+  //
+
+  /**
+   * Primary metric
+   * @type {string}
+   */
+  metricUrn: null,
+
+  /**
+   * Edges cache
+   * @type {object}
+   */
+  edges: null,
+
+  //
+  // internal properties
+  //
+
+  /**
+   * Tracks presence of callgraph edges
+   * @type {boolean}
+   */
+  hasEdges: computed('edges', function () {
+    const { edges } = getProperties(this, 'edges');
+    return !_.isEmpty(edges);
+  }),
+
+  /**
+   * Fabrics analyzed
+   * @type {array}
+   */
+  fabrics: computed('metricUrn', function () {
+    const { metricUrn } = getProperties(this, 'metricUrn');
+    if (_.isEmpty(metricUrn)) { return []; }
+
+    const fabrics = toFilters(metricUrn).filter(t => t[0] === 'data_center').map(t => t[2]);
+    if (_.isEmpty(fabrics)) { return ['all']; }
+
+    return fabrics;
+  }),
+
+  /**
+   * Page keys analyzed
+   * @type {array}
+   */
+  pageKeys: computed('metricUrn', function () {
+    const { metricUrn } = getProperties(this, 'metricUrn');
+    if (_.isEmpty(metricUrn)) { return []; }
+
+    const pageKeys = toFilters(metricUrn).filter(t => t[0] === 'page_key').map(t => t[2]);
+
+    return pageKeys;
+  }),
+
+  /**
+   * Tracks presence of page keys
+   * @type {boolean}
+   */
+  hasPageKeys: computed('pageKeys', function () {
+    const { pageKeys } = getProperties(this, 'pageKeys');
+    return !_.isEmpty(pageKeys);
+  }),
+
+  /**
+   * Data for metrics table
+   * @type {Array}
+   */
+  callgraphTableData: computed('edges', function() {
+    const { edges } = getProperties(this, 'edges');
+
+    if (_.isEmpty(edges)) { return []; }
+
+    return Object.keys(edges).map(edge =>
+      toFilters(edge).reduce((agg, t) => { agg[t[0]] = this._parse(t[2]); return agg; }, {}));
+  }),
+
+  /**
+   * Keeps track of items that are selected in the table
+   * @type {Array}
+   */
+  preselectedItems: computed({
+    get () {
+      return [];
+    },
+    set () {
+      // ignore
+    }
+  }),
+
+  /**
+   * Parse values heuristically as string or float
+   * @private
+   */
+  _parse(value) {
+    const f = parseFloat(value);
+    if (!Number.isNaN(f)) {
+      if (f >= 100) {
+        return Math.round(f);
+      }
+      return (f / 1000.0).toFixed(3);
+    }
+    return value;
+  },
+
+  actions: {
+    /**
+     * Triggered on cell selection, ignored.
+     * @param {Object} e
+     */
+    displayDataChanged (e) {
+      // ignore
+    }
+  }
+});
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/rootcause-callgraph-table/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/rootcause-callgraph-table/template.hbs
new file mode 100644
index 0000000..cdc951a
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/components/rootcause-callgraph-table/template.hbs
@@ -0,0 +1,33 @@
+
+{{#if hasPageKeys}}
+  <ul class="list-unstyled">
+    <li><span class="rootcause-callgraph-label">Page Keys: </span>{{pageKeys}}</li>
+    <li><span class="rootcause-callgraph-label">Fabrics: </span>{{fabrics}}</li>
+  </ul>
+
+  {{#if hasEdges}}
+    <div class="rootcause-callgraph-table">
+      {{update-table
+        data=callgraphTableData
+        columns=callgraphTableColumns
+        preselectedItems=preselectedItems
+        showColumnsDropdown=false
+        showGlobalFilter=false
+      }}
+    </div>
+
+  {{else}}
+    <div class="rootcause-alert alert alert-warning fade in">
+      No call graph data available for the selected page keys and time range.
+      You may need to expand the investigation period.
+    </div>
+    
+  {{/if}}
+
+{{else}}
+  <div class="rootcause-alert alert alert-warning fade in">
+    Call Graph analysis requires at least one active page key filter.
+    Please select a metric with a page key support, and select a page key filter to generate the report.
+  </div>
+
+{{/if}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/partials/rootcause/callgraph/template.hbs b/thirdeye/thirdeye-frontend/app/pods/partials/rootcause/callgraph/template.hbs
new file mode 100644
index 0000000..136a395
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/partials/rootcause/callgraph/template.hbs
@@ -0,0 +1,10 @@
+{{#if isLoadingCallgraph}}
+  <div class="spinner-wrapper spinner-wrapper--card">
+    {{ember-spinner}}
+  </div>
+{{/if}}
+
+{{rootcause-callgraph-table
+  edges=edges
+  metricUrn=metricUrn
+}}
diff --git a/thirdeye/thirdeye-frontend/app/pods/rootcause/controller.js b/thirdeye/thirdeye-frontend/app/pods/rootcause/controller.js
index f8ebeee..531a3af 100644
--- a/thirdeye/thirdeye-frontend/app/pods/rootcause/controller.js
+++ b/thirdeye/thirdeye-frontend/app/pods/rootcause/controller.js
@@ -25,6 +25,7 @@ const ROOTCAUSE_TAB_DIMENSIONS = 'dimensions';
 const ROOTCAUSE_TAB_METRICS = 'metrics';
 const ROOTCAUSE_TAB_EVENTS = 'events';
 const ROOTCAUSE_TAB_TREND = 'trend';
+const ROOTCAUSE_TAB_CALLGRAPH = 'callgraph';
 
 const ROOTCAUSE_SETUP_MODE_CONTEXT = "context";
 const ROOTCAUSE_SETUP_MODE_SELECTED = "selected";
@@ -39,6 +40,7 @@ const ROOTCAUSE_SERVICE_TIMESERIES = 'timeseries';
 const ROOTCAUSE_SERVICE_AGGREGATES = 'aggregates';
 const ROOTCAUSE_SERVICE_BREAKDOWNS = 'breakdowns';
 const ROOTCAUSE_SERVICE_ANOMALY_FUNCTIONS = 'anomalyFunctions';
+const ROOTCAUSE_SERVICE_CALLGRAPH = 'callgraph';
 const ROOTCAUSE_SERVICE_ALL = 'all';
 
 const ROOTCAUSE_SESSION_TIMER_INTERVAL = 300000;
@@ -98,6 +100,8 @@ export default Controller.extend({
 
   anomalyFunctionService: service('services/rootcause-anomalyfunction-cache'),
 
+  callgraphService: service('services/rootcause-callgraph-cache'),
+
   //
   // user details
   //
@@ -297,6 +301,9 @@ export default Controller.extend({
    *
    * anomalyfunctions: anomaly function baselines for display in chart
    *                   (typically displayed in timeseries chart)
+   *
+   * callgraph:        service call graph edges as ranked by the backend
+   *                   (typically displayed in call graph table)
    */
   _contextObserver: observer(
     'context',
@@ -305,8 +312,8 @@ export default Controller.extend({
     'sizeMetricUrns',
     'activeTab',
     function () {
-      const { context, selectedUrns, sizeMetricUrns, entitiesService, timeseriesService, aggregatesService, breakdownsService, scoresService, anomalyFunctionService, activeTab, setupMode } =
-        getProperties(this, 'context', 'selectedUrns', 'sizeMetricUrns', 'entitiesService', 'timeseriesService', 'aggregatesService', 'breakdownsService', 'scoresService', 'anomalyFunctionService', 'activeTab', 'setupMode');
+      const { context, selectedUrns, sizeMetricUrns, entitiesService, timeseriesService, aggregatesService, breakdownsService, scoresService, anomalyFunctionService, callgraphService, activeTab, setupMode } =
+        getProperties(this, 'context', 'selectedUrns', 'sizeMetricUrns', 'entitiesService', 'timeseriesService', 'aggregatesService', 'breakdownsService', 'scoresService', 'anomalyFunctionService', 'callgraphService', 'activeTab', 'setupMode');
       if (!context || !selectedUrns) {
         return;
       }
@@ -389,6 +396,13 @@ export default Controller.extend({
         .map(urn => [].concat(anomalyOffsets.map(offset => toOffsetUrn(urn, offset))))
         .reduce((agg, l) => agg.concat(l), []);
 
+      //
+      // call graph
+      //
+      if (activeTab === ROOTCAUSE_SERVICE_CALLGRAPH) {
+        callgraphService.request(context, [...context.urns]);
+      }
+
       aggregatesService.request(context, new Set([...offsetUrns, ...anomalyOffsetUrns]));
 
     }
@@ -485,6 +499,12 @@ export default Controller.extend({
   scores: reads('scoresService.scores'),
 
   /**
+   * Subscribed callgraph edges cache
+   * @type {object}
+   */
+  edges: reads('callgraphService.edges'),
+
+  /**
    * Primary metric urn for rootcause search
    * @type {string}
    */
@@ -611,6 +631,8 @@ export default Controller.extend({
 
   isLoadingMetricData: or('isLoadingAggregates', 'isLoadingScores'),
 
+  isLoadingCallgraph: gt('callgraphService.pending.size', 0),
+
   loadingFrameworks: reads('entitiesService.pending'),
 
   //
@@ -1161,8 +1183,8 @@ export default Controller.extend({
      * Clears error logs of data services and/or route
      */
     clearErrors(type) {
-      const { entitiesService, timeseriesService, aggregatesService, breakdownsService, anomalyFunctionService } =
-        getProperties(this, 'entitiesService', 'timeseriesService', 'aggregatesService', 'breakdownsService', 'anomalyFunctionService');
+      const { entitiesService, timeseriesService, aggregatesService, breakdownsService, anomalyFunctionService, callgraphService } =
+        getProperties(this, 'entitiesService', 'timeseriesService', 'aggregatesService', 'breakdownsService', 'anomalyFunctionService', 'callgraphService');
 
       switch(type) {
         case ROOTCAUSE_SERVICE_ENTITIES:
@@ -1185,6 +1207,10 @@ export default Controller.extend({
           anomalyFunctionService.clearErrors();
           break;
 
+        case ROOTCAUSE_SERVICE_CALLGRAPH:
+          callgraphService.clearErrors();
+          break;
+
         case ROOTCAUSE_SERVICE_ROUTE:
           setProperties(this, { routeErrors: new Set() });
           break;
@@ -1195,6 +1221,7 @@ export default Controller.extend({
           aggregatesService.clearErrors();
           breakdownsService.clearErrors();
           anomalyFunctionService.clearErrors();
+          callgraphService.clearErrors();
           break;
 
       }
diff --git a/thirdeye/thirdeye-frontend/app/pods/rootcause/template.hbs b/thirdeye/thirdeye-frontend/app/pods/rootcause/template.hbs
index 3b984a1..4842585 100644
--- a/thirdeye/thirdeye-frontend/app/pods/rootcause/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/rootcause/template.hbs
@@ -174,6 +174,7 @@
                 {{#tablist.tab name="dimensions"}}Dimensions{{/tablist.tab}}
                 {{#tablist.tab name="events"}}Events{{/tablist.tab}}
                 {{#tablist.tab name="trend"}}Trend{{/tablist.tab}}
+                {{#tablist.tab name="callgraph"}}CallGraph{{/tablist.tab}}
               {{/tabs.tablist}}
               {{!-- metrics --}}
               {{#tabs.tabpanel name="metrics"}}
@@ -191,6 +192,10 @@
               {{#tabs.tabpanel name="trend"}}
                 {{partial 'partials/rootcause/trend'}}
               {{/tabs.tabpanel}}
+              {{!-- callgraph --}}
+              {{#tabs.tabpanel name="callgraph"}}
+                {{partial 'partials/rootcause/callgraph'}}
+              {{/tabs.tabpanel}}
             {{/shared/common-tabs}}
           </div>
         </div>
diff --git a/thirdeye/thirdeye-frontend/app/pods/services/rootcause-callgraph-cache/service.js b/thirdeye/thirdeye-frontend/app/pods/services/rootcause-callgraph-cache/service.js
new file mode 100644
index 0000000..18d0b87
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/pods/services/rootcause-callgraph-cache/service.js
@@ -0,0 +1,106 @@
+import Service from '@ember/service';
+import { inject as service } from '@ember/service';
+import {
+  trimTimeRanges,
+  filterPrefix,
+  toBaselineRange,
+  toDimensionsUrn
+} from 'thirdeye-frontend/utils/rca-utils';
+import { checkStatus } from 'thirdeye-frontend/utils/utils';
+import _ from 'lodash';
+
+const ROOTCAUSE_CALLGRAPH_ENDPOINT = '/rootcause/query';
+const ROOTCAUSE_CALLGRAPH_PRIORITY = 15;
+
+export default Service.extend({
+  edges: null, // {}
+
+  context: null, // {}
+
+  pending: null, // Set
+
+  errors: null, // Set({ urn, error })
+
+  fetcher: service('services/rootcause-fetcher'),
+
+  init() {
+    this._super(...arguments);
+    this.setProperties({ edges: {}, context: {}, pending: new Set(), errors: new Set() });
+  },
+
+  clearErrors() {
+    this.setProperties({ errors: new Set() });
+  },
+
+  request(requestContext, urns) {
+    const { context } = this.getProperties('context');
+
+    const metrics = [...urns].filter(urn => urn.startsWith('thirdeye:metric:'));
+
+    if(_.isEqual(context, requestContext)) {
+      return;
+    }
+
+    // new analysis range: evict all, reload
+    this.get('fetcher').resetPrefix(ROOTCAUSE_CALLGRAPH_ENDPOINT);
+    this.setProperties({ context: _.cloneDeep(requestContext), edges: {}, pending: new Set(metrics) });
+
+    if (_.isEmpty(metrics)) {
+      return;
+    }
+
+    // call graph properties
+    const fetcher = this.get('fetcher');
+
+    const dimensionsUrns = [...metrics].map(toDimensionsUrn);
+
+    // TODO generalize replacement
+    const dimensionsUrnsModified = dimensionsUrns
+      .map(urn => urn.replace(/:data_center/, ':callee_fabric'))
+      .map(urn => urn.replace(/:fabric/, ':callee_fabric'))
+      .map(urn => urn.replace(/:service/, ':callee_container'))
+      .map(urn => urn.replace(/:data_center/, ':callee_fabric'));
+
+    const url = this._makeUrl('callgraph', requestContext, dimensionsUrnsModified);
+    fetcher.fetch(url, ROOTCAUSE_CALLGRAPH_PRIORITY)
+      .then(checkStatus)
+      .then(res => this._complete(requestContext, res))
+      .catch(error => this._handleError(urns, error));
+  },
+
+  _complete(requestContext, incoming) {
+    const { context } = this.getProperties('context');
+
+    if (!_.isEqual(context, requestContext)) {
+      return;
+    }
+
+    const edges = incoming.reduce((agg, e) => { agg[e.urn] = e; return agg; }, {});
+
+    this.setProperties({ edges, pending: new Set() });
+  },
+
+  _makeUrl(framework, context, urns) {
+    const urnString = filterPrefix(urns, 'thirdeye:dimensions:').join(',');
+    const ranges = trimTimeRanges(context.anomalyRange, context.analysisRange);
+
+    const baselineRange = toBaselineRange(ranges.anomalyRange, context.compareMode);
+    return `${ROOTCAUSE_CALLGRAPH_ENDPOINT}?framework=${framework}` +
+      `&anomalyStart=${ranges.anomalyRange[0]}&anomalyEnd=${ranges.anomalyRange[1]}` +
+      `&baselineStart=${baselineRange[0]}&baselineEnd=${baselineRange[1]}` +
+      `&analysisStart=${ranges.analysisRange[0]}&analysisEnd=${ranges.analysisRange[1]}` +
+      `&urns=${urnString}`;
+  },
+
+  _handleError(urns, error) {
+    const { errors, pending } = this.getProperties('errors', 'pending');
+
+    const newError = urns;
+    const newErrors = new Set([...errors, newError]);
+
+    const newPending = new Set(pending);
+    [...urns].forEach(urn => newPending.delete(urn));
+
+    this.setProperties({ errors: newErrors, pending: newPending });
+  }
+});
diff --git a/thirdeye/thirdeye-frontend/app/shared/callgraphTableColumns.js b/thirdeye/thirdeye-frontend/app/shared/callgraphTableColumns.js
new file mode 100644
index 0000000..dcb0d9f
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/shared/callgraphTableColumns.js
@@ -0,0 +1,28 @@
+// rootcause dimensions table columns
+export default [
+  {
+    propertyName: 'callee_container',
+    title: 'Container',
+    className: 'metrics-table__column metrics-table__column--text'
+  }, {
+    propertyName: 'callee_fabric',
+    title: 'Fabric',
+    className: 'metrics-table__column metrics-table__column--text'
+  }, {
+    propertyName: 'callee_api',
+    title: 'API',
+    className: 'metrics-table__column metrics-table__column--text'
+  }, {
+    propertyName: 'currCount',
+    title: 'Traffic',
+    disableFiltering: true,
+    className: 'metrics-table__column metrics-table__column--small'
+  }, {
+    propertyName: 'diffAverage',
+    title: 'Change in Avg. Latency',
+    disableFiltering: true,
+    className: 'metrics-table__column metrics-table__column--small',
+    sortDirection: 'desc',
+    sortPrecedence: 0
+  }
+];
diff --git a/thirdeye/thirdeye-frontend/app/styles/app.scss b/thirdeye/thirdeye-frontend/app/styles/app.scss
index fd476c3..59940ab 100644
--- a/thirdeye/thirdeye-frontend/app/styles/app.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/app.scss
@@ -56,6 +56,7 @@ body {
 @import 'components/rootcause-chart';
 @import 'components/rootcause-trend';
 @import 'components/rootcause-dimensions';
+@import 'components/rootcause-callgraph';
 @import 'components/alert-report-modal';
 @import 'components/te-radio';
 @import 'components/metrics-table';
diff --git a/thirdeye/thirdeye-frontend/app/styles/components/metrics-table.scss b/thirdeye/thirdeye-frontend/app/styles/components/metrics-table.scss
index 9680796..a98cdf2 100644
--- a/thirdeye/thirdeye-frontend/app/styles/components/metrics-table.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/components/metrics-table.scss
@@ -1,6 +1,7 @@
 .metrics-table {
   &__column {
     vertical-align: middle !important; // override ember-models-table
+
     &--checkbox {
       width: 24px;
     }
@@ -10,8 +11,14 @@
       text-align: right;
     }
 
+    &--text {
+      width: 80px;
+      text-align: left;
+    }
+
     &--large {
       min-width: 200px;
+      text-align: left;
     }
   }
 
diff --git a/thirdeye/thirdeye-frontend/app/styles/components/rootcause-callgraph.scss b/thirdeye/thirdeye-frontend/app/styles/components/rootcause-callgraph.scss
new file mode 100644
index 0000000..0a320e6
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/styles/components/rootcause-callgraph.scss
@@ -0,0 +1,7 @@
+.rootcause-callgraph-label {
+  font-weight: bold;
+}
+
+.rootcause-callgraph-table {
+  margin-top: 16px;
+}
diff --git a/thirdeye/thirdeye-frontend/app/utils/rca-utils.js b/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
index 4c2103c..e96493b 100644
--- a/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
+++ b/thirdeye/thirdeye-frontend/app/utils/rca-utils.js
@@ -111,6 +111,12 @@ export function stripTail(urn) {
   if (urn.startsWith('frontend:anomalyfunction:')) {
     return _.slice(parts, 0, 3).join(':');
   }
+  if (urn.startsWith('thirdeye:dimensions:')) {
+    return _.slice(parts, 0, 2).join(':');
+  }
+  if (urn.startsWith('thirdeye:callgraph:')) {
+    return _.slice(parts, 0, 2).join(':');
+  }
   return urn;
 }
 
@@ -124,13 +130,19 @@ export function stripTail(urn) {
 export function extractTail(urn) {
   const parts = urn.split(':');
   if (urn.startsWith('thirdeye:metric:')) {
-    return _.slice(parts, 3);
+    return _.slice(parts, 3).filter(p => !_.isEmpty(p));
   }
   if (urn.startsWith('frontend:metric:')) {
-    return _.slice(parts, 4);
+    return _.slice(parts, 4).filter(p => !_.isEmpty(p));
   }
   if (urn.startsWith('frontend:anomalyfunction:')) {
-    return _.slice(parts, 3);
+    return _.slice(parts, 3).filter(p => !_.isEmpty(p));
+  }
+  if (urn.startsWith('thirdeye:dimensions:')) {
+    return _.slice(parts, 2).filter(p => !_.isEmpty(p));
+  }
+  if (urn.startsWith('thirdeye:callgraph:')) {
+    return _.slice(parts, 2).filter(p => !_.isEmpty(p));
   }
   return [];
 }
@@ -190,6 +202,17 @@ export function toBaselineUrn(urn) {
 }
 
 /**
+ * Converts any metric urn to its dimensions equivalent
+ * Example: 'thirdeye:metric:123:country=IT' returns 'thirdeye:dimensions:country=IT'
+ *
+ * @param {string} urn metric urn
+ * @returns {string} dimensions urn
+ */
+export function toDimensionsUrn(urn) {
+  return appendTail('thirdeye:dimensions:', extractTail(urn));
+}
+
+/**
  * Converts any metric urn to its frontend metric-reference equivalent, with an user-specified offset.
  *
  * @param {string} urn metric urn
@@ -336,7 +359,7 @@ function metricUrnHelper(prefix, urn) {
     const tail = makeUrnTail(parts, 3);
     return `${prefix}${parts[2]}${tail}`;
   }
-  throw new Error(`Requires metric urn, but found ${urn}`);
+  throw new Error(`Requires supported urn, but found ${urn}`);
 }
 
 /**
@@ -467,12 +490,13 @@ export function toAbsoluteRange(urn, currentRange, baselineCompareMode) {
 export function toFilters(urns) {
   const flatten = (agg, l) => agg.concat(l);
   const dimensionFilters = filterPrefix(urns, 'thirdeye:dimension:').map(urn => _.slice(urn.split(':').map(decodeURIComponent), 2, 4).insertAt(1, '='));
-
+  const dimensionsFilters = filterPrefix(urns, 'thirdeye:dimensions:').map(extractTail).map(enc => enc.map(tup => splitFilterFragment(decodeURIComponent(tup)))).reduce(flatten, []);
   const metricFilters = filterPrefix(urns, 'thirdeye:metric:').map(extractTail).map(enc => enc.map(tup => splitFilterFragment(decodeURIComponent(tup)))).reduce(flatten, []);
   const frontendMetricFilters = filterPrefix(urns, 'frontend:metric:').map(extractTail).map(enc => enc.map(tup => splitFilterFragment(decodeURIComponent(tup)))).reduce(flatten, []);
   const anomalyFunctionFilters = filterPrefix(urns, 'frontend:anomalyfunction:').map(extractTail).map(enc => enc.map(tup => splitFilterFragment(decodeURIComponent(tup)))).reduce(flatten, []);
+  const callgraphFilters = filterPrefix(urns, 'thirdeye:callgraph:').map(extractTail).map(enc => enc.map(tup => splitFilterFragment(decodeURIComponent(tup)))).reduce(flatten, []);
 
-  return [...new Set([...dimensionFilters, ...metricFilters, ...frontendMetricFilters, ...anomalyFunctionFilters])].sort();
+  return [...new Set([...dimensionFilters, ...dimensionsFilters, ...metricFilters, ...frontendMetricFilters, ...anomalyFunctionFilters, ...callgraphFilters])].sort();
 }
 
 /**
@@ -729,6 +753,7 @@ export default {
   toMetricUrn,
   toOffsetUrn,
   toAbsoluteUrn,
+  toDimensionsUrn,
   stripTail,
   extractTail,
   appendTail,


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