Fixes #827 - Update Model version during PUT and POST operations

This commit is contained in:
sureshms 2021-10-17 15:42:03 -07:00
parent 8852ba6ec3
commit 7b2c385ce4
6 changed files with 256 additions and 172 deletions

View File

@ -138,6 +138,11 @@ public abstract class DashboardRepository {
return EntityUtil.listBefore(entityRepository, Dashboard.class, fields, serviceName, limitParam, before);
}
@Transaction
public Dashboard get(String id, Fields fields) throws IOException {
return setFields(EntityUtil.validate(id, dashboardDAO().findById(id), Dashboard.class), fields);
}
@Transaction
public Dashboard getByName(String fqn, Fields fields) throws IOException {
Dashboard dashboard = EntityUtil.validate(fqn, dashboardDAO().findByFQN(fqn), Dashboard.class);
@ -207,10 +212,6 @@ public abstract class DashboardRepository {
return EntityUtil.populateOwner(userDAO(), teamDAO(), dashboard.getOwner());
}
public Dashboard get(String id, Fields fields) throws IOException {
return setFields(EntityUtil.validate(id, dashboardDAO().findById(id), Dashboard.class), fields);
}
private Dashboard setFields(Dashboard dashboard, Fields fields) throws IOException {
dashboard.setDisplayName(dashboard.getDisplayName());
dashboard.setOwner(fields.contains("owner") ? getOwner(dashboard) : null);

View File

@ -16,17 +16,21 @@
package org.openmetadata.catalog.jdbi3;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.entity.data.Dashboard;
import org.openmetadata.catalog.entity.data.Model;
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
import org.openmetadata.catalog.exception.EntityNotFoundException;
import org.openmetadata.catalog.jdbi3.DashboardRepository.DashboardDAO;
import org.openmetadata.catalog.jdbi3.TeamRepository.TeamDAO;
import org.openmetadata.catalog.jdbi3.UserRepository.UserDAO;
import org.openmetadata.catalog.jdbi3.UsageRepository.UsageDAO;
import org.openmetadata.catalog.jdbi3.UserRepository.UserDAO;
import org.openmetadata.catalog.resources.models.ModelResource;
import org.openmetadata.catalog.resources.models.ModelResource.ModelList;
import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.type.TagLabel;
import org.openmetadata.catalog.util.EntityInterface;
import org.openmetadata.catalog.util.EntityUpdater;
import org.openmetadata.catalog.util.EntityUtil;
import org.openmetadata.catalog.util.EntityUtil.Fields;
import org.openmetadata.catalog.util.JsonUtils;
@ -37,6 +41,8 @@ import org.skife.jdbi.v2.sqlobject.CreateSqlObject;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.json.JsonPatch;
import javax.ws.rs.core.Response.Status;
@ -45,10 +51,12 @@ import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import static org.openmetadata.catalog.exception.CatalogExceptionMessage.entityNotFound;
public abstract class ModelRepository {
private static final Logger LOG = LoggerFactory.getLogger(ModelRepository.class);
private static final Fields MODEL_UPDATE_FIELDS = new Fields(ModelResource.FIELD_LIST,
"owner,dashboard,tags");
private static final Fields MODEL_PATCH_FIELDS = new Fields(ModelResource.FIELD_LIST,
@ -73,6 +81,9 @@ public abstract class ModelRepository {
@CreateSqlObject
abstract UsageDAO usageDAO();
@CreateSqlObject
abstract DashboardDAO dashboardDAO();
@CreateSqlObject
abstract TagRepository.TagDAO tagDAO();
@ -131,30 +142,20 @@ public abstract class ModelRepository {
}
@Transaction
public PutResponse<Model> createOrUpdate(Model updatedModel,
EntityReference newOwner) throws IOException {
String fqn = getFQN(updatedModel);
Model storedModel = JsonUtils.readValue(modelDAO().findByFQN(fqn), Model.class);
if (storedModel == null) {
return new PutResponse<>(Status.CREATED, createInternal(updatedModel, newOwner));
}
// Update existing model
EntityUtil.populateOwner(userDAO(), teamDAO(), newOwner); // Validate new owner
if (storedModel.getDescription() == null || storedModel.getDescription().isEmpty()) {
storedModel.withDescription(updatedModel.getDescription());
}
//update the display name from source
if (updatedModel.getDisplayName() != null && !updatedModel.getDisplayName().isEmpty()) {
storedModel.withDisplayName(updatedModel.getDisplayName());
public PutResponse<Model> createOrUpdate(Model updated, EntityReference newOwner) throws IOException {
String fqn = getFQN(updated);
Model stored = JsonUtils.readValue(modelDAO().findByFQN(fqn), Model.class);
if (stored == null) {
return new PutResponse<>(Status.CREATED, createInternal(updated, newOwner));
}
setFields(stored, MODEL_UPDATE_FIELDS);
updated.setId(stored.getId());
validateRelationships(updated, newOwner);
modelDAO().update(storedModel.getId().toString(), JsonUtils.pojoToJson(storedModel));
// Update owner relationship
setFields(storedModel, MODEL_UPDATE_FIELDS); // First get the ownership information
updateOwner(storedModel, storedModel.getOwner(), newOwner);
return new PutResponse<>(Status.OK, storedModel);
ModelUpdater modelUpdater = new ModelUpdater(stored, updated, false);
modelUpdater.updateAll();
modelUpdater.store();
return new PutResponse<>(Status.OK, updated);
}
@Transaction
@ -202,6 +203,7 @@ public abstract class ModelRepository {
private Model setFields(Model model, Fields fields) throws IOException {
model.setDisplayName(model.getDisplayName());
model.setOwner(fields.contains("owner") ? getOwner(model) : null);
model.setDashboard(fields.contains("dashboard") ? getDashboard(model) : null);
model.setFollowers(fields.contains("followers") ? getFollowers(model) : null);
model.setTags(fields.contains("tags") ? getTags(model.getFullyQualifiedName()) : null);
model.setUsageSummary(fields.contains("usageSummary") ? EntityUtil.getLatestUsage(usageDAO(),
@ -216,35 +218,56 @@ public abstract class ModelRepository {
private Model createInternal(Model model, EntityReference owner)
throws IOException {
String fqn = model.getName();
model.setFullyQualifiedName(fqn);
EntityUtil.populateOwner(userDAO(), teamDAO(), owner); // Validate owner
modelDAO().insert(JsonUtils.pojoToJson(model));
validateRelationships(model, owner);
storeModel(model, false);
addRelationships(model);
return model;
}
private void validateRelationships(Model model, EntityReference owner) throws IOException {
model.setFullyQualifiedName(getFQN(model));
EntityUtil.populateOwner(userDAO(), teamDAO(), owner); // Validate owner
if (model.getDashboard() != null) {
String dashboardId = model.getDashboard().getId().toString();
model.setDashboard(EntityUtil.getEntityReference(
EntityUtil.validate(dashboardId, dashboardDAO().findById(dashboardId), Dashboard.class)));
}
model.setTags(EntityUtil.addDerivedTags(tagDAO(), model.getTags()));
}
private void addRelationships(Model model) throws IOException {
setOwner(model, model.getOwner());
setDashboard(model, model.getDashboard());
applyTags(model);
}
private void storeModel(Model model, boolean update) throws JsonProcessingException {
// Relationships and fields such as href are derived and not stored as part of json
EntityReference owner = model.getOwner();
List<TagLabel> tags = model.getTags();
EntityReference dashboard = model.getDashboard();
// Don't store owner, dashboard, href and tags as JSON. Build it on the fly based on relationships
model.withOwner(null).withDashboard(null).withHref(null).withTags(null);
if (update) {
modelDAO().update(model.getId().toString(), JsonUtils.pojoToJson(model));
} else {
modelDAO().insert(JsonUtils.pojoToJson(model));
}
// Restore the relationships
model.withOwner(owner).withDashboard(dashboard).withTags(tags);
}
private void patch(Model original, Model updated) throws IOException {
String modelId = original.getId().toString();
if (!original.getId().equals(updated.getId())) {
throw new IllegalArgumentException(CatalogExceptionMessage.readOnlyAttribute(Entity.MODEL, "id"));
}
if (!original.getName().equals(updated.getName())) {
throw new IllegalArgumentException(CatalogExceptionMessage.readOnlyAttribute(Entity.MODEL, "name"));
}
// Validate new owner
EntityReference newOwner = EntityUtil.populateOwner(userDAO(), teamDAO(), updated.getOwner());
// Remove previous tags. Merge tags from the update and the existing tags
EntityUtil.removeTags(tagDAO(), original.getFullyQualifiedName());
updated.setHref(null);
updated.setOwner(null);
modelDAO().update(modelId, JsonUtils.pojoToJson(updated));
updateOwner(updated, original.getOwner(), newOwner);
applyTags(updated);
// Patch can't make changes to following fields. Ignore the changes
updated.withFullyQualifiedName(original.getFullyQualifiedName()).withName(original.getName())
.withId(original.getId());
validateRelationships(updated, updated.getOwner());
ModelRepository.ModelUpdater modelUpdater = new ModelRepository.ModelUpdater(original, updated, true);
modelUpdater.updateAll();
modelUpdater.store();
}
private EntityReference getOwner(Model model) throws IOException {
@ -257,9 +280,30 @@ public abstract class ModelRepository {
model.setOwner(owner);
}
private void updateOwner(Model model, EntityReference origOwner, EntityReference newOwner) {
EntityUtil.updateOwner(relationshipDAO(), origOwner, newOwner, model.getId(), Entity.MODEL);
model.setOwner(newOwner);
private EntityReference getDashboard(Model model) throws IOException {
if (model != null) {
List<EntityReference> ids = relationshipDAO().findTo(model.getId().toString(), Relationship.USES.ordinal());
if (ids.size() > 1) {
LOG.warn("Possible database issues - multiple dashboards {} found for model {}", ids, model.getId());
}
if (!ids.isEmpty()) {
String dashboardId = ids.get(0).getId().toString();
return EntityUtil.getEntityReference(EntityUtil.validate(dashboardId, dashboardDAO().findById(dashboardId),
Dashboard.class));
}
}
return null;
}
public void setDashboard(Model model, EntityReference dashboard) {
if (dashboard != null) {
relationshipDAO().insert(model.getId().toString(), model.getDashboard().getId().toString(),
Entity.MODEL, Entity.DASHBOARD, Relationship.USES.ordinal());
}
}
public void removeDashboard(Model model) {
relationshipDAO().deleteFrom(model.getId().toString(), Relationship.USES.ordinal(), Entity.DASHBOARD);
}
private void applyTags(Model model) throws IOException {
@ -268,13 +312,6 @@ public abstract class ModelRepository {
model.setTags(getTags(model.getFullyQualifiedName())); // Update tag to handle additional derived tags
}
private void addRelationships(Model model) throws IOException {
// Add owner relationship
EntityUtil.setOwner(relationshipDAO(), model.getId(), Entity.MODEL, model.getOwner());
// Add tag to model relationship
applyTags(model);
}
private List<EntityReference> getFollowers(Model model) throws IOException {
return model == null ? null : EntityUtil.getFollowers(model.getId(), relationshipDAO(), userDAO());
}
@ -319,4 +356,99 @@ public abstract class ModelRepository {
@SqlUpdate("DELETE FROM model_entity WHERE id = :id")
int delete(@Bind("id") String id);
}
static class ModelEntityInterface implements EntityInterface {
private final Model model;
ModelEntityInterface(Model Model) {
this.model = Model;
}
@Override
public UUID getId() {
return model.getId();
}
@Override
public String getDescription() {
return model.getDescription();
}
@Override
public String getDisplayName() {
return model.getDisplayName();
}
@Override
public EntityReference getOwner() {
return model.getOwner();
}
@Override
public String getFullyQualifiedName() {
return model.getFullyQualifiedName();
}
@Override
public List<TagLabel> getTags() {
return model.getTags();
}
@Override
public void setDescription(String description) {
model.setDescription(description);
}
@Override
public void setDisplayName(String displayName) {
model.setDisplayName(displayName);
}
@Override
public void setTags(List<TagLabel> tags) {
model.setTags(tags);
}
}
/**
* Handles entity updated from PUT and POST operation.
*/
public class ModelUpdater extends EntityUpdater {
final Model orig;
final Model updated;
public ModelUpdater(Model orig, Model updated, boolean patchOperation) {
super(new ModelRepository.ModelEntityInterface(orig), new ModelRepository.ModelEntityInterface(updated), patchOperation, relationshipDAO(),
tagDAO());
this.orig = orig;
this.updated = updated;
}
public void updateAll() throws IOException {
super.updateAll();
updateAlgorithm();
updateDashboard();
}
private void updateAlgorithm() {
update("algorithm", orig.getAlgorithm(), updated.getAlgorithm());
}
private void updateDashboard() {
// Remove existing dashboards
removeDashboard(orig);
EntityReference origOwner = orig.getDashboard();
EntityReference updatedOwner = updated.getDashboard();
if (update("owner", origOwner == null ? null : origOwner.getId(),
updatedOwner == null ? null : updatedOwner.getId())) {
setDashboard(updated, updated.getDashboard());
}
}
public void store() throws IOException {
updated.setVersion(getNewVersion(orig.getVersion()));
storeModel(updated, true);
}
}
}

View File

@ -54,6 +54,7 @@ public enum Relationship {
// {Dashboard|Pipeline|Query} --- uses ---> Table
// {User} --- uses ---> {Table|Dashboard|Query}
// {Model} --- uses ---> {Dashboard}
USES("uses"),
// {User|Team|Org} --- owns ---> {Table|Dashboard|Query}

View File

@ -336,7 +336,7 @@ public class DashboardResourceTest extends CatalogApplicationTest {
Dashboard dashboard = createAndCheckDashboard(request, adminAuthHeaders());
//Update the table as Owner
updateAndCheckDashboard(dashboard, request.withDisplayName(test.getDisplayName()).withDescription(null),
OK, authHeaders(USER1.getEmail()), NO_CHANGE);
OK, authHeaders(USER1.getEmail()), MINOR_UPDATE);
}

View File

@ -524,7 +524,6 @@ public class TableResourceTest extends CatalogApplicationTest {
updatedColumns.remove(1);
table = updateAndCheckTable(table, request.withColumns(updatedColumns), OK, adminAuthHeaders(), MAJOR_UPDATE);
assertEquals(1, table.getColumns().size());
validateTags(columns.get(0), table.getColumns().get(0));
// Ensure tag usage counts are updated to reflect removal of column c2
assertEquals(tagCategoryUsageCount + 2, getTagCategoryUsageCount("user", userAuthHeaders()));

View File

@ -26,15 +26,15 @@ import org.openmetadata.catalog.Entity;
import org.openmetadata.catalog.api.data.CreateModel;
import org.openmetadata.catalog.api.services.CreateDashboardService;
import org.openmetadata.catalog.api.services.CreateDashboardService.DashboardServiceType;
import org.openmetadata.catalog.entity.data.Model;
import org.openmetadata.catalog.entity.data.Dashboard;
import org.openmetadata.catalog.entity.data.Model;
import org.openmetadata.catalog.entity.services.DashboardService;
import org.openmetadata.catalog.entity.teams.Team;
import org.openmetadata.catalog.entity.teams.User;
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
import org.openmetadata.catalog.resources.dashboards.DashboardResourceTest;
import org.openmetadata.catalog.resources.models.ModelResource.ModelList;
import org.openmetadata.catalog.resources.services.DashboardServiceResourceTest;
import org.openmetadata.catalog.resources.dashboards.DashboardResourceTest;
import org.openmetadata.catalog.resources.tags.TagResourceTest;
import org.openmetadata.catalog.resources.teams.TeamResourceTest;
import org.openmetadata.catalog.resources.teams.UserResourceTest;
@ -43,6 +43,7 @@ import org.openmetadata.catalog.type.TagLabel;
import org.openmetadata.catalog.util.EntityUtil;
import org.openmetadata.catalog.util.JsonUtils;
import org.openmetadata.catalog.util.TestUtils;
import org.openmetadata.catalog.util.TestUtils.UpdateType;
import org.openmetadata.common.utils.JsonSchemaUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -70,6 +71,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.openmetadata.catalog.exception.CatalogExceptionMessage.entityNotFound;
import static org.openmetadata.catalog.exception.CatalogExceptionMessage.readOnlyAttribute;
import static org.openmetadata.catalog.util.TestUtils.UpdateType.MINOR_UPDATE;
import static org.openmetadata.catalog.util.TestUtils.UpdateType.NO_CHANGE;
import static org.openmetadata.catalog.util.TestUtils.adminAuthHeaders;
import static org.openmetadata.catalog.util.TestUtils.assertEntityPagination;
import static org.openmetadata.catalog.util.TestUtils.assertResponse;
@ -279,52 +282,50 @@ public class ModelResourceTest extends CatalogApplicationTest {
public void put_ModelUpdateWithNoChange_200(TestInfo test) throws HttpResponseException {
// Create a Model with POST
CreateModel request = create(test).withOwner(USER_OWNER1);
createAndCheckModel(request, adminAuthHeaders());
Model model = createAndCheckModel(request, adminAuthHeaders());
// Update Model two times successfully with PUT requests
updateAndCheckModel(request, OK, adminAuthHeaders());
updateAndCheckModel(request, OK, adminAuthHeaders());
model = updateAndCheckModel(model, request, OK, adminAuthHeaders(), NO_CHANGE);
updateAndCheckModel(model, request, OK, adminAuthHeaders(), NO_CHANGE);
}
@Test
public void put_ModelCreate_200(TestInfo test) throws HttpResponseException {
// Create a new Model with put
// Create a new Model with PUT
CreateModel request = create(test).withOwner(USER_OWNER1);
updateAndCheckModel(request.withName(test.getDisplayName()).withDescription(null), CREATED, adminAuthHeaders());
updateAndCheckModel(null, request.withName(test.getDisplayName()).withDescription(null), CREATED,
adminAuthHeaders(), NO_CHANGE);
}
@Test
public void put_ModelCreate_as_owner_200(TestInfo test) throws HttpResponseException {
// Create a new Model with put
CreateModel request = create(test).withOwner(USER_OWNER1);
// Add Owner as admin
createAndCheckModel(request, adminAuthHeaders());
//Update the table as Owner
updateAndCheckModel(request.withName(test.getDisplayName()).withDescription(null),
CREATED, authHeaders(USER1.getEmail()));
// Add model as admin
Model model = createAndCheckModel(request, adminAuthHeaders());
// Update the table as Owner
updateAndCheckModel(model, request, OK, authHeaders(USER1.getEmail()), NO_CHANGE);
}
@Test
public void put_ModelNullDescriptionUpdate_200(TestInfo test) throws HttpResponseException {
CreateModel request = create(test).withDescription(null);
createAndCheckModel(request, adminAuthHeaders());
Model model = createAndCheckModel(request, adminAuthHeaders());
// Update null description with a new description
Model db = updateAndCheckModel(request.withDisplayName("model1").
withDescription("newDescription"), OK, adminAuthHeaders());
assertEquals("newDescription", db.getDescription());
assertEquals("model1", db.getDisplayName());
Model db = updateAndCheckModel(model, request.withDisplayName("model1").
withDescription("newDescription"), OK, adminAuthHeaders(), MINOR_UPDATE);
assertEquals("model1", db.getDisplayName()); // Move this check to validate method
}
@Test
public void put_ModelEmptyDescriptionUpdate_200(TestInfo test) throws HttpResponseException {
// Create table with empty description
CreateModel request = create(test).withDescription("");
createAndCheckModel(request, adminAuthHeaders());
Model model = createAndCheckModel(request, adminAuthHeaders());
// Update empty description with a new description
Model db = updateAndCheckModel(request.withDescription("newDescription"), OK, adminAuthHeaders());
assertEquals("newDescription", db.getDescription());
updateAndCheckModel(model, request.withDescription("newDescription"), OK, adminAuthHeaders(), MINOR_UPDATE);
}
@Test
@ -340,30 +341,28 @@ public class ModelResourceTest extends CatalogApplicationTest {
@Test
public void put_ModelUpdateOwner_200(TestInfo test) throws HttpResponseException {
CreateModel request = create(test).withDescription("");
createAndCheckModel(request, adminAuthHeaders());
Model model = createAndCheckModel(request, adminAuthHeaders());
// Change ownership from USER_OWNER1 to TEAM_OWNER1
updateAndCheckModel(request.withOwner(TEAM_OWNER1), OK, adminAuthHeaders());
model = updateAndCheckModel(model, request.withOwner(TEAM_OWNER1), OK, adminAuthHeaders(), MINOR_UPDATE);
// Remove ownership
Model db = updateAndCheckModel(request.withOwner(null), OK, adminAuthHeaders());
assertNull(db.getOwner());
model = updateAndCheckModel(model, request.withOwner(null), OK, adminAuthHeaders(), MINOR_UPDATE);
assertNull(model.getOwner());
}
@Test
public void put_ModelUpdateAlgorithm_200(TestInfo test) throws HttpResponseException {
CreateModel request = create(test).withDescription("");
createAndCheckModel(request, adminAuthHeaders());
updateAndCheckModel(request.withAlgorithm("SVM"), OK, adminAuthHeaders());
Model model = createAndCheckModel(request, adminAuthHeaders());
updateAndCheckModel(model, request.withAlgorithm("SVM"), OK, adminAuthHeaders(), MINOR_UPDATE);
}
@Test
public void put_ModelUpdateDashboard_200(TestInfo test) throws HttpResponseException {
CreateModel request = create(test).withDescription("");
createAndCheckModel(request, adminAuthHeaders());
updateAndCheckModel(request.withDashboard(DASHBOARD_REFERENCE), OK, adminAuthHeaders());
Model model = createAndCheckModel(request, adminAuthHeaders());
updateAndCheckModel(model, request.withDashboard(DASHBOARD_REFERENCE), OK, adminAuthHeaders(), MINOR_UPDATE);
}
@Test
@ -401,52 +400,18 @@ public class ModelResourceTest extends CatalogApplicationTest {
List<TagLabel> modelTags = singletonList(TIER_1);
// Add description, owner when previously they were null
model = patchModelAttributesAndCheck(model, "description",
TEAM_OWNER1, modelTags, adminAuthHeaders());
model = patchModelAttributesAndCheck(model, "description", TEAM_OWNER1, modelTags,
adminAuthHeaders(), MINOR_UPDATE);
model.setOwner(TEAM_OWNER1); // Get rid of href and name returned in the response for owner
modelTags = singletonList(USER_ADDRESS_TAG_LABEL);
// Replace description, tier, owner
model = patchModelAttributesAndCheck(model, "description1",
USER_OWNER1, modelTags, adminAuthHeaders());
model = patchModelAttributesAndCheck(model, "description1", USER_OWNER1, modelTags,
adminAuthHeaders(), MINOR_UPDATE);
model.setOwner(USER_OWNER1); // Get rid of href and name returned in the response for owner
// Remove description, tier, owner
patchModelAttributesAndCheck(model, null, null, modelTags, adminAuthHeaders());
}
@Test
public void patch_ModelIDChange_400(TestInfo test) throws HttpResponseException, JsonProcessingException {
// Ensure Model ID can't be changed using patch
Model model = createModel(create(test), adminAuthHeaders());
UUID modelId = model.getId();
String modelJson = JsonUtils.pojoToJson(model);
model.setId(UUID.randomUUID());
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
patchModel(modelId, modelJson, model, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.MODEL, "id"));
// ID can't be deleted
model.setId(null);
exception = assertThrows(HttpResponseException.class, () ->
patchModel(modelId, modelJson, model, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.MODEL, "id"));
}
@Test
public void patch_ModelNameChange_400(TestInfo test) throws HttpResponseException, JsonProcessingException {
// Ensure Model name can't be changed using patch
Model model = createModel(create(test), adminAuthHeaders());
String modelJson = JsonUtils.pojoToJson(model);
model.setName("newName");
HttpResponseException exception = assertThrows(HttpResponseException.class, () ->
patchModel(modelJson, model, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.MODEL, "name"));
// Name can't be removed
model.setName(null);
exception = assertThrows(HttpResponseException.class, () ->
patchModel(modelJson, model, adminAuthHeaders()));
assertResponse(exception, BAD_REQUEST, readOnlyAttribute(Entity.MODEL, "name"));
patchModelAttributesAndCheck(model, null, null, modelTags, adminAuthHeaders(), MINOR_UPDATE);
}
@Test
@ -485,12 +450,17 @@ public class ModelResourceTest extends CatalogApplicationTest {
return getAndValidate(model.getId(), create, authHeaders, updatedBy);
}
public static Model updateAndCheckModel(CreateModel create,
Status status,
Map<String, String> authHeaders) throws HttpResponseException {
public static Model updateAndCheckModel(Model before, CreateModel create, Status status,
Map<String, String> authHeaders, UpdateType updateType)
throws HttpResponseException {
String updatedBy = TestUtils.getPrincipal(authHeaders);
Model updatedModel = updateModel(create, status, authHeaders);
validateModel(updatedModel, create.getDescription(), create.getOwner(), updatedBy);
if (before == null) {
assertEquals(0.1, updatedModel.getVersion()); // First version created
} else {
TestUtils.validateUpdate(before.getVersion(), updatedModel.getVersion(), updateType);
}
return getAndValidate(updatedModel.getId(), create, authHeaders, updatedBy);
}
@ -529,8 +499,8 @@ public class ModelResourceTest extends CatalogApplicationTest {
model = byName ? getModelByName(model.getFullyQualifiedName(), fields, adminAuthHeaders()) :
getModel(model.getId(), fields, adminAuthHeaders());
assertNotNull(model.getOwner());
assertNotNull(model.getAlgorithm());
assertNotNull(model.getDashboard());
assertNotNull(model.getAlgorithm()); // Provided as default field
assertNull(model.getDashboard());
// .../models?fields=owner,algorithm
fields = "owner,algorithm";
@ -538,7 +508,7 @@ public class ModelResourceTest extends CatalogApplicationTest {
getModel(model.getId(), fields, adminAuthHeaders());
assertNotNull(model.getOwner());
assertNotNull(model.getAlgorithm());
assertNotNull(model.getDashboard());
assertNull(model.getDashboard());
// .../models?fields=owner,algorithm, dashboard
fields = "owner,algorithm,dashboard";
@ -548,7 +518,6 @@ public class ModelResourceTest extends CatalogApplicationTest {
assertNotNull(model.getAlgorithm());
assertNotNull(model.getDashboard());
TestUtils.validateEntityReference(model.getDashboard());
}
private static Model validateModel(Model model, String expectedDisplayName,
@ -595,48 +564,30 @@ public class ModelResourceTest extends CatalogApplicationTest {
assertNotNull(model.getOwner().getHref());
}
validateTags(expectedTags, model.getTags());
TestUtils.validateTags(model.getFullyQualifiedName(), expectedTags, model.getTags());
return model;
}
private static void validateTags(List<TagLabel> expectedList, List<TagLabel> actualList)
throws HttpResponseException {
if (expectedList == null) {
return;
}
// When tags from the expected list is added to an entity, the derived tags for those tags are automatically added
// So add to the expectedList, the derived tags before validating the tags
List<TagLabel> updatedExpectedList = new ArrayList<>(expectedList);
for (TagLabel expected : expectedList) {
List<TagLabel> derived = EntityUtil.getDerivedTags(expected, TagResourceTest.getTag(expected.getTagFQN(),
adminAuthHeaders()));
updatedExpectedList.addAll(derived);
}
updatedExpectedList = updatedExpectedList.stream().distinct().collect(Collectors.toList());
assertTrue(actualList.containsAll(updatedExpectedList));
assertTrue(updatedExpectedList.containsAll(actualList));
}
private Model patchModelAttributesAndCheck(Model model, String newDescription,
EntityReference newOwner, List<TagLabel> tags,
Map<String, String> authHeaders)
private Model patchModelAttributesAndCheck(Model before, String newDescription, EntityReference newOwner,
List<TagLabel> tags, Map<String, String> authHeaders,
UpdateType updateType)
throws JsonProcessingException, HttpResponseException {
String updatedBy = TestUtils.getPrincipal(authHeaders);
String modelJson = JsonUtils.pojoToJson(model);
String modelJson = JsonUtils.pojoToJson(before);
// Update the table attributes
model.setDescription(newDescription);
model.setOwner(newOwner);
model.setTags(tags);
before.setDescription(newDescription);
before.setOwner(newOwner);
before.setTags(tags);
// Validate information returned in patch response has the updates
Model updatedModel = patchModel(modelJson, model, authHeaders);
validateModel(updatedModel, model.getDescription(), newOwner, tags, updatedBy);
Model updatedModel = patchModel(modelJson, before, authHeaders);
validateModel(updatedModel, before.getDescription(), newOwner, tags, updatedBy);
TestUtils.validateUpdate(before.getVersion(), updatedModel.getVersion(), updateType);
// GET the table and Validate information returned
Model getModel = getModel(model.getId(), "owner,tags", authHeaders);
validateModel(getModel, model.getDescription(), newOwner, tags, updatedBy);
Model getModel = getModel(before.getId(), "owner,tags", authHeaders);
validateModel(getModel, before.getDescription(), newOwner, tags, updatedBy);
return updatedModel;
}