package com.linkedin.thirdeye.dashboard.resources; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.validation.constraints.NotNull; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.node.ObjectNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.linkedin.thirdeye.api.MetricType; import com.linkedin.thirdeye.client.DAORegistry; import com.linkedin.thirdeye.dashboard.Utils; import com.linkedin.thirdeye.datalayer.bao.DashboardConfigManager; import com.linkedin.thirdeye.datalayer.bao.MetricConfigManager; import com.linkedin.thirdeye.datalayer.dto.DashboardConfigDTO; import com.linkedin.thirdeye.datalayer.dto.MetricConfigDTO; import com.linkedin.thirdeye.util.JsonResponseUtil; import com.linkedin.thirdeye.util.ThirdEyeUtils; @Path(value = "/thirdeye-admin/metric-config") @Produces(MediaType.APPLICATION_JSON) public class MetricConfigResource { private static final Logger LOG = LoggerFactory.getLogger(MetricConfigResource.class); private static final DAORegistry DAO_REGISTRY = DAORegistry.getInstance(); private MetricConfigManager metricConfigDao; private DashboardConfigManager dashboardConfigDAO; public MetricConfigResource() { this.metricConfigDao = DAO_REGISTRY.getMetricConfigDAO(); this.dashboardConfigDAO = DAO_REGISTRY.getDashboardConfigDAO(); } @GET @Path("/create") public String createMetricConfig(@QueryParam("dataset") String dataset, @QueryParam("name") String name, @QueryParam("datatype") String metricType, @QueryParam("active") boolean active, @QueryParam("derived") boolean derived, @QueryParam("derivedFunctionType") String derivedFunctionType, @QueryParam("numerator") String numerator, @QueryParam("denominator") String denominator, @QueryParam("derivedMetricExpression") String derivedMetricExpression, @QueryParam("inverseMetric") boolean inverseMetric, @QueryParam("cellSizeExpression") String cellSizeExpression, @QueryParam("rollupThreshold") Double rollupThreshold) { try { MetricConfigDTO metricConfigDTO = new MetricConfigDTO(); populateMetricConfig(metricConfigDTO, dataset, name, metricType, active, derived, derivedFunctionType, numerator, denominator, derivedMetricExpression, inverseMetric, cellSizeExpression, rollupThreshold); Long id = metricConfigDao.save(metricConfigDTO); metricConfigDTO.setId(id); return JsonResponseUtil.buildResponseJSON(metricConfigDTO).toString(); } catch (Exception e) { LOG.warn("Failed to create metric:{}", name, e); return JsonResponseUtil.buildErrorResponseJSON("Failed to create metric:" + name + " Message:" + e.getMessage()).toString(); } } private void populateMetricConfig(MetricConfigDTO metricConfigDTO, String dataset, String name, String metricType, boolean active, boolean derived, String derivedFunctionType, String numerator, String denominator, String derivedMetricExpression, boolean inverseMetric, String cellSizeExpression, Double rollupThreshold) { metricConfigDTO.setDataset(dataset); metricConfigDTO.setName(name); metricConfigDTO.setAlias(ThirdEyeUtils.constructMetricAlias(dataset, name)); metricConfigDTO.setDatatype(MetricType.valueOf(metricType)); metricConfigDTO.setActive(active); // optional ones metricConfigDTO.setCellSizeExpression(cellSizeExpression); metricConfigDTO.setInverseMetric(inverseMetric); if (rollupThreshold != null) { metricConfigDTO.setRollupThreshold(rollupThreshold); } // handle derived if (derived) { if (StringUtils.isEmpty(derivedMetricExpression) && numerator != null && denominator != null) { MetricConfigDTO numMetricConfigDTO = metricConfigDao.findByAliasAndDataset(numerator, dataset); MetricConfigDTO denMetricConfigDTO = metricConfigDao.findByAliasAndDataset(denominator, dataset); if ("RATIO".equals(derivedFunctionType)) { derivedMetricExpression = String.format("id%s/id%s", numMetricConfigDTO.getId(), denMetricConfigDTO.getId()); } else if ("PERCENT".equals(derivedFunctionType)) { derivedMetricExpression = String.format("id%s*100/id%s", numMetricConfigDTO.getId(), denMetricConfigDTO.getId()); } } metricConfigDTO.setDerived(derived); metricConfigDTO.setDerivedMetricExpression(derivedMetricExpression); } } @GET @Path("/metrics") public String getMetricsForDataset(@NotNull @QueryParam("dataset") String dataset) { Map<String, Object> filters = new HashMap<>(); filters.put("dataset", dataset); List<MetricConfigDTO> metricConfigDTOs = metricConfigDao.findByParams(filters); List<String> metrics = new ArrayList<>(); for (MetricConfigDTO metricConfigDTO : metricConfigDTOs) { metrics.add(metricConfigDTO.getAlias()); } return JsonResponseUtil.buildResponseJSON(metrics).toString(); } @GET @Path("/update") public String updateMetricConfig(@NotNull @QueryParam("id") long metricConfigId, @QueryParam("dataset") String dataset, @QueryParam("name") String name, @QueryParam("datatype") String metricType, @QueryParam("active") boolean active, @QueryParam("derived") boolean derived, @QueryParam("derivedFunctionType") String derivedFunctionType, @QueryParam("numerator") String numerator, @QueryParam("denominator") String denominator, @QueryParam("derivedMetricExpression") String derivedMetricExpression, @QueryParam("inverseMetric") boolean inverseMetric, @QueryParam("cellSizeExpression") String cellSizeExpression, @QueryParam("rollupThreshold") Double rollupThreshold) { try { MetricConfigDTO metricConfigDTO = metricConfigDao.findById(metricConfigId); populateMetricConfig(metricConfigDTO, dataset, name, metricType, active, derived, derivedFunctionType, numerator, denominator, derivedMetricExpression, inverseMetric, cellSizeExpression, rollupThreshold); int numRowsUpdated = metricConfigDao.update(metricConfigDTO); if (numRowsUpdated == 1) { return JsonResponseUtil.buildResponseJSON(metricConfigDTO).toString(); } else { return JsonResponseUtil.buildErrorResponseJSON("Failed to update metric id:" + metricConfigId).toString(); } } catch (Exception e) { return JsonResponseUtil.buildErrorResponseJSON("Failed to update metric id:" + metricConfigId + ". Exception:" + e.getMessage()).toString(); } } @GET @Path("/delete") public String deleteMetricConfig(@NotNull @QueryParam("dataset") String dataset, @NotNull @QueryParam("id") Long metricConfigId) { metricConfigDao.deleteById(metricConfigId); DashboardConfigDTO dashboardConfigDTO = dashboardConfigDAO.findByName(ThirdEyeUtils.getDefaultDashboardName(dataset)); if (dashboardConfigDTO != null) { List<Long> metricIds = dashboardConfigDTO.getMetricIds(); metricIds.removeAll(Lists.newArrayList(metricConfigId)); dashboardConfigDTO.setMetricIds(metricIds); dashboardConfigDAO.update(dashboardConfigDTO); } return JsonResponseUtil.buildSuccessResponseJSON("Successully deleted " + metricConfigId).toString(); } @GET @Path("/list") @Produces(MediaType.APPLICATION_JSON) public String viewMetricConfig(@NotNull @QueryParam("dataset") String dataset, @DefaultValue("0") @QueryParam("jtStartIndex") int jtStartIndex, @DefaultValue("100") @QueryParam("jtPageSize") int jtPageSize) { Map<String, Object> filters = new HashMap<>(); filters.put("dataset", dataset); List<MetricConfigDTO> metricConfigDTOs = metricConfigDao.findByParams(filters); List<MetricConfigDTO> subList = Utils.sublist(metricConfigDTOs, jtStartIndex, jtPageSize); ObjectNode rootNode = JsonResponseUtil.buildResponseJSON(subList); return rootNode.toString(); } @GET @Path("/view") @Produces(MediaType.APPLICATION_JSON) public MetricConfigDTO viewMetricConfigByIdOrName(@QueryParam("id") String id, @QueryParam("dataset") String dataset, @QueryParam("metric") String metric) { MetricConfigDTO metricConfigDTO = null; if (StringUtils.isBlank(id) && (StringUtils.isBlank(dataset) || StringUtils.isBlank(metric))) { LOG.error("Must provide either id or metric+dataset {} {} {}", id, metric, dataset); } if (StringUtils.isNotBlank(id)) { metricConfigDTO = metricConfigDao.findById(Long.valueOf(id)); } else { metricConfigDTO = metricConfigDao.findByMetricAndDataset(metric, dataset); } return metricConfigDTO; } }