MINOR - Data Contract fixes and improvements (#23043)

* fix change event handling for alerts

* contract is deleted when asset is deleted

* add support for custom properties

* Update generated TypeScript types

* handle suite index deletion

* validate owner is not coming back if not requested

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Pere Miquel Brull 2025-08-22 12:58:06 +02:00 committed by GitHub
parent 7ad16f5623
commit bdf3659b41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 192 additions and 66 deletions

View File

@ -0,0 +1,4 @@
-- Update the relation between table and dataContract to 0 (CONTAINS)
UPDATE entity_relationship
SET relation = 0
WHERE fromEntity = 'table' AND toEntity = 'dataContract' AND relation = 10;

View File

@ -0,0 +1,4 @@
-- Update the relation between table and dataContract to 0 (CONTAINS)
UPDATE entity_relationship
SET relation = 0
WHERE fromEntity = 'table' AND toEntity = 'dataContract' AND relation = 10;

View File

@ -1,6 +1,7 @@
package org.openmetadata.service.apps.bundles.dataContracts;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
import static org.openmetadata.service.apps.scheduler.OmAppJobListener.APP_RUN_STATS;
import java.util.HashMap;
@ -14,14 +15,18 @@ import org.openmetadata.schema.entity.data.DataContract;
import org.openmetadata.schema.entity.datacontract.DataContractResult;
import org.openmetadata.schema.system.Stats;
import org.openmetadata.schema.system.StepStats;
import org.openmetadata.schema.type.ChangeEvent;
import org.openmetadata.schema.type.EventType;
import org.openmetadata.schema.utils.JsonUtils;
import org.openmetadata.service.Entity;
import org.openmetadata.service.apps.AbstractNativeApplication;
import org.openmetadata.service.formatter.util.FormatterUtil;
import org.openmetadata.service.jdbi3.CollectionDAO;
import org.openmetadata.service.jdbi3.DataContractRepository;
import org.openmetadata.service.jdbi3.ListFilter;
import org.openmetadata.service.search.SearchRepository;
import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.RestUtil;
import org.openmetadata.service.util.ResultList;
import org.quartz.JobExecutionContext;
@ -70,8 +75,15 @@ public class DataContractValidationApp extends AbstractNativeApplication {
for (DataContract dataContract : contractBatch) {
try {
LOG.debug("Validating data contract: {}", dataContract.getFullyQualifiedName());
DataContractResult validationResult = repository.validateContract(dataContract);
RestUtil.PutResponse<DataContractResult> validationResponse =
repository.validateContract(dataContract);
DataContractResult validationResult = validationResponse.getEntity();
ChangeEvent changeEvent =
FormatterUtil.getDataContractResultEvent(
validationResult, ADMIN_USER_NAME, EventType.ENTITY_UPDATED);
changeEvent.setEntity(JsonUtils.pojoToMaskedJson(dataContract));
Entity.getCollectionDAO().changeEventDAO().insert(JsonUtils.pojoToJson(changeEvent));
LOG.debug(
"Validation completed for {}: Status = {}",
dataContract.getFullyQualifiedName(),

View File

@ -346,24 +346,27 @@ public class FormatterUtil {
if (entityTimeSeries instanceof DataContractResult) {
DataContractResult result =
JsonUtils.readOrConvertValue(entityTimeSeries, DataContractResult.class);
DataContract contract =
Entity.getEntityByName(
Entity.DATA_CONTRACT, result.getDataContractFQN(), "", Include.ALL);
ChangeEvent changeEvent =
getChangeEvent(updateBy, eventType, contract.getEntityReference().getType(), contract);
return changeEvent
.withChangeDescription(
new ChangeDescription()
.withFieldsUpdated(
List.of(
new FieldChange().withName(DATA_CONTRACT_RESULT).withNewValue(result))))
.withEntity(contract)
.withEntityFullyQualifiedName(contract.getFullyQualifiedName());
return getDataContractResultEvent(result, updateBy, eventType);
}
return null;
}
public static ChangeEvent getDataContractResultEvent(
DataContractResult result, String updateBy, EventType eventType) {
DataContract contract =
Entity.getEntityByName(Entity.DATA_CONTRACT, result.getDataContractFQN(), "", Include.ALL);
ChangeEvent changeEvent =
getChangeEvent(updateBy, eventType, contract.getEntityReference().getType(), contract);
return changeEvent
.withChangeDescription(
new ChangeDescription()
.withFieldsUpdated(
List.of(new FieldChange().withName(DATA_CONTRACT_RESULT).withNewValue(result))))
.withEntity(contract)
.withEntityFullyQualifiedName(contract.getFullyQualifiedName());
}
private static ChangeEvent getChangeEventForThread(
String updateBy, EventType eventType, String entityType, Thread thread) {
return new ChangeEvent()

View File

@ -82,9 +82,9 @@ import org.openmetadata.service.util.RestUtil;
public class DataContractRepository extends EntityRepository<DataContract> {
private static final String DATA_CONTRACT_UPDATE_FIELDS =
"entity,owners,reviewers,status,schema,qualityExpectations,contractUpdates,semantics,latestResult";
"entity,owners,reviewers,status,schema,qualityExpectations,contractUpdates,semantics,latestResult,extension";
private static final String DATA_CONTRACT_PATCH_FIELDS =
"entity,owners,reviewers,status,schema,qualityExpectations,contractUpdates,semantics,latestResult";
"entity,owners,reviewers,status,schema,qualityExpectations,contractUpdates,semantics,latestResult,extension";
public static final String RESULT_EXTENSION = "dataContract.dataContractResult";
public static final String RESULT_SCHEMA = "dataContractResult";
@ -390,6 +390,7 @@ public class DataContractRepository extends EntityRepository<DataContract> {
(TestSuiteRepository) Entity.getEntityRepository(Entity.TEST_SUITE);
TestSuite testSuite = getOrCreateTestSuite(dataContract);
testSuiteRepository.deleteLogicalTestSuite(ADMIN_USER_NAME, testSuite, true);
testSuiteRepository.deleteFromSearch(testSuite, true);
}
private TestSuite getOrCreateTestSuite(DataContract dataContract) {
@ -475,7 +476,7 @@ public class DataContractRepository extends EntityRepository<DataContract> {
}
}
public DataContractResult validateContract(DataContract dataContract) {
public RestUtil.PutResponse<DataContractResult> validateContract(DataContract dataContract) {
// Check if there's a running validation and abort it before starting a new one
abortRunningValidation(dataContract);
@ -520,8 +521,7 @@ public class DataContractRepository extends EntityRepository<DataContract> {
}
// Add the result to the data contract and update the time series
addContractResult(dataContract, result);
return result;
return addContractResult(dataContract, result);
}
public void deployAndTriggerDQValidation(DataContract dataContract) {
@ -878,7 +878,7 @@ public class DataContractRepository extends EntityRepository<DataContract> {
dataContract.getId(),
dataContract.getEntity().getType(),
Entity.DATA_CONTRACT,
Relationship.HAS);
Relationship.CONTAINS);
storeOwners(dataContract, dataContract.getOwners());
storeReviewers(dataContract, dataContract.getReviewers());

View File

@ -43,6 +43,7 @@ public class DataContractMapper {
.withEffectiveFrom(create.getEffectiveFrom())
.withEffectiveUntil(create.getEffectiveUntil())
.withSourceUrl(create.getSourceUrl())
.withExtension(create.getExtension())
.withUpdatedBy(user)
.withUpdatedAt(System.currentTimeMillis());

View File

@ -73,6 +73,7 @@ import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.OperationContext;
import org.openmetadata.service.security.policyevaluator.ResourceContext;
import org.openmetadata.service.util.EntityUtil.Fields;
import org.openmetadata.service.util.RestUtil;
import org.openmetadata.service.util.ResultList;
@Slf4j
@ -85,7 +86,7 @@ import org.openmetadata.service.util.ResultList;
@Collection(name = "dataContracts")
public class DataContractResource extends EntityResource<DataContract, DataContractRepository> {
public static final String COLLECTION_PATH = "v1/dataContracts/";
static final String FIELDS = "owners,reviewers";
static final String FIELDS = "owners,reviewers,extension";
@Override
public DataContract addHref(UriInfo uriInfo, DataContract dataContract) {
@ -872,8 +873,8 @@ public class DataContractResource extends EntityResource<DataContract, DataContr
new ResourceContext<>(Entity.DATA_CONTRACT, id, null);
authorizer.authorize(securityContext, operationContext, resourceContext);
DataContractResult result = repository.validateContract(dataContract);
return Response.ok(result).build();
RestUtil.PutResponse<DataContractResult> result = repository.validateContract(dataContract);
return result.toResponse();
}
// Add runId and dataContractFQN to the result if not incoming

View File

@ -3578,7 +3578,7 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
// PUT and update the entity with extension field intA to a new value
JsonNode intAValue = mapper.convertValue(2, JsonNode.class);
jsonNode.set("intA", intAValue);
create = createRequest(test).withExtension(jsonNode).withName(entity.getName());
create = create.withExtension(jsonNode).withName(entity.getName());
change = getChangeDescription(entity, MINOR_UPDATE);
fieldUpdated(
change,
@ -3600,7 +3600,7 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
// PUT and remove field intA from the entity extension - *** for BOT this should be ignored ***
JsonNode oldNode = JsonUtils.valueToTree(entity.getExtension());
jsonNode.remove("intA");
create = createRequest(test).withExtension(jsonNode).withName(entity.getName());
create = create.withExtension(jsonNode).withName(entity.getName());
entity = updateEntity(create, Status.OK, INGESTION_BOT_AUTH_HEADERS);
assertNotEquals(
JsonUtils.valueToTree(create.getExtension()), JsonUtils.valueToTree(entity.getExtension()));

View File

@ -14,6 +14,7 @@
package org.openmetadata.service.resources.data;
import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
import static jakarta.ws.rs.core.Response.Status.OK;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -43,6 +44,7 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -567,7 +569,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
target = target.queryParam("fields", fields);
}
Response response = SecurityUtil.addHeaders(target, ADMIN_AUTH_HEADERS).get();
return TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
return TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
}
private DataContract getDataContractByName(String name, String fields)
@ -577,7 +579,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
target = target.queryParam("fields", fields);
}
Response response = SecurityUtil.addHeaders(target, ADMIN_AUTH_HEADERS).get();
return TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
return TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
}
private DataContract getDataContractByEntityId(UUID entityId, String entityType)
@ -596,14 +598,14 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
target = target.queryParam("fields", fields);
}
Response response = SecurityUtil.addHeaders(target, ADMIN_AUTH_HEADERS).get();
return TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
return TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
}
private DataContract updateDataContract(CreateDataContract create) throws IOException {
WebTarget target = getCollection();
Response response =
SecurityUtil.addHeaders(target, ADMIN_AUTH_HEADERS).put(Entity.json(create));
return TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
return TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
}
private DataContract patchDataContract(UUID id, String originalJson, DataContract updated)
@ -624,18 +626,19 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
private void deleteDataContract(UUID id) throws IOException {
WebTarget target = getResource(id);
Response response = SecurityUtil.addHeaders(target, ADMIN_AUTH_HEADERS).delete();
TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
}
private void deleteDataContract(UUID id, boolean recursive) throws IOException {
WebTarget target = getResource(id);
target = target.queryParam("recursive", recursive);
Response response = SecurityUtil.addHeaders(target, ADMIN_AUTH_HEADERS).delete();
TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
}
private void deleteTable(UUID id) {
WebTarget tableTarget = APP.client().target(getTableUri() + "/" + id);
WebTarget tableTarget =
APP.client().target(getTableUri() + "/" + id).queryParam("recursive", true);
Response response = SecurityUtil.addHeaders(tableTarget, ADMIN_AUTH_HEADERS).delete();
response.readEntity(String.class); // Consume response
}
@ -666,16 +669,14 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
throws HttpResponseException {
WebTarget latestTarget = getResource(dataContract.getId()).path("/results/latest");
Response latestResponse = SecurityUtil.addHeaders(latestTarget, ADMIN_AUTH_HEADERS).get();
return TestUtils.readResponse(
latestResponse, DataContractResult.class, Status.OK.getStatusCode());
return TestUtils.readResponse(latestResponse, DataContractResult.class, OK.getStatusCode());
}
private DataContractResult runValidate(DataContract dataContract) throws HttpResponseException {
WebTarget validateTarget = getResource(dataContract.getId()).path("/validate");
Response validateResponse =
SecurityUtil.addHeaders(validateTarget, ADMIN_AUTH_HEADERS).post(null);
return TestUtils.readResponse(
validateResponse, DataContractResult.class, Status.OK.getStatusCode());
return TestUtils.readResponse(validateResponse, DataContractResult.class, OK.getStatusCode());
}
private Response getResultById(UUID dataContractId, UUID resultId) {
@ -1776,8 +1777,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
WebTarget target = getCollection();
Response response =
SecurityUtil.addHeaders(target, ADMIN_AUTH_HEADERS).put(Entity.json(create));
DataContract updated =
TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
DataContract updated = TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
assertEquals(ContractStatus.Active, updated.getStatus());
}
@ -1797,7 +1797,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
() -> {
Response response =
SecurityUtil.addHeaders(target, TEST_AUTH_HEADERS).put(Entity.json(create));
TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
},
Status.FORBIDDEN,
"Principal: CatalogPrincipal{name='test'} operations [EditAll] not allowed");
@ -1869,7 +1869,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
// Delete as admin should work
WebTarget target = getResource(created.getId());
Response response = SecurityUtil.addHeaders(target, ADMIN_AUTH_HEADERS).delete();
TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
// Verify deletion
assertThrows(HttpResponseException.class, () -> getDataContract(created.getId(), null));
@ -1888,12 +1888,37 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
assertResponse(
() -> {
Response response = SecurityUtil.addHeaders(target, TEST_AUTH_HEADERS).delete();
TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
},
Status.FORBIDDEN,
"Principal: CatalogPrincipal{name='test'} operations [Delete] not allowed");
}
@Test
@Execution(ExecutionMode.CONCURRENT)
void testDataContractIsDeletedWhenTableIsDeleted(TestInfo test) throws IOException {
// Create a table
Table table = createUniqueTable(test.getDisplayName());
// Create a data contract for the table
CreateDataContract create = createDataContractRequest(test.getDisplayName(), table);
DataContract dataContract = createDataContract(create);
// Verify the data contract was created
assertNotNull(dataContract);
assertEquals(table.getId(), dataContract.getEntity().getId());
// Verify we can get the data contract before deletion
DataContract retrieved = getDataContract(dataContract.getId(), null);
assertNotNull(retrieved);
// Delete the table
deleteTable(table.getId());
// Verify that the data contract is also deleted (should throw HttpResponseException)
assertThrows(HttpResponseException.class, () -> getDataContract(dataContract.getId(), null));
}
@Test
@Execution(ExecutionMode.CONCURRENT)
void testAllUsersCanReadDataContracts(TestInfo test) throws IOException {
@ -1905,7 +1930,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
WebTarget target = getResource(created.getId());
Response response = SecurityUtil.addHeaders(target, TEST_AUTH_HEADERS).get();
DataContract retrieved =
TestUtils.readResponse(response, DataContract.class, Status.OK.getStatusCode());
TestUtils.readResponse(response, DataContract.class, OK.getStatusCode());
assertEquals(created.getId(), retrieved.getId());
assertEquals(created.getName(), retrieved.getName());
@ -1935,7 +1960,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
Map<String, String> authHeaders = SecurityUtil.authHeaders("admin@open-metadata.org");
WebTarget userTarget = getResource("users").path("name").path("admin");
Response response = SecurityUtil.addHeaders(userTarget, authHeaders).get();
User adminUser = TestUtils.readResponse(response, User.class, Status.OK.getStatusCode());
User adminUser = TestUtils.readResponse(response, User.class, OK.getStatusCode());
// Create user references for reviewers using the full entity reference
List<EntityReference> reviewers = new ArrayList<>();
@ -1967,7 +1992,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
Map<String, String> authHeaders = SecurityUtil.authHeaders("admin@open-metadata.org");
WebTarget userTarget = getResource("users").path("name").path("admin");
Response response = SecurityUtil.addHeaders(userTarget, authHeaders).get();
User adminUser = TestUtils.readResponse(response, User.class, Status.OK.getStatusCode());
User adminUser = TestUtils.readResponse(response, User.class, OK.getStatusCode());
// Get the full data contract with all fields
created = getDataContract(created.getId(), "reviewers");
@ -1994,7 +2019,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
Map<String, String> authHeaders = SecurityUtil.authHeaders("admin@open-metadata.org");
WebTarget userTarget = getResource("users").path("name").path("admin");
Response response = SecurityUtil.addHeaders(userTarget, authHeaders).get();
User adminUser = TestUtils.readResponse(response, User.class, Status.OK.getStatusCode());
User adminUser = TestUtils.readResponse(response, User.class, OK.getStatusCode());
// Create with reviewers
List<EntityReference> initialReviewers = new ArrayList<>();
@ -2029,11 +2054,11 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
Map<String, String> authHeaders = SecurityUtil.authHeaders("admin@open-metadata.org");
WebTarget userTarget = getResource("users").path("name").path("admin");
Response response = SecurityUtil.addHeaders(userTarget, authHeaders).get();
User adminUser = TestUtils.readResponse(response, User.class, Status.OK.getStatusCode());
User adminUser = TestUtils.readResponse(response, User.class, OK.getStatusCode());
userTarget = getResource("users").path("name").path("test");
response = SecurityUtil.addHeaders(userTarget, authHeaders).get();
User testUser = TestUtils.readResponse(response, User.class, Status.OK.getStatusCode());
User testUser = TestUtils.readResponse(response, User.class, OK.getStatusCode());
// Create with one reviewer
List<EntityReference> initialReviewers = new ArrayList<>();
@ -2084,7 +2109,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
Response response =
SecurityUtil.addHeaders(resultsTarget, ADMIN_AUTH_HEADERS).put(Entity.json(createResult));
DataContractResult result =
TestUtils.readResponse(response, DataContractResult.class, Status.OK.getStatusCode());
TestUtils.readResponse(response, DataContractResult.class, OK.getStatusCode());
assertNotNull(result);
assertNotNull(result.getId());
@ -2111,7 +2136,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
getResource(dataContract.getId()).path("/results").path(result.getId().toString());
Response getResponse = SecurityUtil.addHeaders(getResultTarget, ADMIN_AUTH_HEADERS).get();
DataContractResult updatedResult =
TestUtils.readResponse(getResponse, DataContractResult.class, Status.OK.getStatusCode());
TestUtils.readResponse(getResponse, DataContractResult.class, OK.getStatusCode());
assertNotNull(updatedResult);
assertEquals(dataContract.getFullyQualifiedName(), updatedResult.getDataContractFQN());
@ -2147,15 +2172,14 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
WebTarget resultsTarget = getResource(dataContract.getId()).path("/results");
Response response =
SecurityUtil.addHeaders(resultsTarget, ADMIN_AUTH_HEADERS).put(Entity.json(createResult));
assertEquals(Status.OK.getStatusCode(), response.getStatus());
assertEquals(OK.getStatusCode(), response.getStatus());
response.readEntity(String.class); // Consume response
}
// List results
WebTarget listTarget = getResource(dataContract.getId()).path("/results");
Response listResponse = SecurityUtil.addHeaders(listTarget, ADMIN_AUTH_HEADERS).get();
String jsonResponse =
TestUtils.readResponse(listResponse, String.class, Status.OK.getStatusCode());
String jsonResponse = TestUtils.readResponse(listResponse, String.class, OK.getStatusCode());
ResultList<DataContractResult> results =
JsonUtils.readValue(
jsonResponse,
@ -2195,7 +2219,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
WebTarget resultsTarget = getResource(dataContract.getId()).path("/results");
Response response =
SecurityUtil.addHeaders(resultsTarget, ADMIN_AUTH_HEADERS).put(Entity.json(createResult));
assertEquals(Status.OK.getStatusCode(), response.getStatus());
assertEquals(OK.getStatusCode(), response.getStatus());
response.readEntity(String.class); // Consume response
}
@ -2227,14 +2251,13 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
WebTarget resultsTarget = getResource(dataContract.getId()).path("/results");
Response createResponse =
SecurityUtil.addHeaders(resultsTarget, ADMIN_AUTH_HEADERS).put(Entity.json(createResult));
assertEquals(Status.OK.getStatusCode(), createResponse.getStatus());
assertEquals(OK.getStatusCode(), createResponse.getStatus());
createResponse.readEntity(String.class); // Consume response
// Verify result exists
WebTarget listTarget = getResource(dataContract.getId()).path("/results");
Response listResponse = SecurityUtil.addHeaders(listTarget, ADMIN_AUTH_HEADERS).get();
String jsonResponse =
TestUtils.readResponse(listResponse, String.class, Status.OK.getStatusCode());
String jsonResponse = TestUtils.readResponse(listResponse, String.class, OK.getStatusCode());
ResultList<DataContractResult> results =
JsonUtils.readValue(
jsonResponse,
@ -2244,12 +2267,11 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
// Delete the result
WebTarget deleteTarget = getResource(dataContract.getId()).path("/results/" + timestamp);
Response deleteResponse = SecurityUtil.addHeaders(deleteTarget, ADMIN_AUTH_HEADERS).delete();
assertEquals(Status.OK.getStatusCode(), deleteResponse.getStatus());
assertEquals(OK.getStatusCode(), deleteResponse.getStatus());
// Verify result is deleted
Response listResponse2 = SecurityUtil.addHeaders(listTarget, ADMIN_AUTH_HEADERS).get();
String jsonResponse2 =
TestUtils.readResponse(listResponse2, String.class, Status.OK.getStatusCode());
String jsonResponse2 = TestUtils.readResponse(listResponse2, String.class, OK.getStatusCode());
ResultList<DataContractResult> results2 =
JsonUtils.readValue(
jsonResponse2,
@ -2566,7 +2588,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
Response response1 =
SecurityUtil.addHeaders(resultsTarget, ADMIN_AUTH_HEADERS).put(Entity.json(createResult1));
DataContractResult result1 =
TestUtils.readResponse(response1, DataContractResult.class, Status.OK.getStatusCode());
TestUtils.readResponse(response1, DataContractResult.class, OK.getStatusCode());
// Verify first result becomes latestResult
DataContract contractAfterFirst = getDataContract(dataContract.getId(), "");
@ -2615,7 +2637,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
Response response3 =
SecurityUtil.addHeaders(resultsTarget, ADMIN_AUTH_HEADERS).put(Entity.json(createResult3));
DataContractResult result3 =
TestUtils.readResponse(response3, DataContractResult.class, Status.OK.getStatusCode());
TestUtils.readResponse(response3, DataContractResult.class, OK.getStatusCode());
// Verify latestResult IS updated to the newest result
DataContract contractAfterThird = getDataContract(dataContract.getId(), "");
@ -3003,8 +3025,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
// Finally, verify that we have 2 DataContractResults properly stored
WebTarget listTarget = getResource(dataContract.getId()).path("/results");
Response listResponse = SecurityUtil.addHeaders(listTarget, ADMIN_AUTH_HEADERS).get();
String jsonResponse =
TestUtils.readResponse(listResponse, String.class, Status.OK.getStatusCode());
String jsonResponse = TestUtils.readResponse(listResponse, String.class, OK.getStatusCode());
ResultList<DataContractResult> allResults =
JsonUtils.readValue(
jsonResponse,
@ -3077,8 +3098,7 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
// Verify we have 2 results: one aborted, one successful
WebTarget listTarget = getResource(dataContract.getId()).path("/results");
Response listResponse = SecurityUtil.addHeaders(listTarget, ADMIN_AUTH_HEADERS).get();
String jsonResponse =
TestUtils.readResponse(listResponse, String.class, Status.OK.getStatusCode());
String jsonResponse = TestUtils.readResponse(listResponse, String.class, OK.getStatusCode());
ResultList<DataContractResult> allResults =
JsonUtils.readValue(
jsonResponse,
@ -3173,6 +3193,18 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
assertNotNull(testSuite.getTests());
assertEquals(2, testSuite.getTests().size());
// Verify test suite exists in search index
Map<String, String> searchQueryParams = new HashMap<>();
searchQueryParams.put("fullyQualifiedName", testSuite.getFullyQualifiedName());
searchQueryParams.put("fields", "tests");
ResultList<TestSuite> testSuitesInSearchIndex =
testSuiteResourceTest.listEntitiesFromSearch(searchQueryParams, 10, 0, ADMIN_AUTH_HEADERS);
assertNotNull(testSuitesInSearchIndex);
assertFalse(testSuitesInSearchIndex.getData().isEmpty());
assertEquals(1, testSuitesInSearchIndex.getData().size());
assertEquals(testSuite.getId(), testSuitesInSearchIndex.getData().get(0).getId());
// Verify both test cases exist and are accessible before deletion
TestCase retrievedTestCase1 =
testCaseResourceTest.getEntity(testCase1.getId(), "*", ADMIN_AUTH_HEADERS);
@ -3193,6 +3225,13 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
() ->
testSuiteResourceTest.getEntityByName(expectedTestSuiteName, "*", ADMIN_AUTH_HEADERS));
// Verify test suite is no longer in search index
ResultList<TestSuite> testSuitesInSearchIndexAfterDeletion =
testSuiteResourceTest.listEntitiesFromSearch(searchQueryParams, 10, 0, ADMIN_AUTH_HEADERS);
assertNotNull(testSuitesInSearchIndexAfterDeletion);
assertTrue(testSuitesInSearchIndexAfterDeletion.getData().isEmpty());
// CRITICAL ASSERTION: Verify the test cases are NOT deleted - they should still exist
// independently
TestCase testCase1AfterDeletion =
@ -3275,4 +3314,50 @@ public class DataContractResourceTest extends EntityResourceTest<DataContract, C
assertEquals(1, testSuite.getTests().size());
assertEquals(testCase.getId(), testSuite.getTests().get(0).getId());
}
@Test
@Execution(ExecutionMode.CONCURRENT)
void testDataContractOwnerFieldFiltering(TestInfo test) throws IOException {
Table table = createUniqueTable(test.getDisplayName());
// Create owners list
List<EntityReference> owners = new ArrayList<>();
owners.add(USER1_REF);
CreateDataContract create =
createDataContractRequest(test.getDisplayName(), table).withOwners(owners);
DataContract created = createDataContract(create);
// Verify owner was set during creation
assertNotNull(created.getOwners());
assertEquals(1, created.getOwners().size());
// Test 1: Get without specifying fields - should not include owner information
DataContract retrievedWithoutFields = getDataContract(created.getId(), null);
assertNotNull(retrievedWithoutFields);
assertEquals(created.getId(), retrievedWithoutFields.getId());
assertNull(retrievedWithoutFields.getOwners());
// Test 2: Get with "owners" in fields - should include owner information
DataContract retrievedWithOwners = getDataContract(created.getId(), "owners");
assertNotNull(retrievedWithOwners);
assertEquals(created.getId(), retrievedWithOwners.getId());
assertNotNull(retrievedWithOwners.getOwners());
assertEquals(1, retrievedWithOwners.getOwners().size());
assertEquals(USER1_REF.getId(), retrievedWithOwners.getOwners().get(0).getId());
// Test 3: Get with multiple fields including owners - should include owner information
DataContract retrievedWithMultipleFields = getDataContract(created.getId(), "owners,reviewers");
assertNotNull(retrievedWithMultipleFields);
assertEquals(created.getId(), retrievedWithMultipleFields.getId());
assertNotNull(retrievedWithMultipleFields.getOwners());
assertEquals(1, retrievedWithMultipleFields.getOwners().size());
// Test 4: Get with fields that exclude owners - should not include owner information
DataContract retrievedWithoutOwnerField = getDataContract(created.getId(), "reviewers");
assertNotNull(retrievedWithoutOwnerField);
assertEquals(created.getId(), retrievedWithoutOwnerField.getId());
assertNull(retrievedWithoutOwnerField.getOwners());
}
}

View File

@ -76,6 +76,10 @@
"sourceUrl": {
"description": "Source URL of the data contract.",
"$ref": "../../type/basic.json#/definitions/sourceUrl"
},
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
}
},
"required": [

View File

@ -197,6 +197,10 @@
}
},
"additionalProperties": false
},
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
}
},
"required": [

View File

@ -34,6 +34,10 @@ export interface CreateDataContract {
* Reference to the data entity (table, topic, etc.) this contract applies to.
*/
entity: EntityReference;
/**
* Entity extension data with custom attributes added to the entity.
*/
extension?: any;
/**
* Name of the data contract.
*/

View File

@ -46,6 +46,10 @@ export interface DataContract {
* Reference to the data entity (table, topic, etc.) this contract applies to.
*/
entity: EntityReference;
/**
* Entity extension data with custom attributes added to the entity.
*/
extension?: any;
/**
* Fully qualified name of the data contract.
*/