This is an automated email from the ASF dual-hosted git repository.
akshayrai09 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 4f66513 [TE] Flatten Anomaly Results to a list of Map (#4491)
4f66513 is described below
commit 4f6651392bde5ea106f0ff94a8df5955667d4c41
Author: Yungyu Chung <[email protected]>
AuthorDate: Mon Aug 5 11:09:51 2019 -0700
[TE] Flatten Anomaly Results to a list of Map (#4491)
* Flatten anomaly results to a Map for UI presentation
* Add findByStartTimeInRangeAndDetectionConfigId to
MergedAnomalyResultManager and add test cases
---
.../resources/AnomalyFlattenResource.java | 143 +++++++++++++++++++++
.../datalayer/bao/MergedAnomalyResultManager.java | 2 +
.../bao/jdbc/MergedAnomalyResultManagerImpl.java | 9 ++
.../bao/TestMergedAnomalyResultManager.java | 66 ++++++++++
.../resources/TestAnomalyFlattenResource.java | 124 ++++++++++++++++++
5 files changed, 344 insertions(+)
diff --git
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/AnomalyFlattenResource.java
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/AnomalyFlattenResource.java
new file mode 100644
index 0000000..2238e81
--- /dev/null
+++
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/AnomalyFlattenResource.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.dashboard.resources;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Preconditions;
+import io.swagger.annotations.ApiOperation;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.pinot.thirdeye.anomalydetection.context.AnomalyFeedback;
+import org.apache.pinot.thirdeye.common.dimension.DimensionMap;
+import org.apache.pinot.thirdeye.datalayer.bao.MergedAnomalyResultManager;
+import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
+import org.apache.pinot.thirdeye.datasource.DAORegistry;
+
+
+/**
+ * Flatten the anomaly results for UI purpose.
+ * Convert a list of anomalies to rows of columns so that UI can directly
convert the anomalies to table
+ */
+@Path("thirdeye/table")
+public class AnomalyFlattenResource {
+ private static final DAORegistry DAO_REGISTRY = DAORegistry.getInstance();
+ private MergedAnomalyResultManager mergedAnomalyResultDAO;
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ public static final String ANOMALY_ID = "anomalyId";
+ public static final String ANOMALY_COMMENT = "comment";
+ public static final String FUNCTION_ID = "functionId";
+ public static final String WINDOW_START = "start";
+ public static final String WINDOW_END = "end";
+ private static final String DIMENSION_KEYS = "keys";
+
+ private static final List<String> REQUIRED_JSON_KEYS
=Arrays.asList(FUNCTION_ID, WINDOW_START, WINDOW_END);
+ private static final String DEFAULT_DELIMINATOR = ",";
+
+ public AnomalyFlattenResource() {
+ this.mergedAnomalyResultDAO = DAO_REGISTRY.getMergedAnomalyResultDAO();
+ }
+
+ public AnomalyFlattenResource(MergedAnomalyResultManager
mergedAnomalyResultDAO) {
+ this.mergedAnomalyResultDAO = mergedAnomalyResultDAO;
+ }
+
+ /**
+ * Flatten a list of anomaly results to a list of map
+ * @param jsonPayload a json string; the jsonPayload should include keys:
[functionId, start, end].
+ * start and end are in the format of epoch time
+ * @return a list of map
+ * @throws IOException
+ */
+ @GET
+ @ApiOperation(value = "View a flatted merged anomalies for collection")
+ public List<Map<String, String>> flatAnomalyResults(String jsonPayload)
throws IOException {
+ if (StringUtils.isBlank(jsonPayload)) {
+ throw new IllegalArgumentException("jsonPayload cannot be null or
empty");
+ }
+ Map<String, Object> inputMap = OBJECT_MAPPER.readValue(jsonPayload,
Map.class);
+
+ // Assert if reuired keys are in the map
+ for (String requiredKey : REQUIRED_JSON_KEYS) {
+ if (!inputMap.containsKey(requiredKey)) {
+ throw new IllegalArgumentException(String.format("Miss %s in input
json String; %s are required", requiredKey,
+ REQUIRED_JSON_KEYS.toString()));
+ }
+ }
+
+ // Retrieve anomalies
+ long functionId = Long.valueOf(inputMap.get(FUNCTION_ID).toString());
+ long start = Long.valueOf(inputMap.get(WINDOW_START).toString());
+ long end = Long.valueOf(inputMap.get(WINDOW_END).toString());
+ List<MergedAnomalyResultDTO> anomalies = mergedAnomalyResultDAO.
+ findByStartTimeInRangeAndDetectionConfigId(start, end, functionId);
+
+ // flatten anomaly result information
+ List<String> tableKeys = null;
+ if (inputMap.containsKey(DIMENSION_KEYS)){
+ Object dimensionKeysObj = inputMap.get(DIMENSION_KEYS);
+ if (dimensionKeysObj instanceof List) {
+ tableKeys = (List<String>) dimensionKeysObj;
+ } else {
+ tableKeys =
Arrays.asList(dimensionKeysObj.toString().split(DEFAULT_DELIMINATOR));
+ }
+ }
+ List<Map<String, String>> resultList = new ArrayList<>();
+ for (MergedAnomalyResultDTO result : anomalies) {
+ resultList.add(flatAnomalyResult(result, tableKeys));
+ }
+ return resultList;
+ }
+
+ /**
+ * Flat an anomaly to a flat map structure
+ * @param anomalyResult an instance of MergedAnomalyResultDTO
+ * @param tableKeys a list of keys in dimensions; if null, use all keys in
dimension
+ * @return a map of information in the anomaly result with the required keys
+ */
+ public static Map<String, String> flatAnomalyResult(MergedAnomalyResultDTO
anomalyResult,
+ List<String> tableKeys) {
+ Preconditions.checkNotNull(anomalyResult);
+ Map<String, String> flatMap = new HashMap<>();
+ flatMap.put(ANOMALY_ID, Long.toString(anomalyResult.getId()));
+ DimensionMap dimension = anomalyResult.getDimensions();
+ if (tableKeys == null) {
+ tableKeys = new ArrayList<>(dimension.keySet());
+ }
+ for (String key : tableKeys) {
+ flatMap.put(key, dimension.containsKey(key)? dimension.get(key) : "");
+ }
+ AnomalyFeedback feedback = anomalyResult.getFeedback();
+ if (feedback != null) {
+ flatMap.put(ANOMALY_COMMENT, feedback.getComment());
+ } else {
+ flatMap.put(ANOMALY_COMMENT, "");
+ }
+ flatMap.put(anomalyResult.getMetric(),
Double.toString(anomalyResult.getAvgCurrentVal()));
+ return flatMap;
+ }
+}
diff --git
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/MergedAnomalyResultManager.java
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/MergedAnomalyResultManager.java
index b147193..dad46e2 100644
---
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/MergedAnomalyResultManager.java
+++
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/MergedAnomalyResultManager.java
@@ -60,6 +60,8 @@ public interface MergedAnomalyResultManager extends
AbstractManager<MergedAnomal
List<MergedAnomalyResultDTO> findByStartTimeInRangeAndFunctionId(long
startTime, long endTime, long functionId);
+ List<MergedAnomalyResultDTO> findByStartTimeInRangeAndDetectionConfigId(long
startTime, long endTime, long detectionConfigId);
+
List<MergedAnomalyResultDTO> findByTime(long startTime, long endTime);
List<MergedAnomalyResultDTO>
findUnNotifiedByFunctionIdAndIdLesserThanAndEndTimeGreaterThanLastOneDay(long
functionId,
diff --git
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/jdbc/MergedAnomalyResultManagerImpl.java
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/jdbc/MergedAnomalyResultManagerImpl.java
index c66721f..1960c39 100644
---
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/jdbc/MergedAnomalyResultManagerImpl.java
+++
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/jdbc/MergedAnomalyResultManagerImpl.java
@@ -253,6 +253,15 @@ public class MergedAnomalyResultManagerImpl extends
AbstractManagerImpl<MergedAn
Predicate predicate =
Predicate.AND(Predicate.LT("startTime", endTime),
Predicate.GT("endTime", startTime),
Predicate.EQ("functionId", functionId));
+ return findByPredicate(predicate);
+ }
+
+ @Override
+ public List<MergedAnomalyResultDTO>
findByStartTimeInRangeAndDetectionConfigId(long startTime, long endTime,
+ long detectionConfigId) {
+ Predicate predicate =
+ Predicate.AND(Predicate.LT("startTime", endTime),
Predicate.GT("endTime", startTime),
+ Predicate.EQ("detectionConfigId", detectionConfigId));
List<MergedAnomalyResultBean> list = genericPojoDao.get(predicate,
MergedAnomalyResultBean.class);
return convertMergedAnomalyBean2DTO(list);
}
diff --git
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/datalayer/bao/TestMergedAnomalyResultManager.java
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/datalayer/bao/TestMergedAnomalyResultManager.java
index fe85c0b..0f55842 100644
---
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/datalayer/bao/TestMergedAnomalyResultManager.java
+++
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/datalayer/bao/TestMergedAnomalyResultManager.java
@@ -17,7 +17,9 @@
package org.apache.pinot.thirdeye.datalayer.bao;
import org.apache.pinot.thirdeye.anomalydetection.context.AnomalyResult;
+import org.apache.pinot.thirdeye.common.dimension.DimensionMap;
import org.apache.pinot.thirdeye.datalayer.DaoTestUtils;
+import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO;
import org.apache.pinot.thirdeye.datasource.DAORegistry;
import java.util.ArrayList;
import java.util.Arrays;
@@ -26,6 +28,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import org.joda.time.DateTime;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
@@ -44,6 +47,7 @@ public class TestMergedAnomalyResultManager{
private DAOTestBase testDAOProvider;
private AnomalyFunctionManager anomalyFunctionDAO;
+ private DetectionConfigManager detectionConfigDAO;
private MergedAnomalyResultManager mergedAnomalyResultDAO;
@BeforeClass
@@ -51,6 +55,7 @@ public class TestMergedAnomalyResultManager{
testDAOProvider = DAOTestBase.getInstance();
DAORegistry daoRegistry = DAORegistry.getInstance();
anomalyFunctionDAO = daoRegistry.getAnomalyFunctionDAO();
+ detectionConfigDAO = daoRegistry.getDetectionConfigManager();
mergedAnomalyResultDAO = daoRegistry.getMergedAnomalyResultDAO();
}
@@ -137,6 +142,33 @@ public class TestMergedAnomalyResultManager{
}
@Test
+ public void testFindByStartTimeInRangeAndDetectionConfigId() {
+ long detectionConfigId = detectionConfigDAO.save(mockDetectionConfig());
+ List<MergedAnomalyResultDTO> anomalies = mockAnomalies(detectionConfigId);
+ for (MergedAnomalyResultDTO anomaly : anomalies) {
+ this.mergedAnomalyResultDAO.save(anomaly);
+ }
+ List<MergedAnomalyResultDTO> fetchedAnomalies = mergedAnomalyResultDAO
+ .findByStartTimeInRangeAndDetectionConfigId(
+ new DateTime(2019, 1, 1, 0, 0).getMillis(),
+ new DateTime(2019, 1, 3, 0, 0).getMillis(),
+ detectionConfigId);
+ Assert.assertEquals(fetchedAnomalies.size(), anomalies.size());
+ for (int i = 0; i < anomalies.size(); i ++) {
+ MergedAnomalyResultDTO actual = fetchedAnomalies.get(i);
+ MergedAnomalyResultDTO expected = anomalies.get(i);
+ Assert.assertNotNull(actual.getId());
+ Assert.assertEquals(actual.getDetectionConfigId(),
expected.getDetectionConfigId());
+ Assert.assertEquals(actual.getDimensions(), expected.getDimensions());
+ }
+ // Clean up
+ for (int i = 0; i < anomalies.size(); i++) {
+ this.mergedAnomalyResultDAO.delete(fetchedAnomalies.get(i));
+ }
+ this.detectionConfigDAO.deleteById(detectionConfigId);
+ }
+
+ @Test
public void testSaveChildrenIndependently() {
MergedAnomalyResultDTO parent = new MergedAnomalyResultDTO();
parent.setStartTime(1000);
@@ -248,4 +280,38 @@ public class TestMergedAnomalyResultManager{
Assert.assertFalse(read.getChildren().iterator().next().getChildren().iterator().next().getChildren().isEmpty());
Assert.assertEquals(read.getChildren().iterator().next().getChildren().iterator().next().getChildren().iterator().next().getStartTime(),
1600);
}
+
+
+ public static DetectionConfigDTO mockDetectionConfig() {
+ DetectionConfigDTO detectionConfig = new DetectionConfigDTO();
+ detectionConfig.setName("Only For Test");
+ return detectionConfig;
+ }
+
+ public static List<MergedAnomalyResultDTO> mockAnomalies(long
detectionConfigId) {
+ MergedAnomalyResultDTO anomaly1 = new MergedAnomalyResultDTO();
+ anomaly1.setMetric("metric");
+ anomaly1.setDetectionConfigId(detectionConfigId);
+ anomaly1.setStartTime(new DateTime(2019, 1, 1, 0, 0).getMillis());
+ anomaly1.setEndTime(new DateTime(2019, 1, 1, 12, 0).getMillis());
+ DimensionMap dimension1 = new DimensionMap();
+ dimension1.put("what", "a");
+ dimension1.put("where", "b");
+ dimension1.put("when", "c");
+ dimension1.put("how", "d");
+ anomaly1.setDimensions(dimension1);
+ MergedAnomalyResultDTO anomaly2 = new MergedAnomalyResultDTO();
+ anomaly2.setMetric("metric");
+ anomaly2.setDetectionConfigId(detectionConfigId);
+ anomaly2.setStartTime(new DateTime(2019, 1, 2, 10, 0).getMillis());
+ anomaly2.setEndTime(new DateTime(2019, 1, 2, 20, 0).getMillis());
+ DimensionMap dimension2 = new DimensionMap();
+ dimension2.put("what", "e");
+ dimension2.put("where", "f");
+ dimension2.put("when", "g");
+ dimension2.put("how", "h");
+ anomaly2.setDimensions(dimension2);
+
+ return Arrays.asList(anomaly1, anomaly2);
+ }
}
diff --git
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/datasource/resources/TestAnomalyFlattenResource.java
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/datasource/resources/TestAnomalyFlattenResource.java
new file mode 100644
index 0000000..a0317e2
--- /dev/null
+++
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/datasource/resources/TestAnomalyFlattenResource.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.datasource.resources;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.pinot.thirdeye.dashboard.resources.AnomalyFlattenResource;
+import org.apache.pinot.thirdeye.datalayer.bao.DAOTestBase;
+import org.apache.pinot.thirdeye.datalayer.bao.DetectionConfigManager;
+import org.apache.pinot.thirdeye.datalayer.bao.MergedAnomalyResultManager;
+import org.apache.pinot.thirdeye.datalayer.bao.TestMergedAnomalyResultManager;
+import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
+import org.apache.pinot.thirdeye.datasource.DAORegistry;
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static
org.apache.pinot.thirdeye.dashboard.resources.AnomalyFlattenResource.*;
+
+
+public class TestAnomalyFlattenResource {
+ private DAOTestBase testDAOProvider;
+ public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ private DetectionConfigManager detectionConfigDAO;
+ private MergedAnomalyResultManager mergedAnomalyResultDAO;
+ private long detectionConfigId;
+ @BeforeClass
+ void beforeClass() {
+ testDAOProvider = DAOTestBase.getInstance();
+ DAORegistry daoRegistry = DAORegistry.getInstance();
+ this.detectionConfigDAO = daoRegistry.getDetectionConfigManager();
+ this.mergedAnomalyResultDAO = daoRegistry.getMergedAnomalyResultDAO();
+ this.detectionConfigId =
detectionConfigDAO.save(TestMergedAnomalyResultManager.mockDetectionConfig());
+ for (MergedAnomalyResultDTO anomaly :
TestMergedAnomalyResultManager.mockAnomalies(detectionConfigId)) {
+ mergedAnomalyResultDAO.save(anomaly);
+ }
+ }
+
+ @AfterClass(alwaysRun = true)
+ void afterClass() {
+ testDAOProvider.cleanup();
+ }
+
+ @Test
+ public void testFlatAnomalyResults() throws Exception {
+ Map<String, Object> jsonMap = new HashMap<String, Object>() {
+ {
+ put(FUNCTION_ID, detectionConfigId);
+ put(WINDOW_START, new DateTime(2019, 1, 1, 0, 0).getMillis());
+ put(WINDOW_END, new DateTime(2019, 1, 3, 0, 0).getMillis());
+ }
+ };
+ AnomalyFlattenResource resource = new AnomalyFlattenResource();
+ List<Map<String, String>> actualResults =
resource.flatAnomalyResults(OBJECT_MAPPER.writeValueAsString(jsonMap));
+ List<Map<String, String>> expectedResults = Arrays.asList(
+ new HashMap<String, String>() {
+ {
+ put(ANOMALY_ID, Long.toString(2));
+ put("what", "a");
+ put("where", "b");
+ put("when", "c");
+ put("how", "d");
+ put("metric", Double.toString(0d));
+ put(ANOMALY_COMMENT, "");
+ }
+ },
+ new HashMap<String, String>() {
+ {
+ put(ANOMALY_ID, Long.toString(3));
+ put("what", "e");
+ put("where", "f");
+ put("when", "g");
+ put("how", "h");
+ put("metric", Double.toString(0d));
+ put(ANOMALY_COMMENT, "");
+ }
+ }
+ );
+ Assert.assertEquals(actualResults, expectedResults);
+ }
+
+ @Test
+ public void testFlatAnomalyResult() {
+ List<MergedAnomalyResultDTO> anomalies =
TestMergedAnomalyResultManager.mockAnomalies(detectionConfigId);
+ MergedAnomalyResultDTO anomaly = anomalies.get(0);
+ anomaly.setId(1l);
+ Map<String, String> actualMap =
AnomalyFlattenResource.flatAnomalyResult(anomaly, null);
+ Map<String, String> expectMap = new HashMap<String, String>() {
+ {
+ put(ANOMALY_ID, Long.toString(anomaly.getId()));
+ put("what", "a");
+ put("where", "b");
+ put("when", "c");
+ put("how", "d");
+ put("metric", Double.toString(0d));
+ put(ANOMALY_COMMENT, "");
+ }
+ };
+ Assert.assertEquals(actualMap, expectMap);
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]