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 2021/01/19 19:14:32 UTC

[incubator-pinot] branch master updated: [TE]frontend - Add support for feedback propagation for Entity Monitoring (#6447)

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 174a77b  [TE]frontend - Add support for feedback propagation for Entity Monitoring (#6447)
174a77b is described below

commit 174a77bbbca2ed1990e8f2816256cdb230913f14
Author: Tejas Ajmera <33...@users.noreply.github.com>
AuthorDate: Tue Jan 19 11:14:16 2021 -0800

    [TE]frontend - Add support for feedback propagation for Entity Monitoring (#6447)
    
    Feedback propagation in Entity Monitoring entails exposing options to cascade/not cascade the feedback. Under the cascade option, all the child nodes for the anomaly will have the selected feedback cascaded along right up to the leaf level nodes. Any previously selected feedback at any point in the subtree will be overwritten.
---
 .../components/composite-anomalies/component.js    | 14 +++--
 .../entity-metrics-anomalies/component.js          |  3 +-
 .../entity-metrics-anomalies/template.hbs          |  2 +-
 .../group-constituents-anomalies/component.js      |  3 +-
 .../group-constituents-anomalies/template.hbs      |  2 +-
 .../parent-anomalies/component.js                  |  3 +-
 .../parent-anomalies/template.hbs                  |  2 +-
 .../resolution/component.js                        | 60 +++++++++++++++---
 .../resolution/template.hbs                        | 65 +++++++++++++++----
 thirdeye/thirdeye-frontend/app/styles/app.scss     |  2 +-
 .../pods/custom/composite-anomalies-table.scss     | 53 ++++++++++++++++
 .../styles/pods/custom/parent-anomalies-table.scss | 24 --------
 .../app/utils/anomalies-tree-parser.js             | 58 ++++++++++++++++-
 thirdeye/thirdeye-frontend/app/utils/anomaly.js    |  5 +-
 .../app/utils/composite-anomalies.js               |  2 +-
 .../entity-metrics-anomalies/component-test.js     |  4 +-
 .../group-constituents-anomalies/component-test.js |  4 +-
 .../parent-anomalies/component-test.js             |  4 +-
 .../tests/unit/utils/anomalies-tree-parser-test.js | 72 +++++++++++++++++++++-
 19 files changed, 321 insertions(+), 61 deletions(-)

diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/component.js b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/component.js
index b7e43f0..197d103 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/component.js
@@ -1,7 +1,7 @@
 import Component from '@ember/component';
 import { A as EmberArray } from '@ember/array';
 
-import { parseRoot, parseSubtree } from 'thirdeye-frontend/utils/anomalies-tree-parser';
+import { parseRoot, parseSubtree, updateAnomalyFeedback } from 'thirdeye-frontend/utils/anomalies-tree-parser';
 import pubSub from 'thirdeye-frontend/utils/pub-sub';
 
 export default Component.extend({
@@ -82,7 +82,7 @@ export default Component.extend({
     this.set('data', output);
 
     //Processing on each drilldown
-    const subscription = pubSub.subscribe('onAnomalyDrilldown', (anomalyId) => {
+    const anomalyDrilldownSubscription = pubSub.subscribe('onAnomalyDrilldown', (anomalyId) => {
       const { output, breadcrumbInfo } = parseSubtree(anomalyId, this.anomalies);
 
       this.set('breadcrumbList', [...this.breadcrumbList, breadcrumbInfo]);
@@ -91,7 +91,12 @@ export default Component.extend({
       this.onDrilldown();
     });
 
-    this.set('subscription', subscription);
+    const feedbackSubscription = pubSub.subscribe('onFeedback', ({ anomalyId, feedbackType, cascade }) => {
+      updateAnomalyFeedback(anomalyId, feedbackType, cascade, this.anomalies);
+    });
+
+    this.set('anomalyDrilldownSubscription', anomalyDrilldownSubscription);
+    this.set('feedbackSubscription', feedbackSubscription);
   },
 
   /**
@@ -101,7 +106,8 @@ export default Component.extend({
    * @override
    */
   willDestroyElement() {
-    this.subscription.unSubscribe();
+    this.anomalyDrilldownSubscription.unSubscribe();
+    this.feedbackSubscription.unSubscribe();
     this._super(...arguments);
   },
 
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/component.js b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/component.js
index 29402a7..ca2f4db 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/component.js
@@ -74,7 +74,8 @@ export default Component.extend({
           metric: d.metric,
           dimensions: d.dimensions,
           currentPredicted: d.currentPredicted,
-          feedback: getFeedback(d.feedback)
+          feedback: getFeedback(d.feedback),
+          isLeaf: true
         };
         computedTableData.push(row);
       });
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/template.hbs
index 4c67aa6..5363395 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/entity-metrics-anomalies/template.hbs
@@ -1 +1 @@
-{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords}}
\ No newline at end of file
+{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords customClasses=customClasses}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/component.js b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/component.js
index 2a868d4..fcca0c8 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/component.js
@@ -69,7 +69,8 @@ export default Component.extend({
           groupName: d.groupName,
           criticalityScore: d.criticality,
           currentPredicted: d.currentPredicted,
-          feedback: getFeedback(d.feedback)
+          feedback: getFeedback(d.feedback),
+          isLeaf: false
         };
         computedTableData.push(row);
       });
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/template.hbs
index 4c67aa6..5363395 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/group-constituents-anomalies/template.hbs
@@ -1 +1 @@
-{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords}}
\ No newline at end of file
+{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords customClasses=customClasses}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/component.js b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/component.js
index de9049e..0ebb4db 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/component.js
@@ -57,7 +57,8 @@ export default Component.extend({
           anomalyId: d.id,
           startDuration: getAnomaliesStartDuration(d.startTime, d.endTime, true),
           anomaliesDetails: this.getAnomaliesDetails(d.details),
-          feedback: getFeedback(d.feedback)
+          feedback: getFeedback(d.feedback),
+          isLeaf: false
         };
         computedTableData.push(row);
       });
diff --git a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/template.hbs b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/template.hbs
index 4c67aa6..5363395 100644
--- a/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/components/composite-anomalies/parent-anomalies/template.hbs
@@ -1 +1 @@
-{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords}}
\ No newline at end of file
+{{composite-anomalies/data-table title=title tableData=tableData tableColumns=tableColumns noRecords=noRecords customClasses=customClasses}}
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/resolution/component.js b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/resolution/component.js
index 0c8f79c..035b61d 100644
--- a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/resolution/component.js
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/resolution/component.js
@@ -1,15 +1,61 @@
 import Component from '@ember/component';
-import { set } from '@ember/object';
+import { set, getWithDefault, setProperties } from '@ember/object';
+
+import { anomalyResponseObj, updateAnomalyFeedback, verifyAnomalyFeedback } from 'thirdeye-frontend/utils/anomaly';
+import pubSub from 'thirdeye-frontend/utils/pub-sub';
 
 export default Component.extend({
-  didReceiveAttrs() {
-    this._super(...arguments);
+  renderStatusIcon: true,
+  showResponseFailed: false,
+  showResponseSaved: false,
+  cascadeSelection: false,
+
+  async submitFeedback() {
+    const {
+      feedback: { selected: selection },
+      anomalyId
+    } = this.record;
+
+    try {
+      const feedbackType = anomalyResponseObj.find((f) => f.name === selection).value;
+      await updateAnomalyFeedback(anomalyId, feedbackType, this.cascadeSelection);
+
+      // We make a call to ensure our new response got saved
+      const savedAnomaly = await verifyAnomalyFeedback(anomalyId);
+      const filterMap = getWithDefault(savedAnomaly, 'feedback.feedbackType', null);
+      // This verifies that the status change got saved
+      const keyPresent = filterMap && filterMap === feedbackType;
+
+      if (keyPresent) {
+        set(this, 'showResponseSaved', true);
+      } else {
+        throw 'Response not saved';
+      }
+
+      pubSub.publish('onFeedback', { anomalyId, feedbackType, cascade: this.cascadeSelection });
+    } catch (err) {
+      setProperties(this, {
+        showResponseFailed: true,
+        showResponseSaved: false
+      });
+    }
   },
+
   actions: {
-    onChangeAnomalyResponse: (anomalyObject, selection, options) => {
-      set(options, 'selected', selection); // set selected option to user's selection
-      set(anomalyObject.feedback, 'selected', selection); // set anomalies feedback to user's selection
-      /* TODO: add API call to update anomalies on the BackEnd */
+    onChangeAnomalyResponse: async function ({ feedback, isLeaf = false }, selection) {
+      set(feedback, 'selected', selection);
+
+      if (isLeaf) {
+        this.submitFeedback();
+      }
+    },
+
+    onSelectCascade(selection) {
+      this.set('cascadeSelection', selection);
+    },
+
+    onSubmit: async function () {
+      this.submitFeedback();
     }
   }
 });
diff --git a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/resolution/template.hbs b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/resolution/template.hbs
index e3e97bd..1735f96 100644
--- a/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/resolution/template.hbs
+++ b/thirdeye/thirdeye-frontend/app/pods/custom/composite-anomalies-table/resolution/template.hbs
@@ -1,11 +1,54 @@
-{{#power-select
-  triggerId=record.anomalyId
-  triggerClass="te-anomaly-table__select te-anomaly-table__select--margin-left"
-  options=record.feedback.options
-  searchEnabled=false
-  selected=record.feedback.selected
-  onchange=(action "onChangeAnomalyResponse" record)
-  as |response|
-}}
-  {{response}}
-{{/power-select}}
\ No newline at end of file
+<div class="composite-anomalies-table-form__feedback-selections">
+  <div class="composite-anomalies-table-form__selection-groups">
+    <div
+      class="composite-anomalies-table-form__selection-groups composite-anomalies-table-form__selection-groups--dropdown-group"
+    >
+      <div class="te-anomaly-table__icon-wrapper">
+        {{#if renderStatusIcon}}
+          {{#if showResponseSaved}}
+            <i class="te-anomaly-table__icon--status-no-margin glyphicon glyphicon-ok-circle"></i>
+          {{/if}}
+          {{#if showResponseFailed}}
+            <i
+              class="te-anomaly-table__icon--status-no-margin te-anomaly-table__icon--error glyphicon glyphicon-remove-circle"
+            ></i>
+          {{/if}}
+        {{/if}}
+      </div>
+      {{#power-select
+        triggerId=record.anomalyId
+        triggerClass="te-anomaly-table__select te-anomaly-table__select--margin-left"
+        options=record.feedback.options
+        searchEnabled=false
+        selected=record.feedback.selected
+        onchange=(action "onChangeAnomalyResponse" record) as |response|
+      }}
+        {{response}}
+      {{/power-select}}
+    </div>
+    {{#unless record.isLeaf}}
+      <div
+        class="composite-anomalies-table-form__selection-groups composite-anomalies-table-form__selection-groups--radio-group"
+      >
+        {{#radio-button value=true groupValue=cascadeSelection changed=(action "onSelectCascade")}}
+          Cascade down
+        {{/radio-button}}
+        {{#tooltip-on-element class="te-tooltip"}}
+          This will overwrite all the child feedbacks
+        {{/tooltip-on-element}}
+        {{#radio-button value=false groupValue=cascadeSelection changed=(action "onSelectCascade")}}
+          Don't cascade
+        {{/radio-button}}
+      </div>
+    {{/unless}}
+  </div>
+  {{#unless record.isLeaf}}
+    {{bs-button
+      defaultText="Submit"
+      type="primary"
+      buttonType="submit"
+      onClick=(action "onSubmit")
+      class="te-button te-button--submit"
+    }}
+  {{/unless}}
+</div>
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/styles/app.scss b/thirdeye/thirdeye-frontend/app/styles/app.scss
index 7229309..2940f75 100644
--- a/thirdeye/thirdeye-frontend/app/styles/app.scss
+++ b/thirdeye/thirdeye-frontend/app/styles/app.scss
@@ -82,5 +82,5 @@ body {
 @import 'pods/preview';
 @import 'pods/home/index/dashboard';
 @import 'pods/custom/anomalies-table';
-@import 'pods/custom/parent-anomalies-table';
+@import 'pods/custom/composite-anomalies-table';
 @import 'pods/custom/dimensions-table';
diff --git a/thirdeye/thirdeye-frontend/app/styles/pods/custom/composite-anomalies-table.scss b/thirdeye/thirdeye-frontend/app/styles/pods/custom/composite-anomalies-table.scss
new file mode 100644
index 0000000..9f2a303
--- /dev/null
+++ b/thirdeye/thirdeye-frontend/app/styles/pods/custom/composite-anomalies-table.scss
@@ -0,0 +1,53 @@
+.composite-anomalies-table {
+  width: 100%;
+  border: 1px solid #ccc;
+  tr {
+    border-bottom: 1px solid #ccc;
+  }
+  td,
+  th {
+    padding: 10px 10px 10px 20px;
+  }
+  &__multi-line-column {
+    margin: 0;
+    color: black;
+  }
+  &__duration {
+    margin: 0;
+  }
+  margin-bottom: 20px;
+}
+
+.composite-anomalies-table-form {
+  &__feedback-selections {
+    display: flex;
+    flex-direction: column;
+  }
+
+  &__selection-groups {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-evenly;
+  }
+
+  &__selection-groups--dropdown-group {
+    display: flex;
+    flex-direction: row;
+    height: 20px;
+    align-items: center;
+    max-width: 220px;
+    margin-right: 10px;
+  }
+
+  &__selection-groups--radio-group {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+
+.composite-anomalies-no-records {
+  padding: 10px 10px 10px 20px;
+  font-size: 16px;
+}
diff --git a/thirdeye/thirdeye-frontend/app/styles/pods/custom/parent-anomalies-table.scss b/thirdeye/thirdeye-frontend/app/styles/pods/custom/parent-anomalies-table.scss
deleted file mode 100644
index ff2ee95..0000000
--- a/thirdeye/thirdeye-frontend/app/styles/pods/custom/parent-anomalies-table.scss
+++ /dev/null
@@ -1,24 +0,0 @@
-.composite-anomalies-table {
-      width: 100%;
-      border: 1px solid #ccc;
-      tr {
-          border-bottom: 1px solid #ccc;
-      }
-      td, th {
-          padding: 10px 10px 10px 20px;
-      }
-      &__multi-line-column {
-          margin: 0;
-          color: black;
-      }
-      &__duration {
-          margin: 0;
-      }
-      margin-bottom: 20px;
-  }
-  
-  .composite-anomalies-no-records {
-      padding: 10px 10px 10px 20px;
-      font-size: 16px;
-  }
-  
\ No newline at end of file
diff --git a/thirdeye/thirdeye-frontend/app/utils/anomalies-tree-parser.js b/thirdeye/thirdeye-frontend/app/utils/anomalies-tree-parser.js
index 72d3128..bdb2006 100644
--- a/thirdeye/thirdeye-frontend/app/utils/anomalies-tree-parser.js
+++ b/thirdeye/thirdeye-frontend/app/utils/anomalies-tree-parser.js
@@ -155,13 +155,14 @@ const setMetricsBucket = (buckets, anomaly, metric) => {
     METRICS: { KEY: metricKey, DEFAULT_TITLE, COMPONENT_PATH }
   } = CLASSIFICATIONS;
   const { [metricKey]: { data } = {} } = buckets;
-  const { id, startTime, endTime, feedback, avgCurrentVal: current, avgBaselineVal: predicted } = anomaly;
+  const { id, startTime, endTime, feedback, dimensions, avgCurrentVal: current, avgBaselineVal: predicted } = anomaly;
 
   const metricTableRow = {
     id,
     startTime,
     endTime,
     metric,
+    dimensions,
     feedback,
     currentPredicted: {
       current: humanizeFloat(current),
@@ -465,6 +466,32 @@ const parseCompositeAnomaly = (input) => {
 };
 
 /**
+ * Update the feedback on current node and then use DFS to recursively update the feedback
+ * for the entire subree if the cascading option is selected
+ *
+ * @param {Object} anomaly
+ *   The starting node(root of a subtree) to update the feedback
+ * @param {String} feedbackType
+ *   The feedback to update to
+ * @param {Boolean} cascade
+ *   Whether or not the feedback should be cascaded downstream
+ */
+const setTreeFeedback = (anomaly, feedbackType, cascade) => {
+  anomaly.feedback = {
+    ...anomaly.feedback,
+    feedbackType: feedbackType
+  };
+
+  if (cascade) {
+    const { children = [] } = anomaly;
+
+    for (const child of children) {
+      setTreeFeedback(child, feedbackType, cascade);
+    }
+  }
+};
+
+/**
  * Perform depth-first-search to retrieve anomaly in the tree
  *
  * @param {Number} id
@@ -558,3 +585,32 @@ export const parseSubtree = (id, input) => {
     return parseCompositeAnomaly(anomaly);
   }
 };
+
+/**
+ * Update anomaly feedback for the current node and subtree nodes, provided the subtree nodes are not already
+ * explicitly tagged with feedback
+ *
+ * @param {Number} id
+ *   The anomaly id
+ * @param {String} feedback
+ *   The feedback submitted by the user
+ * @param {Boolean} cascade
+ *   Whether or not the feedback should be cascaded downstream
+ * @param {Array<Object> or Object} input
+ *   The tree structure hosting the anomaly referenced by the id.
+ *      -If the entire tree is being passed, it would be in an array form
+ *      -If a subtree is being passed, it would be in an object form
+ *
+ */
+export const updateAnomalyFeedback = (id, feedback, cascade, input) => {
+  let anomaly;
+  if (Array.isArray(input)) {
+    for (const entry of input) {
+      anomaly = findAnomaly(id, entry);
+    }
+  } else {
+    anomaly = findAnomaly(id, input);
+  }
+
+  setTreeFeedback(anomaly, feedback, cascade);
+};
diff --git a/thirdeye/thirdeye-frontend/app/utils/anomaly.js b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
index ac6f920..6f309f0 100644
--- a/thirdeye/thirdeye-frontend/app/utils/anomaly.js
+++ b/thirdeye/thirdeye-frontend/app/utils/anomaly.js
@@ -69,11 +69,12 @@ export const anomalyResponseMapAll = {};
  * @method updateAnomalyFeedback
  * @param {Number} anomalyId - the id of the anomaly to update
  * @param {String} feedbackType - key for feedback type
+ * @param {Boolean} propagate - whether or not feedback is to be propagated
  * @return {Ember.RSVP.Promise}
  */
-export function updateAnomalyFeedback(anomalyId, feedbackType) {
+export function updateAnomalyFeedback(anomalyId, feedbackType, propagate) {
   const url = `/dashboard/anomaly-merged-result/feedback/${anomalyId}`;
-  const data = { feedbackType, comment: '' };
+  const data = { feedbackType, comment: '', propagate };
   return fetch(url, postProps(data)).then((res) => checkStatus(res, 'post'));
 }
 
diff --git a/thirdeye/thirdeye-frontend/app/utils/composite-anomalies.js b/thirdeye/thirdeye-frontend/app/utils/composite-anomalies.js
index b08d764..a095834 100644
--- a/thirdeye/thirdeye-frontend/app/utils/composite-anomalies.js
+++ b/thirdeye/thirdeye-frontend/app/utils/composite-anomalies.js
@@ -38,7 +38,7 @@ export const getAnomaliesStartDuration = (start, end, showLink = false) => {
  */
 export const getFeedback = (feedback) => {
   const selectedFeedback = feedback
-    ? anomalyUtil.anomalyResponseObj.find((f) => f.value === feedback)
+    ? anomalyUtil.anomalyResponseObj.find((f) => f.value === feedback.feedbackType)
     : anomalyUtil.anomalyResponseObj[0];
 
   return {
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/entity-metrics-anomalies/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/entity-metrics-anomalies/component-test.js
index 21b4da5..8aad3f1 100644
--- a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/entity-metrics-anomalies/component-test.js
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/entity-metrics-anomalies/component-test.js
@@ -61,7 +61,9 @@ test('it renders', function (assert) {
   // Check other values based on feedback
 
   this.feedbackOptionNames.forEach((option, index) => {
-    this.tableData[0].feedback = this.feedbackOptionValues[index];
+    this.tableData[0].feedback = {
+      feedbackType: this.feedbackOptionValues[index]
+    };
 
     this.render(hbs`
       {{composite-anomalies/entity-metrics-anomalies data=tableData}}
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/group-constituents-anomalies/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/group-constituents-anomalies/component-test.js
index ddd8279..6c69b05 100644
--- a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/group-constituents-anomalies/component-test.js
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/group-constituents-anomalies/component-test.js
@@ -70,7 +70,9 @@ test('it renders', function (assert) {
   // Check other values based on feedback
 
   this.feedbackOptionNames.forEach((option, index) => {
-    this.tableData[0].feedback = this.feedbackOptionValues[index];
+    this.tableData[0].feedback = {
+      feedbackType: this.feedbackOptionValues[index]
+    };
 
     this.render(hbs`
       {{composite-anomalies/group-constituents-anomalies data=tableData}}
diff --git a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/parent-anomalies/component-test.js b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/parent-anomalies/component-test.js
index 1c8dbc9..7a4970b 100644
--- a/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/parent-anomalies/component-test.js
+++ b/thirdeye/thirdeye-frontend/tests/integration/pods/components/composite-anomalies/parent-anomalies/component-test.js
@@ -58,7 +58,9 @@ test('it renders', function (assert) {
   // Check other values based on feedback
 
   this.feedbackOptionNames.forEach((option, index) => {
-    this.tableData[0].feedback = this.feedbackOptionValues[index];
+    this.tableData[0].feedback = {
+      feedbackType: this.feedbackOptionValues[index]
+    };
 
     this.render(hbs`
       {{composite-anomalies/parent-anomalies data=tableData}}
diff --git a/thirdeye/thirdeye-frontend/tests/unit/utils/anomalies-tree-parser-test.js b/thirdeye/thirdeye-frontend/tests/unit/utils/anomalies-tree-parser-test.js
index 61fc716..8b809fc 100644
--- a/thirdeye/thirdeye-frontend/tests/unit/utils/anomalies-tree-parser-test.js
+++ b/thirdeye/thirdeye-frontend/tests/unit/utils/anomalies-tree-parser-test.js
@@ -1,6 +1,6 @@
 import { mockData } from 'thirdeye-frontend/mocks/compositeAnomalies';
 import { module, test } from 'qunit';
-import { parseRoot, parseSubtree } from 'thirdeye-frontend/utils/anomalies-tree-parser';
+import { parseRoot, parseSubtree, updateAnomalyFeedback } from 'thirdeye-frontend/utils/anomalies-tree-parser';
 
 module('Unit | Utility | Anomalies tree parser utils', function () {
   test('it parses root level parent anomalies correctly', function (assert) {
@@ -124,6 +124,7 @@ module('Unit | Utility | Anomalies tree parser utils', function () {
             startTime: 1599462000000,
             endTime: 1599462000000,
             metric: 'metric_one',
+            dimensions: undefined,
             feedback: null,
             currentPredicted: {
               current: '4.00',
@@ -137,6 +138,7 @@ module('Unit | Utility | Anomalies tree parser utils', function () {
             startTime: 1599462000000,
             endTime: 1599462000000,
             metric: 'metric_one',
+            dimensions: undefined,
             feedback: null,
             currentPredicted: {
               current: '4.00',
@@ -150,6 +152,7 @@ module('Unit | Utility | Anomalies tree parser utils', function () {
             startTime: 1599462000000,
             endTime: 1599462000000,
             metric: 'metric_two',
+            dimensions: undefined,
             feedback: null,
             currentPredicted: {
               current: '4.00',
@@ -266,6 +269,7 @@ module('Unit | Utility | Anomalies tree parser utils', function () {
             startTime: 1599462000000,
             endTime: 1599462000000,
             metric: 'metric_three',
+            dimensions: undefined,
             feedback: null,
             currentPredicted: {
               current: '4.00',
@@ -289,4 +293,70 @@ module('Unit | Utility | Anomalies tree parser utils', function () {
     //output tests
     assert.deepEqual(output, expectedOutput);
   });
+
+  test('it updates anomaly feedback correctly when cascading is not selected)', function (assert) {
+    const anomalyId = 18;
+    const feedbackType = 'ANOMALY';
+    const cascade = false;
+    const secondLevelChildNodes = mockData[0].children.length;
+
+    updateAnomalyFeedback(anomalyId, feedbackType, cascade, mockData);
+
+    const expectedOutput = {
+      id: 18,
+      startTime: 1599462000000,
+      endTime: 1599462000000,
+      feedback: { feedbackType },
+      metric: null,
+      properties: {},
+      children: [
+        {
+          id: 19,
+          startTime: 1599462000000,
+          endTime: 1599462000000,
+          avgCurrentVal: 4,
+          avgBaselineVal: 2,
+          feedback: null,
+          children: [],
+          metric: 'metric_three'
+        }
+      ]
+    };
+
+    //output tests
+    assert.deepEqual(mockData[0].children[secondLevelChildNodes - 1], expectedOutput);
+  });
+
+  test('it updates anomaly feedback correctly when cascading is selected)', function (assert) {
+    const anomalyId = 18;
+    const feedbackType = 'ANOMALY';
+    const cascade = true;
+    const secondLevelChildNodes = mockData[0].children.length;
+
+    updateAnomalyFeedback(anomalyId, feedbackType, cascade, mockData);
+
+    const expectedOutput = {
+      id: 18,
+      startTime: 1599462000000,
+      endTime: 1599462000000,
+      feedback: { feedbackType },
+      metric: null,
+      properties: {},
+      children: [
+        {
+          id: 19,
+          startTime: 1599462000000,
+          endTime: 1599462000000,
+          avgCurrentVal: 4,
+          avgBaselineVal: 2,
+          feedback: { feedbackType },
+          children: [],
+          metric: 'metric_three'
+        }
+      ]
+    };
+
+    //output tests
+    assert.deepEqual(mockData[0].children[secondLevelChildNodes - 1], expectedOutput);
+  });
 });


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