Fixes #3069 - Add PATCH support Glossary and GlossaryTerms (#3070)

This commit is contained in:
Suresh Srinivas 2022-03-02 01:02:16 -08:00 committed by GitHub
parent bf25787709
commit c56e78f3f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 272 additions and 83 deletions

View File

@ -408,6 +408,7 @@ public abstract class EntityRepository<T> {
T original = setFields(dao.findEntityById(id), patchFields); T original = setFields(dao.findEntityById(id), patchFields);
// Apply JSON patch to the original entity to get the updated entity // Apply JSON patch to the original entity to get the updated entity
System.out.println("XXX patch is " + patch);
T updated = JsonUtils.applyPatch(original, patch, entityClass); T updated = JsonUtils.applyPatch(original, patch, entityClass);
EntityInterface<T> updatedEntity = getEntityInterface(updated); EntityInterface<T> updatedEntity = getEntityInterface(updated);
updatedEntity.setUpdateDetails(user, System.currentTimeMillis()); updatedEntity.setUpdateDetails(user, System.currentTimeMillis());
@ -861,6 +862,14 @@ public abstract class EntityRepository<T> {
return daoCollection.relationshipDAO().insert(fromId, toId, fromEntity, toEntity, relationship.ordinal()); return daoCollection.relationshipDAO().insert(fromId, toId, fromEntity, toEntity, relationship.ordinal());
} }
public int addBidirectionalRelationship(
UUID fromId, UUID toId, String fromEntity, String toEntity, Relationship relationship) {
if (fromId.compareTo(toId) < 0) {
return daoCollection.relationshipDAO().insert(fromId, toId, fromEntity, toEntity, relationship.ordinal());
}
return daoCollection.relationshipDAO().insert(toId, fromId, toEntity, fromEntity, relationship.ordinal());
}
public void setOwner(UUID ownedEntityId, String ownedEntityType, EntityReference owner) { public void setOwner(UUID ownedEntityId, String ownedEntityType, EntityReference owner) {
// Add relationship owner --- owns ---> ownedEntity // Add relationship owner --- owns ---> ownedEntity
if (owner != null) { if (owner != null) {
@ -891,6 +900,26 @@ public abstract class EntityRepository<T> {
.findTo(fromId.toString(), fromEntity, relationship.ordinal(), toEntity, deleted); .findTo(fromId.toString(), fromEntity, relationship.ordinal(), toEntity, deleted);
} }
public void validateUsers(List<EntityReference> entityReferences) throws IOException {
if (entityReferences != null) {
entityReferences.sort(EntityUtil.compareEntityReference);
for (EntityReference entityReference : entityReferences) {
EntityReference ref = daoCollection.userDAO().findEntityReferenceById(entityReference.getId());
entityReference.withType(ref.getType()).withName(ref.getName()).withDisplayName(ref.getDisplayName());
}
}
}
public void validateRoles(List<EntityReference> entityReferences) throws IOException {
if (entityReferences != null) {
entityReferences.sort(EntityUtil.compareEntityReference);
for (EntityReference entityReference : entityReferences) {
EntityReference ref = daoCollection.roleDAO().findEntityReferenceById(entityReference.getId());
entityReference.withType(ref.getType()).withName(ref.getName()).withDisplayName(ref.getDisplayName());
}
}
}
enum Operation { enum Operation {
PUT, PUT,
PATCH, PATCH,
@ -1135,6 +1164,77 @@ public abstract class EntityRepository<T> {
return !addedItems.isEmpty() || !deletedItems.isEmpty(); return !addedItems.isEmpty() || !deletedItems.isEmpty();
} }
/**
* Remove `fromEntityType:fromId` -- `relationType` ---> `toEntityType:origToRefs` Add `fromEntityType:fromId` --
* `relationType` ---> `toEntityType:updatedToRefs` and record it as change for entity field `field`.
*/
public final void updateToRelationships(
String field,
String fromEntityType,
UUID fromId,
Relationship relationshipType,
String toEntityType,
List<EntityReference> origToRefs,
List<EntityReference> updatedToRefs)
throws JsonProcessingException {
List<EntityReference> added = new ArrayList<>();
List<EntityReference> deleted = new ArrayList<>();
if (!recordListChange(field, origToRefs, updatedToRefs, added, deleted, entityReferenceMatch)) {
// No changes between original and updated.
return;
}
// Remove relationships from original
daoCollection
.relationshipDAO()
.deleteFrom(fromId.toString(), fromEntityType, relationshipType.ordinal(), toEntityType);
// Add relationships from updated
for (EntityReference ref : updatedToRefs) {
System.out.println(
String.format(
"XXX relationship %s:%s to %s:%s of type %s",
fromEntityType, fromId, toEntityType, ref.getId(), relationshipType));
addRelationship(fromId, ref.getId(), fromEntityType, toEntityType, relationshipType);
}
updatedToRefs.sort(EntityUtil.compareEntityReference);
origToRefs.sort(EntityUtil.compareEntityReference);
}
/**
* Remove `fromEntityType:origFromRefs` -- `relationType` ---> `toEntityType:toId` Add
* `fromEntityType:updatedFromRefs` -- `relationType` ---> `toEntityType:toId` and record it as change for entity
* field `field`.
*/
public final void updateFromRelationships(
String field,
String fromEntityType,
List<EntityReference> originFromRefs,
List<EntityReference> updatedFromRefs,
Relationship relationshipType,
String toEntityType,
UUID toId)
throws JsonProcessingException {
List<EntityReference> added = new ArrayList<>();
List<EntityReference> deleted = new ArrayList<>();
if (!recordListChange(field, originFromRefs, updatedFromRefs, added, deleted, entityReferenceMatch)) {
// No changes between original and updated.
return;
}
// Remove relationships from original
daoCollection
.relationshipDAO()
.deleteTo(toId.toString(), fromEntityType, relationshipType.ordinal(), toEntityType);
// Add relationships from updated
for (EntityReference ref : updatedFromRefs) {
System.out.println(
String.format(
"XXX relationship %s:%s to %s:%s of type %s",
fromEntityType, ref, toEntityType, toId, relationshipType));
addRelationship(ref.getId(), toId, fromEntityType, toEntityType, relationshipType);
}
updatedFromRefs.sort(EntityUtil.compareEntityReference);
originFromRefs.sort(EntityUtil.compareEntityReference);
}
public final void storeUpdate() throws IOException { public final void storeUpdate() throws IOException {
if (updateVersion(original.getVersion())) { // Update changed the entity version if (updateVersion(original.getVersion())) { // Update changed the entity version
storeOldVersion(); // Store old version for listing previous versions of the entity storeOldVersion(); // Store old version for listing previous versions of the entity

View File

@ -19,10 +19,13 @@ package org.openmetadata.catalog.jdbi3;
import static org.openmetadata.catalog.Entity.FIELD_OWNER; import static org.openmetadata.catalog.Entity.FIELD_OWNER;
import static org.openmetadata.catalog.Entity.helper; import static org.openmetadata.catalog.Entity.helper;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.text.ParseException; import java.text.ParseException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.jdbi.v3.sqlobject.transaction.Transaction; import org.jdbi.v3.sqlobject.transaction.Transaction;
import org.openmetadata.catalog.Entity; import org.openmetadata.catalog.Entity;
@ -30,6 +33,7 @@ import org.openmetadata.catalog.entity.data.Glossary;
import org.openmetadata.catalog.resources.glossary.GlossaryResource; import org.openmetadata.catalog.resources.glossary.GlossaryResource;
import org.openmetadata.catalog.type.ChangeDescription; import org.openmetadata.catalog.type.ChangeDescription;
import org.openmetadata.catalog.type.EntityReference; import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.type.Relationship;
import org.openmetadata.catalog.type.TagLabel; import org.openmetadata.catalog.type.TagLabel;
import org.openmetadata.catalog.util.EntityInterface; import org.openmetadata.catalog.util.EntityInterface;
import org.openmetadata.catalog.util.EntityUtil; import org.openmetadata.catalog.util.EntityUtil;
@ -37,8 +41,8 @@ import org.openmetadata.catalog.util.EntityUtil.Fields;
import org.openmetadata.catalog.util.JsonUtils; import org.openmetadata.catalog.util.JsonUtils;
public class GlossaryRepository extends EntityRepository<Glossary> { public class GlossaryRepository extends EntityRepository<Glossary> {
private static final Fields GLOSSARY_UPDATE_FIELDS = new Fields(GlossaryResource.ALLOWED_FIELDS, "owner,tags"); private static final Fields UPDATE_FIELDS = new Fields(GlossaryResource.ALLOWED_FIELDS, "owner,tags,reviewers");
private static final Fields GLOSSARY_PATCH_FIELDS = new Fields(GlossaryResource.ALLOWED_FIELDS, "owner,tags"); private static final Fields PATCH_FIELDS = new Fields(GlossaryResource.ALLOWED_FIELDS, "owner,tags,reviewers");
public GlossaryRepository(CollectionDAO dao) { public GlossaryRepository(CollectionDAO dao) {
super( super(
@ -47,8 +51,8 @@ public class GlossaryRepository extends EntityRepository<Glossary> {
Glossary.class, Glossary.class,
dao.glossaryDAO(), dao.glossaryDAO(),
dao, dao,
GLOSSARY_PATCH_FIELDS, PATCH_FIELDS,
GLOSSARY_UPDATE_FIELDS, UPDATE_FIELDS,
true, true,
true, true,
false); false);
@ -63,13 +67,14 @@ public class GlossaryRepository extends EntityRepository<Glossary> {
public Glossary setFields(Glossary glossary, Fields fields) throws IOException, ParseException { public Glossary setFields(Glossary glossary, Fields fields) throws IOException, ParseException {
glossary.setOwner(fields.contains(FIELD_OWNER) ? getOwner(glossary) : null); glossary.setOwner(fields.contains(FIELD_OWNER) ? getOwner(glossary) : null);
glossary.setTags(fields.contains("tags") ? getTags(glossary.getName()) : null); glossary.setTags(fields.contains("tags") ? getTags(glossary.getName()) : null);
glossary.setReviewers(fields.contains("reviewers") ? getReviewers(glossary) : null);
return glossary; return glossary;
} }
@Override @Override
public void prepare(Glossary glossary) throws IOException, ParseException { public void prepare(Glossary glossary) throws IOException, ParseException {
glossary.setOwner(helper(glossary).validateOwnerOrNull()); glossary.setOwner(helper(glossary).validateOwnerOrNull());
// TODO validate reviewers validateUsers(glossary.getReviewers());
glossary.setTags(EntityUtil.addDerivedTags(daoCollection.tagDAO(), glossary.getTags())); glossary.setTags(EntityUtil.addDerivedTags(daoCollection.tagDAO(), glossary.getTags()));
} }
@ -78,7 +83,7 @@ public class GlossaryRepository extends EntityRepository<Glossary> {
// Relationships and fields such as href are derived and not stored as part of json // Relationships and fields such as href are derived and not stored as part of json
EntityReference owner = glossary.getOwner(); EntityReference owner = glossary.getOwner();
List<TagLabel> tags = glossary.getTags(); List<TagLabel> tags = glossary.getTags();
// TODO Add relationships for reviewers List<EntityReference> reviewers = glossary.getReviewers();
// Don't store owner, href and tags as JSON. Build it on the fly based on relationships // Don't store owner, href and tags as JSON. Build it on the fly based on relationships
glossary.withOwner(null).withHref(null).withTags(null); glossary.withOwner(null).withHref(null).withTags(null);
@ -90,14 +95,16 @@ public class GlossaryRepository extends EntityRepository<Glossary> {
} }
// Restore the relationships // Restore the relationships
glossary.withOwner(owner).withTags(tags); glossary.withOwner(owner).withTags(tags).withReviewers(reviewers);
} }
@Override @Override
public void storeRelationships(Glossary glossary) { public void storeRelationships(Glossary glossary) {
// TODO Add relationships for related terms, and reviewers
setOwner(glossary, glossary.getOwner()); setOwner(glossary, glossary.getOwner());
applyTags(glossary); applyTags(glossary);
for (EntityReference reviewer : Optional.ofNullable(glossary.getReviewers()).orElse(Collections.emptyList())) {
addRelationship(reviewer.getId(), glossary.getId(), Entity.USER, Entity.GLOSSARY, Relationship.REVIEWS);
}
} }
@Override @Override
@ -110,6 +117,13 @@ public class GlossaryRepository extends EntityRepository<Glossary> {
return new GlossaryUpdater(original, updated, operation); return new GlossaryUpdater(original, updated, operation);
} }
private List<EntityReference> getReviewers(Glossary entity) throws IOException {
List<String> ids =
findFrom(entity.getId(), Entity.GLOSSARY, Relationship.REVIEWS, Entity.USER, entity.getDeleted());
System.out.println("XXX reviewers for " + entity.getId() + " " + ids);
return EntityUtil.populateEntityReferences(ids, Entity.USER);
}
public static class GlossaryEntityInterface implements EntityInterface<Glossary> { public static class GlossaryEntityInterface implements EntityInterface<Glossary> {
private final Glossary entity; private final Glossary entity;
@ -260,5 +274,25 @@ public class GlossaryRepository extends EntityRepository<Glossary> {
public GlossaryUpdater(Glossary original, Glossary updated, Operation operation) { public GlossaryUpdater(Glossary original, Glossary updated, Operation operation) {
super(original, updated, operation); super(original, updated, operation);
} }
@Override
public void entitySpecificUpdate() throws IOException, ParseException {
updateReviewers(original.getEntity(), updated.getEntity());
}
private void updateReviewers(Glossary origGlossary, Glossary updatedGlossary) throws JsonProcessingException {
List<EntityReference> origUsers =
Optional.ofNullable(origGlossary.getReviewers()).orElse(Collections.emptyList());
List<EntityReference> updatedUsers =
Optional.ofNullable(updatedGlossary.getReviewers()).orElse(Collections.emptyList());
updateFromRelationships(
"reviewers",
Entity.USER,
origUsers,
updatedUsers,
Relationship.REVIEWS,
Entity.GLOSSARY,
origGlossary.getId());
}
} }
} }

View File

@ -16,9 +16,13 @@
package org.openmetadata.catalog.jdbi3; package org.openmetadata.catalog.jdbi3;
import static org.openmetadata.catalog.util.EntityUtil.stringMatch;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -39,8 +43,8 @@ import org.openmetadata.catalog.util.JsonUtils;
@Slf4j @Slf4j
public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> { public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
private static final Fields UPDATE_FIELDS = new Fields(GlossaryResource.ALLOWED_FIELDS, "tags"); private static final Fields UPDATE_FIELDS = new Fields(GlossaryResource.ALLOWED_FIELDS, "tags,reviewers");
private static final Fields PATCH_FIELDS = new Fields(GlossaryResource.ALLOWED_FIELDS, "tags"); private static final Fields PATCH_FIELDS = new Fields(GlossaryResource.ALLOWED_FIELDS, "tags,reviewers");
public GlossaryTermRepository(CollectionDAO dao) { public GlossaryTermRepository(CollectionDAO dao) {
super( super(
@ -166,11 +170,10 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
} }
for (EntityReference relTerm : Optional.ofNullable(entity.getRelatedTerms()).orElse(Collections.emptyList())) { for (EntityReference relTerm : Optional.ofNullable(entity.getRelatedTerms()).orElse(Collections.emptyList())) {
// Make this bidirectional relationship // Make this bidirectional relationship
addRelationship( addBidirectionalRelationship(
entity.getId(), relTerm.getId(), Entity.GLOSSARY_TERM, Entity.GLOSSARY_TERM, Relationship.RELATED_TO); entity.getId(), relTerm.getId(), Entity.GLOSSARY_TERM, Entity.GLOSSARY_TERM, Relationship.RELATED_TO);
} }
for (EntityReference reviewer : Optional.ofNullable(entity.getReviewers()).orElse(Collections.emptyList())) { for (EntityReference reviewer : Optional.ofNullable(entity.getReviewers()).orElse(Collections.emptyList())) {
// Make this bidirectional relationship
addRelationship(reviewer.getId(), entity.getId(), Entity.USER, Entity.GLOSSARY_TERM, Relationship.REVIEWS); addRelationship(reviewer.getId(), entity.getId(), Entity.USER, Entity.GLOSSARY_TERM, Relationship.REVIEWS);
} }
@ -349,7 +352,31 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
@Override @Override
public void entitySpecificUpdate() throws IOException { public void entitySpecificUpdate() throws IOException {
// TODO updateSynonyms(original.getEntity(), updated.getEntity());
updateReviewers(original.getEntity(), updated.getEntity());
}
private void updateSynonyms(GlossaryTerm origTerm, GlossaryTerm updatedTerm) throws JsonProcessingException {
List<String> origSynonyms = Optional.ofNullable(origTerm.getSynonyms()).orElse(Collections.emptyList());
List<String> updatedSynonyms = Optional.ofNullable(updatedTerm.getSynonyms()).orElse(Collections.emptyList());
List<String> added = new ArrayList<>();
List<String> deleted = new ArrayList<>();
recordListChange("synonyms", origSynonyms, updatedSynonyms, added, deleted, stringMatch);
}
private void updateReviewers(GlossaryTerm origTerm, GlossaryTerm updatedTerm) throws JsonProcessingException {
List<EntityReference> origUsers = Optional.ofNullable(origTerm.getReviewers()).orElse(Collections.emptyList());
List<EntityReference> updatedUsers =
Optional.ofNullable(updatedTerm.getReviewers()).orElse(Collections.emptyList());
updateFromRelationships(
"reviewers",
Entity.USER,
origUsers,
updatedUsers,
Relationship.REVIEWS,
Entity.GLOSSARY_TERM,
origTerm.getId());
} }
} }
} }

View File

@ -13,7 +13,6 @@
package org.openmetadata.catalog.jdbi3; package org.openmetadata.catalog.jdbi3;
import static org.openmetadata.catalog.util.EntityUtil.entityReferenceMatch;
import static org.openmetadata.catalog.util.EntityUtil.toBoolean; import static org.openmetadata.catalog.util.EntityUtil.toBoolean;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -63,26 +62,6 @@ public class TeamRepository extends EntityRepository<Team> {
return entityReferences; return entityReferences;
} }
public void validateEntityReferences(List<EntityReference> entityReferences, String entityType) throws IOException {
if (entityReferences != null) {
entityReferences.sort(EntityUtil.compareEntityReference);
for (EntityReference entityReference : entityReferences) {
EntityReference ref;
switch (entityType) {
case Entity.USER:
ref = daoCollection.userDAO().findEntityReferenceById(entityReference.getId());
break;
case Entity.ROLE:
ref = daoCollection.roleDAO().findEntityReferenceById(entityReference.getId());
break;
default:
throw new IllegalArgumentException("Unsupported entity reference for validation");
}
entityReference.withType(ref.getType()).withName(ref.getName()).withDisplayName(ref.getDisplayName());
}
}
}
@Override @Override
public Team setFields(Team team, Fields fields) throws IOException { public Team setFields(Team team, Fields fields) throws IOException {
if (!fields.contains("profile")) { if (!fields.contains("profile")) {
@ -107,8 +86,8 @@ public class TeamRepository extends EntityRepository<Team> {
@Override @Override
public void prepare(Team team) throws IOException { public void prepare(Team team) throws IOException {
validateEntityReferences(team.getUsers(), Entity.USER); validateUsers(team.getUsers());
validateEntityReferences(team.getDefaultRoles(), Entity.ROLE); validateRoles(team.getDefaultRoles());
} }
@Override @Override
@ -297,8 +276,8 @@ public class TeamRepository extends EntityRepository<Team> {
private void updateUsers(Team origTeam, Team updatedTeam) throws JsonProcessingException { private void updateUsers(Team origTeam, Team updatedTeam) throws JsonProcessingException {
List<EntityReference> origUsers = Optional.ofNullable(origTeam.getUsers()).orElse(Collections.emptyList()); List<EntityReference> origUsers = Optional.ofNullable(origTeam.getUsers()).orElse(Collections.emptyList());
List<EntityReference> updatedUsers = Optional.ofNullable(updatedTeam.getUsers()).orElse(Collections.emptyList()); List<EntityReference> updatedUsers = Optional.ofNullable(updatedTeam.getUsers()).orElse(Collections.emptyList());
updateEntityRelationships( updateToRelationships(
"users", origTeam.getId(), updatedTeam.getId(), Relationship.HAS, Entity.USER, origUsers, updatedUsers); "users", Entity.TEAM, origTeam.getId(), Relationship.HAS, Entity.USER, origUsers, updatedUsers);
} }
private void updateDefaultRoles(Team origTeam, Team updatedTeam) throws JsonProcessingException { private void updateDefaultRoles(Team origTeam, Team updatedTeam) throws JsonProcessingException {
@ -306,41 +285,14 @@ public class TeamRepository extends EntityRepository<Team> {
Optional.ofNullable(origTeam.getDefaultRoles()).orElse(Collections.emptyList()); Optional.ofNullable(origTeam.getDefaultRoles()).orElse(Collections.emptyList());
List<EntityReference> updatedDefaultRoles = List<EntityReference> updatedDefaultRoles =
Optional.ofNullable(updatedTeam.getDefaultRoles()).orElse(Collections.emptyList()); Optional.ofNullable(updatedTeam.getDefaultRoles()).orElse(Collections.emptyList());
updateEntityRelationships( updateToRelationships(
"defaultRoles", "defaultRoles",
Entity.TEAM,
origTeam.getId(), origTeam.getId(),
updatedTeam.getId(),
Relationship.HAS, Relationship.HAS,
Entity.ROLE, Entity.ROLE,
origDefaultRoles, origDefaultRoles,
updatedDefaultRoles); updatedDefaultRoles);
} }
private void updateEntityRelationships(
String field,
UUID origId,
UUID updatedId,
Relationship relationshipType,
String toEntityType,
List<EntityReference> origRefs,
List<EntityReference> updatedRefs)
throws JsonProcessingException {
List<EntityReference> added = new ArrayList<>();
List<EntityReference> deleted = new ArrayList<>();
if (!recordListChange(field, origRefs, updatedRefs, added, deleted, entityReferenceMatch)) {
// No changes between original and updated.
return;
}
// Remove relationships from original
daoCollection
.relationshipDAO()
.deleteFrom(origId.toString(), Entity.TEAM, relationshipType.ordinal(), toEntityType);
// Add relationships from updated
for (EntityReference ref : updatedRefs) {
addRelationship(updatedId, ref.getId(), Entity.TEAM, toEntityType, relationshipType);
}
updatedRefs.sort(EntityUtil.compareEntityReference);
origRefs.sort(EntityUtil.compareEntityReference);
}
} }
} }

View File

@ -172,7 +172,7 @@ public class UserRepository extends EntityRepository<User> {
.findTo(user.getId().toString(), Entity.USER, Relationship.FOLLOWS.ordinal(), toBoolean(toInclude(user)))); .findTo(user.getId().toString(), Entity.USER, Relationship.FOLLOWS.ordinal(), toBoolean(toInclude(user))));
} }
public List<EntityReference> validateRoles(List<UUID> roleIds) throws IOException { public List<EntityReference> validateRolesByIds(List<UUID> roleIds) throws IOException {
if (roleIds == null) { if (roleIds == null) {
return Collections.emptyList(); // Return an empty roles list return Collections.emptyList(); // Return an empty roles list
} }

View File

@ -88,6 +88,7 @@ public class GlossaryResource {
public static Glossary addHref(UriInfo uriInfo, Glossary glossary) { public static Glossary addHref(UriInfo uriInfo, Glossary glossary) {
glossary.setHref(RestUtil.getHref(uriInfo, COLLECTION_PATH, glossary.getId())); glossary.setHref(RestUtil.getHref(uriInfo, COLLECTION_PATH, glossary.getId()));
Entity.withHref(uriInfo, glossary.getOwner()); Entity.withHref(uriInfo, glossary.getOwner());
Entity.withHref(uriInfo, glossary.getReviewers());
return glossary; return glossary;
} }
@ -110,7 +111,7 @@ public class GlossaryResource {
} }
} }
static final String FIELDS = "owner,tags"; static final String FIELDS = "owner,tags,reviewers";
public static final List<String> ALLOWED_FIELDS = Entity.getEntityFields(Glossary.class); public static final List<String> ALLOWED_FIELDS = Entity.getEntityFields(Glossary.class);
@GET @GET

View File

@ -434,6 +434,6 @@ public class UserResource {
.withUpdatedBy(securityContext.getUserPrincipal().getName()) .withUpdatedBy(securityContext.getUserPrincipal().getName())
.withUpdatedAt(System.currentTimeMillis()) .withUpdatedAt(System.currentTimeMillis())
.withTeams(dao.validateTeams(create.getTeams())) .withTeams(dao.validateTeams(create.getTeams()))
.withRoles(dao.validateRoles(create.getRoles())); .withRoles(dao.validateRolesByIds(create.getRoles()));
} }
} }

View File

@ -18,10 +18,10 @@
"type": "string" "type": "string"
}, },
"reviewers": { "reviewers": {
"description": "User names of the reviewers for this glossary.", "description": "User references of the reviewers for this glossary.",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "$ref": "../../type/entityReference.json"
} }
}, },
"owner": { "owner": {

View File

@ -2,7 +2,7 @@
"$id": "https://open-metadata.org/schema/entity/data/glossary.json", "$id": "https://open-metadata.org/schema/entity/data/glossary.json",
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "Glossary", "title": "Glossary",
"description": "This schema defines the Glossary entity based on SKOS.", "description": "This schema defines the Glossary entity. A Glossary is collection of hierarchical GlossaryTerms.",
"type": "object", "type": "object",
"definitions": { "definitions": {
"name": { "name": {
@ -47,10 +47,10 @@
"$ref": "../../type/basic.json#/definitions/href" "$ref": "../../type/basic.json#/definitions/href"
}, },
"reviewers": { "reviewers": {
"description": "User names of the reviewers for this glossary.", "description": "User references of the reviewers for this glossary.",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "$ref": "../../type/entityReference.json"
} }
}, },
"owner": { "owner": {

View File

@ -2,7 +2,7 @@
"$id": "https://open-metadata.org/schema/entity/data/glossaryTerm.json", "$id": "https://open-metadata.org/schema/entity/data/glossaryTerm.json",
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "GlossaryTerm", "title": "GlossaryTerm",
"description": "This schema defines the Glossary term entities.", "description": "This schema defines te Glossary term entities.",
"type": "object", "type": "object",
"definitions": { "definitions": {
"name": { "name": {
@ -94,10 +94,6 @@
"description": "User who made the update.", "description": "User who made the update.",
"type": "string" "type": "string"
}, },
"skos": {
"description": "SKOS data in JSON-LD format",
"type": "string"
},
"href": { "href": {
"description": "Link to the resource corresponding to this entity.", "description": "Link to the resource corresponding to this entity.",
"$ref": "../../type/basic.json#/definitions/href" "$ref": "../../type/basic.json#/definitions/href"

View File

@ -1952,7 +1952,14 @@ public abstract class EntityResourceTest<T, K> extends CatalogApplicationTest {
for (EntityReference expected : expectedList) { for (EntityReference expected : expectedList) {
EntityReference actual = EntityReference actual =
actualList.stream().filter(a -> EntityUtil.entityReferenceMatch.test(a, expected)).findAny().orElse(null); actualList.stream().filter(a -> EntityUtil.entityReferenceMatch.test(a, expected)).findAny().orElse(null);
assertNotNull(actual, "Expected entity " + expected.getId() + " not found"); assertNotNull(actual, "Expected entity reference " + expected.getId() + " not found");
}
}
protected void assertStrings(List<String> expectedList, List<String> actualList) {
for (String expected : expectedList) {
String actual = actualList.stream().filter(a -> EntityUtil.stringMatch.test(a, expected)).findAny().orElse(null);
assertNotNull(actual, "Expected string " + expected + " not found");
} }
} }

View File

@ -22,10 +22,12 @@ import static org.openmetadata.catalog.util.TestUtils.assertListNull;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.http.client.HttpResponseException; import org.apache.http.client.HttpResponseException;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.openmetadata.catalog.Entity; import org.openmetadata.catalog.Entity;
@ -33,8 +35,12 @@ import org.openmetadata.catalog.api.data.CreateGlossary;
import org.openmetadata.catalog.entity.data.Glossary; import org.openmetadata.catalog.entity.data.Glossary;
import org.openmetadata.catalog.jdbi3.GlossaryRepository.GlossaryEntityInterface; import org.openmetadata.catalog.jdbi3.GlossaryRepository.GlossaryEntityInterface;
import org.openmetadata.catalog.resources.EntityResourceTest; import org.openmetadata.catalog.resources.EntityResourceTest;
import org.openmetadata.catalog.type.ChangeDescription;
import org.openmetadata.catalog.type.EntityReference; import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.type.FieldChange;
import org.openmetadata.catalog.util.JsonUtils;
import org.openmetadata.catalog.util.TestUtils; import org.openmetadata.catalog.util.TestUtils;
import org.openmetadata.catalog.util.TestUtils.UpdateType;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlossary> { public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlossary> {
@ -57,6 +63,26 @@ public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlo
super.setup(test); super.setup(test);
} }
@Test
void patch_addDeleteReviewers(TestInfo test) throws IOException {
CreateGlossary create = createRequest(getEntityName(test), "", "", null);
Glossary glossary = createEntity(create, ADMIN_AUTH_HEADERS);
// Add reviewer USER1 in PATCH request
String origJson = JsonUtils.pojoToJson(glossary);
glossary.withReviewers(List.of(USER_OWNER1));
ChangeDescription change = getChangeDescription(glossary.getVersion());
change.getFieldsAdded().add(new FieldChange().withName("reviewers").withNewValue(List.of(USER_OWNER1)));
glossary = patchEntityAndCheck(glossary, origJson, ADMIN_AUTH_HEADERS, UpdateType.MINOR_UPDATE, change);
// Remove a reviewer in PATCH request
origJson = JsonUtils.pojoToJson(glossary);
glossary.withReviewers(null);
change = getChangeDescription(glossary.getVersion());
change.getFieldsDeleted().add(new FieldChange().withName("reviewers").withOldValue(List.of(USER_OWNER1)));
patchEntityAndCheck(glossary, origJson, ADMIN_AUTH_HEADERS, UpdateType.MINOR_UPDATE, change);
}
@Override @Override
public CreateGlossary createRequest(String name, String description, String displayName, EntityReference owner) { public CreateGlossary createRequest(String name, String description, String displayName, EntityReference owner) {
return new CreateGlossary() return new CreateGlossary()
@ -91,6 +117,7 @@ public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlo
// Entity specific validation // Entity specific validation
TestUtils.validateTags(expected.getTags(), patched.getTags()); TestUtils.validateTags(expected.getTags(), patched.getTags());
TestUtils.assertEntityReferenceList(expected.getReviewers(), patched.getReviewers());
} }
@Override @Override
@ -120,6 +147,13 @@ public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlo
if (expected == actual) { if (expected == actual) {
return; return;
} }
assertCommonFieldChange(fieldName, expected, actual); if (fieldName.equals("reviewers")) {
@SuppressWarnings("unchecked")
List<EntityReference> expectedRefs = (List<EntityReference>) expected;
List<EntityReference> actualRefs = JsonUtils.readObjects(actual.toString(), EntityReference.class);
assertEntityReferencesFieldChange(expectedRefs, actualRefs);
} else {
assertCommonFieldChange(fieldName, expected, actual);
}
} }
} }

View File

@ -50,10 +50,14 @@ import org.openmetadata.catalog.entity.data.GlossaryTerm;
import org.openmetadata.catalog.jdbi3.GlossaryRepository.GlossaryEntityInterface; import org.openmetadata.catalog.jdbi3.GlossaryRepository.GlossaryEntityInterface;
import org.openmetadata.catalog.jdbi3.GlossaryTermRepository.GlossaryTermEntityInterface; import org.openmetadata.catalog.jdbi3.GlossaryTermRepository.GlossaryTermEntityInterface;
import org.openmetadata.catalog.resources.EntityResourceTest; import org.openmetadata.catalog.resources.EntityResourceTest;
import org.openmetadata.catalog.type.ChangeDescription;
import org.openmetadata.catalog.type.EntityReference; import org.openmetadata.catalog.type.EntityReference;
import org.openmetadata.catalog.type.FieldChange;
import org.openmetadata.catalog.util.EntityUtil; import org.openmetadata.catalog.util.EntityUtil;
import org.openmetadata.catalog.util.JsonUtils;
import org.openmetadata.catalog.util.ResultList; import org.openmetadata.catalog.util.ResultList;
import org.openmetadata.catalog.util.TestUtils; import org.openmetadata.catalog.util.TestUtils;
import org.openmetadata.catalog.util.TestUtils.UpdateType;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class GlossaryTermResourceTest extends EntityResourceTest<GlossaryTerm, CreateGlossaryTerm> { public class GlossaryTermResourceTest extends EntityResourceTest<GlossaryTerm, CreateGlossaryTerm> {
@ -178,6 +182,28 @@ public class GlossaryTermResourceTest extends EntityResourceTest<GlossaryTerm, C
glossaryTermMismatch(term1.getId().toString(), glossary2.getId().toString())); glossaryTermMismatch(term1.getId().toString(), glossary2.getId().toString()));
} }
@Test
void patch_addDeleteReviewers(TestInfo test) throws IOException {
CreateGlossaryTerm create = createRequest(getEntityName(test), "", "", null).withReviewers(null).withSynonyms(null);
GlossaryTerm term = createEntity(create, ADMIN_AUTH_HEADERS);
// Add reviewer USER1 in PATCH request
String origJson = JsonUtils.pojoToJson(term);
term.withReviewers(List.of(USER_OWNER1)).withSynonyms(List.of("synonym1"));
ChangeDescription change = getChangeDescription(term.getVersion());
change.getFieldsAdded().add(new FieldChange().withName("reviewers").withNewValue(List.of(USER_OWNER1)));
change.getFieldsAdded().add(new FieldChange().withName("synonyms").withNewValue(List.of("synonym1")));
term = patchEntityAndCheck(term, origJson, ADMIN_AUTH_HEADERS, UpdateType.MINOR_UPDATE, change);
// Remove a reviewer in PATCH request
origJson = JsonUtils.pojoToJson(term);
term.withReviewers(null).withSynonyms(null);
change = getChangeDescription(term.getVersion());
change.getFieldsDeleted().add(new FieldChange().withName("reviewers").withOldValue(List.of(USER_OWNER1)));
change.getFieldsDeleted().add(new FieldChange().withName("synonyms").withOldValue(List.of("synonym1")));
patchEntityAndCheck(term, origJson, ADMIN_AUTH_HEADERS, UpdateType.MINOR_UPDATE, change);
}
public GlossaryTerm createTerm(Glossary glossary, GlossaryTerm parent, String termName) throws HttpResponseException { public GlossaryTerm createTerm(Glossary glossary, GlossaryTerm parent, String termName) throws HttpResponseException {
EntityReference glossaryRef = new GlossaryEntityInterface(glossary).getEntityReference(); EntityReference glossaryRef = new GlossaryEntityInterface(glossary).getEntityReference();
EntityReference parentRef = parent != null ? new GlossaryTermEntityInterface(parent).getEntityReference() : null; EntityReference parentRef = parent != null ? new GlossaryTermEntityInterface(parent).getEntityReference() : null;
@ -281,6 +307,18 @@ public class GlossaryTermResourceTest extends EntityResourceTest<GlossaryTerm, C
if (expected == actual) { if (expected == actual) {
return; return;
} }
assertCommonFieldChange(fieldName, expected, actual); if (fieldName.equals("reviewers")) {
@SuppressWarnings("unchecked")
List<EntityReference> expectedRefs = (List<EntityReference>) expected;
List<EntityReference> actualRefs = JsonUtils.readObjects(actual.toString(), EntityReference.class);
assertEntityReferencesFieldChange(expectedRefs, actualRefs);
} else if (fieldName.equals("synonyms")) {
@SuppressWarnings("unchecked")
List<String> expectedRefs = (List<String>) expected;
List<String> actualRefs = JsonUtils.readObjects(actual.toString(), String.class);
assertStrings(expectedRefs, actualRefs);
} else {
assertCommonFieldChange(fieldName, expected, actual);
}
} }
} }