mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-01 13:48:04 +00:00
* Fix #15809 : Update asset indexes on glossary term hierarchy change * move logic to new method updateAssetIndexesOnGlossaryTermUpdate() * enbale skip cypress test for the asset not found * fix loss of tags and other relations while glossary parent change --------- Co-authored-by: Mohit Yadav <105265192+mohityadav766@users.noreply.github.com> Co-authored-by: Ashish Gupta <ashish@getcollate.io>
This commit is contained in:
parent
0ea05f8f8b
commit
3db9dadaa6
@ -2536,6 +2536,18 @@ public interface CollectionDAO {
|
|||||||
updateTagPrefixInternal(update);
|
updateTagPrefixInternal(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void updateTargetFQNHashPrefix(
|
||||||
|
int source, String oldTargetFQNHashPrefix, String newTargetFQNHashPrefix) {
|
||||||
|
String update =
|
||||||
|
String.format(
|
||||||
|
"UPDATE tag_usage SET targetFQNHash = REPLACE(targetFQNHash, '%s.', '%s.') WHERE source = %s AND targetFQNHash LIKE '%s.%%'",
|
||||||
|
FullyQualifiedName.buildHash(oldTargetFQNHashPrefix),
|
||||||
|
FullyQualifiedName.buildHash(newTargetFQNHashPrefix),
|
||||||
|
source,
|
||||||
|
FullyQualifiedName.buildHash(oldTargetFQNHashPrefix));
|
||||||
|
updateTagPrefixInternal(update);
|
||||||
|
}
|
||||||
|
|
||||||
default void rename(int source, String oldFQN, String newFQN) {
|
default void rename(int source, String oldFQN, String newFQN) {
|
||||||
renameInternal(source, oldFQN, newFQN, newFQN); // First rename tagFQN from oldFQN to newFQN
|
renameInternal(source, oldFQN, newFQN, newFQN); // First rename tagFQN from oldFQN to newFQN
|
||||||
updateTagPrefix(
|
updateTagPrefix(
|
||||||
@ -2543,6 +2555,18 @@ public interface CollectionDAO {
|
|||||||
newFQN); // Rename all the tagFQN prefixes starting with the oldFQN to newFQN
|
newFQN); // Rename all the tagFQN prefixes starting with the oldFQN to newFQN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void renameByTargetFQNHash(
|
||||||
|
int source, String oldTargetFQNHash, String newTargetFQNHash) {
|
||||||
|
renameByTargetFQNHashInternal(
|
||||||
|
source,
|
||||||
|
(oldTargetFQNHash),
|
||||||
|
newTargetFQNHash); // First rename targetFQN from oldFQN to newFQN
|
||||||
|
updateTargetFQNHashPrefix(
|
||||||
|
source,
|
||||||
|
oldTargetFQNHash,
|
||||||
|
newTargetFQNHash); // Rename all the targetFQN prefixes starting with the oldFQN to newFQN
|
||||||
|
}
|
||||||
|
|
||||||
/** Rename the tagFQN */
|
/** Rename the tagFQN */
|
||||||
@SqlUpdate(
|
@SqlUpdate(
|
||||||
"Update tag_usage set tagFQN = :newFQN, tagFQNHash = :newFQNHash WHERE source = :source AND tagFQNHash = :oldFQNHash")
|
"Update tag_usage set tagFQN = :newFQN, tagFQNHash = :newFQNHash WHERE source = :source AND tagFQNHash = :oldFQNHash")
|
||||||
@ -2552,6 +2576,14 @@ public interface CollectionDAO {
|
|||||||
@Bind("newFQN") String newFQN,
|
@Bind("newFQN") String newFQN,
|
||||||
@BindFQN("newFQNHash") String newFQNHash);
|
@BindFQN("newFQNHash") String newFQNHash);
|
||||||
|
|
||||||
|
/** Rename the targetFQN */
|
||||||
|
@SqlUpdate(
|
||||||
|
"Update tag_usage set targetFQNHash = :newTargetFQNHash WHERE source = :source AND targetFQNHash = :oldTargetFQNHash")
|
||||||
|
void renameByTargetFQNHashInternal(
|
||||||
|
@Bind("source") int source,
|
||||||
|
@BindFQN("oldTargetFQNHash") String oldTargetFQNHash,
|
||||||
|
@BindFQN("newTargetFQNHash") String newTargetFQNHash);
|
||||||
|
|
||||||
@SqlUpdate("<update>")
|
@SqlUpdate("<update>")
|
||||||
void updateTagPrefixInternal(@Define("update") String update);
|
void updateTagPrefixInternal(@Define("update") String update);
|
||||||
|
|
||||||
|
@ -463,6 +463,59 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, EntityReference> getGlossaryTermsContainingFQNFromES(
|
||||||
|
String termFQN, int size) {
|
||||||
|
try {
|
||||||
|
String queryJson = String.format("fullyQualifiedName:%s*", termFQN);
|
||||||
|
|
||||||
|
SearchRequest searchRequest =
|
||||||
|
new SearchRequest.ElasticSearchRequestBuilder(
|
||||||
|
queryJson, size, "glossary_term_search_index")
|
||||||
|
.from(0)
|
||||||
|
.fetchSource(true)
|
||||||
|
.trackTotalHits(false)
|
||||||
|
.sortFieldParam("_score")
|
||||||
|
.deleted(false)
|
||||||
|
.sortOrder("desc")
|
||||||
|
.includeSourceFields(new ArrayList<>())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Execute the search and parse the response
|
||||||
|
Response response = searchRepository.search(searchRequest);
|
||||||
|
String json = (String) response.getEntity();
|
||||||
|
Set<EntityReference> fqns = new TreeSet<>(compareEntityReferenceById);
|
||||||
|
|
||||||
|
// Extract hits from the response JSON and create entity references
|
||||||
|
for (Iterator<JsonNode> it =
|
||||||
|
((ArrayNode) JsonUtils.extractValue(json, "hits", "hits")).elements();
|
||||||
|
it.hasNext(); ) {
|
||||||
|
JsonNode jsonNode = it.next();
|
||||||
|
String id = JsonUtils.extractValue(jsonNode, "_source", "id");
|
||||||
|
String fqn = JsonUtils.extractValue(jsonNode, "_source", "fullyQualifiedName");
|
||||||
|
String type = JsonUtils.extractValue(jsonNode, "_source", "entityType");
|
||||||
|
if (!CommonUtil.nullOrEmpty(fqn) && !CommonUtil.nullOrEmpty(type)) {
|
||||||
|
fqns.add(
|
||||||
|
new EntityReference()
|
||||||
|
.withId(UUID.fromString(id))
|
||||||
|
.withFullyQualifiedName(fqn)
|
||||||
|
.withType(type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the results into a map by the hash of the FQN
|
||||||
|
return fqns.stream()
|
||||||
|
.collect(
|
||||||
|
Collectors.toMap(
|
||||||
|
entityReference ->
|
||||||
|
FullyQualifiedName.buildHash(entityReference.getFullyQualifiedName()),
|
||||||
|
entityReference -> entityReference));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("Error while fetching glossary terms with prefix from ES", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
public BulkOperationResult bulkRemoveGlossaryToAssets(
|
public BulkOperationResult bulkRemoveGlossaryToAssets(
|
||||||
UUID glossaryTermId, AddGlossaryToAssetsRequest request) {
|
UUID glossaryTermId, AddGlossaryToAssetsRequest request) {
|
||||||
GlossaryTerm term = this.get(null, glossaryTermId, getFields("id,tags"));
|
GlossaryTerm term = this.get(null, glossaryTermId, getFields("id,tags"));
|
||||||
@ -532,6 +585,10 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
closeApprovalTask(updated, "Rejected the glossary term");
|
closeApprovalTask(updated, "Rejected the glossary term");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!nullOrEmpty(original)
|
||||||
|
&& !original.getFullyQualifiedName().equals(updated.getFullyQualifiedName())) {
|
||||||
|
updateAssetIndexesOnGlossaryTermUpdate(original, updated);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -678,6 +735,42 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateAssetIndexesOnGlossaryTermUpdate(GlossaryTerm original, GlossaryTerm updated) {
|
||||||
|
// Update ES indexes of entity tagged with the glossary term and its children terms to reflect
|
||||||
|
// its latest value.
|
||||||
|
Set<String> targetFQNHashesFromDb =
|
||||||
|
new HashSet<>(
|
||||||
|
daoCollection.tagUsageDAO().getTargetFQNHashForTag(updated.getFullyQualifiedName()));
|
||||||
|
|
||||||
|
List<EntityReference> childTerms =
|
||||||
|
super.getChildren(updated); // get new value of children terms from DB
|
||||||
|
for (EntityReference child : childTerms) {
|
||||||
|
targetFQNHashesFromDb.addAll( // for each child term find the targetFQNHashes of assets
|
||||||
|
daoCollection.tagUsageDAO().getTargetFQNHashForTag(child.getFullyQualifiedName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of entity references tagged with the glossary term
|
||||||
|
Map<String, EntityReference> targetFQNFromES =
|
||||||
|
getGlossaryUsageFromES(original.getFullyQualifiedName(), targetFQNHashesFromDb.size());
|
||||||
|
Map<String, EntityReference> childrenTerms =
|
||||||
|
getGlossaryTermsContainingFQNFromES(
|
||||||
|
original.getFullyQualifiedName(),
|
||||||
|
getChildrenCount(updated)); // get old value of children term from ES
|
||||||
|
|
||||||
|
for (EntityReference child : childrenTerms.values()) {
|
||||||
|
targetFQNFromES.putAll( // List of entity references tagged with the children term
|
||||||
|
getGlossaryUsageFromES(child.getFullyQualifiedName(), targetFQNHashesFromDb.size()));
|
||||||
|
searchRepository.updateEntity(child); // update es index of child term
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetFQNFromES.size() == targetFQNHashesFromDb.size()) {
|
||||||
|
for (String fqnHash : targetFQNHashesFromDb) {
|
||||||
|
EntityReference refDetails = targetFQNFromES.get(fqnHash);
|
||||||
|
searchRepository.updateEntity(refDetails); // update ES index of assets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Handles entity updated from PUT and POST operation. */
|
/** Handles entity updated from PUT and POST operation. */
|
||||||
public class GlossaryTermUpdater extends EntityUpdater {
|
public class GlossaryTermUpdater extends EntityUpdater {
|
||||||
public GlossaryTermUpdater(GlossaryTerm original, GlossaryTerm updated, Operation operation) {
|
public GlossaryTermUpdater(GlossaryTerm original, GlossaryTerm updated, Operation operation) {
|
||||||
@ -844,6 +937,14 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
TagSource.GLOSSARY.ordinal(),
|
TagSource.GLOSSARY.ordinal(),
|
||||||
original.getFullyQualifiedName(),
|
original.getFullyQualifiedName(),
|
||||||
updated.getFullyQualifiedName());
|
updated.getFullyQualifiedName());
|
||||||
|
|
||||||
|
daoCollection
|
||||||
|
.tagUsageDAO()
|
||||||
|
.renameByTargetFQNHash(
|
||||||
|
TagSource.CLASSIFICATION.ordinal(),
|
||||||
|
original.getFullyQualifiedName(),
|
||||||
|
updated.getFullyQualifiedName());
|
||||||
|
|
||||||
if (glossaryChanged) {
|
if (glossaryChanged) {
|
||||||
updateGlossaryRelationship(original, updated);
|
updateGlossaryRelationship(original, updated);
|
||||||
recordChange(
|
recordChange(
|
||||||
|
@ -349,6 +349,47 @@ public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlo
|
|||||||
EntityUpdater.setSessionTimeout(10 * 60 * 1000); // Turn consolidation of changes back on
|
EntityUpdater.setSessionTimeout(10 * 60 * 1000); // Turn consolidation of changes back on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test_patch_changeParent_UpdateHierarchy(TestInfo test) throws IOException {
|
||||||
|
CreateGlossary create = createRequest(getEntityName(test), "", "", null);
|
||||||
|
Glossary glossary = createEntity(create, ADMIN_AUTH_HEADERS);
|
||||||
|
//
|
||||||
|
// These test move a glossary term to different parts of the glossary hierarchy and to different
|
||||||
|
// glossaries
|
||||||
|
//
|
||||||
|
|
||||||
|
// Create glossary with the following hierarchy
|
||||||
|
// -> t1 -> t11
|
||||||
|
// -> t2
|
||||||
|
// Create a Classification with the same name as glossary and assign it to a table
|
||||||
|
ClassificationResourceTest classificationResourceTest = new ClassificationResourceTest();
|
||||||
|
TagResourceTest tagResourceTest = new TagResourceTest();
|
||||||
|
CreateClassification createClassification =
|
||||||
|
classificationResourceTest.createRequest("SampleTags");
|
||||||
|
classificationResourceTest.createEntity(createClassification, ADMIN_AUTH_HEADERS);
|
||||||
|
Tag tag1 = tagResourceTest.createTag("tag1", "SampleTags", null);
|
||||||
|
Tag tag2 = tagResourceTest.createTag("tag2", "SampleTags", null);
|
||||||
|
GlossaryTermResourceTest glossaryTermResourceTest = new GlossaryTermResourceTest();
|
||||||
|
GlossaryTerm t1 = createGlossaryTerm(glossaryTermResourceTest, glossary, null, "parentTerm1");
|
||||||
|
|
||||||
|
// GlossaryTerm t11 = createGlossaryTerm(glossaryTermResourceTest, glossary, t1,
|
||||||
|
// "parentTerm11").withTags(toTagLabels(tag1,tag2));
|
||||||
|
GlossaryTerm t11 =
|
||||||
|
createGlossaryTermWithTags(
|
||||||
|
glossaryTermResourceTest, glossary, t1, "parentTerm11", toTagLabels(tag1, tag2));
|
||||||
|
|
||||||
|
GlossaryTerm originalT1 = new GlossaryTerm();
|
||||||
|
copyGlossaryTerm(t11, originalT1);
|
||||||
|
|
||||||
|
GlossaryTerm t2 = createGlossaryTerm(glossaryTermResourceTest, glossary, null, "parentTerm2");
|
||||||
|
LOG.info(" t11 == {}", t11.getTags());
|
||||||
|
LOG.info(" originalT1 == {}", originalT1.getTags());
|
||||||
|
glossaryTermResourceTest.moveGlossaryTerm(
|
||||||
|
glossary.getEntityReference(), t2.getEntityReference(), t11);
|
||||||
|
|
||||||
|
TestUtils.validateTags(originalT1.getTags(), t11.getTags());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void patch_moveGlossaryTermParentToChild() {}
|
void patch_moveGlossaryTermParentToChild() {}
|
||||||
|
|
||||||
@ -447,7 +488,8 @@ public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlo
|
|||||||
.withParent(from.getParent())
|
.withParent(from.getParent())
|
||||||
.withFullyQualifiedName(from.getFullyQualifiedName())
|
.withFullyQualifiedName(from.getFullyQualifiedName())
|
||||||
.withChangeDescription(from.getChangeDescription())
|
.withChangeDescription(from.getChangeDescription())
|
||||||
.withVersion(from.getVersion());
|
.withVersion(from.getVersion())
|
||||||
|
.withTags(from.getTags());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -517,6 +559,24 @@ public class GlossaryResourceTest extends EntityResourceTest<Glossary, CreateGlo
|
|||||||
return resource.createEntity(create, ADMIN_AUTH_HEADERS);
|
return resource.createEntity(create, ADMIN_AUTH_HEADERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GlossaryTerm createGlossaryTermWithTags(
|
||||||
|
GlossaryTermResourceTest resource,
|
||||||
|
Glossary glossary,
|
||||||
|
GlossaryTerm parent,
|
||||||
|
String name,
|
||||||
|
List<TagLabel> tags)
|
||||||
|
throws HttpResponseException {
|
||||||
|
CreateGlossaryTerm create =
|
||||||
|
new CreateGlossaryTerm()
|
||||||
|
.withName(name)
|
||||||
|
.withDescription("d")
|
||||||
|
.withGlossary(glossary.getFullyQualifiedName())
|
||||||
|
.withParent(getFqn(parent))
|
||||||
|
.withProvider(ProviderType.USER)
|
||||||
|
.withTags(tags);
|
||||||
|
return resource.createEntity(create, ADMIN_AUTH_HEADERS);
|
||||||
|
}
|
||||||
|
|
||||||
public void renameGlossaryAndCheck(Glossary glossary, String newName) throws IOException {
|
public void renameGlossaryAndCheck(Glossary glossary, String newName) throws IOException {
|
||||||
String oldName = glossary.getName();
|
String oldName = glossary.getName();
|
||||||
String json = JsonUtils.pojoToJson(glossary);
|
String json = JsonUtils.pojoToJson(glossary);
|
||||||
|
@ -1260,12 +1260,9 @@ describe('Glossary page should work properly', { tags: 'Governance' }, () => {
|
|||||||
verifyResponseStatusCode('@saveGlossaryTermData', 200);
|
verifyResponseStatusCode('@saveGlossaryTermData', 200);
|
||||||
verifyResponseStatusCode('@fetchGlossaryTermData', 200);
|
verifyResponseStatusCode('@fetchGlossaryTermData', 200);
|
||||||
|
|
||||||
/**
|
cy.get('[data-testid="assets"] [data-testid="filter-count"]')
|
||||||
* Todo: Enable this once this asset issue is resolve https://github.com/open-metadata/OpenMetadata/issues/15809
|
.should('be.visible')
|
||||||
*/
|
.contains('3');
|
||||||
// cy.get('[data-testid="assets"] [data-testid="filter-count"]')
|
|
||||||
// .should('be.visible')
|
|
||||||
// .contains('3');
|
|
||||||
|
|
||||||
// checking the breadcrumb, if the change parent term is updated and displayed
|
// checking the breadcrumb, if the change parent term is updated and displayed
|
||||||
cy.get('[data-testid="breadcrumb-link"]')
|
cy.get('[data-testid="breadcrumb-link"]')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user