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]