package com.linkedin.thirdeye.anomaly.alert.grouping;
import com.linkedin.thirdeye.api.DimensionMap;
import com.linkedin.thirdeye.datalayer.dto.GroupedAnomalyResultsDTO;
import com.linkedin.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A grouper that groups anomalies by each of the specified dimensions individually.
*
* An usage example:
* Assume that we have four anomalies, whose dimensions are enclosed in brackets: a1={D1=G1, D2=H1}, a2={D1=G1, D2=H2},
* a3={D1=G2, D2=H1}, and a4={D1=G2, D2=H2}. If we group by these dimensions: "D1,D2", then this grouper returns these
* groups:
*
* groupKey={D1=G1} : a1, a2
* groupKey={D1=G2} : a3, a4
* groupKey={D2=H1} : a1, a3
* groupKey={D2=H2} : a2, a4
*/
public class HorizontalDimensionalAlertGrouper extends BaseAlertGrouper {
private static final Logger LOG = LoggerFactory.getLogger(HorizontalDimensionalAlertGrouper.class);
// Used when the user does not specify any dimensions to group by
private static final DummyAlertGrouper DUMMY_ALERT_GROUPER = new DummyAlertGrouper();
public static final String GROUP_BY_KEY = "dimensions";
public static final String GROUP_BY_SEPARATOR = ",";
// The dimension names to group the anomalies (e.g., country, page_name)
private List<String> groupByDimensions = new ArrayList<>();
@Override
public void setParameters(Map<String, String> props) {
super.setParameters(props);
// Initialize dimension names to be grouped by
if (this.props.containsKey(GROUP_BY_KEY)) {
String[] dimensions = this.props.get(GROUP_BY_KEY).split(GROUP_BY_SEPARATOR);
for (String dimension : dimensions) {
groupByDimensions.add(dimension.trim());
}
}
}
@Override
public Map<DimensionMap, GroupedAnomalyResultsDTO> group(List<MergedAnomalyResultDTO> anomalyResults) {
if (CollectionUtils.isEmpty(groupByDimensions)) {
return DUMMY_ALERT_GROUPER.group(anomalyResults);
} else {
Map<DimensionMap, GroupedAnomalyResultsDTO> groupedAnomaliesMap = new HashMap<>();
for (String groupByDimension : groupByDimensions) {
for (MergedAnomalyResultDTO anomalyResult : anomalyResults) {
DimensionMap anomalyDimensionMap = anomalyResult.getDimensions();
DimensionMap alertGroupKey = this.constructGroupKey(anomalyDimensionMap, groupByDimension);
if (groupedAnomaliesMap.containsKey(alertGroupKey)) {
GroupedAnomalyResultsDTO groupedAnomalyResults = groupedAnomaliesMap.get(alertGroupKey);
groupedAnomalyResults.getAnomalyResults().add(anomalyResult);
} else {
GroupedAnomalyResultsDTO groupedAnomalyResults = new GroupedAnomalyResultsDTO();
groupedAnomalyResults.getAnomalyResults().add(anomalyResult);
groupedAnomaliesMap.put(alertGroupKey, groupedAnomalyResults);
}
}
}
return groupedAnomaliesMap;
}
}
@Override
public String groupEmailRecipients(DimensionMap alertGroupKey) {
return EMPTY_RECIPIENTS;
}
private DimensionMap constructGroupKey(DimensionMap rawKey, String dimensionName) {
DimensionMap alertGroupKey = new DimensionMap();
if (rawKey.containsKey(dimensionName)) {
alertGroupKey.put(dimensionName, rawKey.get(dimensionName));
}
return alertGroupKey;
}
}