mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-30 20:06:19 +00:00
* feat: added dynamic method to build search aggregation from string * fix: moved summary computation to ES * fix: added+updated tests * style: ran java linting
This commit is contained in:
parent
8960c159eb
commit
7424b2f430
@ -335,6 +335,12 @@
|
||||
<version>2.40</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.25.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- JSON-P: Java API for JSON Processing (JSR 374) -->
|
||||
<dependency>
|
||||
<groupId>javax.json</groupId>
|
||||
|
@ -409,13 +409,15 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
||||
updateResultSummaries(testCase, isDeleted, resultSummaries, resultSummary);
|
||||
|
||||
// Update test case result summary attribute for the test suite
|
||||
TestSuiteRepository testSuiteRepository =
|
||||
(TestSuiteRepository) Entity.getEntityRepository(Entity.TEST_SUITE);
|
||||
TestSuite original =
|
||||
TestSuiteRepository.copyTestSuite(
|
||||
testSuite); // we'll need the original state to update the test suite
|
||||
testSuite.setTestCaseResultSummary(resultSummaries);
|
||||
daoCollection
|
||||
.testSuiteDAO()
|
||||
.update(
|
||||
testSuite.getId(),
|
||||
testSuite.getFullyQualifiedName(),
|
||||
JsonUtils.pojoToJson(testSuite));
|
||||
EntityRepository<TestSuite>.EntityUpdater testSuiteUpdater =
|
||||
testSuiteRepository.getUpdater(original, testSuite, Operation.PUT);
|
||||
testSuiteUpdater.update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -652,11 +654,16 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
||||
testSuite.setSummary(null); // we don't want to store the summary in the database
|
||||
List<ResultSummary> resultSummaries = testSuite.getTestCaseResultSummary();
|
||||
resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(testCaseFqn));
|
||||
|
||||
TestSuiteRepository testSuiteRepository =
|
||||
(TestSuiteRepository) Entity.getEntityRepository(Entity.TEST_SUITE);
|
||||
TestSuite original =
|
||||
TestSuiteRepository.copyTestSuite(
|
||||
testSuite); // we'll need the original state to update the test suite
|
||||
testSuite.setTestCaseResultSummary(resultSummaries);
|
||||
daoCollection
|
||||
.testSuiteDAO()
|
||||
.update(
|
||||
testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite));
|
||||
EntityRepository<TestSuite>.EntityUpdater testSuiteUpdater =
|
||||
testSuiteRepository.getUpdater(original, testSuite, Operation.PUT);
|
||||
testSuiteUpdater.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,10 +9,14 @@ import static org.openmetadata.service.Entity.TEST_CASE;
|
||||
import static org.openmetadata.service.Entity.TEST_SUITE;
|
||||
import static org.openmetadata.service.util.FullyQualifiedName.quoteName;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import javax.json.JsonArray;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonValue;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jdbi.v3.sqlobject.transaction.Transaction;
|
||||
@ -23,7 +27,6 @@ import org.openmetadata.schema.tests.type.TestCaseStatus;
|
||||
import org.openmetadata.schema.tests.type.TestSummary;
|
||||
import org.openmetadata.schema.type.EntityReference;
|
||||
import org.openmetadata.schema.type.EventType;
|
||||
import org.openmetadata.schema.type.Include;
|
||||
import org.openmetadata.schema.type.Relationship;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.resources.dqtests.TestSuiteResource;
|
||||
@ -55,7 +58,7 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
|
||||
fields.contains("pipelines") ? getIngestionPipelines(entity) : entity.getPipelines());
|
||||
entity.setSummary(
|
||||
fields.contains("summary") ? getTestCasesExecutionSummary(entity) : entity.getSummary());
|
||||
entity.withTests(fields.contains("tests") ? getTestCases(entity) : entity.getTests());
|
||||
entity.withTests(fields.contains(UPDATE_FIELDS) ? getTestCases(entity) : entity.getTests());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -71,7 +74,7 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
|
||||
public void clearFields(TestSuite entity, EntityUtil.Fields fields) {
|
||||
entity.setPipelines(fields.contains("pipelines") ? entity.getPipelines() : null);
|
||||
entity.setSummary(fields.contains("summary") ? entity.getSummary() : null);
|
||||
entity.withTests(fields.contains("tests") ? entity.getTests() : null);
|
||||
entity.withTests(fields.contains(UPDATE_FIELDS) ? entity.getTests() : null);
|
||||
}
|
||||
|
||||
private TestSummary buildTestSummary(Map<String, Integer> testCaseSummary) {
|
||||
@ -117,31 +120,81 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
|
||||
return buildTestSummary(testCaseSummary);
|
||||
}
|
||||
|
||||
private TestSummary getTestCasesExecutionSummary(List<TestSuite> entities) {
|
||||
if (entities.isEmpty()) return new TestSummary();
|
||||
Map<String, Integer> testsSummary = new HashMap<>();
|
||||
for (TestSuite testSuite : entities) {
|
||||
Map<String, Integer> testSummary = getResultSummary(testSuite);
|
||||
for (Map.Entry<String, Integer> entry : testSummary.entrySet()) {
|
||||
testsSummary.put(
|
||||
entry.getKey(), testsSummary.getOrDefault(entry.getKey(), 0) + entry.getValue());
|
||||
private TestSummary getTestCasesExecutionSummary(JsonObject aggregation) {
|
||||
// Initialize the test summary with 0 values
|
||||
TestSummary testSummary =
|
||||
new TestSummary().withAborted(0).withFailed(0).withSuccess(0).withQueued(0).withTotal(0);
|
||||
JsonObject summary = aggregation.getJsonObject("nested#testCaseResultSummary");
|
||||
testSummary.setTotal(summary.getJsonNumber("doc_count").intValue());
|
||||
|
||||
JsonObject statusCount = summary.getJsonObject("sterms#status_counts");
|
||||
JsonArray buckets = statusCount.getJsonArray("buckets");
|
||||
|
||||
for (JsonValue bucket : buckets) {
|
||||
String key = ((JsonObject) bucket).getString("key");
|
||||
Integer count = ((JsonObject) bucket).getJsonNumber("doc_count").intValue();
|
||||
switch (key) {
|
||||
case "Success":
|
||||
testSummary.setSuccess(count);
|
||||
break;
|
||||
case "Failed":
|
||||
testSummary.setFailed(count);
|
||||
break;
|
||||
case "Aborted":
|
||||
testSummary.setAborted(count);
|
||||
break;
|
||||
case "Queued":
|
||||
testSummary.setQueued(count);
|
||||
break;
|
||||
}
|
||||
testSuite.getTestCaseResultSummary().size();
|
||||
}
|
||||
return buildTestSummary(testsSummary);
|
||||
return testSummary;
|
||||
}
|
||||
|
||||
public TestSummary getTestSummary(UUID testSuiteId) {
|
||||
public TestSummary getTestSummary(UUID testSuiteId) throws IOException {
|
||||
String aggregationQuery =
|
||||
"""
|
||||
{
|
||||
"aggregations": {
|
||||
"test_case_results": {
|
||||
"nested": {
|
||||
"path": "testCaseResultSummary"
|
||||
},
|
||||
"aggs": {
|
||||
"status_counts": {
|
||||
"terms": {
|
||||
"field": "testCaseResultSummary.status"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
JsonObject aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject();
|
||||
TestSummary testSummary;
|
||||
if (testSuiteId == null) {
|
||||
ListFilter filter = new ListFilter();
|
||||
filter.addQueryParam("testSuiteType", "executable");
|
||||
List<TestSuite> testSuites = listAll(EntityUtil.Fields.EMPTY_FIELDS, filter);
|
||||
testSummary = getTestCasesExecutionSummary(testSuites);
|
||||
JsonObject testCaseResultSummary =
|
||||
searchRepository.aggregate(null, TEST_SUITE, aggregationJson);
|
||||
testSummary = getTestCasesExecutionSummary(testCaseResultSummary);
|
||||
} else {
|
||||
String query =
|
||||
"""
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": {
|
||||
"term": {"id": "%s"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
.formatted(testSuiteId);
|
||||
// don't want to get it from the cache as test results summary may be stale
|
||||
TestSuite testSuite = Entity.getEntity(TEST_SUITE, testSuiteId, "", Include.ALL, false);
|
||||
testSummary = getTestCasesExecutionSummary(testSuite);
|
||||
JsonObject testCaseResultSummary =
|
||||
searchRepository.aggregate(query, TEST_SUITE, aggregationJson);
|
||||
testSummary = getTestCasesExecutionSummary(testCaseResultSummary);
|
||||
}
|
||||
return testSummary;
|
||||
}
|
||||
@ -211,6 +264,26 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
|
||||
return new RestUtil.DeleteResponse<>(updated, changeType);
|
||||
}
|
||||
|
||||
public static TestSuite copyTestSuite(TestSuite testSuite) {
|
||||
return new TestSuite()
|
||||
.withConnection(testSuite.getConnection())
|
||||
.withDescription(testSuite.getDescription())
|
||||
.withChangeDescription(testSuite.getChangeDescription())
|
||||
.withDeleted(testSuite.getDeleted())
|
||||
.withDisplayName(testSuite.getDisplayName())
|
||||
.withFullyQualifiedName(testSuite.getFullyQualifiedName())
|
||||
.withHref(testSuite.getHref())
|
||||
.withId(testSuite.getId())
|
||||
.withName(testSuite.getName())
|
||||
.withExecutable(testSuite.getExecutable())
|
||||
.withExecutableEntityReference(testSuite.getExecutableEntityReference())
|
||||
.withServiceType(testSuite.getServiceType())
|
||||
.withOwner(testSuite.getOwner())
|
||||
.withUpdatedBy(testSuite.getUpdatedBy())
|
||||
.withUpdatedAt(testSuite.getUpdatedAt())
|
||||
.withVersion(testSuite.getVersion());
|
||||
}
|
||||
|
||||
public class TestSuiteUpdater extends EntityUpdater {
|
||||
public TestSuiteUpdater(TestSuite original, TestSuite updated, Operation operation) {
|
||||
super(original, updated, operation);
|
||||
@ -221,7 +294,13 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
|
||||
public void entitySpecificUpdate() {
|
||||
List<EntityReference> origTests = listOrEmpty(original.getTests());
|
||||
List<EntityReference> updatedTests = listOrEmpty(updated.getTests());
|
||||
recordChange("tests", origTests, updatedTests);
|
||||
List<ResultSummary> origTestCaseResultSummary =
|
||||
listOrEmpty(original.getTestCaseResultSummary());
|
||||
List<ResultSummary> updatedTestCaseResultSummary =
|
||||
listOrEmpty(updated.getTestCaseResultSummary());
|
||||
recordChange(UPDATE_FIELDS, origTests, updatedTests);
|
||||
recordChange(
|
||||
"testCaseResultSummary", origTestCaseResultSummary, updatedTestCaseResultSummary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.json.JsonPatch;
|
||||
@ -321,7 +322,8 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
|
||||
description = "get summary for a specific test suite",
|
||||
schema = @Schema(type = "String", format = "uuid"))
|
||||
@QueryParam("testSuiteId")
|
||||
UUID testSuiteId) {
|
||||
UUID testSuiteId)
|
||||
throws IOException {
|
||||
ResourceContext<?> resourceContext = getResourceContext();
|
||||
OperationContext operationContext =
|
||||
new OperationContext(Entity.TABLE, MetadataOperation.VIEW_TESTS);
|
||||
|
@ -8,6 +8,7 @@ import java.text.ParseException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import javax.json.JsonObject;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@ -87,6 +88,8 @@ public interface SearchClient {
|
||||
|
||||
Response aggregate(String index, String fieldName, String value, String query) throws IOException;
|
||||
|
||||
JsonObject aggregate(String query, String index, JsonObject aggregationJson) throws IOException;
|
||||
|
||||
Response suggest(SearchRequest request) throws IOException;
|
||||
|
||||
void createEntity(String indexName, String docId, String doc);
|
||||
|
@ -669,6 +669,11 @@ public class SearchRepository {
|
||||
return searchClient.aggregate(index, fieldName, value, query);
|
||||
}
|
||||
|
||||
public JsonObject aggregate(String query, String index, JsonObject aggregationJson)
|
||||
throws IOException {
|
||||
return searchClient.aggregate(query, index, aggregationJson);
|
||||
}
|
||||
|
||||
public Response suggest(SearchRequest request) throws IOException {
|
||||
return searchClient.suggest(request);
|
||||
}
|
||||
|
@ -105,6 +105,7 @@ import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
import javax.json.JsonObject;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.ws.rs.core.Response;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -704,6 +705,82 @@ public class ElasticSearchClient implements SearchClient {
|
||||
return Response.status(OK).entity(response).build();
|
||||
}
|
||||
|
||||
/*
|
||||
Build dynamic aggregation from elasticsearch JSON like aggregation query.
|
||||
See TestSuiteResourceTest for example usage (ln. 506) for tested aggregation query.
|
||||
|
||||
@param aggregations - JsonObject containing the aggregation query
|
||||
*/
|
||||
public static List<AggregationBuilder> buildAggregation(JsonObject aggregations) {
|
||||
List<AggregationBuilder> aggregationBuilders = new ArrayList<>();
|
||||
for (String key : aggregations.keySet()) {
|
||||
JsonObject aggregation = aggregations.getJsonObject(key);
|
||||
for (String aggregationType : aggregation.keySet()) {
|
||||
switch (aggregationType) {
|
||||
case "terms":
|
||||
JsonObject termAggregation = aggregation.getJsonObject(aggregationType);
|
||||
TermsAggregationBuilder termsAggregationBuilder =
|
||||
AggregationBuilders.terms(key).field(termAggregation.getString("field"));
|
||||
aggregationBuilders.add(termsAggregationBuilder);
|
||||
break;
|
||||
case "nested":
|
||||
JsonObject nestedAggregation = aggregation.getJsonObject("nested");
|
||||
AggregationBuilder nestedAggregationBuilder =
|
||||
AggregationBuilders.nested(
|
||||
nestedAggregation.getString("path"), nestedAggregation.getString("path"));
|
||||
JsonObject nestedAggregations = aggregation.getJsonObject("aggs");
|
||||
|
||||
List<AggregationBuilder> nestedAggregationBuilders =
|
||||
buildAggregation(nestedAggregations);
|
||||
for (AggregationBuilder nestedAggregationBuilder1 : nestedAggregationBuilders) {
|
||||
nestedAggregationBuilder.subAggregation(nestedAggregationBuilder1);
|
||||
}
|
||||
aggregationBuilders.add(nestedAggregationBuilder);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return aggregationBuilders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject aggregate(String query, String index, JsonObject aggregationJson)
|
||||
throws IOException {
|
||||
JsonObject aggregations = aggregationJson.getJsonObject("aggregations");
|
||||
if (aggregations == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<AggregationBuilder> aggregationBuilder = buildAggregation(aggregations);
|
||||
es.org.elasticsearch.action.search.SearchRequest searchRequest =
|
||||
new es.org.elasticsearch.action.search.SearchRequest(
|
||||
Entity.getSearchRepository().getIndexOrAliasName(index));
|
||||
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
|
||||
if (query != null) {
|
||||
XContentParser queryParser =
|
||||
XContentType.JSON
|
||||
.xContent()
|
||||
.createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, query);
|
||||
QueryBuilder parsedQuery = SearchSourceBuilder.fromXContent(queryParser).query();
|
||||
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(parsedQuery);
|
||||
searchSourceBuilder.query(boolQueryBuilder);
|
||||
}
|
||||
|
||||
searchSourceBuilder.size(0).timeout(new TimeValue(30, TimeUnit.SECONDS));
|
||||
|
||||
for (AggregationBuilder aggregation : aggregationBuilder) {
|
||||
searchSourceBuilder.aggregation(aggregation);
|
||||
}
|
||||
|
||||
searchRequest.source(searchSourceBuilder);
|
||||
|
||||
String response = client.search(searchRequest, RequestOptions.DEFAULT).toString();
|
||||
JsonObject jsonResponse = JsonUtils.readJson(response).asJsonObject();
|
||||
return jsonResponse.getJsonObject("aggregations");
|
||||
}
|
||||
|
||||
private static ScriptScoreFunctionBuilder boostScore() {
|
||||
return ScoreFunctionBuilders.scriptFunction(
|
||||
"double score = _score;"
|
||||
|
@ -37,6 +37,7 @@ import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
import javax.json.JsonObject;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.ws.rs.core.Response;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -724,6 +725,82 @@ public class OpenSearchClient implements SearchClient {
|
||||
return Response.status(OK).entity(response).build();
|
||||
}
|
||||
|
||||
/*
|
||||
Build dynamic aggregation from elasticsearch JSON like aggregation query.
|
||||
See TestSuiteResourceTest for example usage (ln. 506) for tested aggregation query.
|
||||
|
||||
@param aggregations - JsonObject containing the aggregation query
|
||||
*/
|
||||
public static List<AggregationBuilder> buildAggregation(JsonObject aggregations) {
|
||||
List<AggregationBuilder> aggregationBuilders = new ArrayList<>();
|
||||
for (String key : aggregations.keySet()) {
|
||||
JsonObject aggregation = aggregations.getJsonObject(key);
|
||||
for (String aggregationType : aggregation.keySet()) {
|
||||
switch (aggregationType) {
|
||||
case "terms":
|
||||
JsonObject termAggregation = aggregation.getJsonObject(aggregationType);
|
||||
TermsAggregationBuilder termsAggregationBuilder =
|
||||
AggregationBuilders.terms(key).field(termAggregation.getString("field"));
|
||||
aggregationBuilders.add(termsAggregationBuilder);
|
||||
break;
|
||||
case "nested":
|
||||
JsonObject nestedAggregation = aggregation.getJsonObject("nested");
|
||||
AggregationBuilder nestedAggregationBuilder =
|
||||
AggregationBuilders.nested(
|
||||
nestedAggregation.getString("path"), nestedAggregation.getString("path"));
|
||||
JsonObject nestedAggregations = aggregation.getJsonObject("aggs");
|
||||
|
||||
List<AggregationBuilder> nestedAggregationBuilders =
|
||||
buildAggregation(nestedAggregations);
|
||||
for (AggregationBuilder nestedAggregationBuilder1 : nestedAggregationBuilders) {
|
||||
nestedAggregationBuilder.subAggregation(nestedAggregationBuilder1);
|
||||
}
|
||||
aggregationBuilders.add(nestedAggregationBuilder);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return aggregationBuilders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject aggregate(String query, String index, JsonObject aggregationJson)
|
||||
throws IOException {
|
||||
JsonObject aggregations = aggregationJson.getJsonObject("aggregations");
|
||||
if (aggregations == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<AggregationBuilder> aggregationBuilder = buildAggregation(aggregations);
|
||||
os.org.opensearch.action.search.SearchRequest searchRequest =
|
||||
new os.org.opensearch.action.search.SearchRequest(
|
||||
Entity.getSearchRepository().getIndexOrAliasName(index));
|
||||
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
|
||||
if (query != null) {
|
||||
XContentParser queryParser =
|
||||
XContentType.JSON
|
||||
.xContent()
|
||||
.createParser(X_CONTENT_REGISTRY, LoggingDeprecationHandler.INSTANCE, query);
|
||||
QueryBuilder parsedQuery = SearchSourceBuilder.fromXContent(queryParser).query();
|
||||
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(parsedQuery);
|
||||
searchSourceBuilder.query(boolQueryBuilder);
|
||||
}
|
||||
|
||||
searchSourceBuilder.size(0).timeout(new TimeValue(30, TimeUnit.SECONDS));
|
||||
|
||||
for (AggregationBuilder aggregation : aggregationBuilder) {
|
||||
searchSourceBuilder.aggregation(aggregation);
|
||||
}
|
||||
|
||||
searchRequest.source(searchSourceBuilder);
|
||||
|
||||
String response = client.search(searchRequest, RequestOptions.DEFAULT).toString();
|
||||
JsonObject jsonResponse = JsonUtils.readJson(response).asJsonObject();
|
||||
return jsonResponse.getJsonObject("aggregations");
|
||||
}
|
||||
|
||||
public void updateSearch(UpdateRequest updateRequest) {
|
||||
if (updateRequest != null) {
|
||||
updateRequest.docAsUpsert(true);
|
||||
|
@ -84,6 +84,30 @@
|
||||
"entityType": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"testCaseResultSummary": {
|
||||
"type": "nested",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"testCaseName": {
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"type": "completion",
|
||||
"contexts": [
|
||||
|
@ -82,6 +82,27 @@
|
||||
"entityType": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"testCaseResultSummary": {
|
||||
"type": "nested",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"testCaseName": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer_jp",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"type": "completion",
|
||||
"contexts": [
|
||||
|
@ -67,6 +67,28 @@
|
||||
"entityType": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"testCaseResultSummary": {
|
||||
"type": "nested",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"testCaseName": {
|
||||
"type": "text",
|
||||
"analyzer": "ik_max_word",
|
||||
"search_analyzer": "ik_smart",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fqnParts": {
|
||||
"type": "keyword"
|
||||
},
|
||||
|
@ -381,17 +381,14 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
ADMIN_AUTH_HEADERS);
|
||||
verifyTestCaseResults(testCaseResults, testCase1ResultList, 4);
|
||||
|
||||
TestSummary testSummary = getTestSummary(ADMIN_AUTH_HEADERS, null);
|
||||
assertNotEquals(0, testSummary.getFailed());
|
||||
assertNotEquals(0, testSummary.getSuccess());
|
||||
assertNotEquals(0, testSummary.getTotal());
|
||||
assertEquals(0, testSummary.getAborted());
|
||||
|
||||
String randomUUID = UUID.randomUUID().toString();
|
||||
assertResponseContains(
|
||||
() -> getTestSummary(ADMIN_AUTH_HEADERS, randomUUID),
|
||||
NOT_FOUND,
|
||||
"testSuite instance for " + randomUUID + " not found");
|
||||
TestSummary testSummary;
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
testSummary = getTestSummary(ADMIN_AUTH_HEADERS, null);
|
||||
assertNotEquals(0, testSummary.getFailed());
|
||||
assertNotEquals(0, testSummary.getSuccess());
|
||||
assertNotEquals(0, testSummary.getTotal());
|
||||
assertEquals(0, testSummary.getAborted());
|
||||
}
|
||||
|
||||
// Test that we can get the test summary for a logical test suite and that
|
||||
// adding a logical test suite does not change the total number of tests
|
||||
@ -403,25 +400,30 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
testCaseIds.add(testCase1.getId());
|
||||
testSuiteResourceTest.addTestCasesToLogicalTestSuite(logicalTestSuite, testCaseIds);
|
||||
|
||||
testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(1, testSummary.getTotal());
|
||||
assertEquals(1, testSummary.getFailed());
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(1, testSummary.getTotal());
|
||||
assertEquals(1, testSummary.getFailed());
|
||||
}
|
||||
|
||||
// add a new test case to the logical test suite to validate if the
|
||||
// summary is updated correctly
|
||||
testCaseIds.clear();
|
||||
testCaseIds.add(testCase.getId());
|
||||
testSuiteResourceTest.addTestCasesToLogicalTestSuite(logicalTestSuite, testCaseIds);
|
||||
|
||||
testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(2, testSummary.getTotal());
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(2, testSummary.getTotal());
|
||||
}
|
||||
|
||||
// remove test case from logical test suite and validate
|
||||
// the summary is updated as expected
|
||||
deleteLogicalTestCase(logicalTestSuite, testCase.getId());
|
||||
|
||||
testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(1, testSummary.getTotal());
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(1, testSummary.getTotal());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -458,37 +460,52 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
testCaseIds.add(testCase1.getId());
|
||||
testSuiteResourceTest.addTestCasesToLogicalTestSuite(logicalTestSuite, testCaseIds);
|
||||
|
||||
// test we get the right summary for the executable test suite
|
||||
TestSummary executableTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, testCase.getTestSuite().getId().toString());
|
||||
TestSuite testSuite =
|
||||
testSuiteResourceTest.getEntity(testCase.getTestSuite().getId(), "*", ADMIN_AUTH_HEADERS);
|
||||
assertEquals(testSuite.getTests().size(), executableTestSummary.getTotal());
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
// test we get the right summary for the executable test suite
|
||||
TestSummary executableTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, testCase.getTestSuite().getId().toString());
|
||||
assertEquals(testSuite.getTests().size(), executableTestSummary.getTotal());
|
||||
}
|
||||
|
||||
// test we get the right summary for the logical test suite
|
||||
TestSummary logicalTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(1, logicalTestSummary.getTotal());
|
||||
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
TestSummary logicalTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(1, logicalTestSummary.getTotal());
|
||||
}
|
||||
testCaseIds.clear();
|
||||
testCaseIds.add(testCase.getId());
|
||||
testSuiteResourceTest.addTestCasesToLogicalTestSuite(logicalTestSuite, testCaseIds);
|
||||
logicalTestSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(2, logicalTestSummary.getTotal());
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
TestSummary logicalTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(2, logicalTestSummary.getTotal());
|
||||
}
|
||||
deleteEntity(testCase1.getId(), ADMIN_AUTH_HEADERS);
|
||||
executableTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, testCase.getTestSuite().getId().toString());
|
||||
testSuite =
|
||||
testSuiteResourceTest.getEntity(testCase.getTestSuite().getId(), "*", ADMIN_AUTH_HEADERS);
|
||||
assertEquals(testSuite.getTests().size(), executableTestSummary.getTotal());
|
||||
|
||||
logicalTestSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(2, logicalTestSummary.getTotal());
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
TestSummary executableTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, testCase.getTestSuite().getId().toString());
|
||||
assertEquals(testSuite.getTests().size(), executableTestSummary.getTotal());
|
||||
TestSummary logicalTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
assertEquals(2, logicalTestSummary.getTotal());
|
||||
}
|
||||
// check the deletion of the test case from the executable test suite
|
||||
// cascaded to the logical test suite
|
||||
deleteLogicalTestCase(logicalTestSuite, testCase.getId());
|
||||
logicalTestSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
// check the deletion of the test case from the logical test suite is reflected in the summary
|
||||
assertEquals(1, logicalTestSummary.getTotal());
|
||||
|
||||
if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) {
|
||||
TestSummary logicalTestSummary =
|
||||
getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString());
|
||||
// check the deletion of the test case from the logical test suite is reflected in the summary
|
||||
assertEquals(1, logicalTestSummary.getTotal());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -2,6 +2,7 @@ package org.openmetadata.service.resources.dqtests;
|
||||
|
||||
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
|
||||
@ -11,6 +12,8 @@ import static org.openmetadata.service.util.TestUtils.assertListNull;
|
||||
import static org.openmetadata.service.util.TestUtils.assertResponse;
|
||||
import static org.openmetadata.service.util.TestUtils.assertResponseContains;
|
||||
|
||||
import es.org.elasticsearch.search.aggregations.AggregationBuilder;
|
||||
import es.org.elasticsearch.search.aggregations.AggregationBuilders;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
@ -19,6 +22,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.json.JsonObject;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.apache.http.client.HttpResponseException;
|
||||
@ -41,6 +45,7 @@ import org.openmetadata.schema.type.Include;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.resources.EntityResourceTest;
|
||||
import org.openmetadata.service.resources.databases.TableResourceTest;
|
||||
import org.openmetadata.service.search.elasticsearch.ElasticSearchClient;
|
||||
import org.openmetadata.service.util.JsonUtils;
|
||||
import org.openmetadata.service.util.ResultList;
|
||||
import org.openmetadata.service.util.TestUtils;
|
||||
@ -58,6 +63,7 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
TestSuiteResource.TestSuiteList.class,
|
||||
"dataQuality/testSuites",
|
||||
TestSuiteResource.FIELDS);
|
||||
supportsSearchIndex = true;
|
||||
}
|
||||
|
||||
public void setupTestSuites(TestInfo test) throws IOException {
|
||||
@ -496,6 +502,121 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
TestUtils.getEntityNameLengthError(entityClass));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildElasticsearchAggregationFromJson(TestInfo test) {
|
||||
JsonObject aggregationJson;
|
||||
List<AggregationBuilder> actual;
|
||||
List<AggregationBuilder> expected = new ArrayList<>();
|
||||
String aggregationQuery;
|
||||
|
||||
// Test aggregation with nested aggregation
|
||||
aggregationQuery =
|
||||
"""
|
||||
{
|
||||
"aggregations": {
|
||||
"test_case_results": {
|
||||
"nested": {
|
||||
"path": "testCaseResultSummary"
|
||||
},
|
||||
"aggs": {
|
||||
"status_counts": {
|
||||
"terms": {
|
||||
"field": "testCaseResultSummary.status"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
expected.add(
|
||||
AggregationBuilders.nested("testCaseResultSummary", "testCaseResultSummary")
|
||||
.subAggregation(
|
||||
AggregationBuilders.terms("status_counts").field("testCaseResultSummary.status")));
|
||||
|
||||
aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject();
|
||||
actual = ElasticSearchClient.buildAggregation(aggregationJson.getJsonObject("aggregations"));
|
||||
assertThat(actual).hasSameElementsAs(expected);
|
||||
|
||||
// Test aggregation with multiple aggregations
|
||||
aggregationQuery =
|
||||
"""
|
||||
{
|
||||
"aggregations": {
|
||||
"my-first-agg-name": {
|
||||
"terms": {
|
||||
"field": "my-field"
|
||||
}
|
||||
},
|
||||
"my-second-agg-name": {
|
||||
"terms": {
|
||||
"field": "my-other-field"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject();
|
||||
|
||||
expected.clear();
|
||||
expected.addAll(
|
||||
List.of(
|
||||
AggregationBuilders.terms("my-second-agg-name").field("my-other-field"),
|
||||
AggregationBuilders.terms("my-first-agg-name").field("my-field")));
|
||||
|
||||
actual = ElasticSearchClient.buildAggregation(aggregationJson.getJsonObject("aggregations"));
|
||||
assertThat(actual).hasSameElementsAs(expected);
|
||||
|
||||
// Test aggregation with multiple aggregations including a nested one which has itself multiple
|
||||
// aggregations
|
||||
aggregationQuery =
|
||||
"""
|
||||
{
|
||||
"aggregations": {
|
||||
"my-first-agg-name": {
|
||||
"terms": {
|
||||
"field": "my-field"
|
||||
}
|
||||
},
|
||||
"test_case_results": {
|
||||
"nested": {
|
||||
"path": "testCaseResultSummary"
|
||||
},
|
||||
"aggs": {
|
||||
"status_counts": {
|
||||
"terms": {
|
||||
"field": "testCaseResultSummary.status"
|
||||
}
|
||||
},
|
||||
"other_status_counts": {
|
||||
"terms": {
|
||||
"field": "testCaseResultSummary.status"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject();
|
||||
|
||||
expected.clear();
|
||||
expected.addAll(
|
||||
List.of(
|
||||
AggregationBuilders.nested("testCaseResultSummary", "testCaseResultSummary")
|
||||
.subAggregation(
|
||||
AggregationBuilders.terms("status_counts")
|
||||
.field("testCaseResultSummary.status"))
|
||||
.subAggregation(
|
||||
AggregationBuilders.terms("other_status_counts")
|
||||
.field("testCaseResultSummary.status")),
|
||||
AggregationBuilders.terms("my-first-agg-name").field("my-field")));
|
||||
|
||||
actual = ElasticSearchClient.buildAggregation(aggregationJson.getJsonObject("aggregations"));
|
||||
assertThat(actual).hasSameElementsAs(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_LogicalTestSuite_200(TestInfo test) throws IOException {
|
||||
TestCaseResourceTest testCaseResourceTest = new TestCaseResourceTest();
|
||||
|
Loading…
x
Reference in New Issue
Block a user