Skip to content

fix(alerts): thread events now trigger entity-scoped notification alerts#28122

Merged
sonika-shah merged 1 commit into
mainfrom
fix/thread-event-alert-filtering
May 15, 2026
Merged

fix(alerts): thread events now trigger entity-scoped notification alerts#28122
sonika-shah merged 1 commit into
mainfrom
fix/thread-event-alert-filtering

Conversation

@yan-3005
Copy link
Copy Markdown
Contributor

@yan-3005 yan-3005 commented May 14, 2026

Related to #27889 (partial fix — see Scope below)

Problem

Users configured Notification Alerts for entity types (e.g. GlossaryTerm) and selected Thread Creation / Thread Update event types expecting email notifications when conversations were posted on their glossary terms. No events appeared in the Recent Events tab and no emails were sent. Alerts for Entity Change on the same subscription worked correctly.


Root Cause Analysis

How thread change events are produced

When a thread is created (POST /api/v1/feed), FeedResource sets a custom header:

// FeedResource.java:421
return Response.created(thread.getHref())
    .entity(thread)
    .header(CHANGE_CUSTOM_HEADER, THREAD_CREATED)
    .build();

ChangeEventHandler intercepts the response and calls FormatterUtil.createChangeEventForThread(), which produces a ChangeEvent with:

entityType = "THREAD"   ← constant Entity.THREAD, NOT the parent entity's type
eventType  = THREAD_CREATED / THREAD_UPDATED / POST_CREATED
entity     = Thread object (with entityRef pointing to the GlossaryTerm)

The event is stored in change_event table — so the events are persisted correctly.


Where the events were dropped

AbstractEventConsumer.publishEvents() calls AlertUtil.getFilteredEvents()checkIfChangeEventIsAllowed()shouldTriggerAlert().

Before the fix, shouldTriggerAlert() had two paths for thread events:

// Path 1 — only matches if resource is "conversation" / "task" / "announcement"
if (event.getEntityType().equals(THREAD)
    && (config.getResources().get(0).equals("announcement")
        || config.getResources().get(0).equals("task")
        || config.getResources().get(0).equals("conversation"))) {
  Thread thread = AlertsRuleEvaluator.getThread(event);
  return config.getResources().get(0).equalsIgnoreCase(thread.getType().value());
}

// Path 2 — fallback for all other events
return config.getResources().contains(event.getEntityType());
//  ["glossaryTerm"].contains("THREAD")  →  false  ❌

When a user creates a GlossaryTerm alert:

  • config.getResources() = ["glossaryTerm"]
  • Path 1 condition: "glossaryTerm".equals("announcement" | "task" | "conversation")false — skipped
  • Path 2: ["glossaryTerm"].contains("THREAD")false — event dropped

The thread event is filtered out before evaluateAlertConditions() even runs, so the matchAnyEventType("threadCreated", "threadUpdated") rule never gets a chance to evaluate.


Why Entity Change events worked

Entity change events on a GlossaryTerm carry entityType = "glossaryTerm" (the actual entity type), not a constant. Path 2 correctly evaluates:

["glossaryTerm"].contains("glossaryTerm")  →  true  ✅

Thread-type resources ("conversation" / "task" / "announcement") also worked

If a user created an alert with resource = "conversation", Path 1 matched and the thread's ThreadType was compared against the resource. This path was correct and is preserved by the fix.


Fix

Extracted shouldTriggerAlertForThread() to consolidate both thread-matching cases:

// AlertUtil.java — before
if (event.getEntityType().equals(THREAD)
    && (config.getResources().get(0).equals("announcement")
        || config.getResources().get(0).equals("task")
        || config.getResources().get(0).equals("conversation"))) {
  Thread thread = AlertsRuleEvaluator.getThread(event);
  return config.getResources().get(0).equalsIgnoreCase(thread.getType().value());
}

// AlertUtil.java — after
if (event.getEntityType().equals(THREAD)) {
  return shouldTriggerAlertForThread(event, config.getResources().get(0));
}

private static boolean shouldTriggerAlertForThread(ChangeEvent event, String resource) {
  Thread thread = AlertsRuleEvaluator.getThread(event);
  if (thread == null) {
    return false;
  }
  // Thread-type resource ("conversation", "task", "announcement"): existing behaviour
  if (THREAD_TYPE_RESOURCES.contains(resource.toLowerCase(Locale.ROOT))) {
    return resource.equalsIgnoreCase(thread.getType().value());
  }
  // Entity-type resource ("glossaryTerm", "table", …): match via the thread's parent entity type
  return thread.getEntityRef() != null
      && resource.equalsIgnoreCase(thread.getEntityRef().getType());
}

The two dispatch cases are now explicit:

Resource type Example How it matches
Thread-type conversation, task, announcement thread.getType().value() (unchanged)
Entity-type glossaryTerm, table, dashboard, … thread.getEntityRef().getType() (new)

Files Changed

File Change
openmetadata-service/.../events/subscription/AlertUtil.java Bug fix + shouldTriggerAlertForThread() helper
openmetadata-service/.../events/subscription/AlertUtilTest.java 14 new unit tests

Tests

14 new unit tests added to AlertUtilTest — covering every branch of the fixed logic:

Test Asserts
shouldTriggerAlert_nullConfig_returnsTrue null config always passes
shouldTriggerAlert_allResource_returnsTrue "all" resource always passes
shouldTriggerAlert_entityEvent_matchingResource_returnsTrue entity event matches its resource
shouldTriggerAlert_entityEvent_nonMatchingResource_returnsFalse entity event rejects wrong resource
shouldTriggerAlert_conversationThread_conversationResource_returnsTrue existing conversation→conversation path preserved
shouldTriggerAlert_taskThread_conversationResource_returnsFalse task thread rejected by conversation resource
shouldTriggerAlert_taskThread_taskResource_returnsTrue task thread matches task resource
shouldTriggerAlert_announcementThread_announcementResource_returnsTrue announcement thread matches announcement resource
shouldTriggerAlert_conversationOnGlossaryTerm_glossaryTermResource_returnsTrue bug fix — THREAD_CREATED fires for glossaryTerm alert
shouldTriggerAlert_threadUpdateOnGlossaryTerm_glossaryTermResource_returnsTrue bug fix — THREAD_UPDATED fires for glossaryTerm alert
shouldTriggerAlert_postCreatedOnGlossaryTerm_glossaryTermResource_returnsTrue bug fix — POST_CREATED fires for glossaryTerm alert
shouldTriggerAlert_threadOnTable_glossaryTermResource_returnsFalse thread on wrong entity type is correctly rejected
shouldTriggerAlert_threadOnTable_tableResource_returnsTrue bug fix — works for any entity type, not just glossaryTerm
shouldTriggerAlert_threadWithNullEntityRef_entityTypeResource_returnsFalse null entityRef handled safely, no NPE

All 23 tests in AlertUtilTest pass (Tests run: 23, Failures: 0, Errors: 0).


How to Reproduce (before fix)

  1. Create a Notification Alert → resource: GlossaryTerm → add filterByEventType with threadCreated / threadUpdated
  2. Add a conversation to any GlossaryTerm
  3. Observe: Recent Events tab shows nothing, no email sent
  4. Create an alert for the same GlossaryTerm with entityUpdated → works immediately

Scope

Fixes the customer-reported reproduction in #27889: resource glossaryTerm + filter threadCreated / threadUpdated / postCreated.

Does not fix (still open after this merges):

  • TaskResource.resolveTask / closeTask emit no change events.
  • taskCreated / taskUpdated EventType values are never emitted by any code.
  • AlertsUtil.tsx offers every EventType regardless of selected resource.

Thread change events (THREAD_CREATED, THREAD_UPDATED, POST_CREATED) were
silently dropped by shouldTriggerAlert() when the subscription resource was an
entity type such as 'glossaryTerm' or 'table'.

Root cause: thread ChangeEvents always have entityType='THREAD' (a constant),
not the type of the entity the thread belongs to. The fallback check
  config.getResources().contains(event.getEntityType())
evaluated ['glossaryTerm'].contains('THREAD') == false, filtering the event
out before the matchAnyEventType rule could run. Entity Change events worked
because their entityType matched the resource directly.

Fix: extract shouldTriggerAlertForThread(). For thread-type resources
('conversation', 'task', 'announcement') the existing semantics are preserved.
For any other resource the method now checks the thread's parent entity type
via thread.getEntityRef().getType(), so a 'glossaryTerm' alert correctly
fires when a conversation is created or updated on a GlossaryTerm.
Copilot AI review requested due to automatic review settings May 14, 2026 18:04
@yan-3005 yan-3005 added safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch backend labels May 14, 2026
@gitar-bot
Copy link
Copy Markdown

gitar-bot Bot commented May 14, 2026

Code Review ✅ Approved

Enables entity-scoped notification alerts for thread events by updating AlertUtil to resolve the parent entity type. Includes 14 new unit tests to verify accurate filtering across all resource types.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes notification alert resource filtering so thread events on entity-scoped resources (for example, conversations on glossary terms or tables) are no longer dropped before event rules are evaluated.

Changes:

  • Routes THREAD change events through a dedicated helper.
  • Preserves conversation/task/announcement resource matching by thread type.
  • Adds unit coverage for entity events, thread-type resources, and entity-scoped thread events.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
openmetadata-service/src/main/java/org/openmetadata/service/events/subscription/AlertUtil.java Adds thread-specific resource matching against either thread type or parent entity reference type.
openmetadata-service/src/test/java/org/openmetadata/service/events/subscription/AlertUtilTest.java Adds tests covering alert triggering for null/all configs, entity events, thread resources, and entity-scoped thread events.

@sonarqubecloud
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (15 flaky)

✅ 4055 passed · ❌ 0 failed · 🟡 15 flaky · ⏭️ 103 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 1 298 0 1 4
🟡 Shard 2 744 0 6 25
🟡 Shard 3 783 0 1 7
🟡 Shard 4 788 0 2 18
🟡 Shard 5 707 0 2 41
🟡 Shard 6 735 0 3 8
🟡 15 flaky test(s) (passed on retry)
  • Pages/AuditLogs.spec.ts › should create audit log entry when glossary is created (shard 1, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/Glossary/GlossaryWorkflow.spec.ts › should display correct status badge color and icon (shard 2, 1 retry)
  • Features/Glossary/GlossaryWorkflow.spec.ts › should start term as Draft when glossary has reviewers (shard 2, 1 retry)
  • Features/KnowledgeCenterList.spec.ts › Knowledge Center List - Test infinite scroll/pagination (shard 2, 2 retries)
  • Features/KnowledgeCenterTextEditor.spec.ts › Rich Text Editor - Text Formatting (shard 2, 1 retry)
  • Features/KnowledgeCenterTextEditor.spec.ts › Rich Text Editor - Text Formatting (shard 2, 1 retry)
  • Features/Table.spec.ts › Table pagination with sorting should works (shard 3, 1 retry)
  • Pages/CustomProperties.spec.ts › Entity Reference (shard 4, 1 retry)
  • Pages/DataContractsSemanticRules.spec.ts › Validate Entity Version Less than < (shard 4, 1 retry)
  • Pages/EntityDataConsumer.spec.ts › No edit owner permission (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 5, 2 retries)
  • Features/AutoPilot.spec.ts › Create Service and check the AutoPilot status (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/ServiceEntity.spec.ts › Tier Add, Update and Remove (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

@sonika-shah sonika-shah merged commit 65dd09b into main May 15, 2026
55 of 84 checks passed
@sonika-shah sonika-shah deleted the fix/thread-event-alert-filtering branch May 15, 2026 10:16
@github-actions
Copy link
Copy Markdown
Contributor

Changes have been cherry-picked to the 1.13 branch.

github-actions Bot pushed a commit that referenced this pull request May 15, 2026
…rts (#28122)

Thread change events (THREAD_CREATED, THREAD_UPDATED, POST_CREATED) were
silently dropped by shouldTriggerAlert() when the subscription resource was an
entity type such as 'glossaryTerm' or 'table'.

Root cause: thread ChangeEvents always have entityType='THREAD' (a constant),
not the type of the entity the thread belongs to. The fallback check
  config.getResources().contains(event.getEntityType())
evaluated ['glossaryTerm'].contains('THREAD') == false, filtering the event
out before the matchAnyEventType rule could run. Entity Change events worked
because their entityType matched the resource directly.

Fix: extract shouldTriggerAlertForThread(). For thread-type resources
('conversation', 'task', 'announcement') the existing semantics are preserved.
For any other resource the method now checks the thread's parent entity type
via thread.getEntityRef().getType(), so a 'glossaryTerm' alert correctly
fires when a conversation is created or updated on a GlossaryTerm.

(cherry picked from commit 65dd09b)
@github-actions
Copy link
Copy Markdown
Contributor

Changes have been cherry-picked to the 1.12.9 branch.

github-actions Bot pushed a commit that referenced this pull request May 15, 2026
…rts (#28122)

Thread change events (THREAD_CREATED, THREAD_UPDATED, POST_CREATED) were
silently dropped by shouldTriggerAlert() when the subscription resource was an
entity type such as 'glossaryTerm' or 'table'.

Root cause: thread ChangeEvents always have entityType='THREAD' (a constant),
not the type of the entity the thread belongs to. The fallback check
  config.getResources().contains(event.getEntityType())
evaluated ['glossaryTerm'].contains('THREAD') == false, filtering the event
out before the matchAnyEventType rule could run. Entity Change events worked
because their entityType matched the resource directly.

Fix: extract shouldTriggerAlertForThread(). For thread-type resources
('conversation', 'task', 'announcement') the existing semantics are preserved.
For any other resource the method now checks the thread's parent entity type
via thread.getEntityRef().getType(), so a 'glossaryTerm' alert correctly
fires when a conversation is created or updated on a GlossaryTerm.

(cherry picked from commit 65dd09b)
sonika-shah added a commit that referenced this pull request May 15, 2026
… 1.12.9)

Cherry-pick of #28122 referenced EventType.TASK_CREATED, which was added
to changeEventType.json on main but is not present on the 1.12.9 schema.
Switch to TASK_RESOLVED (a task event that exists in this branch) so the
test compiles. The production logic only inspects thread.getType(), not
the event type, so test intent is preserved.
sonika-shah added a commit that referenced this pull request May 15, 2026
… 1.13)

Cherry-pick of #28122 referenced EventType.TASK_CREATED, which was added
to changeEventType.json on main but is not present on the 1.13 schema.
Switch to TASK_RESOLVED (a task event that exists in this branch) so the
test compiles. The production logic only inspects thread.getType(), not
the event type, so test intent is preserved.
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.

4 participants