Skip to content

fix(alerts): match Entity FQN filter against an entity and its descendants#28833

Merged
manerow merged 3 commits into
mainfrom
fix/alert-fqn-hierarchical-match
Jun 10, 2026
Merged

fix(alerts): match Entity FQN filter against an entity and its descendants#28833
manerow merged 3 commits into
mainfrom
fix/alert-fqn-hierarchical-match

Conversation

@manerow

@manerow manerow commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #28834

Problem

PR #27987 ("strict literal matching across alert filter functions") changed AlertsRuleEvaluator.matchAnyEntityFqn from a substring regex (Pattern.compile(value).matcher(fqn).find()) to exact equality (entityFqns.contains(fqn)). That was a correct security/correctness fix (regex injection, crashes on metacharacter names, substring false positives) — but it silently removed a capability customers relied on: filtering by a parent FQN (a service / database / glossary / classification) to match all of its descendants.

Example: an alert with Source = databaseSchema and matchAnyEntityFqn({'Snowflake Integration'}) (a service FQN) matched all schemas under that service before the upgrade, and matched nothing after — "no change events". This regressed every hierarchical source (table, database, chart/dashboard, topic, mlmodel, pipeline, glossaryTerm, tag, dataProduct, …), not just databaseSchema.

Fix

Backend (the restore). matchAnyEntityFqn now matches when the entity FQN equals a listed FQN or is a descendant of one, using the existing literal, segment-anchored helper FullyQualifiedName.isParent (no regex). This restores subtree matching for existing alerts with no migration (the stored condition just starts matching again) while preserving every guarantee from #27987 — its regex-metacharacter and "unrelated FQN" tests still pass.

Backend-driven hierarchy. The source→ancestor mapping lives in the backend: a new containerEntities field on the notification/filter resource descriptors (subscriptionResourceDescriptor / filterResourceDescriptor), populated per source in EventSubResourceDescriptor.json and served through getNotificationsFilterDescriptors. The UI consumes it — no hardcoded hierarchy.

UI picker. The "Entity FQN" filter now searches the source index plus its ancestor indexes, so a user can select a parent (service/database/…) for a child source. Ancestor options render as Snowflake Integration.* (display only) to convey "everything under this"; the stored value stays the plain FQN.

Tests

Notes

  • No data migration needed; existing alerts heal on deploy.
  • The truly-dynamic alternative (deriving the hierarchy from entity relationships) isn't available; containerEntities is the curated list, now owned by the backend and served to all clients.
  • Separate from fix(alerts): dedup successful change events to prevent Postgres ON CONFLICT abort #28827 (Postgres ON CONFLICT batch dedup), which addressed a different alert bug surfaced in the same customer report.

@manerow manerow requested a review from a team as a code owner June 8, 2026 19:35
@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Jun 8, 2026
Comment thread openmetadata-service/src/main/resources/json/data/EventSubResourceDescriptor.json Outdated
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

✅ TypeScript Types Auto-Updated

The generated TypeScript types have been automatically updated based on JSON schema changes in this PR.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 62%
62.83% (67219/106983) 44.05% (37089/84195) 46.42% (11410/24575)

@open-metadata open-metadata deleted a comment from github-actions Bot Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (18 flaky)

✅ 4266 passed · ❌ 0 failed · 🟡 18 flaky · ⏭️ 88 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 1 300 0 1 4
🟡 Shard 2 804 0 2 9
🟡 Shard 3 805 0 3 8
🟡 Shard 4 840 0 3 12
🟡 Shard 5 719 0 2 47
🟡 Shard 6 798 0 7 8
🟡 18 flaky test(s) (passed on retry)
  • Flow/Tour.spec.ts › Tour should work from help section (shard 1, 1 retry)
  • Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts › EditAll User: Complete export-import-validate flow (shard 2, 1 retry)
  • Features/DataQuality/TestCaseResultPermissions.spec.ts › User with only VIEW cannot PATCH results (shard 2, 1 retry)
  • Features/RTL.spec.ts › Verify Following widget functionality (shard 3, 2 retries)
  • Features/Tasks/DomainFiltering.spec.ts › selecting All Domains removes domain filter from feed API call (shard 3, 2 retries)
  • Features/UserProfileOnlineStatus.spec.ts › Should show online status badge on user profile for active users (shard 3, 1 retry)
  • Pages/CustomProperties.spec.ts › Should verify property name is visible for apiCollection in right panel (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Date (shard 4, 1 retry)
  • Pages/DataContractsSemanticRules.spec.ts › Validate Description Rule Is_Set (shard 4, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should navigate to lineage and test controls for searchIndex (shard 5, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboardDataModel -> searchIndex (shard 6, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for mlModel -> container (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage service filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage service type filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab IS visible for supported type: searchIndex (shard 6, 1 retry)
  • Pages/Lineage/PlatformLineage.spec.ts › Verify domain platform view (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

@gitar-bot

gitar-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 4 resolved / 4 findings

Restores hierarchical FQN filtering by matching entities against exact names or descendants using backend-defined container relationships. Resolved issues related to NPEs, duplicated logic, and label overflow.

✅ 4 resolved
Edge Case: matchesFqnOrDescendant can NPE when entity FQN is null

📄 openmetadata-service/src/main/java/org/openmetadata/service/events/subscription/AlertsRuleEvaluator.java:147-161
The previous implementation used entityFqns.contains(entity.getFullyQualifiedName()), which is null-safe (List.contains(null) returns false). The new matchesFqnOrDescendant calls entityFqn.equals(listedFqn) and FullyQualifiedName.isParent(entityFqn, listedFqn) (which does childFqn.startsWith(...)). If entity.getFullyQualifiedName() is null for some change event, this now throws a NullPointerException instead of returning false. Most entities have an FQN set, so this is an edge case, but it is a behavioral regression from null-safe to NPE. Consider guarding against a null entity FQN before the loop.

Bug: dataProduct->domain mapping is not an FQN ancestor, won't match

📄 openmetadata-service/src/main/resources/json/data/EventSubResourceDescriptor.json:326-327
In EventSubResourceDescriptor.json the dataProduct source declares containerEntities: ["domain"]. The new descendant-matching relies on FullyQualifiedName.isParent(entityFqn, listedFqn), which is a pure string prefix check (entityFqn.startsWith(parentFqn + ".")). However a DataProduct's fullyQualifiedName is NOT a descendant of its Domain's FQN: DataProductRepository sets the FQN to just the quoted data-product name (findByNameOrNull(FullyQualifiedName.quoteName(updated.getName()))), and the Domain is only a relationship, not an FQN prefix. As a result, the UI will let a user pick a Domain (rendered as Domain.*) as the Entity FQN filter for a dataProduct source, the plain domain FQN is stored, but isParent(dataProductFqn, domainFqn) is always false — so the alert silently matches nothing. This re-creates exactly the "no change events" regression this PR is trying to fix, for the dataProduct source.

Contrast with the other mappings which are genuine FQN prefixes (tag->classification = classification.tag, glossaryTerm->glossary = glossary.term, table->databaseService/database/databaseSchema, etc.) and do work.

Suggested fix: remove the containerEntities entry for dataProduct (since domain is not an FQN ancestor), or implement domain scoping for data products via a mechanism other than FQN-prefix matching.

Quality: Duplicated 'all' container-entity logic and magic string across two pages

📄 openmetadata-ui/src/main/resources/ui/src/pages/AddNotificationPage/AddNotificationPage.tsx:234-244 📄 openmetadata-ui/src/main/resources/ui/src/pages/AddObservabilityPage/AddObservabilityPage.tsx:203-213
The containerEntities memo logic added in AddNotificationPage.tsx (lines 234-244) and AddObservabilityPage.tsx (lines 203-213) is byte-for-byte identical, including the hardcoded selectedTrigger === 'all' comparison and the same explanatory comment. There is no centralized EntityType.ALL constant, so the raw string 'all' is used directly in both files (and implicitly mirrors SearchIndex.ALL = 'all' used in AlertsUtil.getFqnSearchIndexes). This duplication invites drift: a future change to how the 'all' source aggregates container entities must be applied in two places, and the magic string is easy to typo. Consider extracting a shared helper (e.g. in AlertsUtil) such as getContainerEntitiesForTrigger(resources, selectedTrigger) consumed by both pages, and referencing a shared constant instead of the literal 'all'.

Quality: Removed maxTagTextLength may let long FQN tags overflow

📄 openmetadata-ui/src/main/resources/ui/src/components/Alerts/FQNListSelect/FQNListSelect.component.tsx:101-115 📄 openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtil.tsx:952-962
The migration from AsyncSelect to FQNListSelect dropped the maxTagTextLength={45} prop and replaced it with a custom tagRender that uses <Typography.Text className="break-all">{label}</Typography.Text> with no truncation. Previously long FQNs were truncated to 45 characters in the multi-select tags; now they wrap and can make tags very tall, especially when several long FQNs are selected. Since the .* suffix already lengthens container labels, consider re-applying truncation (e.g. via CSS ellipsis or by truncating label in tagRender) to preserve the prior compact layout.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@github-actions

Copy link
Copy Markdown
Contributor

Failed to cherry-pick changes to the 1.12.11 branch.
Please cherry-pick the changes manually.
You can find more details here.

@github-actions

Copy link
Copy Markdown
Contributor

Changes have been cherry-picked to the 1.13 branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Alert Entity-FQN filter no longer matches descendants after #27987 (exact-only matching)

4 participants