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

xhsun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 72f7775  [ThirdEye] OTHER now show the nodes that are not picked up by 
the summary (#5090)
72f7775 is described below

commit 72f7775d06aa9277349b47d04cfdcdd56c8eefe6
Author: Xiaohui Sun <[email protected]>
AuthorDate: Wed Mar 4 11:56:00 2020 -0800

    [ThirdEye] OTHER now show the nodes that are not picked up by the summary 
(#5090)
    
    * OTHER now show the nodes that are not picked up by the summary
    
    * Add license header
    
    * Fix null pointer issue when tracing parents of a node
---
 .../custom/dimensions-table/dimension/template.hbs |   2 +-
 .../pinot/thirdeye/cube/summary/Summary.java       |   2 +-
 .../thirdeye/cube/summary/SummaryResponse.java     |  54 +++++--
 .../thirdeye/cube/summary/SummaryResponseTest.java | 166 +++++++++++++++++++++
 4 files changed, 207 insertions(+), 17 deletions(-)

diff --git 
a/thirdeye/thirdeye-frontend/app/pods/custom/dimensions-table/dimension/template.hbs
 
b/thirdeye/thirdeye-frontend/app/pods/custom/dimensions-table/dimension/template.hbs
index 2fc5d29..4cd7976 100644
--- 
a/thirdeye/thirdeye-frontend/app/pods/custom/dimensions-table/dimension/template.hbs
+++ 
b/thirdeye/thirdeye-frontend/app/pods/custom/dimensions-table/dimension/template.hbs
@@ -11,7 +11,7 @@
       <a class="te-anomaly-table__link" href="#">
         {{dimensionValue}}
         {{#tooltip-on-element}}
-          ALL EXCEPT => {{excludedDimensions}}
+          OTHER includes => {{excludedDimensions}}
         {{/tooltip-on-element}}
       </a>
     {{else}}
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/cube/summary/Summary.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/cube/summary/Summary.java
index 83bda4b..a1eb888 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/cube/summary/Summary.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/cube/summary/Summary.java
@@ -303,7 +303,7 @@ public class Summary {
    * Returns null if no ancestor exists in the target set.
    */
   private static CubeNode findAncestor(CubeNode node, CubeNode ceiling, 
Set<CubeNode> targets) {
-    while ((node = node.getParent()) != ceiling) {
+    while (node != null && (node = node.getParent()) != ceiling) {
       if (targets.contains(node)) {
         return node;
       }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/cube/summary/SummaryResponse.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/cube/summary/SummaryResponse.java
index 073f22f..40c065e 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/cube/summary/SummaryResponse.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/cube/summary/SummaryResponse.java
@@ -19,6 +19,8 @@
 
 package org.apache.pinot.thirdeye.cube.summary;
 
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import org.apache.pinot.thirdeye.cube.data.cube.Cube;
 import org.apache.pinot.thirdeye.cube.cost.CostFunction;
 import org.apache.pinot.thirdeye.cube.data.cube.DimNameValueCostEntry;
@@ -42,6 +44,7 @@ public class SummaryResponse {
 
   static final String ALL = "(ALL)";
   static final String NOT_ALL = "(ALL)-";
+  static final String EMPTY = "";
   static final String NOT_AVAILABLE = "-na-";
 
   @JsonProperty("metricUrn")
@@ -203,12 +206,23 @@ public class SummaryResponse {
     nodes = SummaryResponseTree.sortResponseTree(nodes, targetLevelCount, 
costFunction);
     //   Build name tag for each row of responses
     Map<CubeNode, NameTag> nameTags = new HashMap<>();
-    Map<CubeNode, List<String>> otherDimensionValues = new HashMap<>();
+    Map<CubeNode, LinkedHashSet<String>> otherDimensionValues = new 
HashMap<>();
     for (CubeNode node : nodes) {
       NameTag tag = new NameTag(targetLevelCount);
       nameTags.put(node, tag);
       tag.copyNames(node.getDimensionValues());
-      otherDimensionValues.put(node, new ArrayList<String>());
+
+      // Put all children name to other dimension values, which will be shown 
on UI if this node is (ALL)-
+      // Later, each picked child will remove itself from this parent's other 
dimension values.
+      LinkedHashSet<String> childrenNames = new LinkedHashSet<>();
+      List<CubeNode> children = node.getChildren();
+      for (CubeNode child : children) {
+        String childName = 
child.getDimensionValues().get(node.getLevel()).trim();
+        if (!childName.isEmpty()) {
+          childrenNames.add(child.getDimensionValues().get(node.getLevel()));
+        }
+      }
+      otherDimensionValues.put(node, childrenNames);
     }
     //   pre-condition: parent node is processed before its children nodes
     for (CubeNode node : nodes) {
@@ -217,17 +231,16 @@ public class SummaryResponse {
       while ((parent = parent.getParent()) != null) {
         NameTag parentNameTag = nameTags.get(parent);
         if (parentNameTag != null) {
-          // Set tag from ALL to NOT_ALL String.
+          // Set parent's name tag from ALL to NOT_ALL String.
           int notAllLevel = node.getLevel()-levelDiff;
           parentNameTag.setNotAll(notAllLevel);
-          // For users' ease of understanding, we append what dimension values 
are excluded from NOT_ALL
-          StringBuilder sb = new StringBuilder();
-          String separator = "";
-          for (int i = notAllLevel; i < node.getDimensionValues().size(); ++i) 
{
-            sb.append(separator).append(node.getDimensionValues().get(i));
-            separator = ".";
+          // After that, set the names after NOT_ALL to empty, e.g., [home 
page, (ALL)-, ""]
+          for (int i = notAllLevel + 1; i < targetLevelCount; ++i) {
+            parentNameTag.setEmpty(i);
           }
-          otherDimensionValues.get(parent).add(sb.toString());
+          // Each picked child will remove itself from this parent's other 
dimension values.
+          // Thus, the (ALL)- node will only show the names of the children 
name that are NOT picked in the summary.
+          
otherDimensionValues.get(parent).remove(node.getDimensionValues().get(parent.getLevel()));
           break;
         }
         ++levelDiff;
@@ -247,10 +260,17 @@ public class SummaryResponse {
       row.contributionToOverallChange =
           computeContributionToOverallChange(row.baselineValue, 
row.currentValue, baselineTotal, currentTotal);
       row.cost = node.getCost();
+      // Add other dimension values if this node is (ALL)-
       StringBuilder sb = new StringBuilder();
       String separator = "";
-      for (String s : otherDimensionValues.get(node)) {
-        sb.append(separator).append(s);
+      Iterator<String> iterator = otherDimensionValues.get(node).iterator();
+      int counter = 0;
+      while (iterator.hasNext()) {
+        if (++counter > 10) { // Get at most 10 children
+          sb.append(separator).append("and more...");
+          break;
+        }
+        sb.append(separator).append(iterator.next());
         separator = ", ";
       }
       row.otherDimensionValues = sb.toString();
@@ -303,21 +323,25 @@ public class SummaryResponse {
   private static class NameTag {
     private List<String> names;
 
-    public NameTag(int levelCount) {
+    NameTag(int levelCount) {
       names = new ArrayList<>(levelCount);
       for (int i = 0; i < levelCount; ++i) {
         names.add(ALL);
       }
     }
 
-    public void copyNames(DimensionValues dimensionValues) {
+    void copyNames(DimensionValues dimensionValues) {
       for (int i = 0; i < dimensionValues.size(); ++i) {
         names.set(i, dimensionValues.get(i));
       }
     }
 
-    public void setNotAll(int index) {
+    void setNotAll(int index) {
       names.set(index, NOT_ALL);
     }
+
+    void setEmpty(int index) {
+      names.set(index, EMPTY);
+    }
   }
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/cube/summary/SummaryResponseTest.java
 
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/cube/summary/SummaryResponseTest.java
new file mode 100644
index 0000000..523ddef
--- /dev/null
+++ 
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/cube/summary/SummaryResponseTest.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright (C) 2014-2018 LinkedIn Corp. ([email protected])
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pinot.thirdeye.cube.summary;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.pinot.thirdeye.cube.additive.AdditiveCubeNode;
+import org.apache.pinot.thirdeye.cube.additive.AdditiveRow;
+import org.apache.pinot.thirdeye.cube.cost.BalancedCostFunction;
+import org.apache.pinot.thirdeye.cube.data.dbrow.DimensionValues;
+import org.apache.pinot.thirdeye.cube.data.dbrow.Dimensions;
+import org.apache.pinot.thirdeye.cube.data.dbrow.Row;
+import org.apache.pinot.thirdeye.cube.data.node.CubeNode;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import static org.apache.pinot.thirdeye.cube.summary.SummaryResponse.*;
+
+
+public class SummaryResponseTest {
+  private static double EPSILON = 0.0001d;
+
+  @Test
+  public void testBuildDiffSummary() {
+    // Create test case
+    List<CubeNode> cubeNodes = buildHierarchicalNodes();
+    int rootIdx = cubeNodes.size() - 1;
+    double baselineTotal = cubeNodes.get(rootIdx).getOriginalBaselineValue();
+    double currentTotal = cubeNodes.get(rootIdx).getOriginalCurrentValue();
+    double baselineSize = cubeNodes.get(rootIdx).getOriginalBaselineSize();
+    double currentSize = cubeNodes.get(rootIdx).getOriginalCurrentSize();
+    // Build the response
+    SummaryResponse response = new SummaryResponse(baselineTotal, 
currentTotal, baselineSize, currentSize);
+    response.buildDiffSummary(cubeNodes, 2, new BalancedCostFunction());
+    response.setMetricUrn("testMetricUrn");
+
+    // Validation
+    List<SummaryResponseRow> responseRows = response.getResponseRows();
+    Assert.assertEquals(responseRows.size(), 2); // Our test summary contains 
only root (OTHER) and US node.
+    List<SummaryResponseRow> expectedResponseRows = 
buildExpectedResponseRows();
+    for (int i = 0; i < expectedResponseRows.size(); ++i) {
+      SummaryResponseRow actualRow = responseRows.get(i);
+      SummaryResponseRow expectedRow = expectedResponseRows.get(i);
+      Assert.assertEquals(actualRow.names, expectedRow.names);
+      Assert.assertEquals(actualRow.otherDimensionValues, 
expectedRow.otherDimensionValues);
+      Assert.assertEquals(actualRow.cost, expectedRow.cost, EPSILON);
+      Assert.assertEquals(actualRow.baselineValue, expectedRow.baselineValue);
+      Assert.assertEquals(actualRow.currentValue, expectedRow.currentValue);
+      
Assert.assertEquals(Double.parseDouble(actualRow.percentageChange.split("%")[0]),
 Double.parseDouble(expectedRow.percentageChange.split("%")[0]));
+      Assert.assertEquals(actualRow.sizeFactor, expectedRow.sizeFactor, 
EPSILON);
+    }
+  }
+
+  /**
+   * Provides data for this hierarchy:
+   *       root
+   *     /  |  \
+   *    US  IN  FR
+   */
+  private List<List<Row>> buildHierarchicalRows() {
+    List<List<Row>> hierarchicalRows = new ArrayList<>();
+    List<String> dimensions = Collections.singletonList("country");
+
+    // Root level
+    List<Row> rootLevel = new ArrayList<>();
+    rootLevel.add(new AdditiveRow(new Dimensions(dimensions), new 
DimensionValues(), 45, 58));
+    hierarchicalRows.add(rootLevel);
+
+    // Level 1
+    List<Row> level1 = new ArrayList<>();
+    Row row1 =
+        new AdditiveRow(new Dimensions(dimensions), new 
DimensionValues(Collections.singletonList("US")), 20, 30);
+    level1.add(row1);
+
+    Row row2 =
+        new AdditiveRow(new Dimensions(dimensions), new 
DimensionValues(Collections.singletonList("IN")), 10, 11);
+    level1.add(row2);
+
+    Row row3 =
+        new AdditiveRow(new Dimensions(dimensions), new 
DimensionValues(Collections.singletonList("FR")), 15, 17);
+    level1.add(row3);
+
+    hierarchicalRows.add(level1);
+
+    return hierarchicalRows;
+  }
+
+  /**
+   * Builds hierarchy:
+   *      root (IN, FR)
+   *     /
+   *    US
+   */
+  private List<CubeNode> buildHierarchicalNodes() {
+    List<List<Row>> rows = buildHierarchicalRows();
+    // Root level
+    AdditiveRow rootRow = (AdditiveRow) rows.get(0).get(0);
+    AdditiveCubeNode rootNode = new AdditiveCubeNode(rootRow);
+
+    // Level 1
+    AdditiveRow USRow = (AdditiveRow) rows.get(1).get(0);
+    AdditiveCubeNode USNode = new AdditiveCubeNode(1, 0, USRow, rootNode);
+
+    AdditiveRow INRow = (AdditiveRow) rows.get(1).get(1);
+    AdditiveCubeNode INNode = new AdditiveCubeNode(1, 1, INRow, rootNode);
+
+    AdditiveRow FRRow = (AdditiveRow) rows.get(1).get(2);
+    AdditiveCubeNode FRNode = new AdditiveCubeNode(1, 2, FRRow, rootNode);
+
+    // Assume that US is the only child that is picked by the summary
+    rootNode.removeNodeValues(USNode);
+
+    List<CubeNode> res = new ArrayList<>();
+    res.add(USNode);
+    // Root node is located at the end of this list.
+    res.add(rootNode);
+
+    return res;
+  }
+
+  /**
+   * Builds expected hierarchy:
+   *      root (IN, FR)
+   *     /
+   *    US
+   */
+  private List<SummaryResponseRow> buildExpectedResponseRows() {
+    SummaryResponseRow root = new SummaryResponseRow();
+    root.names = Collections.singletonList(NOT_ALL);
+    root.otherDimensionValues = "IN, FR";
+    root.cost = 0d; // root doesn't have cost
+    root.baselineValue = 25d;
+    root.currentValue = 28d;
+    root.sizeFactor = 0.5145d;
+    root.percentageChange = (28d - 25d) / 25d * 100 + "%";
+
+    SummaryResponseRow US = new SummaryResponseRow();
+    US.names = Collections.singletonList("US");
+    US.otherDimensionValues = "";
+    US.cost = 1.1587d;
+    US.baselineValue = 20d;
+    US.currentValue = 30d;
+    US.sizeFactor = 0.4854d; // UPDATE THIS
+    US.percentageChange = (30d - 20d) / 20d * 100 + "%";
+
+    List<SummaryResponseRow> rows = new ArrayList<>();
+    rows.add(root);
+    rows.add(US);
+    return rows;
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to