This is an automated email from the ASF dual-hosted git repository. jihao 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 775d578 [TE] alerts search and pagination endpoint (#5413) 775d578 is described below commit 775d57827b38f293cf62748b118d940617b459ed Author: Jihao Zhang <jihzh...@linkedin.com> AuthorDate: Wed May 20 13:28:52 2020 -0700 [TE] alerts search and pagination endpoint (#5413) This commit adds the endpoint for alert search and pagination. Specifically, - The endpoint to search and paginate the alerts. - The Alert searcher to retrieve the alerts in the database based on the search filter. - Add the capability of counting, pagination, and find by JSON value predicate to the generic DAO. - Unit tests --- .../dashboard/ThirdEyeDashboardApplication.java | 2 + .../resources/v2/alerts/AlertResource.java | 79 +++++++ .../resources/v2/alerts/AlertSearchFilter.java | 157 +++++++++++++ .../resources/v2/alerts/AlertSearcher.java | 259 +++++++++++++++++++++ .../thirdeye/datalayer/bao/AbstractManager.java | 29 ++- .../datalayer/bao/jdbc/AbstractManagerImpl.java | 15 ++ .../thirdeye/datalayer/dao/GenericPojoDao.java | 82 +++++++ .../datalayer/entity/DetectionConfigIndex.java | 18 ++ .../thirdeye/datalayer/util/SqlQueryBuilder.java | 40 ++++ .../thirdeye/detection/DetectionResource.java | 1 - .../src/main/resources/schema/create-schema.sql | 4 + .../resources/v2/alerts/AlertSearcherTest.java | 75 ++++++ 12 files changed, 759 insertions(+), 2 deletions(-) diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/ThirdEyeDashboardApplication.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/ThirdEyeDashboardApplication.java index 1ac0973..ccd70d3 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/ThirdEyeDashboardApplication.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/ThirdEyeDashboardApplication.java @@ -44,6 +44,7 @@ import org.apache.pinot.thirdeye.dashboard.resources.CacheResource; import org.apache.pinot.thirdeye.dashboard.resources.CustomizedEventResource; import org.apache.pinot.thirdeye.dashboard.resources.DashboardResource; import org.apache.pinot.thirdeye.dashboard.resources.DatasetConfigResource; +import org.apache.pinot.thirdeye.dashboard.resources.v2.alerts.AlertResource; import org.apache.pinot.thirdeye.dashboard.resources.DetectionJobResource; import org.apache.pinot.thirdeye.dashboard.resources.EmailResource; import org.apache.pinot.thirdeye.dashboard.resources.EntityManagerResource; @@ -194,6 +195,7 @@ public class ThirdEyeDashboardApplication env.jersey().register(new YamlResource(config.getAlerterConfiguration(), config.getDetectionPreviewConfig(), config.getAlertOnboardingPermitPerSecond())); env.jersey().register(new SqlDataSourceResource()); + env.jersey().register(new AlertResource()); TimeSeriesLoader timeSeriesLoader = new DefaultTimeSeriesLoader( DAO_REGISTRY.getMetricConfigDAO(), DAO_REGISTRY.getDatasetConfigDAO(), diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertResource.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertResource.java new file mode 100644 index 0000000..1bc4f93 --- /dev/null +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertResource.java @@ -0,0 +1,79 @@ +/* + * 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.v2.alerts; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import java.util.List; +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 javax.ws.rs.core.Response; +import org.apache.pinot.thirdeye.api.Constants; + + +/** + * The Alert resource. + */ +@Path(value = "/alerts") +@Produces(MediaType.APPLICATION_JSON) +@Api(tags = {Constants.DETECTION_TAG}) +public class AlertResource { + private final AlertSearcher alertSearcher; + + /** + * Instantiates a new Alert resource. + */ + public AlertResource() { + this.alertSearcher = new AlertSearcher(); + } + + /** + * Search the alerts with result pagination. It will return record from No.(offset+1) to record No.(offset + limit). + * + * @param limit the returned result limit + * @param offset the offset of the start position + * @param applications the applications for the alerts + * @param subscriptionGroups the subscription groups for the alerts + * @param names the names for the alerts + * @param createdBy the owners for the alerts + * @param ruleTypes the rule types for the alerts + * @param metrics the metrics for the alerts + * @param datasets the datasets for the alerts + * @param active if the alert is active + * @return the response + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation("Search and paginate alerts according to the parameters") + public Response findAlerts(@QueryParam("limit") @DefaultValue("10") long limit, + @QueryParam("offset") @DefaultValue("0") long offset, @QueryParam("application") List<String> applications, + @QueryParam("subscriptionGroup") List<String> subscriptionGroups, @QueryParam("names") List<String> names, + @QueryParam("createdBy") List<String> createdBy, @QueryParam("ruleType") List<String> ruleTypes, + @QueryParam("metric") List<String> metrics, @QueryParam("dataset") List<String> datasets, + @QueryParam("active") Boolean active) { + AlertSearchFilter searchFilter = new AlertSearchFilter(applications, subscriptionGroups, names, createdBy, ruleTypes, metrics, datasets, active); + return Response.ok().entity(this.alertSearcher.search(searchFilter, limit, offset)).build(); + } +} diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearchFilter.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearchFilter.java new file mode 100644 index 0000000..0c488ff --- /dev/null +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearchFilter.java @@ -0,0 +1,157 @@ +/* + * 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.v2.alerts; + +import java.util.Collections; +import java.util.List; + + +/** + * The type Alert search filter. + */ +public class AlertSearchFilter { + private final List<String> applications; + private final List<String> subscriptionGroups; + private final List<String> createdBy; + private final List<String> ruleTypes; + private final List<String> metrics; + private final List<String> datasets; + private final List<String> names; + private final Boolean active; + + public AlertSearchFilter() { + this.applications = Collections.emptyList(); + this.subscriptionGroups = Collections.emptyList(); + this.createdBy = Collections.emptyList(); + this.ruleTypes = Collections.emptyList(); + this.datasets = Collections.emptyList(); + this.metrics = Collections.emptyList(); + this.names = Collections.emptyList(); + this.active = null; + } + + /** + * Instantiates a new Alert search filter. + * + * @param applications the applications + * @param subscriptionGroups the subscription groups + * @param names the names + * @param createdBy the createdBy + * @param ruleTypes the rule types + * @param metrics the metrics + * @param datasets the datasets + * @param active the active + */ + public AlertSearchFilter(List<String> applications, List<String> subscriptionGroups, List<String> names, + List<String> createdBy, List<String> ruleTypes, List<String> metrics, List<String> datasets, Boolean active) { + this.applications = applications; + this.subscriptionGroups = subscriptionGroups; + this.names = names; + this.createdBy = createdBy; + this.ruleTypes = ruleTypes; + this.metrics = metrics; + this.datasets = datasets; + this.active = active; + } + + /** + * Gets applications. + * + * @return the applications + */ + public List<String> getApplications() { + return applications; + } + + /** + * Gets subscription groups. + * + * @return the subscription groups + */ + public List<String> getSubscriptionGroups() { + return subscriptionGroups; + } + + /** + * Gets createdBy. + * + * @return the owners + */ + public List<String> getCreatedBy() { + return createdBy; + } + + /** + * Gets rule types. + * + * @return the rule types + */ + public List<String> getRuleTypes() { + return ruleTypes; + } + + /** + * Gets metrics. + * + * @return the metrics + */ + public List<String> getMetrics() { + return metrics; + } + + /** + * Gets datasets. + * + * @return the datasets + */ + public List<String> getDatasets() { + return datasets; + } + + /** + * Gets names. + * + * @return the names + */ + public List<String> getNames() { + return names; + } + + /** + * Gets active. + * + * @return the active + */ + public Boolean getActive() { + return active; + } + + /** + * If all the search filters are empty. + * + * @return the boolean value of the result + */ + public boolean isEmpty() { + return this.applications.isEmpty() && this.subscriptionGroups.isEmpty() && this.names.isEmpty() + && this.createdBy.isEmpty() && this.ruleTypes.isEmpty() && this.metrics.isEmpty() && this.datasets.isEmpty() + && active == null; + } +} diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearcher.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearcher.java new file mode 100644 index 0000000..78fe3a1 --- /dev/null +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearcher.java @@ -0,0 +1,259 @@ +/* + * 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.v2.alerts; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import org.apache.commons.collections4.MapUtils; +import org.apache.pinot.thirdeye.datalayer.bao.DatasetConfigManager; +import org.apache.pinot.thirdeye.datalayer.bao.DetectionAlertConfigManager; +import org.apache.pinot.thirdeye.datalayer.bao.DetectionConfigManager; +import org.apache.pinot.thirdeye.datalayer.bao.MetricConfigManager; +import org.apache.pinot.thirdeye.datalayer.dto.AbstractDTO; +import org.apache.pinot.thirdeye.datalayer.dto.DetectionAlertConfigDTO; +import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO; +import org.apache.pinot.thirdeye.datalayer.util.Predicate; +import org.apache.pinot.thirdeye.datasource.DAORegistry; +import org.apache.pinot.thirdeye.formatter.DetectionConfigFormatter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The Alert searcher. + */ +public class AlertSearcher { + private static final Logger LOG = LoggerFactory.getLogger(AlertSearcher.class.getName()); + private final DetectionConfigManager detectionConfigDAO; + private final DetectionAlertConfigManager detectionAlertConfigDAO; + private final MetricConfigManager metricDAO; + private final DatasetConfigManager datasetDAO; + private final DetectionConfigFormatter detectionConfigFormatter; + + /** + * The Alert search query. + */ + static class AlertSearchQuery { + /** + * The Search filter. + */ + final AlertSearchFilter searchFilter; + /** + * The Limit. + */ + final long limit; + /** + * The Offset. + */ + final long offset; + + /** + * Instantiates a new Alert search query. + * + * @param searchFilter the search filter + * @param limit the limit + * @param offset the offset + */ + public AlertSearchQuery(AlertSearchFilter searchFilter, long limit, long offset) { + this.searchFilter = searchFilter; + this.limit = limit; + this.offset = offset; + } + } + + /** + * Instantiates a new Alert searcher. + */ + public AlertSearcher() { + this.detectionConfigDAO = DAORegistry.getInstance().getDetectionConfigManager(); + this.detectionAlertConfigDAO = DAORegistry.getInstance().getDetectionAlertConfigManager(); + this.metricDAO = DAORegistry.getInstance().getMetricConfigDAO(); + this.datasetDAO = DAORegistry.getInstance().getDatasetConfigDAO(); + this.detectionConfigFormatter = new DetectionConfigFormatter(metricDAO, datasetDAO); + } + + /** + * Search and retrive all the alerts matching to the search filter and limits. + * + * @param searchFilter the search filter + * @param limit the limit + * @param offset the offset + * @return the result + */ + public Map<String, Object> search(AlertSearchFilter searchFilter, long limit, long offset) { + AlertSearchQuery searchQuery = new AlertSearchQuery(searchFilter, limit, offset); + List<DetectionAlertConfigDTO> subscriptionGroups = findRelatedSubscriptionGroups(searchQuery); + List<DetectionConfigDTO> detectionConfigs = findDetectionConfig(searchQuery, subscriptionGroups); + return getResult(searchQuery, subscriptionGroups, detectionConfigs); + } + + private List<DetectionAlertConfigDTO> findRelatedSubscriptionGroups(AlertSearchQuery searchQuery) { + AlertSearchFilter searchFilter = searchQuery.searchFilter; + List<Predicate> predicates = new ArrayList<>(); + if (!searchFilter.getApplications().isEmpty()) { + predicates.add(Predicate.IN("application", searchFilter.getApplications().toArray())); + } + if (!searchFilter.getSubscriptionGroups().isEmpty()) { + predicates.add(Predicate.IN("name", searchFilter.getSubscriptionGroups().toArray())); + } + if (!predicates.isEmpty()) { + return this.detectionAlertConfigDAO.findByPredicate(Predicate.AND(predicates.toArray(new Predicate[0]))); + } else { + return this.detectionAlertConfigDAO.findAll(); + } + } + + private List<DetectionConfigDTO> findDetectionConfig(AlertSearchQuery searchQuery, + List<DetectionAlertConfigDTO> subscriptionGroups) { + AlertSearchFilter searchFilter = searchQuery.searchFilter; + if (searchFilter.isEmpty()) { + // if no search filter is applied, by default, retrieve the paginated result from db + return this.detectionConfigDAO.list(searchQuery.limit, searchQuery.offset); + } + + // look up and run the search filters on the detection config index + List<DetectionConfigDTO> indexedResult = new ArrayList<>(); + List<Predicate> indexPredicates = new ArrayList<>(); + if (!searchFilter.getApplications().isEmpty() || !searchFilter.getSubscriptionGroups().isEmpty()) { + Set<Long> detectionConfigIds = new TreeSet<>(); + for (DetectionAlertConfigDTO subscriptionGroup : subscriptionGroups) { + detectionConfigIds.addAll(subscriptionGroup.getVectorClocks().keySet()); + } + indexPredicates.add(Predicate.IN("baseId", detectionConfigIds.toArray())); + } + if (!searchFilter.getCreatedBy().isEmpty()) { + indexPredicates.add(Predicate.IN("createdBy", searchFilter.getCreatedBy().toArray())); + } + if (!searchFilter.getNames().isEmpty()) { + indexPredicates.add(Predicate.IN("name", searchFilter.getNames().toArray())); + } + if (searchFilter.getActive() != null) { + indexPredicates.add(Predicate.EQ("active", searchFilter.getActive() ? 1 : 0)); + } + if (!indexPredicates.isEmpty()) { + indexedResult = this.detectionConfigDAO.findByPredicate(Predicate.AND(indexPredicates.toArray(new Predicate[0]))); + } + + // for metrics, datasets, rule types filters, run the search filters in the generic table + List<DetectionConfigDTO> jsonValResult = new ArrayList<>(); + List<Predicate> jsonValPredicates = new ArrayList<>(); + if (!searchFilter.getRuleTypes().isEmpty()) { + List<Predicate> ruleTypePredicates = new ArrayList<>(); + for (String ruleType : searchFilter.getRuleTypes()) { + ruleTypePredicates.add(Predicate.LIKE("jsonVal", "%componentSpecs%:" + ruleType + "\"%")); + } + jsonValPredicates.add(Predicate.OR(ruleTypePredicates.toArray(new Predicate[0]))); + } + + Set<Long> metricIds = new HashSet<>(); + if (!searchFilter.getMetrics().isEmpty()) { + for (String metric : searchFilter.getMetrics()) { + metricIds = + this.metricDAO.findByMetricName(metric).stream().map(AbstractDTO::getId).collect(Collectors.toSet()); + } + } + + if (!searchFilter.getDatasets().isEmpty()) { + for (String dataset : searchFilter.getDatasets()) { + metricIds.retainAll( + this.metricDAO.findByDataset(dataset).stream().map(AbstractDTO::getId).collect(Collectors.toSet())); + } + } + + if (!metricIds.isEmpty()) { + List<Predicate> metricUrnPredicates = new ArrayList<>(); + for (Long id : metricIds) { + metricUrnPredicates.add(Predicate.LIKE("jsonVal", "%thirdeye:metric:" + id + "%")); + } + jsonValPredicates.add(Predicate.OR(metricUrnPredicates.toArray(new Predicate[0]))); + } + + if (!jsonValPredicates.isEmpty()) { + jsonValResult = + this.detectionConfigDAO.findByPredicateJsonVal(Predicate.AND(jsonValPredicates.toArray(new Predicate[0]))); + } + + List<DetectionConfigDTO> result; + if (!jsonValPredicates.isEmpty() && !indexPredicates.isEmpty()) { + // merge the result from both tables + result = jsonValResult.stream().filter(indexedResult::contains).collect(Collectors.toList()); + } else { + jsonValResult.addAll(indexedResult); + result = jsonValResult; + } + return result; + } + + /** + * Format and generate the final search result + */ + private Map<String, Object> getResult(AlertSearchQuery searchQuery, List<DetectionAlertConfigDTO> subscriptionGroups, + List<DetectionConfigDTO> detectionConfigs) { + long count; + if (searchQuery.searchFilter.isEmpty()) { + // if not filter is applied, execute count query + count = this.detectionConfigDAO.count(); + } else { + // count and limit the filtered results + count = detectionConfigs.size(); + if (searchQuery.offset >= count) { + // requested page is out of bound + detectionConfigs.clear(); + } else { + detectionConfigs = detectionConfigs.subList((int) searchQuery.offset, + (int) Math.min(searchQuery.offset + searchQuery.limit, count)); + } + } + + // format the results + List<Map<String, Object>> alerts = detectionConfigs.parallelStream().map(config -> { + try { + return this.detectionConfigFormatter.format(config); + } catch (Exception e) { + LOG.warn("formatting detection config failed {}", config.getId(), e); + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + // join detections with subscription groups + Multimap<Long, String> detectionIdToSubscriptionGroups = ArrayListMultimap.create(); + for (DetectionAlertConfigDTO subscriptionGroup : subscriptionGroups) { + for (long detectionConfigId : subscriptionGroup.getVectorClocks().keySet()) { + detectionIdToSubscriptionGroups.put(detectionConfigId, subscriptionGroup.getName()); + } + } + for (Map<String, Object> alert : alerts) { + alert.put("subscriptionGroup", detectionIdToSubscriptionGroups.get(MapUtils.getLong(alert, "id"))); + } + + return ImmutableMap.of("count", count, "limit", searchQuery.limit, "offset", searchQuery.offset, "elements", + alerts); + } +} diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/AbstractManager.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/AbstractManager.java index 69e6354..9faa14d 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/AbstractManager.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/AbstractManager.java @@ -21,7 +21,7 @@ package org.apache.pinot.thirdeye.datalayer.bao; import java.util.List; import java.util.Map; - +import org.apache.commons.lang3.NotImplementedException; import org.apache.pinot.thirdeye.datalayer.dto.AbstractDTO; import org.apache.pinot.thirdeye.datalayer.util.Predicate; @@ -57,4 +57,31 @@ public interface AbstractManager<E extends AbstractDTO> { List<Long> findIdsByPredicate(Predicate predicate); int update(E entity, Predicate predicate); + + /** + * Find the entities based on the JSON value predicate + * @param predicate the predicate + * @return the list of entities that match with the predicate + */ + default List<E> findByPredicateJsonVal(Predicate predicate) { + throw new NotImplementedException("Not Implemented"); + } + + /** + * List the entities with pagination + * @param limit the limit for the number of elements returned + * @param offset the offset position + * @return the list of entities ordered by id in descending order + */ + default List<E> list(long limit, long offset) { + throw new NotImplementedException("Not Implemented"); + } + + /** + * Count how many entities are there in the table + * @return the number of total entities + */ + default long count() { + throw new NotImplementedException("Not Implemented"); + } } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/jdbc/AbstractManagerImpl.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/jdbc/AbstractManagerImpl.java index c13cea9..81a8535 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/jdbc/AbstractManagerImpl.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/bao/jdbc/AbstractManagerImpl.java @@ -171,6 +171,21 @@ public abstract class AbstractManagerImpl<E extends AbstractDTO> implements Abst return genericPojoDao.getIdsByPredicate(predicate, beanClass); } + @Override + public List<E> list(long limit, long offset) { + return convertBeanListToDTOList(genericPojoDao.list(beanClass, limit, offset)); + } + + @Override + public List<E> findByPredicateJsonVal(Predicate predicate) { + return convertBeanListToDTOList(genericPojoDao.getByPredicateJsonVal(predicate, beanClass)); + } + + @Override + public long count() { + return genericPojoDao.count(beanClass); + } + protected List<E> convertBeanListToDTOList(List<? extends AbstractBean> beans) { List<E> result = new ArrayList<>(); for (AbstractBean bean : beans) { diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dao/GenericPojoDao.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dao/GenericPojoDao.java index 8e983c5..227c9b1 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dao/GenericPojoDao.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dao/GenericPojoDao.java @@ -442,6 +442,88 @@ public class GenericPojoDao { } } + public <E extends AbstractBean> List<E> list(final Class<E> beanClass, long limit, long offset) { + long tStart = System.nanoTime(); + try { + return runTask(connection -> { + List<GenericJsonEntity> entities; + Predicate predicate = Predicate.EQ("beanClass", beanClass.getName()); + try (PreparedStatement selectStatement = + sqlQueryBuilder.createfindByParamsStatementWithLimit(connection, GenericJsonEntity.class, predicate, limit, offset)) { + try (ResultSet resultSet = selectStatement.executeQuery()) { + entities = genericResultSetMapper.mapAll(resultSet, GenericJsonEntity.class); + } + } + List<E> result = new ArrayList<>(); + if (entities != null) { + for (GenericJsonEntity entity : entities) { + ThirdeyeMetricsUtil.dbReadByteCounter.inc(entity.getJsonVal().length()); + E e = OBJECT_MAPPER.readValue(entity.getJsonVal(), beanClass); + e.setId(entity.getId()); + e.setUpdateTime(entity.getUpdateTime()); + result.add(e); + } + } + return result; + }, Collections.emptyList()); + } finally { + ThirdeyeMetricsUtil.dbReadCallCounter.inc(); + ThirdeyeMetricsUtil.dbReadDurationCounter.inc(System.nanoTime() - tStart); + } + } + + public <E extends AbstractBean> List<E> getByPredicateJsonVal(Predicate predicate, final Class<E> beanClass) { + long tStart = System.nanoTime(); + try { + return runTask(connection -> { + List<GenericJsonEntity> entities; + Predicate p = Predicate.AND(predicate, Predicate.EQ("beanClass", beanClass.getName())); + try (PreparedStatement selectStatement = + sqlQueryBuilder.createFindByParamsStatement(connection, GenericJsonEntity.class, p)) { + try (ResultSet resultSet = selectStatement.executeQuery()) { + entities = genericResultSetMapper.mapAll(resultSet, GenericJsonEntity.class); + } + } + List<E> result = new ArrayList<>(); + if (entities != null) { + for (GenericJsonEntity entity : entities) { + ThirdeyeMetricsUtil.dbReadByteCounter.inc(entity.getJsonVal().length()); + E e = OBJECT_MAPPER.readValue(entity.getJsonVal(), beanClass); + e.setId(entity.getId()); + e.setUpdateTime(entity.getUpdateTime()); + result.add(e); + } + } + return result; + }, Collections.emptyList()); + } finally { + ThirdeyeMetricsUtil.dbReadCallCounter.inc(); + ThirdeyeMetricsUtil.dbReadDurationCounter.inc(System.nanoTime() - tStart); + } + } + + public <E extends AbstractBean> long count(final Class<E> beanClass) { + long tStart = System.nanoTime(); + try { + return runTask(connection -> { + PojoInfo pojoInfo = pojoInfoMap.get(beanClass); + try (PreparedStatement selectStatement = + sqlQueryBuilder.createCountStatement(connection, pojoInfo.indexEntityClass)) { + try (ResultSet resultSet = selectStatement.executeQuery()) { + if (resultSet.next()) { + return resultSet.getInt(1); + } else { + throw new IllegalStateException("can't parse count query response"); + } + } + } + }, -1); + } finally { + ThirdeyeMetricsUtil.dbReadCallCounter.inc(); + ThirdeyeMetricsUtil.dbReadDurationCounter.inc(System.nanoTime() - tStart); + } + } + public <E extends AbstractBean> E get(final Long id, final Class<E> pojoClass) { long tStart = System.nanoTime(); try { diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/entity/DetectionConfigIndex.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/entity/DetectionConfigIndex.java index 39e9d64..2c4011f 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/entity/DetectionConfigIndex.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/entity/DetectionConfigIndex.java @@ -21,6 +21,8 @@ package org.apache.pinot.thirdeye.datalayer.entity; public class DetectionConfigIndex extends AbstractIndexEntity { String name; + boolean active; + String createdBy; public String getName() { return name; @@ -29,4 +31,20 @@ public class DetectionConfigIndex extends AbstractIndexEntity { public void setName(String name) { this.name = name; } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } } diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/util/SqlQueryBuilder.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/util/SqlQueryBuilder.java index 88df8de..6f4076c 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/util/SqlQueryBuilder.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/util/SqlQueryBuilder.java @@ -338,6 +338,46 @@ public class SqlQueryBuilder { return prepareStatement; } + public PreparedStatement createfindByParamsStatementWithLimit(Connection connection, + Class<? extends AbstractEntity> entityClass, Predicate predicate, Long limit, Long offset) throws Exception { + String tableName = entityMappingHolder.tableToEntityNameMap.inverse().get(entityClass.getSimpleName()); + BiMap<String, String> entityNameToDBNameMapping = + entityMappingHolder.columnMappingPerTable.get(tableName).inverse(); + StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM " + tableName); + StringBuilder whereClause = new StringBuilder(" WHERE "); + List<Pair<String, Object>> parametersList = new ArrayList<>(); + generateWhereClause(entityNameToDBNameMapping, predicate, parametersList, whereClause); + sqlBuilder.append(whereClause.toString()); + sqlBuilder.append(" ORDER BY id DESC"); + if (limit != null) { + sqlBuilder.append(" LIMIT ").append(limit); + } + if (offset != null) { + sqlBuilder.append(" OFFSET ").append(offset); + } + PreparedStatement prepareStatement = connection.prepareStatement(sqlBuilder.toString()); + int parameterIndex = 1; + LinkedHashMap<String, ColumnInfo> columnInfoMap = + entityMappingHolder.columnInfoPerTable.get(tableName); + for (Pair<String, Object> pair : parametersList) { + String dbFieldName = pair.getKey(); + ColumnInfo info = columnInfoMap.get(dbFieldName); + Preconditions.checkNotNull(info, String.format("Found field '%s' but expected %s", dbFieldName, columnInfoMap.keySet())); + prepareStatement.setObject(parameterIndex++, pair.getValue(), info.sqlType); + LOG.debug("Setting {} to {}", pair.getKey(), pair.getValue()); + } + return prepareStatement; + } + + public PreparedStatement createCountStatement(Connection connection, + Class<? extends AbstractIndexEntity> indexEntityClass) throws Exception { + String tableName = + entityMappingHolder.tableToEntityNameMap.inverse().get(indexEntityClass.getSimpleName()); + String sql = "Select count(*) from " + tableName; + PreparedStatement prepareStatement = connection.prepareStatement(sql); + return prepareStatement; + } + private void generateWhereClause(BiMap<String, String> entityNameToDBNameMapping, Predicate predicate, List<Pair<String, Object>> parametersList, StringBuilder whereClause) { String columnName = null; diff --git a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionResource.java b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionResource.java index 6c0637f..e861cec 100644 --- a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionResource.java +++ b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionResource.java @@ -85,7 +85,6 @@ import org.apache.pinot.thirdeye.formatter.DetectionAlertConfigFormatter; import org.apache.pinot.thirdeye.formatter.DetectionConfigFormatter; import org.apache.pinot.thirdeye.rootcause.impl.MetricEntity; import org.apache.pinot.thirdeye.util.AnomalyOffset; -import org.h2.command.dml.Merge; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Interval; diff --git a/thirdeye/thirdeye-pinot/src/main/resources/schema/create-schema.sql b/thirdeye/thirdeye-pinot/src/main/resources/schema/create-schema.sql index 00e6180..c93b922 100644 --- a/thirdeye/thirdeye-pinot/src/main/resources/schema/create-schema.sql +++ b/thirdeye/thirdeye-pinot/src/main/resources/schema/create-schema.sql @@ -380,6 +380,8 @@ create index session_principal_type_idx ON session_index(principal_type); create table if not exists detection_config_index ( base_id bigint(20) not null, `name` VARCHAR(256) not null, + active BOOLEAN, + created_by VARCHAR(256), create_time timestamp, update_time timestamp default current_timestamp, version int(10) @@ -387,6 +389,8 @@ create table if not exists detection_config_index ( ALTER TABLE `detection_config_index` ADD UNIQUE `detection_config_unique_index`(`name`); create index detection_config_base_id_idx ON detection_config_index(base_id); create index detection_config_name_idx ON detection_config_index(`name`); +create index detection_config_active_idx ON detection_config_index(active); +create index detection_config_created_by_index ON detection_config_index(created_by); create table if not exists detection_alert_config_index ( base_id bigint(20) not null, diff --git a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearcherTest.java b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearcherTest.java new file mode 100644 index 0000000..3e25461 --- /dev/null +++ b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/dashboard/resources/v2/alerts/AlertSearcherTest.java @@ -0,0 +1,75 @@ +/* + * 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.v2.alerts; + +import java.util.Collections; +import java.util.Map; +import org.apache.pinot.thirdeye.datalayer.bao.DAOTestBase; +import org.apache.pinot.thirdeye.datalayer.bao.DetectionConfigManager; +import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO; +import org.apache.pinot.thirdeye.datasource.DAORegistry; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + + +public class AlertSearcherTest { + private DAOTestBase testDAOProvider; + + @BeforeMethod + public void setUp() { + testDAOProvider = DAOTestBase.getInstance(); + DetectionConfigManager detectionDAO = DAORegistry.getInstance().getDetectionConfigManager(); + DetectionConfigDTO detectionConfig1 = new DetectionConfigDTO(); + detectionConfig1.setName("test_detection1"); + DetectionConfigDTO detectionConfig2 = new DetectionConfigDTO(); + detectionConfig2.setName("test_detection2"); + DetectionConfigDTO detectionConfig3 = new DetectionConfigDTO(); + detectionConfig3.setName("test_detection3"); + detectionConfig3.setCreatedBy("t...@example.com"); + DetectionConfigDTO detectionConfig4 = new DetectionConfigDTO(); + detectionConfig4.setActive(true); + detectionConfig4.setName("test_detection4"); + + detectionDAO.save(detectionConfig1); + detectionDAO.save(detectionConfig2); + detectionDAO.save(detectionConfig3); + detectionDAO.save(detectionConfig4); + } + + @Test + public void testSearch() { + AlertSearcher searcher = new AlertSearcher(); + Map<String, Object> result = searcher.search(new AlertSearchFilter(), 10 ,0); + Assert.assertEquals(result.get("count"), 4L); + Assert.assertEquals(result.get("limit"), 10L); + Assert.assertEquals(result.get("offset"), 0L); + } + + @Test + public void testSearchActive() { + AlertSearcher searcher = new AlertSearcher(); + Map<String, Object> result = searcher.search(new AlertSearchFilter(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), true), 10 ,0); + Assert.assertEquals(result.get("count"), 1L); + Assert.assertEquals(result.get("limit"), 10L); + Assert.assertEquals(result.get("offset"), 0L); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org