FIX #20250 - Suggestions to handle mutually exclusive tags (#20251)

This commit is contained in:
Pere Miquel Brull 2025-03-14 16:50:23 +01:00 committed by GitHub
parent 96e75373d0
commit 55b945bbb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 89 additions and 2 deletions

View File

@ -15,7 +15,9 @@ import static org.openmetadata.service.jdbi3.UserRepository.TEAMS_FIELD;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.json.JsonPatch;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
@ -39,11 +41,13 @@ import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.resources.feeds.MessageParser;
import org.openmetadata.service.resources.feeds.SuggestionsResource;
import org.openmetadata.service.resources.tags.TagLabelUtil;
import org.openmetadata.service.security.AuthorizationException;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.OperationContext;
import org.openmetadata.service.security.policyevaluator.ResourceContext;
import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.FullyQualifiedName;
import org.openmetadata.service.util.JsonUtils;
import org.openmetadata.service.util.RestUtil;
import org.openmetadata.service.util.ResultList;
@ -172,8 +176,7 @@ public class SuggestionRepository {
entity, entityLink.getFullyQualifiedFieldValue(), suggestion);
} else {
if (suggestion.getType().equals(SuggestionType.SuggestTagLabel)) {
List<TagLabel> tags = new ArrayList<>(entity.getTags());
tags.addAll(suggestion.getTagLabels());
List<TagLabel> tags = mergeTags(entity.getTags(), suggestion.getTagLabels());
entity.setTags(tags);
return entity;
} else if (suggestion.getType().equals(SuggestionType.SuggestDescription)) {
@ -186,6 +189,50 @@ public class SuggestionRepository {
}
}
private static List<TagLabel> mergeTags(
List<TagLabel> existingTags, List<TagLabel> incomingTags) {
if (incomingTags == null || incomingTags.isEmpty()) {
return existingTags;
}
// Throw an error if incoming tags are mutually exclusive
TagLabelUtil.checkMutuallyExclusive(incomingTags);
ArrayList<TagLabel> tags = new ArrayList<>();
Set<String> incomingClassification =
incomingTags.stream()
.map(t -> FullyQualifiedName.getParentFQN(t.getTagFQN()))
.collect(Collectors.toSet());
// We'll give priority to incoming tags over existing tags
// so we'll skip any existing tag that is mutually exclusive and clashing with incoming
// classification
for (TagLabel tag : existingTags) {
if (TagLabelUtil.mutuallyExclusive(tag)
&& incomingClassification.contains(FullyQualifiedName.getParentFQN(tag.getTagFQN()))) {
LOG.debug(
String.format(
"Incoming tags are mutually exclusive with existing tag [%s]", tag.getTagFQN()));
} else {
tags.add(tag);
}
}
return naiveMergeTags(tags, incomingTags);
}
// Add all tags without repeats
private static List<TagLabel> naiveMergeTags(
List<TagLabel> existingTags, List<TagLabel> incomingTags) {
List<TagLabel> tags = new ArrayList<>(existingTags);
Set<String> existingTagFQNs =
existingTags.stream().map(TagLabel::getTagFQN).collect(Collectors.toSet());
for (TagLabel incomingTag : incomingTags) {
if (!existingTagFQNs.contains(incomingTag.getTagFQN())) {
tags.add(incomingTag);
}
}
return tags;
}
public RestUtil.PutResponse<Suggestion> acceptSuggestion(
UriInfo uriInfo,
Suggestion suggestion,

View File

@ -12,6 +12,8 @@ import static org.openmetadata.service.resources.EntityResourceTest.C1;
import static org.openmetadata.service.resources.EntityResourceTest.C2;
import static org.openmetadata.service.resources.EntityResourceTest.PERSONAL_DATA_TAG_LABEL;
import static org.openmetadata.service.resources.EntityResourceTest.PII_SENSITIVE_TAG_LABEL;
import static org.openmetadata.service.resources.EntityResourceTest.TIER1_TAG_LABEL;
import static org.openmetadata.service.resources.EntityResourceTest.TIER2_TAG_LABEL;
import static org.openmetadata.service.security.SecurityUtil.authHeaders;
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
import static org.openmetadata.service.util.TestUtils.assertResponse;
@ -528,6 +530,37 @@ public class SuggestionsResourceTest extends OpenMetadataApplicationTest {
}
}
@Test
@Order(6)
void put_acceptSuggestion_mutuallyExclusiveTags_200(TestInfo test) throws IOException {
TableResourceTest tableResourceTest = new TableResourceTest();
CreateTable createTable = tableResourceTest.createRequest(test);
Table table = tableResourceTest.createAndCheckEntity(createTable, ADMIN_AUTH_HEADERS);
MessageParser.EntityLink entityLink =
new MessageParser.EntityLink(Entity.TABLE, table.getFullyQualifiedName());
CreateSuggestion create = createTierSuggestion(TIER1_TAG_LABEL, entityLink);
Suggestion suggestion = createSuggestion(create, USER_AUTH_HEADERS);
Assertions.assertEquals(create.getEntityLink(), suggestion.getEntityLink());
// When accepting the suggestion, we'll get the Tier1 tag applied to the table
acceptSuggestion(suggestion.getId(), USER_AUTH_HEADERS);
table = tableResourceTest.getEntity(table.getId(), "tags", USER_AUTH_HEADERS);
List<TagLabel> expectedTags = new ArrayList<>(table.getTags());
expectedTags.addAll(suggestion.getTagLabels());
validateAppliedTags(expectedTags, table.getTags());
// Not, let's try to apply the Tier2 tag, which is mutually exclusive with the Tier1 tag
// The table should then only have the Tier2
create = createTierSuggestion(TIER2_TAG_LABEL, entityLink);
suggestion = createSuggestion(create, USER_AUTH_HEADERS);
acceptSuggestion(suggestion.getId(), USER_AUTH_HEADERS);
table = tableResourceTest.getEntity(table.getId(), "tags", USER_AUTH_HEADERS);
expectedTags = new ArrayList<>(table.getTags());
expectedTags.addAll(suggestion.getTagLabels());
validateAppliedTags(expectedTags, table.getTags());
}
public Suggestion createSuggestion(CreateSuggestion create, Map<String, String> authHeaders)
throws HttpResponseException {
return TestUtils.post(getResource("suggestions"), create, Suggestion.class, authHeaders);
@ -552,6 +585,13 @@ public class SuggestionsResourceTest extends OpenMetadataApplicationTest {
.withEntityLink(TABLE_LINK);
}
public CreateSuggestion createTierSuggestion(TagLabel tier, MessageParser.EntityLink entityLink) {
return new CreateSuggestion()
.withTagLabels(List.of(tier))
.withType(SuggestionType.SuggestTagLabel)
.withEntityLink(entityLink.getLinkString());
}
public Suggestion getSuggestion(UUID id, Map<String, String> authHeaders)
throws HttpResponseException {
WebTarget target = getResource("suggestions/" + id);