diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java index 837a03f6a74..95afd0d3a62 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java @@ -2536,6 +2536,18 @@ public interface CollectionDAO { 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) { renameInternal(source, oldFQN, newFQN, newFQN); // First rename tagFQN from oldFQN to newFQN updateTagPrefix( @@ -2543,6 +2555,18 @@ public interface CollectionDAO { 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 */ @SqlUpdate( "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, @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("") void updateTagPrefixInternal(@Define("update") String update); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryTermRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryTermRepository.java index 29b44764eb8..31dc4ab6d78 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryTermRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/GlossaryTermRepository.java @@ -463,6 +463,59 @@ public class GlossaryTermRepository extends EntityRepository { return new HashMap<>(); } + private Map 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 fqns = new TreeSet<>(compareEntityReferenceById); + + // Extract hits from the response JSON and create entity references + for (Iterator 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( UUID glossaryTermId, AddGlossaryToAssetsRequest request) { GlossaryTerm term = this.get(null, glossaryTermId, getFields("id,tags")); @@ -532,6 +585,10 @@ public class GlossaryTermRepository extends EntityRepository { closeApprovalTask(updated, "Rejected the glossary term"); } } + if (!nullOrEmpty(original) + && !original.getFullyQualifiedName().equals(updated.getFullyQualifiedName())) { + updateAssetIndexesOnGlossaryTermUpdate(original, updated); + } } @Override @@ -678,6 +735,42 @@ public class GlossaryTermRepository extends EntityRepository { } } + 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 targetFQNHashesFromDb = + new HashSet<>( + daoCollection.tagUsageDAO().getTargetFQNHashForTag(updated.getFullyQualifiedName())); + + List 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 targetFQNFromES = + getGlossaryUsageFromES(original.getFullyQualifiedName(), targetFQNHashesFromDb.size()); + Map 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. */ public class GlossaryTermUpdater extends EntityUpdater { public GlossaryTermUpdater(GlossaryTerm original, GlossaryTerm updated, Operation operation) { @@ -844,6 +937,14 @@ public class GlossaryTermRepository extends EntityRepository { TagSource.GLOSSARY.ordinal(), original.getFullyQualifiedName(), updated.getFullyQualifiedName()); + + daoCollection + .tagUsageDAO() + .renameByTargetFQNHash( + TagSource.CLASSIFICATION.ordinal(), + original.getFullyQualifiedName(), + updated.getFullyQualifiedName()); + if (glossaryChanged) { updateGlossaryRelationship(original, updated); recordChange( diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/glossary/GlossaryResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/glossary/GlossaryResourceTest.java index 8561e2cdf53..7d163e47e45 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/glossary/GlossaryResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/glossary/GlossaryResourceTest.java @@ -349,6 +349,47 @@ public class GlossaryResourceTest extends EntityResourceTest 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 void patch_moveGlossaryTermParentToChild() {} @@ -447,7 +488,8 @@ public class GlossaryResourceTest extends EntityResourceTest 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 { String oldName = glossary.getName(); String json = JsonUtils.pojoToJson(glossary); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.ts b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.ts index 79f132c7ac5..891c55344f3 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.ts @@ -1260,12 +1260,9 @@ describe('Glossary page should work properly', { tags: 'Governance' }, () => { verifyResponseStatusCode('@saveGlossaryTermData', 200); verifyResponseStatusCode('@fetchGlossaryTermData', 200); - /** - * Todo: Enable this once this asset issue is resolve https://github.com/open-metadata/OpenMetadata/issues/15809 - */ - // cy.get('[data-testid="assets"] [data-testid="filter-count"]') - // .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 cy.get('[data-testid="breadcrumb-link"]')