Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
96a68c6
fix(tags): return parent glossary terms on child fields and stop the …
mohityadav766 Aug 20, 2026
f4e16f5
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Aug 20, 2026
5223341
test(tags): cover the child glossary term projection, and stop a proj…
mohityadav766 Aug 20, 2026
4335616
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 1, 2026
6ef706e
fix(tags): rebuild column search docs when a table's tags change
mohityadav766 Sep 1, 2026
d5728eb
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 2, 2026
37f2155
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 7, 2026
14fa0dc
fix(tags): drive the child cascade from the bulk asset APIs and stop …
mohityadav766 Sep 8, 2026
c203168
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 8, 2026
f7ebf30
fix(ui): apply checkstyle formatting and drop the Metric CSV change
mohityadav766 Sep 8, 2026
a73372c
Merge remote-tracking branch 'origin/main' into glossary-derived-tag-…
mohityadav766 Sep 16, 2026
efdb833
chore(playwright): auto-refresh impact-map.generated.json
github-actions[bot] Sep 16, 2026
458499e
Merge remote-tracking branch 'origin/main' into glossary-derived-tag-…
harshach Sep 20, 2026
ca5514e
fix(search): propagate glossary tags to test time series
harshach Sep 20, 2026
62ca0a0
fix(search): discard stale tag deltas during retry
harshach Sep 20, 2026
2d6c3e5
Merge branch 'main' into glossary-derived-tag-propagation-bugs
harshach Sep 20, 2026
1b500d3
Merge branch 'main' into glossary-derived-tag-propagation-bugs
harshach Sep 21, 2026
77e15b4
fix(search): avoid column reindex for table tags
harshach Sep 21, 2026
4b46a49
fix(ui): handle unresolved tree select tags
harshach Sep 21, 2026
b0ed6c0
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 21, 2026
3960316
Merge branch 'main' into glossary-derived-tag-propagation-bugs
harshach Sep 21, 2026
8c8fb41
Merge branch 'main' into glossary-derived-tag-propagation-bugs
harshach Sep 21, 2026
278a845
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 21, 2026
b3e24e6
Merge branch 'main' into glossary-derived-tag-propagation-bugs
harshach Sep 21, 2026
ea021ea
Merge remote-tracking branch 'origin/main' into glossary-derived-tag-…
mohityadav766 Sep 22, 2026
848b6f2
fix(tags): derive propagated glossary terms in time-series indexes
mohityadav766 Sep 22, 2026
07f6cef
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 22, 2026
ef831c7
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 22, 2026
ccfe6c8
Merge branch 'main' into glossary-derived-tag-propagation-bugs
harshach Sep 22, 2026
dac302e
Merge remote-tracking branch 'origin/main' into glossary-derived-tag-…
mohityadav766 Sep 23, 2026
981fdca
Merge remote-tracking branch 'origin/glossary-derived-tag-propagation…
mohityadav766 Sep 23, 2026
9d7774c
Merge remote-tracking branch 'origin/main' into glossary-derived-tag-…
mohityadav766 Sep 24, 2026
6873415
Merge branch 'main' into glossary-derived-tag-propagation-bugs
mohityadav766 Sep 25, 2026
16e6101
chore(playwright): auto-refresh impact-map.generated.json
github-actions[bot] Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/playwright/impact-map.generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -8872,6 +8872,7 @@
"specs": [
"playwright/e2e/Features/BulkImport.spec.ts",
"playwright/e2e/Features/ContextCenterPermission.spec.ts",
"playwright/e2e/Features/DataAssetRulesDisabled.spec.ts",
"playwright/e2e/Features/DataAssetRulesEnabled.spec.ts",
"playwright/e2e/Features/EntityRightCollapsablePanel.spec.ts",
"playwright/e2e/Features/Glossary/GlossaryMiscOperations.spec.ts",
Expand Down Expand Up @@ -10778,12 +10779,17 @@
"openmetadata-ui/src/main/resources/ui/src/generated/entity/services/ingestionPipelines/ingestionPipeline.ts"
],
"specs": [
"playwright/e2e/Features/AIMode/ObservabilityLayout.spec.ts",
"playwright/e2e/Features/DataQuality/BundleSuiteBulkOperations.spec.ts",
"playwright/e2e/Features/DataQuality/DataQualityDashboardAfterReindex.spec.ts",
"playwright/e2e/Features/DataQuality/IncidentManagerAfterReindex.spec.ts",
"playwright/e2e/Features/DataQuality/ProfilerSettingsAfterReindex.spec.ts",
"playwright/e2e/Features/DataQuality/TestCaseDetailsPage.spec.ts",
"playwright/e2e/Features/DomainIsolation/DomainIncidentIsolation.spec.ts",
"playwright/e2e/Features/IncidentManager.spec.ts",
"playwright/e2e/Features/IncidentManagerPagination.spec.ts"
"playwright/e2e/Features/IncidentManagerPagination.spec.ts",
"playwright/e2e/Pages/LogsViewer.spec.ts",
"playwright/e2e/Pages/TestSuite.spec.ts"
]
},
{
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.openmetadata.schema.type.TagLabel;
import org.openmetadata.schema.type.csv.CsvFile;
import org.openmetadata.schema.type.csv.CsvHeader;
import org.openmetadata.service.resources.tags.TagLabelUtil;

public final class CsvUtil {
public static final String SEPARATOR = ",";
Expand Down Expand Up @@ -292,7 +293,7 @@ public static List<String> addTagLabels(List<String> csvRecord, List<TagLabel> t
tagLabel ->
tagLabel.getSource().equals(TagLabel.TagSource.CLASSIFICATION)
&& !tagLabel.getTagFQN().split("\\.")[0].equals("Tier")
&& !tagLabel.getLabelType().equals(TagLabel.LabelType.DERIVED))
&& !TagLabelUtil.isSystemGenerated(tagLabel))
.map(TagLabel::getTagFQN)
.collect(Collectors.joining(FIELD_SEPARATOR)));

Expand All @@ -307,7 +308,11 @@ public static List<String> addGlossaryTerms(List<String> csvRecord, List<TagLabe
.filter(
tagLabel ->
tagLabel.getSource().equals(TagLabel.TagSource.GLOSSARY)
&& !tagLabel.getTagFQN().split("\\.")[0].equals("Tier"))
&& !tagLabel.getTagFQN().split("\\.")[0].equals("Tier")
// A projected or recomputed label is not the entity's own. Exporting it
// and re-importing would store it as MANUAL in tag_usage, pinning a
// label that should vanish when its source is removed.
&& !TagLabelUtil.isSystemGenerated(tagLabel))
.map(TagLabel::getTagFQN)
.collect(Collectors.joining(FIELD_SEPARATOR)));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

import static org.openmetadata.common.utils.CommonUtil.listOf;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTagsGracefully;
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTagsWithPreFetched;
import static org.openmetadata.service.resources.tags.TagLabelUtil.batchFetchDerivedTagsGracefully;
import static org.openmetadata.service.util.EntityUtil.getFlattenedEntityField;
import static org.openmetadata.service.util.EntityUtil.mergeTags;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.ws.rs.BadRequestException;
Expand Down Expand Up @@ -52,6 +54,7 @@
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.MetadataOperation;
import org.openmetadata.schema.type.TagLabel;
import org.openmetadata.schema.utils.JsonUtils;
import org.openmetadata.service.audit.AuditLogRepository;
import org.openmetadata.service.events.lifecycle.EntityLifecycleEventDispatcher;
import org.openmetadata.service.events.lifecycle.handlers.DomainSyncHandler;
Expand Down Expand Up @@ -995,23 +998,64 @@ public static <T extends FieldInterface> void populateEntityFieldTags(
// Get Flattened Fields
List<T> flattenedFields = getFlattenedEntityField(fields);

// Fetch All tags belonging to Prefix
Map<String, List<TagLabel>> allTags = repository.getTagsByPrefix(fqnPrefix, ".%");
// Fetch all tags belonging to the prefix. "%" rather than ".%" so the parent's own row arrives
// in the same query — its glossary terms are projected onto every field below. The DAO returns
// null for an entity type that does not support tags.
Map<String, List<TagLabel>> prefixTags = repository.getTagsByPrefix(fqnPrefix, "%");
Map<String, List<TagLabel>> allTags = prefixTags == null ? Map.of() : prefixTags;
List<TagLabel> propagatedTags =
propagatedParentTags(allTags.get(FullyQualifiedName.buildHash(fqnPrefix)));
// Prefetched once for the whole entity: every field now carries at least the projected label,
// so resolving derived tags per field would issue one query per column.
Map<String, List<TagLabel>> derivedTags =
batchFetchDerivedTagsGracefully(
allTags.values().stream().flatMap(List::stream).collect(Collectors.toList()));
for (T c : listOrEmpty(flattenedFields)) {
if (setTags) {
List<TagLabel> columnTag =
allTags.get(FullyQualifiedName.buildHash(c.getFullyQualifiedName()));
if (columnTag == null) {
c.setTags(new ArrayList<>());
} else {
c.setTags(addDerivedTagsGracefully(columnTag));
}
setFieldTags(c, allTags, propagatedTags, derivedTags);
} else {
c.setTags(c.getTags());
}
}
}

/**
* A glossary term applied to a parent (table, dashboard data model, API endpoint, topic) also
* applies to every field beneath it. The labels are projected on read instead of being stored per
* field in {@code tag_usage}, so fields ingested after the parent was tagged inherit them with no
* backfill and no per-field write amplification.
*
* <p>{@link TagLabel.LabelType#DERIVED} cannot be used as the marker: it means "recomputed on read
* from the glossary term's own classification tags" and every write path strips it (see {@code
* EntityRepository.applyTags}). {@code PROPAGATED} is the schema's marker for a propagated label
* and survives both read and write.
*/
public static List<TagLabel> propagatedParentTags(List<TagLabel> parentTags) {
return listOrEmpty(parentTags).stream()
.filter(tag -> TagLabel.TagSource.GLOSSARY.equals(tag.getSource()))
.filter(tag -> !TagLabel.LabelType.DERIVED.equals(tag.getLabelType()))
.map(tag -> JsonUtils.deepCopy(tag, TagLabel.class))
.map(tag -> tag.withLabelType(TagLabel.LabelType.PROPAGATED))
.toList();
}

private static <T extends FieldInterface> void setFieldTags(
T field,
Map<String, List<TagLabel>> allTags,
List<TagLabel> propagatedTags,
Map<String, List<TagLabel>> derivedTags) {
List<TagLabel> fieldTags =
allTags.get(FullyQualifiedName.buildHash(field.getFullyQualifiedName()));
List<TagLabel> merged = new ArrayList<>(listOrEmpty(fieldTags));
// A label the field carries itself wins over the projected one (tagLabelMatch ignores
// labelType), so an explicitly applied term stays MANUAL rather than turning into PROPAGATED.
mergeTags(merged, propagatedTags);
field.setTags(
merged.isEmpty()
? new ArrayList<>()
: new ArrayList<>(addDerivedTagsWithPreFetched(merged, derivedTags)));
}

public static SearchIndex buildSearchIndex(String entityType, Object entity) {
if (searchRepository != null) {
return searchRepository.getSearchIndexFactory().buildIndex(entityType, entity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTags;
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTagsWithPreFetched;
import static org.openmetadata.service.resources.tags.TagLabelUtil.batchFetchDerivedTags;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkMutuallyExclusive;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkMutuallyExclusiveForUserAppliedTags;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand Down Expand Up @@ -325,7 +325,7 @@ private void validateSchemaFieldTags(List<Field> fields) {
for (Field field : fields) {
validateTags(field.getTags());
field.setTags(addDerivedTags(field.getTags()));
checkMutuallyExclusive(field.getTags());
checkMutuallyExclusiveForUserAppliedTags(field.getTags());
if (field.getChildren() != null) {
validateSchemaFieldTags(field.getChildren());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@ default void applyTagsBatchMultiTarget(Map<String, List<TagLabel>> tagsByTarget)

String targetFQNHash = FullyQualifiedName.buildHash(targetFQN);
for (TagLabel tagLabel : tagLabels) {
if (tagLabel.getLabelType().equals(TagLabel.LabelType.DERIVED)) {
if (TagLabelUtil.isSystemGenerated(tagLabel)) {
continue;
}
rows.add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTagsGracefully;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkDisabledTags;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkMutuallyExclusive;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkMutuallyExclusiveForUserAppliedTags;
import static org.openmetadata.service.resources.tags.TagLabelUtil.populateTagLabel;
import static org.openmetadata.service.security.DefaultAuthorizer.getSubjectContext;
import static org.openmetadata.service.util.EntityUtil.compareTagLabel;
Expand Down Expand Up @@ -536,7 +537,7 @@
@Getter protected final Set<String> allowedFields;
public final boolean supportsSoftDelete;
@Getter protected final boolean supportsTags;
@Getter protected final boolean supportsOwners;

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / python / Build Backend Distribution

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / python / Build Backend Distribution

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / Build Integration Test Runtime

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / Build Integration Test Runtime

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / Build Integration Test Runtime

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / playwright / build

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / maven-sonarcloud-ci

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / search-it-nightly (2)

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / openmetadata-service-unit-tests

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / search-it-nightly (1)

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / ui-it-nightly (elasticsearch, 1)

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / ui-it-nightly (opensearch, 0)

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / ui-it-nightly (opensearch, 1)

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / search-it-nightly (0)

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / py-run-build-tests

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / playwright-visual-regression

Not generating isSupportsOwners(): A method with that name already exists

Check warning on line 540 in openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java

View workflow job for this annotation

GitHub Actions / ui-it-nightly (elasticsearch, 0)

Not generating isSupportsOwners(): A method with that name already exists
@Getter protected final boolean supportsStyle;
@Getter protected final boolean supportsLifeCycle;
@Getter protected final boolean supportsCertification;
Expand Down Expand Up @@ -3299,7 +3300,7 @@
for (T entity : entities) {
List<TagLabel> nonDerivedTags =
listOrEmpty(entity.getTags()).stream()
.filter(t -> !t.getLabelType().equals(TagLabel.LabelType.DERIVED))
.filter(t -> !TagLabelUtil.isSystemGenerated(t))
.toList();
if (nonDerivedTags.isEmpty()) {
continue;
Expand Down Expand Up @@ -6314,7 +6315,7 @@
@Transaction
public final void applyTags(List<TagLabel> tagLabels, String targetFQN) {
for (TagLabel tagLabel : listOrEmpty(tagLabels)) {
if (!tagLabel.getLabelType().equals(TagLabel.LabelType.DERIVED)) {
if (!TagLabelUtil.isSystemGenerated(tagLabel)) {
daoCollection
.tagUsageDAO()
.applyTag(
Expand All @@ -6339,10 +6340,11 @@
if (nullOrEmpty(tagLabels)) {
return;
}
// Filter out DERIVED tags as they are system-generated
// Filter out system-generated tags (DERIVED, PROPAGATED) — they are projections, not the
// entity's own labels, so they must never reach tag_usage.
List<TagLabel> nonDerivedTags =
tagLabels.stream()
.filter(tag -> !tag.getLabelType().equals(TagLabel.LabelType.DERIVED))
.filter(tag -> !TagLabelUtil.isSystemGenerated(tag))
.collect(Collectors.toList());

if (!nonDerivedTags.isEmpty()) {
Expand All @@ -6358,10 +6360,11 @@
if (nullOrEmpty(tagLabels)) {
return;
}
// Filter out DERIVED tags as they are system-generated
// Filter out system-generated tags (DERIVED, PROPAGATED) — they are projections, not the
// entity's own labels, so they must never reach tag_usage.
List<TagLabel> nonDerivedTags =
tagLabels.stream()
.filter(tag -> !tag.getLabelType().equals(TagLabel.LabelType.DERIVED))
.filter(tag -> !TagLabelUtil.isSystemGenerated(tag))
.collect(Collectors.toList());

if (!nonDerivedTags.isEmpty()) {
Expand Down Expand Up @@ -9074,7 +9077,7 @@
for (Column column : listOrEmpty(columns)) {
validateTags(column.getTags());
column.setTags(addDerivedTags(column.getTags()));
checkMutuallyExclusive(column.getTags());
checkMutuallyExclusiveForUserAppliedTags(column.getTags());
Comment on lines 9079 to +9080

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: Excluding DERIVED from mutual-exclusivity check lets conflicting tags in

validateColumnTags calls addDerivedTags and then checkMutuallyExclusiveForUserAppliedTags. That check now drops both DERIVED and PROPAGATED labels, so the classification tags derived from a glossary term are never checked. Before this change, putting glossary term G (which carries PII.Sensitive) on a column together with a manual PII.NonSensitive was rejected, and so were two terms whose derived tags conflict. Both combinations are now accepted and stored. The same applies to the updateTags PUT and PATCH paths at lines 9824 and 9843. The GET→PUT round-trip problem described in the javadoc comes only from PROPAGATED projections. The fix is to exclude only PROPAGATED (inherited from the parent) labels from this check and keep DERIVED labels in it, since they come from the entity's own glossary terms.

In TagLabelUtil, filter out only PROPAGATED so that derived-tag conflicts are still rejected:

public static void checkMutuallyExclusiveForUserAppliedTags(List<TagLabel> tagLabels) {
  checkMutuallyExclusive(
      listOrEmpty(tagLabels).stream()
          .filter(tag -> !TagLabel.LabelType.PROPAGATED.equals(tag.getLabelType()))
          .toList());
}
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

if (column.getChildren() != null) {
validateColumnTags(column.getChildren());
}
Expand Down Expand Up @@ -10008,7 +10011,7 @@
// For PUT, we don't delete any existing tags
// Merge the tags for validation and recording purposes
EntityUtil.mergeTags(updatedTags, origTags);
checkMutuallyExclusive(updatedTags);
checkMutuallyExclusiveForUserAppliedTags(updatedTags);
} else {
// PATCH and an explicit PUT override replace tags.
// Use Set for O(1) lookup performance instead of O(n) stream().anyMatch()
Expand All @@ -10028,7 +10031,7 @@
addedTags.add(updatedTag);
}
}
checkMutuallyExclusive(updatedTags);
checkMutuallyExclusiveForUserAppliedTags(updatedTags);
}

// Filter out certification tags — handled exclusively by updateCertification()
Expand Down Expand Up @@ -10093,9 +10096,7 @@
if (nullOrEmpty(tags)) {
return Collections.emptyList();
}
return tags.stream()
.filter(tag -> !tag.getLabelType().equals(TagLabel.LabelType.DERIVED))
.toList();
return tags.stream().filter(tag -> !TagLabelUtil.isSystemGenerated(tag)).toList();
}

protected final void applyTagsAddInFlush(List<TagLabel> tagLabels, String targetFqn) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1898,6 +1898,10 @@ public BulkOperationResult bulkAddAndValidateGlossaryToAssets(
entityRepository.applyTags(getUniqueTags(tempList), asset.getFullyQualifiedName());

searchRepository.updateEntity(ref);
// updateEntity clears the change description, so the descriptor-driven child fan-out never
// fires from here. Drive it explicitly or entity children (test cases, test suites) never
// pick the term up.
searchRepository.propagateTagChangeToChildren(asset, List.of(tagLabel), List.of());
Comment thread
gitar-bot[bot] marked this conversation as resolved.
RdfUpdater.updateEntity(asset);
}
}
Expand Down Expand Up @@ -2181,13 +2185,26 @@ public BulkOperationResult bulkRemoveGlossaryToAssets(
if (!dryRun) {
// Update ES
searchRepository.updateEntity(ref);
searchRepository.propagateTagChangeToChildren(
asset, List.of(), List.of(removedLabel(term)));
RdfUpdater.updateEntity(asset);
}
}

return result.withSuccessRequest(success);
}

/**
* The label shape the child-doc cascade matches on. Only the FQN is compared, but the labelType
* has to read as system-applied so the delete script leaves a child's own manual label alone.
*/
private static TagLabel removedLabel(GlossaryTerm term) {
return new TagLabel()
.withTagFQN(term.getFullyQualifiedName())
.withSource(TagSource.GLOSSARY)
.withLabelType(TagLabel.LabelType.PROPAGATED);
}

/**
* Remove a glossary term tag from a column through its parent table.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import static org.openmetadata.service.Entity.FIELD_TAGS;
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTags;
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTagsGracefully;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkMutuallyExclusive;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkMutuallyExclusiveForUserAppliedTags;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand Down Expand Up @@ -338,7 +338,7 @@ private void validateSchemaFieldTags(List<SearchIndexField> fields) {
for (SearchIndexField field : listOrEmpty(fields)) {
validateTags(field.getTags());
field.setTags(addDerivedTags(field.getTags()));
checkMutuallyExclusive(field.getTags());
checkMutuallyExclusiveForUserAppliedTags(field.getTags());
if (field.getChildren() != null) {
validateSchemaFieldTags(field.getChildren());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,8 @@ public BulkOperationResult bulkAddAndValidateTagsToAssets(
entityRepository.applyTags(getUniqueTags(tempList), asset.getFullyQualifiedName());

searchRepository.updateEntity(ref);
// updateEntity clears the change description, so the child fan-out never fires from here.
searchRepository.propagateTagChangeToChildren(asset, List.of(tagLabel), List.of());
RdfUpdater.updateEntity(asset);
}
}
Expand Down Expand Up @@ -704,13 +706,25 @@ public BulkOperationResult bulkRemoveAndValidateTagsToAssets(
if (!dryRun) {
// Update ES
searchRepository.updateEntity(ref);
searchRepository.propagateTagChangeToChildren(asset, List.of(), List.of(removedLabel(tag)));
RdfUpdater.updateEntity(asset);
}
}

return result.withSuccessRequest(success);
}

/**
* The label shape the child-doc cascade matches on. Only the FQN is compared, but the labelType
* has to read as system-applied so the delete script leaves a child's own manual label alone.
*/
private static TagLabel removedLabel(Tag tag) {
return new TagLabel()
.withTagFQN(tag.getFullyQualifiedName())
.withSource(TagSource.CLASSIFICATION)
.withLabelType(TagLabel.LabelType.PROPAGATED);
}

/**
* Remove a tag from a column through its parent table.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import static org.openmetadata.service.Entity.populateEntityFieldTags;
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTags;
import static org.openmetadata.service.resources.tags.TagLabelUtil.addDerivedTagsGracefully;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkMutuallyExclusive;
import static org.openmetadata.service.resources.tags.TagLabelUtil.checkMutuallyExclusiveForUserAppliedTags;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand Down Expand Up @@ -369,7 +369,7 @@ private void validateSchemaFieldTags(List<Field> fields) {
for (Field field : fields) {
validateTags(field.getTags());
field.setTags(addDerivedTags(field.getTags()));
checkMutuallyExclusive(field.getTags());
checkMutuallyExclusiveForUserAppliedTags(field.getTags());
if (field.getChildren() != null) {
validateSchemaFieldTags(field.getChildren());
}
Expand Down
Loading
Loading