Fix #10354 - Implement Test Suite filter from Search (#15814)

This commit is contained in:
Teddy 2024-04-08 18:40:39 +02:00 committed by GitHub
parent c671e64f69
commit dc4c4e41b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 643 additions and 156 deletions

View File

@ -5,7 +5,7 @@
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -DjdbcContainerClassName=org.testcontainers.containers.MySQLContainer -DjdbcContainerImage=mysql:8 -DelasticSearchContainerClassName=docker.elastic.co/elasticsearch/elasticsearch:7.16.3 -DopenSearchContainerClassName=opensearchproject/opensearch:1.3.0 -DrunESTestCases=false"/>
<option name="VM_PARAMETERS" value="-ea -DjdbcContainerClassName=org.testcontainers.containers.MySQLContainer -DjdbcContainerImage=mysql:8 -DelasticSearchContainerClassName=docker.elastic.co/elasticsearch/elasticsearch:8.10.2 -DopenSearchContainerClassName=opensearchproject/opensearch:1.3.0 -DrunESTestCases=false"/>
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@ -149,6 +149,7 @@ import org.openmetadata.service.resources.tags.TagLabelUtil;
import org.openmetadata.service.search.SearchClient;
import org.openmetadata.service.search.SearchListFilter;
import org.openmetadata.service.search.SearchRepository;
import org.openmetadata.service.search.SearchSortFilter;
import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.EntityUtil.Fields;
import org.openmetadata.service.util.FullyQualifiedName;
@ -986,8 +987,7 @@ public abstract class EntityRepository<T extends EntityInterface> {
int offset,
String q)
throws IOException {
return listFromSearchWithOffset(
uriInfo, fields, searchListFilter, limit, offset, null, null, q);
return listFromSearchWithOffset(uriInfo, fields, searchListFilter, limit, offset, null, q);
}
public ResultList<T> listFromSearchWithOffset(
@ -996,8 +996,7 @@ public abstract class EntityRepository<T extends EntityInterface> {
SearchListFilter searchListFilter,
int limit,
int offset,
String sortField,
String sortType,
SearchSortFilter searchSortFilter,
String q)
throws IOException {
List<T> entityList = new ArrayList<>();
@ -1006,7 +1005,7 @@ public abstract class EntityRepository<T extends EntityInterface> {
if (limit > 0) {
SearchClient.SearchResultListMapper results =
searchRepository.listWithOffset(
searchListFilter, limit, offset, entityType, sortField, sortType, q);
searchListFilter, limit, offset, entityType, searchSortFilter, q);
total = results.getTotal();
for (Map<String, Object> json : results.getResults()) {
T entity = setFieldsInternal(JsonUtils.readOrConvertValue(json, entityClass), fields);

View File

@ -65,7 +65,8 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
private static final String TEST_CASE_RESULT_FIELD = "testCaseResult";
private static final String INCIDENTS_FIELD = "incidentId";
public static final String COLLECTION_PATH = "/v1/dataQuality/testCases";
private static final String UPDATE_FIELDS = "owner,entityLink,testSuite,testDefinition";
private static final String UPDATE_FIELDS =
"owner,entityLink,testSuite,testSuites,testDefinition";
private static final String PATCH_FIELDS =
"owner,entityLink,testSuite,testDefinition,computePassedFailedRowCount";
public static final String TESTCASE_RESULT_EXTENSION = "testCase.testCaseResult";
@ -254,10 +255,30 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
@Override
protected void postDelete(TestCase test) {
// delete test case from test suite summary when test case is deleted
// from an executable test suite
List<TestSuite> testSuites = test.getTestSuites();
if (!testSuites.isEmpty()) {
for (TestSuite testSuite : testSuites) {
removeTestCaseFromTestSuiteResultSummary(testSuite.getId(), test.getFullyQualifiedName());
}
}
// If we delete the test case, we need to clean up the resolution ts
daoCollection.testCaseResolutionStatusTimeSeriesDao().delete(test.getFullyQualifiedName());
}
@Override
protected void postCreate(TestCase test) {
super.postCreate(test);
// Update test suite with new test case in search index
TestSuiteRepository testSuiteRepository =
(TestSuiteRepository) Entity.getEntityRepository(Entity.TEST_SUITE);
TestSuite testSuite = Entity.getEntity(test.getTestSuite(), "*", ALL);
TestSuite original = testSuiteRepository.copyTestSuite(testSuite);
testSuiteRepository.postUpdate(original, testSuite);
}
public RestUtil.PutResponse<TestCaseResult> addTestCaseResult(
String updatedBy, UriInfo uriInfo, String fqn, TestCaseResult testCaseResult) {
// Validate the request content
@ -413,15 +434,7 @@ 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);
EntityRepository<TestSuite>.EntityUpdater testSuiteUpdater =
testSuiteRepository.getUpdater(original, testSuite, Operation.PUT);
testSuiteUpdater.update();
putUpdateTestSuite(testSuite, resultSummaries);
}
}
@ -601,7 +614,6 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
TestSuite testSuite, List<UUID> testCaseIds) {
bulkAddToRelationship(
testSuite.getId(), testCaseIds, TEST_SUITE, TEST_CASE, Relationship.CONTAINS);
List<EntityReference> testCasesEntityReferences = new ArrayList<>();
List<ResultSummary> resultSummaries = listOrEmpty(testSuite.getTestCaseResultSummary());
for (UUID testCaseId : testCaseIds) {
TestCase testCase = Entity.getEntity(Entity.TEST_CASE, testCaseId, "*", Include.ALL);
@ -620,26 +632,11 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
summary -> summary.getTestCaseName().equals(resultSummary.getTestCaseName()));
resultSummaries.add(resultSummary);
}
testCasesEntityReferences.add(
new EntityReference()
.withId(testCase.getId())
.withName(testCase.getName())
.withFullyQualifiedName(testCase.getFullyQualifiedName())
.withDescription(testCase.getDescription())
.withDisplayName(testCase.getDisplayName())
.withHref(testCase.getHref())
.withDeleted(testCase.getDeleted()));
}
// set test case result summary for logical test suite
// and update it in the database
testSuite.setTestCaseResultSummary(resultSummaries);
testSuite.setSummary(null); // we don't want to store the summary in the database
daoCollection
.testSuiteDAO()
.update(
testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite));
testSuite.setTests(testCasesEntityReferences);
// Fetch the updated test suite to get the latest tests list associated with it and
// set test case result summary for logical test suite and update it in the database
testSuite = Entity.getEntity(TEST_SUITE, testSuite.getId(), "*", Include.ALL, false);
putUpdateTestSuite(testSuite, resultSummaries);
return new RestUtil.PutResponse<>(Response.Status.OK, testSuite, LOGICAL_TEST_CASE_ADDED);
}
@ -661,18 +658,13 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
private void removeTestCaseFromTestSuiteResultSummary(UUID testSuiteId, String testCaseFqn) {
TestSuite testSuite = Entity.getEntity(TEST_SUITE, testSuiteId, "*", Include.ALL, false);
testSuite.setSummary(null); // we don't want to store the summary in the database
// Update the test suite summary
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);
EntityRepository<TestSuite>.EntityUpdater testSuiteUpdater =
testSuiteRepository.getUpdater(original, testSuite, Operation.PUT);
testSuiteUpdater.update();
// Update test case result summary attribute for the test suite
putUpdateTestSuite(testSuite, resultSummaries);
}
@Override
@ -680,18 +672,6 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
return new TestUpdater(original, updated, operation);
}
@Override
protected void preDelete(TestCase entity, String deletedBy) {
// delete test case from test suite summary when test case is deleted
// from an executable test suite
List<TestSuite> testSuites = getTestSuites(entity);
if (!testSuites.isEmpty()) {
for (TestSuite testSuite : testSuites) {
removeTestCaseFromTestSuiteResultSummary(testSuite.getId(), entity.getFullyQualifiedName());
}
}
}
@Override
public ResultList<TestCase> listAfter(
UriInfo uriInfo, Fields fields, ListFilter filter, int limitParam, String after) {
@ -927,4 +907,19 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
recordChange("testCaseResult", original.getTestCaseResult(), updated.getTestCaseResult());
}
}
private void putUpdateTestSuite(TestSuite testSuite, List<ResultSummary> resultSummaries) {
// Update test case result summary attribute for the test suite
TestSuiteRepository testSuiteRepository =
(TestSuiteRepository) Entity.getEntityRepository(Entity.TEST_SUITE);
testSuite.setSummary(null); // we don't want to store the summary in the database
TestSuite original =
TestSuiteRepository.copyTestSuite(
testSuite); // we'll need the original state to update the test suite
testSuite.setTestCaseResultSummary(
resultSummaries != null ? resultSummaries : testSuite.getTestCaseResultSummary());
EntityRepository<TestSuite>.EntityUpdater testSuiteUpdater =
testSuiteRepository.getUpdater(original, testSuite, Operation.PUT);
testSuiteUpdater.update();
}
}

View File

@ -10,9 +10,7 @@ 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;
@ -23,7 +21,6 @@ import org.jdbi.v3.sqlobject.transaction.Transaction;
import org.openmetadata.schema.entity.data.Table;
import org.openmetadata.schema.tests.ResultSummary;
import org.openmetadata.schema.tests.TestSuite;
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;
@ -57,7 +54,7 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
entity.setPipelines(
fields.contains("pipelines") ? getIngestionPipelines(entity) : entity.getPipelines());
entity.setSummary(
fields.contains("summary") ? getTestCasesExecutionSummary(entity) : entity.getSummary());
fields.contains("summary") ? getTestSummary(entity.getId()) : entity.getSummary());
entity.withTests(fields.contains(UPDATE_FIELDS) ? getTestCases(entity) : entity.getTests());
}
@ -77,15 +74,6 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
entity.withTests(fields.contains(UPDATE_FIELDS) ? entity.getTests() : null);
}
private TestSummary buildTestSummary(Map<String, Integer> testCaseSummary) {
return new TestSummary()
.withAborted(testCaseSummary.getOrDefault(TestCaseStatus.Aborted.toString(), 0))
.withFailed(testCaseSummary.getOrDefault(TestCaseStatus.Failed.toString(), 0))
.withSuccess(testCaseSummary.getOrDefault(TestCaseStatus.Success.toString(), 0))
.withQueued(testCaseSummary.getOrDefault(TestCaseStatus.Queued.toString(), 0))
.withTotal(testCaseSummary.values().stream().mapToInt(Integer::valueOf).sum());
}
@Override
public void setFullyQualifiedName(TestSuite testSuite) {
if (testSuite.getExecutableEntityReference() != null) {
@ -97,29 +85,6 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
}
}
private Map<String, Integer> getResultSummary(TestSuite testSuite) {
Map<String, Integer> testCaseSummary = new HashMap<>();
List<EntityReference> testCases = getTestCases(testSuite);
for (ResultSummary resultSummary : testSuite.getTestCaseResultSummary()) {
String status = resultSummary.getStatus().toString();
testCaseSummary.put(status, testCaseSummary.getOrDefault(status, 0) + 1);
}
List<EntityReference> testCasesWithNoResults =
testCases.stream()
.filter(
tc ->
testSuite.getTestCaseResultSummary().stream()
.noneMatch(tcr -> tc.getFullyQualifiedName().equals(tcr.getTestCaseName())))
.toList();
testCaseSummary.put(TestCaseStatus.Queued.toString(), testCasesWithNoResults.size());
return testCaseSummary;
}
private TestSummary getTestCasesExecutionSummary(TestSuite entity) {
Map<String, Integer> testCaseSummary = getResultSummary(entity);
return buildTestSummary(testCaseSummary);
}
private TestSummary getTestCasesExecutionSummary(JsonObject aggregation) {
// Initialize the test summary with 0 values
TestSummary testSummary =
@ -151,7 +116,7 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
return testSummary;
}
public TestSummary getTestSummary(UUID testSuiteId) throws IOException {
public TestSummary getTestSummary(UUID testSuiteId) {
String aggregationQuery =
"""
{
@ -172,31 +137,36 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
}
""";
JsonObject aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject();
TestSummary testSummary;
if (testSuiteId == null) {
JsonObject testCaseResultSummary =
searchRepository.aggregate(null, TEST_SUITE, aggregationJson);
testSummary = getTestCasesExecutionSummary(testCaseResultSummary);
} else {
String query =
"""
{
"query": {
"bool": {
"must": {
"term": {"id": "%s"}
try {
TestSummary testSummary;
if (testSuiteId == null) {
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
JsonObject testCaseResultSummary =
searchRepository.aggregate(query, TEST_SUITE, aggregationJson);
testSummary = getTestCasesExecutionSummary(testCaseResultSummary);
}
"""
.formatted(testSuiteId);
// don't want to get it from the cache as test results summary may be stale
JsonObject testCaseResultSummary =
searchRepository.aggregate(query, TEST_SUITE, aggregationJson);
testSummary = getTestCasesExecutionSummary(testCaseResultSummary);
}
return testSummary;
} catch (IOException e) {
LOG.error("Error reading aggregation query", e);
}
return testSummary;
return null;
}
@Override
@ -216,7 +186,11 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
@Override
public void storeEntity(TestSuite entity, boolean update) {
// we don't want to store the tests in the test suite entity
List<EntityReference> tests = entity.getTests();
entity.setTests(null);
store(entity, update);
entity.setTests(tests);
}
@Override

View File

@ -32,6 +32,7 @@ import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.jdbi3.EntityRepository;
import org.openmetadata.service.jdbi3.ListFilter;
import org.openmetadata.service.search.SearchListFilter;
import org.openmetadata.service.search.SearchSortFilter;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.CreateResourceContext;
import org.openmetadata.service.security.policyevaluator.OperationContext;
@ -152,15 +153,14 @@ public abstract class EntityResource<T extends EntityInterface, K extends Entity
SearchListFilter searchListFilter,
int limit,
int offset,
String sortField,
String sortType,
SearchSortFilter searchSortFilter,
String q,
OperationContext operationContext,
ResourceContextInterface resourceContext)
throws IOException {
authorizer.authorize(securityContext, operationContext, resourceContext);
return repository.listFromSearchWithOffset(
uriInfo, fields, searchListFilter, limit, offset, sortField, sortType, q);
uriInfo, fields, searchListFilter, limit, offset, searchSortFilter, q);
}
public T getInternal(

View File

@ -53,6 +53,7 @@ import org.openmetadata.service.resources.Collection;
import org.openmetadata.service.resources.EntityResource;
import org.openmetadata.service.resources.feeds.MessageParser.EntityLink;
import org.openmetadata.service.search.SearchListFilter;
import org.openmetadata.service.search.SearchSortFilter;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.mask.PIIMasker;
import org.openmetadata.service.security.policyevaluator.OperationContext;
@ -328,6 +329,18 @@ public class TestCaseResource extends EntityResource<TestCase, TestCaseRepositor
schema = @Schema(type = "string"))
@QueryParam("sortField")
String sortField,
@Parameter(
description =
"Set this field if your mapping is nested and you want to sort on a nested field",
schema = @Schema(type = "string"))
@QueryParam("sortNestedPath")
String sortNestedPath,
@Parameter(
description =
"Set this field if your mapping is nested and you want to sort on a nested field",
schema = @Schema(type = "string", example = "min,max,avg,sum,median"))
@QueryParam("sortNestedMode")
String sortNestedMode,
@Parameter(
description = "Sort type",
schema =
@ -350,7 +363,8 @@ public class TestCaseResource extends EntityResource<TestCase, TestCaseRepositor
|| (startTimestamp != null && endTimestamp == null)) {
throw new IllegalArgumentException("startTimestamp and endTimestamp must be used together");
}
SearchSortFilter searchSortFilter =
new SearchSortFilter(sortField, sortType, sortNestedPath, sortNestedMode);
SearchListFilter searchListFilter = new SearchListFilter(include);
searchListFilter.addQueryParam("testSuiteId", testSuiteId);
searchListFilter.addQueryParam("includeAllTests", includeAllTests.toString());
@ -385,8 +399,7 @@ public class TestCaseResource extends EntityResource<TestCase, TestCaseRepositor
searchListFilter,
limit,
offset,
sortField,
sortType,
searchSortFilter,
q,
operationContext,
resourceContextInterface);
@ -717,10 +730,7 @@ public class TestCaseResource extends EntityResource<TestCase, TestCaseRepositor
OperationContext operationContext =
new OperationContext(Entity.TABLE, MetadataOperation.EDIT_TESTS);
authorizer.authorize(securityContext, operationContext, resourceContext);
DeleteResponse<TestCase> response =
repository.delete(securityContext.getUserPrincipal().getName(), id, false, hardDelete);
addHref(uriInfo, response.entity());
return response.toResponse();
return delete(uriInfo, securityContext, id, false, hardDelete);
}
@DELETE

View File

@ -1,5 +1,6 @@
package org.openmetadata.service.resources.dqtests;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.schema.type.Include.ALL;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
@ -35,6 +36,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.api.data.RestoreEntity;
import org.openmetadata.schema.api.tests.CreateTestSuite;
import org.openmetadata.schema.entity.data.Table;
@ -49,6 +51,8 @@ import org.openmetadata.service.jdbi3.ListFilter;
import org.openmetadata.service.jdbi3.TestSuiteRepository;
import org.openmetadata.service.resources.Collection;
import org.openmetadata.service.resources.EntityResource;
import org.openmetadata.service.search.SearchListFilter;
import org.openmetadata.service.search.SearchSortFilter;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.OperationContext;
import org.openmetadata.service.security.policyevaluator.ResourceContext;
@ -169,6 +173,141 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
resourceContext);
}
@GET
@Path("/search/list")
@Operation(
operationId = "listTestSuiteFromSearchService",
summary = "List test suite using search service",
description =
"Get a list of test suite using the search service. Use `fields` "
+ "parameter to get only necessary fields. Use offset/limit pagination to limit the number "
+ "entries in the list using `limit` and `offset` query params."
+ "Use the `tests` field to get the test cases linked to the test suite "
+ "and/or use the `summary` field to get a summary of test case executions.",
responses = {
@ApiResponse(
responseCode = "200",
description = "List of test suites",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = TestSuiteList.class)))
})
public ResultList<TestSuite> listFromSearch(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(
description = "Fields requested in the returned resource",
schema = @Schema(type = "string", example = FIELDS))
@QueryParam("fields")
String fieldsParam,
@Parameter(description = "Limit the number test suite returned. (1 to 1000000, default = 10)")
@DefaultValue("10")
@QueryParam("limit")
@Min(0)
@Max(1000000)
int limit,
@Parameter(
description = "Returns list of test suite after this offset (default = 0)",
schema = @Schema(type = "string"))
@QueryParam("offset")
@DefaultValue("0")
@Min(0)
int offset,
@Parameter(
description =
"Returns executable or logical test suites. If omitted, returns all test suites.",
schema = @Schema(type = "string", example = "executable"))
@QueryParam("testSuiteType")
String testSuiteType,
@Parameter(
description = "Include empty test suite in the response.",
schema = @Schema(type = "boolean", example = "true"))
@QueryParam("includeEmptyTestSuites")
@DefaultValue("true")
Boolean includeEmptyTestSuites,
@Parameter(
description = "Filter a test suite by fully qualified name.",
schema = @Schema(type = "string"))
@QueryParam("fullyQualifiedName")
String fullyQualifiedName,
@Parameter(description = "Filter test suites by owner.", schema = @Schema(type = "string"))
@QueryParam("owner")
String owner,
@Parameter(
description = "Include all, deleted, or non-deleted entities.",
schema = @Schema(implementation = Include.class))
@QueryParam("include")
@DefaultValue("non-deleted")
Include include,
@Parameter(
description = "Field used to sort the test cases listing",
schema = @Schema(type = "string"))
@QueryParam("sortField")
String sortField,
@Parameter(
description =
"Set this field if your mapping is nested and you want to sort on a nested field",
schema = @Schema(type = "string"))
@QueryParam("sortNestedPath")
String sortNestedPath,
@Parameter(
description =
"Set this field if your mapping is nested and you want to sort on a nested field",
schema = @Schema(type = "string", example = "min,max,avg,sum,median"))
@QueryParam("sortNestedMode")
String sortNestedMode,
@Parameter(
description = "Sort type",
schema =
@Schema(
type = "string",
allowableValues = {"asc", "desc"}))
@QueryParam("sortType")
@DefaultValue("desc")
String sortType,
@Parameter(
description = "search query term to use in list",
schema = @Schema(type = "string"))
@QueryParam("q")
String q)
throws IOException {
SearchSortFilter searchSortFilter =
new SearchSortFilter(sortField, sortType, sortNestedPath, sortNestedMode);
SearchListFilter searchListFilter = new SearchListFilter(include);
searchListFilter.addQueryParam("testSuiteType", testSuiteType);
searchListFilter.addQueryParam("includeEmptyTestSuites", includeEmptyTestSuites);
searchListFilter.addQueryParam("fullyQualifiedName", fullyQualifiedName);
if (!nullOrEmpty(owner)) {
EntityInterface entity;
try {
entity = Entity.getEntityByName(Entity.USER, owner, "", ALL);
} catch (Exception e) {
// If the owner is not a user, then we'll try to geta team
entity = Entity.getEntityByName(Entity.TEAM, owner, "", ALL);
}
searchListFilter.addQueryParam("owner", entity.getId().toString());
}
EntityUtil.Fields fields = getFields(fieldsParam);
ResourceContext<?> resourceContext = getResourceContext();
OperationContext operationContext =
new OperationContext(Entity.TABLE, MetadataOperation.VIEW_TESTS);
return super.listInternalFromSearch(
uriInfo,
securityContext,
fields,
searchListFilter,
limit,
offset,
searchSortFilter,
q,
operationContext,
resourceContext);
}
@GET
@Path("/{id}/versions")
@Operation(
@ -322,8 +461,7 @@ 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)
throws IOException {
UUID testSuiteId) {
ResourceContext<?> resourceContext = getResourceContext();
OperationContext operationContext =
new OperationContext(Entity.TABLE, MetadataOperation.VIEW_TESTS);

View File

@ -79,8 +79,7 @@ public interface SearchClient {
int limit,
int offset,
String index,
String sortField,
String sortType,
SearchSortFilter searchSortFilter,
String q)
throws IOException;

View File

@ -29,6 +29,7 @@ public class SearchListFilter extends Filter<SearchListFilter> {
if (entityType != null) {
conditions.add(entityType.equals(Entity.TEST_CASE) ? getTestCaseCondition() : null);
conditions.add(entityType.equals(Entity.TEST_SUITE) ? getTestSuiteCondition() : null);
}
String conditionFilter = addCondition(conditions);
String sourceFilter = getExcludeIncludeFields();
@ -39,8 +40,8 @@ public class SearchListFilter extends Filter<SearchListFilter> {
protected String addCondition(List<String> conditions) {
StringBuffer condition = new StringBuffer();
for (String c : conditions) {
if (!c.isEmpty()) {
if (!condition.isEmpty()) {
if (!nullOrEmpty(c)) {
if (!nullOrEmpty(condition)) {
// Add `,` between conditions
condition.append(",\n");
}
@ -170,6 +171,40 @@ public class SearchListFilter extends Filter<SearchListFilter> {
return addCondition(conditions);
}
private String getTestSuiteCondition() {
ArrayList<String> conditions = new ArrayList<>();
String testSuiteType = getQueryParam("testSuiteType");
String fullyQualifiedName = getQueryParam("fullyQualifiedName");
String owner = getQueryParam("owner");
Boolean includeEmptyTestSuites = Boolean.parseBoolean(getQueryParam("includeEmptyTestSuites"));
if (testSuiteType != null) {
Boolean executable = true;
if (testSuiteType.equals("logical")) {
executable = false;
}
conditions.add(String.format("{\"term\": {\"executable\": \"%s\"}}", executable));
}
if (!includeEmptyTestSuites) {
conditions.add("{\"exists\": {\"field\": \"tests\"}}");
}
if (fullyQualifiedName != null) {
conditions.add(
String.format(
"{\"term\": {\"fullyQualifiedName\": \"%s\"}}",
escapeDoubleQuotes(fullyQualifiedName)));
}
if (owner != null) {
conditions.add(String.format("{\"term\": {\"owner.id\": \"%s\"}}", owner));
}
return addCondition(conditions);
}
private String escapeDoubleQuotes(String str) {
return str.replace("\"", "\\\"");
}

View File

@ -661,8 +661,7 @@ public class SearchRepository {
int limit,
int offset,
String entityType,
String sortField,
String sortType,
SearchSortFilter searchSortFilter,
String q)
throws IOException {
IndexMapping index = entityIndexMap.get(entityType);
@ -671,8 +670,7 @@ public class SearchRepository {
limit,
offset,
index.getIndexName(clusterAlias),
sortField,
sortType,
searchSortFilter,
q);
}

View File

@ -0,0 +1,27 @@
package org.openmetadata.service.search;
import lombok.Getter;
@Getter
public class SearchSortFilter {
String sortField;
String sortType;
String sortNestedPath;
String sortNestedMode;
public SearchSortFilter(
String sortField, String sortType, String sortNestedPath, String sortNestedMode) {
this.sortField = sortField;
this.sortType = sortType;
this.sortNestedPath = sortNestedPath;
this.sortNestedMode = sortNestedMode;
}
public Boolean isSorted() {
return sortField != null && sortType != null;
}
public Boolean isNested() {
return sortNestedPath != null && sortNestedMode != null;
}
}

View File

@ -85,6 +85,10 @@ import es.org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
import es.org.elasticsearch.search.builder.SearchSourceBuilder;
import es.org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import es.org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import es.org.elasticsearch.search.sort.FieldSortBuilder;
import es.org.elasticsearch.search.sort.NestedSortBuilder;
import es.org.elasticsearch.search.sort.SortBuilders;
import es.org.elasticsearch.search.sort.SortMode;
import es.org.elasticsearch.search.sort.SortOrder;
import es.org.elasticsearch.search.suggest.Suggest;
import es.org.elasticsearch.search.suggest.SuggestBuilder;
@ -129,6 +133,7 @@ import org.openmetadata.service.dataInsight.DataInsightAggregatorInterface;
import org.openmetadata.service.jdbi3.DataInsightChartRepository;
import org.openmetadata.service.search.SearchClient;
import org.openmetadata.service.search.SearchRequest;
import org.openmetadata.service.search.SearchSortFilter;
import org.openmetadata.service.search.UpdateSearchEventsConstant;
import org.openmetadata.service.search.elasticsearch.dataInsightAggregators.ElasticSearchAggregatedUnusedAssetsCountAggregator;
import org.openmetadata.service.search.elasticsearch.dataInsightAggregators.ElasticSearchAggregatedUnusedAssetsSizeAggregator;
@ -415,8 +420,7 @@ public class ElasticSearchClient implements SearchClient {
int limit,
int offset,
String index,
String sortField,
String sortType,
SearchSortFilter searchSortFilter,
String q)
throws IOException {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
@ -447,8 +451,18 @@ public class ElasticSearchClient implements SearchClient {
searchSourceBuilder.timeout(new TimeValue(30, TimeUnit.SECONDS));
searchSourceBuilder.from(offset);
searchSourceBuilder.size(limit);
if (sortField != null && sortType != null) {
searchSourceBuilder.sort(sortField, SortOrder.fromString(sortType));
if (searchSortFilter.isSorted()) {
FieldSortBuilder fieldSortBuilder =
SortBuilders.fieldSort(searchSortFilter.getSortField())
.order(SortOrder.fromString(searchSortFilter.getSortType()));
if (searchSortFilter.isNested()) {
NestedSortBuilder nestedSortBuilder =
new NestedSortBuilder(searchSortFilter.getSortNestedPath());
fieldSortBuilder.setNestedSort(nestedSortBuilder);
fieldSortBuilder.sortMode(
SortMode.valueOf(searchSortFilter.getSortNestedMode().toUpperCase()));
}
searchSourceBuilder.sort(fieldSortBuilder);
}
try {
SearchResponse response =
@ -464,7 +478,7 @@ public class ElasticSearchClient implements SearchClient {
if (e.status() == RestStatus.NOT_FOUND) {
throw new SearchIndexNotFoundException(String.format("Failed to to find index %s", index));
} else {
throw new SearchException(String.format("Search failed due to %s", e.getMessage()));
throw new SearchException(String.format("Search failed due to %s", e.getDetailedMessage()));
}
}
}

View File

@ -23,9 +23,7 @@ import static org.openmetadata.service.search.EntityBuilderConstant.UNIFIED;
import static org.openmetadata.service.search.UpdateSearchEventsConstant.SENDING_REQUEST_TO_ELASTIC_SEARCH;
import com.fasterxml.jackson.databind.JsonNode;
import es.org.elasticsearch.ElasticsearchStatusException;
import es.org.elasticsearch.index.IndexNotFoundException;
import es.org.elasticsearch.rest.RestStatus;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
@ -61,6 +59,7 @@ import org.openmetadata.service.dataInsight.DataInsightAggregatorInterface;
import org.openmetadata.service.jdbi3.DataInsightChartRepository;
import org.openmetadata.service.search.SearchClient;
import org.openmetadata.service.search.SearchRequest;
import org.openmetadata.service.search.SearchSortFilter;
import org.openmetadata.service.search.indexes.ContainerIndex;
import org.openmetadata.service.search.indexes.DashboardDataModelIndex;
import org.openmetadata.service.search.indexes.DashboardIndex;
@ -96,6 +95,7 @@ import org.openmetadata.service.search.opensearch.dataInsightAggregator.OpenSear
import org.openmetadata.service.search.opensearch.dataInsightAggregator.OpenSearchTotalEntitiesByTierAggregator;
import org.openmetadata.service.search.opensearch.dataInsightAggregator.OpenSearchUnusedAssetsAggregator;
import org.openmetadata.service.util.JsonUtils;
import os.org.opensearch.OpenSearchStatusException;
import os.org.opensearch.action.ActionListener;
import os.org.opensearch.action.admin.indices.alias.IndicesAliasesRequest;
import os.org.opensearch.action.admin.indices.delete.DeleteIndexRequest;
@ -141,6 +141,7 @@ import os.org.opensearch.index.query.functionscore.ScriptScoreFunctionBuilder;
import os.org.opensearch.index.reindex.BulkByScrollResponse;
import os.org.opensearch.index.reindex.DeleteByQueryRequest;
import os.org.opensearch.index.reindex.UpdateByQueryRequest;
import os.org.opensearch.rest.RestStatus;
import os.org.opensearch.script.Script;
import os.org.opensearch.script.ScriptType;
import os.org.opensearch.search.SearchHit;
@ -158,6 +159,10 @@ import os.org.opensearch.search.aggregations.metrics.SumAggregationBuilder;
import os.org.opensearch.search.builder.SearchSourceBuilder;
import os.org.opensearch.search.fetch.subphase.FetchSourceContext;
import os.org.opensearch.search.fetch.subphase.highlight.HighlightBuilder;
import os.org.opensearch.search.sort.FieldSortBuilder;
import os.org.opensearch.search.sort.NestedSortBuilder;
import os.org.opensearch.search.sort.SortBuilders;
import os.org.opensearch.search.sort.SortMode;
import os.org.opensearch.search.sort.SortOrder;
import os.org.opensearch.search.suggest.Suggest;
import os.org.opensearch.search.suggest.SuggestBuilder;
@ -408,8 +413,7 @@ public class OpenSearchClient implements SearchClient {
int limit,
int offset,
String index,
String sortField,
String sortType,
SearchSortFilter searchSortFilter,
String q)
throws IOException {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
@ -440,8 +444,18 @@ public class OpenSearchClient implements SearchClient {
searchSourceBuilder.timeout(new TimeValue(30, TimeUnit.SECONDS));
searchSourceBuilder.from(offset);
searchSourceBuilder.size(limit);
if (sortField != null && sortType != null) {
searchSourceBuilder.sort(sortField, SortOrder.fromString(sortType));
if (searchSortFilter.isSorted()) {
FieldSortBuilder fieldSortBuilder =
SortBuilders.fieldSort(searchSortFilter.getSortField())
.order(SortOrder.fromString(searchSortFilter.getSortType()));
if (searchSortFilter.isNested()) {
NestedSortBuilder nestedSortBuilder =
new NestedSortBuilder(searchSortFilter.getSortNestedPath());
fieldSortBuilder.setNestedSort(nestedSortBuilder);
fieldSortBuilder.sortMode(
SortMode.valueOf(searchSortFilter.getSortNestedMode().toUpperCase()));
}
searchSourceBuilder.sort(fieldSortBuilder);
}
try {
SearchResponse response =
@ -452,11 +466,11 @@ public class OpenSearchClient implements SearchClient {
SearchHit[] hits = searchHits.getHits();
Arrays.stream(hits).forEach(hit -> results.add(hit.getSourceAsMap()));
return new SearchResultListMapper(results, searchHits.getTotalHits().value);
} catch (ElasticsearchStatusException e) {
} catch (OpenSearchStatusException e) {
if (e.status() == RestStatus.NOT_FOUND) {
throw new SearchIndexNotFoundException(String.format("Failed to to find index %s", index));
} else {
throw new SearchException(String.format("Search failed due to %s", e.getMessage()));
throw new SearchException(String.format("Search failed due to %s", e.getDetailedMessage()));
}
}
}

View File

@ -64,8 +64,7 @@
},
"description": {
"type": "text",
"analyzer": "om_analyzer",
"index_options": "docs"
"analyzer": "om_analyzer"
},
"displayName": {
"type": "text",
@ -108,6 +107,54 @@
}
}
},
"owner": {
"properties": {
"id": {
"type": "keyword",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 36
}
}
},
"type": {
"type": "keyword"
},
"name": {
"type": "keyword",
"normalizer": "lowercase_normalizer",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"displayName": {
"type": "keyword",
"fields": {
"keyword": {
"type": "keyword",
"normalizer": "lowercase_normalizer",
"ignore_above": 256
}
}
},
"fullyQualifiedName": {
"type": "text"
},
"description": {
"type": "text"
},
"deleted": {
"type": "text"
},
"href": {
"type": "text"
}
}
},
"suggest": {
"type": "completion",
"contexts": [
@ -125,7 +172,7 @@
"type": "text"
},
"executable": {
"type": "text"
"type": "boolean"
}
}
}

View File

@ -103,6 +103,54 @@
}
}
},
"owner": {
"properties": {
"id": {
"type": "keyword",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 36
}
}
},
"type": {
"type": "keyword"
},
"name": {
"type": "keyword",
"normalizer": "lowercase_normalizer",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"displayName": {
"type": "keyword",
"fields": {
"keyword": {
"type": "keyword",
"normalizer": "lowercase_normalizer",
"ignore_above": 256
}
}
},
"fullyQualifiedName": {
"type": "text"
},
"description": {
"type": "text"
},
"deleted": {
"type": "text"
},
"href": {
"type": "text"
}
}
},
"suggest": {
"type": "completion",
"contexts": [
@ -120,7 +168,7 @@
"type": "text"
},
"executable": {
"type": "text"
"type": "boolean"
}
}
}

View File

@ -89,6 +89,54 @@
}
}
},
"owner": {
"properties": {
"id": {
"type": "keyword",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 36
}
}
},
"type": {
"type": "keyword"
},
"name": {
"type": "keyword",
"normalizer": "lowercase_normalizer",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"displayName": {
"type": "keyword",
"fields": {
"keyword": {
"type": "keyword",
"normalizer": "lowercase_normalizer",
"ignore_above": 256
}
}
},
"fullyQualifiedName": {
"type": "text"
},
"description": {
"type": "text"
},
"deleted": {
"type": "text"
},
"href": {
"type": "text"
}
}
},
"fqnParts": {
"type": "keyword"
},
@ -109,7 +157,7 @@
"type": "text"
},
"executable": {
"type": "text"
"type": "boolean"
}
}
}

View File

@ -4,6 +4,7 @@ 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
import static org.openmetadata.service.util.TestUtils.LONG_ENTITY_NAME;
@ -11,6 +12,7 @@ import static org.openmetadata.service.util.TestUtils.assertListNotNull;
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 static org.openmetadata.service.util.TestUtils.waitForEsAsyncOp;
import es.org.elasticsearch.search.aggregations.AggregationBuilder;
import es.org.elasticsearch.search.aggregations.AggregationBuilders;
@ -20,12 +22,14 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
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;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.openmetadata.schema.api.data.CreateTable;
@ -292,6 +296,7 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
.withDataLength(10)));
Table table = tableResourceTest.createEntity(tableReq, ADMIN_AUTH_HEADERS);
CreateTestSuite createExecutableTestSuite = createRequest(table.getFullyQualifiedName());
createExecutableTestSuite.withOwner(USER1_REF);
TestSuite executableTestSuite =
createExecutableTestSuite(createExecutableTestSuite, ADMIN_AUTH_HEADERS);
List<EntityReference> testCases1 = new ArrayList<>();
@ -309,6 +314,7 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
// We'll create a logical test suite and associate the test cases to it
CreateTestSuite createTestSuite = createRequest(test);
createTestSuite.withOwner(TEAM11_REF);
TestSuite testSuite = createEntity(createTestSuite, ADMIN_AUTH_HEADERS);
addTestCasesToLogicalTestSuite(
testSuite, testCases1.stream().map(EntityReference::getId).collect(Collectors.toList()));
@ -318,6 +324,102 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
getEntityByName(executableTestSuite.getFullyQualifiedName(), "*", ADMIN_AUTH_HEADERS);
// Check that the logical test suite has the test cases
verifyTestCases(executableTestSuite.getTests(), logicalTestSuite.getTests());
/* We'll then list the test suite from search
List from search test path:
1. List all test suites w/o filters
2. List only executable test suites
3. List only logical test suites
4. List non-empty test suites
5. List test suites with a query
6. List test suites with a nested sort
7. List test suites with fqn
8. List test suites with owner
*/
Map<String, String> queryParams = new HashMap<>();
queryParams.put("fields", "tests");
// 1. List all test suites w/o filters
ResultList<TestSuite> allEntities =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
Assertions.assertTrue(
allEntities.getData().stream().anyMatch(ts -> ts.getId().equals(logicalTestSuite.getId())));
TestSuite finalExecutableTestSuite = executableTestSuite;
Assertions.assertTrue(
allEntities.getData().stream()
.anyMatch(ts -> ts.getId().equals(finalExecutableTestSuite.getId())));
// 2. List only executable test suites
queryParams.put("testSuiteType", "executable");
queryParams.put("fields", "tests");
ResultList<TestSuite> executableTestSuites =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
Assertions.assertTrue(
executableTestSuites.getData().stream()
.anyMatch(ts -> ts.getId().equals(finalExecutableTestSuite.getId())));
// 3. List only logical test suites
queryParams.put("testSuiteType", "logical");
queryParams.put("fields", "tests");
ResultList<TestSuite> logicalTestSuites =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
Assertions.assertTrue(
logicalTestSuites.getData().stream()
.anyMatch(ts -> ts.getId().equals(logicalTestSuite.getId())));
// 4. List non-empty test suites
queryParams.clear();
queryParams.put("includeEmptyTestSuites", "false");
queryParams.put("fields", "tests");
ResultList<TestSuite> nonEmptyTestSuites =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
Assertions.assertTrue(
nonEmptyTestSuites.getData().stream().anyMatch(ts -> !ts.getTests().isEmpty()));
// 5. List test suite with a query
queryParams.clear();
queryParams.put("q", logicalTestSuite.getFullyQualifiedName());
ResultList<TestSuite> queryTestSuites =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
Assertions.assertTrue(
queryTestSuites.getData().stream()
.allMatch(
ts -> ts.getFullyQualifiedName().equals(logicalTestSuite.getFullyQualifiedName())));
// 6. List test suites with a nested sort
queryParams.clear();
queryParams.put("fields", "tests");
queryParams.put("sortField", "testCaseResultSummary.timestamp");
queryParams.put("sortOrder", "asc");
queryParams.put("sortNestedPath", "testCaseResultSummary");
queryParams.put("sortNestedMode", "max");
ResultList<TestSuite> sortedTestSuites =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
assertNotNull(sortedTestSuites.getData());
// 7. List test suites with fqn
queryParams.clear();
queryParams.put("fullyQualifiedName", logicalTestSuite.getFullyQualifiedName());
ResultList<TestSuite> fqnTestSuites =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
Assertions.assertTrue(
fqnTestSuites.getData().stream()
.allMatch(ts -> ts.getId().equals(logicalTestSuite.getId())));
// 8. List test suites with owner
// 8.1 Team owner
queryParams.clear();
queryParams.put("owner", TEAM11_REF.getFullyQualifiedName());
queryParams.put("fields", "owner");
ResultList<TestSuite> teamOwnerTestSuites =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
Assertions.assertTrue(
teamOwnerTestSuites.getData().stream()
.allMatch(ts -> ts.getOwner().getId().equals(TEAM11_REF.getId())));
// 8.2 User owner
queryParams.clear();
queryParams.put("owner", USER1_REF.getFullyQualifiedName());
queryParams.put("fields", "owner");
ResultList<TestSuite> userOwnerTestSuites =
listEntitiesFromSearch(queryParams, 100, 0, ADMIN_AUTH_HEADERS);
Assertions.assertTrue(
userOwnerTestSuites.getData().stream()
.allMatch(ts -> ts.getOwner().getId().equals(USER1_REF.getId())));
}
@Test
@ -663,6 +765,45 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
assertEquals(5, actualExecutableTestSuite.getTests().size());
}
@Test
void get_listTestSuiteFromSearchWithPagination(TestInfo testInfo)
throws IOException, InterruptedException {
if (supportsSearchIndex) {
Random rand = new Random();
int tablesNum = rand.nextInt(3) + 3;
TableResourceTest tableResourceTest = new TableResourceTest();
TestSuiteResourceTest testSuiteResourceTest = new TestSuiteResourceTest();
List<Table> tables = new ArrayList<>();
Map<String, TestSuite> testSuites = new HashMap<>();
for (int i = 0; i < tablesNum; i++) {
CreateTable tableReq =
tableResourceTest
.createRequest(testInfo, i)
.withDatabaseSchema(DATABASE_SCHEMA.getFullyQualifiedName())
.withColumns(
List.of(
new Column()
.withName(C1)
.withDisplayName("c1")
.withDataType(ColumnDataType.VARCHAR)
.withDataLength(10)))
.withOwner(USER1_REF);
Table table = tableResourceTest.createEntity(tableReq, ADMIN_AUTH_HEADERS);
tables.add(table);
CreateTestSuite createTestSuite =
testSuiteResourceTest.createRequest(table.getFullyQualifiedName());
TestSuite testSuite =
testSuiteResourceTest.createExecutableTestSuite(createTestSuite, ADMIN_AUTH_HEADERS);
testSuites.put(table.getFullyQualifiedName(), testSuite);
}
waitForEsAsyncOp();
validateEntityListFromSearchWithPagination(new HashMap<>(), testSuites.size());
}
}
public ResultList<TestSuite> getTestSuites(
Integer limit,
String fields,

View File

@ -645,7 +645,7 @@ public final class TestUtils {
}
public static void waitForEsAsyncOp() throws InterruptedException {
waitForEsAsyncOp(100);
waitForEsAsyncOp(500);
}
public static void waitForEsAsyncOp(Integer milliseconds) throws InterruptedException {