Skip to content

feat(ecosystem): Implement cross-system issue synchronization#6

Open
rupakInfinitiBit wants to merge 1 commit into
ecosystem-sync-integration-beforefrom
ecosystem-sync-integration-after
Open

feat(ecosystem): Implement cross-system issue synchronization#6
rupakInfinitiBit wants to merge 1 commit into
ecosystem-sync-integration-beforefrom
ecosystem-sync-integration-after

Conversation

@rupakInfinitiBit

Copy link
Copy Markdown
Owner

No description provided.

@graphbit-prflow-dev

Copy link
Copy Markdown

@rupakInfinitiBit 👋 I've started reviewing this pull request. I'll post a detailed review once I'm done — this may take a moment.

@pr-review-agent-rupakinfinitibit

Copy link
Copy Markdown

🔍 Reviewing PR #6

fix(ecosystem): Breaks issue sync cycles (#77754)
ecosystem-sync-integration-afterecosystem-sync-integration-before  ·  7 file(s)  ·  @rupakInfinitiBit

⏳ Analyzing changes — review incoming shortly.

@coderabbitai

coderabbitai Bot commented Mar 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 107c1b02-d9d0-4036-896d-9fd743f521cc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ecosystem-sync-integration-after

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@graphbit-prflow-dev graphbit-prflow-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Automated PR Review

PR Title: fix(ecosystem): Breaks issue sync cycles (#77754)
Overall Quality Score: 7/10
Recommendation: REQUEST_CHANGES


🧠 Summary

The PR successfully implements cycle prevention for issue synchronization but contains several quality issues including unsafe dataclass defaults, potential deserialization vulnerabilities, and missing test coverage for edge cases.


⚠️ Critical Issues

No critical issues identified. The code changes are functionally sound with manageable risks.


✅ Key Suggestions

  • Fix the dataclass mutable default argument by using queued: datetime = field(default_factory=timezone.now) instead of queued: datetime = timezone.now()
  • Implement explicit parameter validation in the from_dict method to prevent potential deserialization attacks
  • Add comprehensive test coverage for the sync cycle prevention logic and edge cases with malformed data
  • Standardize method signatures between base and derived classes for consistency
  • Fix the typo in the test method name from test_from_dict_inalid_data to test_from_dict_invalid_data

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Approved

████████░░ 7.5/10  ·  7 files reviewed  ·  5 issues found

Key concerns:

  • Mutable default argument in AssignmentSource dataclass creates shared timestamp state
  • Missing type annotations in base class method creates interface inconsistency
  • No error handling for malformed assignment_source_dict in task
📝 Walkthrough (7 files)
File Changes Summary
src/sentry/integrations/mixins/issues.py ✏️ +18/-3 Added sync cycle prevention logic by checking if assignment source matches current integration ID in should_sync method.
src/sentry/integrations/services/assignment_source.py ✏️ +35/-0 New dataclass to track assignment sources with integration metadata and serialization methods.
src/sentry/integrations/tasks/sync_assignee_outbound.py ✏️ +16/-3 Updated task to accept and parse assignment source information for cycle prevention.
src/sentry/integrations/utils/sync.py ✏️ +25/-4 Enhanced sync utilities to propagate assignment source information through the sync pipeline.
src/sentry/models/groupassignee.py ✏️ +9/-2 Added assignment_source parameter to assign/deassign methods to support sync cycle prevention.
tests/sentry/integrations/services/test_assignment_source.py ✏️ +38/-0 Added comprehensive tests for AssignmentSource serialization and deserialization methods.
tests/sentry/models/test_groupassignee.py ✏️ +71/-3 Enhanced existing tests to verify assignment_source parameter handling and added new test for cycle prevention.

🔍 Issues

🔵 src/sentry/integrations/mixins/issues.py

  • 🔵 Line 66Missing type annotations in base class method

    The base class IssueBasicIntegration.should_sync method lacks type annotations while the subclass IssueSyncIntegration.should_sync has proper typing. This creates inconsistency and makes the interface less clear.

def should_sync(self, attribute: str, sync_source: AssignmentSource | None = None) -> bool:

🟡 src/sentry/integrations/services/assignment_source.py

  • 🟡 Line 18Mutable default argument creates shared state

    Using timezone.now() as a default argument creates a single timestamp that's shared across all instances created without an explicit queued value. This means all instances will have the same timestamp, which is likely not the intended behavior.

queued: datetime | None = None

    def __post_init__(self):
        if self.queued is None:
            object.__setattr__(self, 'queued', timezone.now())

🔵 src/sentry/integrations/tasks/sync_assignee_outbound.py

  • 🔵 Line 54–55No error handling for malformed assignment_source_dict

    If assignment_source_dict contains malformed data, AssignmentSource.from_dict() will return None, but there's no logging or handling of this case. While it won't break execution, it might hide configuration issues.

parsed_assignment_source = None
if assignment_source_dict:
    parsed_assignment_source = AssignmentSource.from_dict(assignment_source_dict)
    if parsed_assignment_source is None:
        logger.warning('Failed to parse assignment_source_dict', extra={'assignment_source_dict': assignment_source_dict})

🔵 tests/sentry/integrations/services/test_assignment_source.py

  • 🔵 Line 13Typo in test method name

    The test method name has a typo: 'inalid_data' should be 'invalid_data'.

def test_from_dict_invalid_data(self):

🔵 tests/sentry/models/test_groupassignee.py

  • 🔵 Line 212Incomplete test method signature

    The test method signature appears to be cut off in the diff. The method should properly test the cycle prevention behavior when assignment source matches the target integration.

def test_assignee_sync_outbound_assign_with_matching_source_integration(self, mock_sync_assignee_outbound):

✨ Strengths

  • ✅ Well-structured approach to preventing sync cycles with clear separation of concerns
  • ✅ Comprehensive test coverage including edge cases and error scenarios
  • ✅ Backwards-compatible implementation with optional assignment_source parameters

🤖 PRFlow  ·  🔴 Critical  🟠 Important  🟡 Suggestion  🔵 Minor  ℹ️ Note

class AssignmentSource:
source_name: str
integration_id: int
queued: datetime = timezone.now()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔧 Improvement | 🟡 Suggestion

Mutable default argument creates shared state

Using timezone.now() as a default argument creates a single timestamp that's shared across all instances created without an explicit queued value. This means all instances will have the same timestamp, which is likely not the intended behavior.

Suggested change
queued: datetime = timezone.now()
queued: datetime | None = None
def __post_init__(self):
if self.queued is None:
object.__setattr__(self, 'queued', timezone.now())
🤖 Prompt for AI Agents
In `@src/sentry/integrations/services/assignment_source.py` around line 18, Using `timezone.now()` as a default argument creates a single timestamp that's shared across all instances created without an explicit `queued` value. This means all instances will have the same timestamp, which is likely not the intended behavior.; apply the proposed fix to resolve this bug.

@rupakInfinitiBit rupakInfinitiBit changed the title fix(ecosystem): Breaks issue sync cycles (#77754) feat(ecosystem): Implement cross-system issue synchronization Mar 13, 2026
@rupakInfinitiBit rupakInfinitiBit marked this pull request as draft April 28, 2026 12:10
@rupakInfinitiBit rupakInfinitiBit marked this pull request as ready for review April 28, 2026 12:11
@graphbit-prflow

Copy link
Copy Markdown

🔍 Reviewing PR #6

feat(ecosystem): Implement cross-system issue synchronization
ecosystem-sync-integration-afterecosystem-sync-integration-before  ·  7 file(s)  ·  @rupakInfinitiBit

⏳ Analyzing changes — review incoming shortly.

@graphbit-prflow graphbit-prflow Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💬 Review Complete

█████████░ 8.6/10  ·  7 files reviewed  ·  3 issues found

Key concerns:

  • Replace the dataclass timestamp default with field(default_factory=timezone.now) so each AssignmentSource gets an accurate queued time.
  • Add a round-trip test for AssignmentSource.to_dict() and from_dict() using the actual serialized payload shape, including queued.
📝 Walkthrough (7 files)
File Changes Summary
src/sentry/integrations/mixins/issues.py ✏️ +18/-3 This file extends sync gating to accept an assignment source and adds same-integration cycle prevention logic for outbound issue sync decisions.
src/sentry/integrations/services/assignment_source.py ✏️ +35/-0 This new file introduces a small immutable value object used to carry integration-origin metadata through assignment sync flows.
src/sentry/integrations/tasks/sync_assignee_outbound.py ✏️ +16/-3 This task now accepts serialized assignment source metadata, reconstructs it, and forwards it into installation sync methods to support cycle prevention.
src/sentry/integrations/utils/sync.py ✏️ +25/-4 This file propagates assignment source metadata through inbound and outbound assignee sync helpers so source-aware recursion prevention works across task boundaries.
src/sentry/models/groupassignee.py ✏️ +9/-2 This change threads optional assignment source information through group assignment and deassignment operations and into outbound integration sync.
tests/sentry/integrations/services/test_assignment_source.py ✏️ +38/-0 These tests validate basic parsing and serialization behavior for the new AssignmentSource helper, but they miss the real task-payload round-trip path.
tests/sentry/models/test_groupassignee.py ✏️ +71/-3 This file adds good coverage for outbound assignment sync propagation, including the important same-integration suppression case.

🔍 Issues

🟡 src/sentry/integrations/services/assignment_source.py

  • 🟡 Line 16Datetime default is evaluated at import time

    Using timezone.now() directly in a dataclass field default evaluates once when the class is defined, so every AssignmentSource created without an explicit queued value will share the same timestamp. That makes the metadata inaccurate and can break any future logic that depends on this field representing queue time.

from dataclasses import asdict, dataclass, field
from datetime import datetime
from typing import TYPE_CHECKING, Any

from django.utils import timezone

if TYPE_CHECKING:
    from sentry.integrations.models import Integration
    from sentry.integrations.services.integration import RpcIntegration


@dataclass(frozen=True)
class AssignmentSource:
    source_name: str
    integration_id: int
    queued: datetime = field(default_factory=timezone.now)

🟡 tests/sentry/integrations/services/test_assignment_source.py

  • 🔵 Line 11Typo in test name reduces readability

    The test name test_from_dict_inalid_data appears to contain a typo. Renaming it to invalid will make the suite easier to scan and maintain.

def test_from_dict_invalid_data(self):
  • 🟡 Line 17Tests do not cover datetime round-trip from serialized task payloads

    The new production flow serializes AssignmentSource with to_dict() before passing it through Celery and reconstructs it with from_dict(). The tests only verify missing/invalid keys and a minimal dict without queued, so they do not exercise the actual payload shape that includes a datetime. A regression here would break cycle prevention silently at runtime.

def test_round_trip_to_dict_from_dict(self):
        source = AssignmentSource(
            source_name="foo-source",
            integration_id=123,
        )

        result = AssignmentSource.from_dict(source.to_dict())
        assert result == source

✨ Strengths

  • ✅ The cycle-prevention logic is well-scoped and avoids blocking propagation to other integrations.
  • ✅ Assignment source metadata is threaded consistently from model operations through utility helpers into async tasks.
  • ✅ The new group assignee tests cover the most important behavioral change: suppressing outbound sync when the source integration matches the target.

🤖 PRFlow  ·  🔴 Critical  🟠 Important  🟡 Suggestion  🔵 Minor  ℹ️ Note


@dataclass(frozen=True)
class AssignmentSource:
source_name: str

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔧 Improvement | 🟡 Suggestion

Datetime default is evaluated at import time

Using timezone.now() directly in a dataclass field default evaluates once when the class is defined, so every AssignmentSource created without an explicit queued value will share the same timestamp. That makes the metadata inaccurate and can break any future logic that depends on this field representing queue time.

Suggested change
source_name: str
from dataclasses import asdict, dataclass, field
from datetime import datetime
from typing import TYPE_CHECKING, Any
from django.utils import timezone
if TYPE_CHECKING:
from sentry.integrations.models import Integration
from sentry.integrations.services.integration import RpcIntegration
@dataclass(frozen=True)
class AssignmentSource:
source_name: str
integration_id: int
queued: datetime = field(default_factory=timezone.now)
🤖 Prompt for AI Agents
In `@src/sentry/integrations/services/assignment_source.py` around line 16, Using `timezone.now()` directly in a dataclass field default evaluates once when the class is defined, so every `AssignmentSource` created without an explicit `queued` value will share the same timestamp. That makes the metadata inaccurate and can break any future logic that depends on this field representing queue time.; apply the proposed fix to resolve this bug.

data = {
"foo": "bar",
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔧 Improvement | 🟡 Suggestion

Tests do not cover datetime round-trip from serialized task payloads

The new production flow serializes AssignmentSource with to_dict() before passing it through Celery and reconstructs it with from_dict(). The tests only verify missing/invalid keys and a minimal dict without queued, so they do not exercise the actual payload shape that includes a datetime. A regression here would break cycle prevention silently at runtime.

Suggested change
def test_round_trip_to_dict_from_dict(self):
source = AssignmentSource(
source_name="foo-source",
integration_id=123,
)
result = AssignmentSource.from_dict(source.to_dict())
assert result == source
🤖 Prompt for AI Agents
In `@tests/sentry/integrations/services/test_assignment_source.py` around line 17, The new production flow serializes `AssignmentSource` with `to_dict()` before passing it through Celery and reconstructs it with `from_dict()`. The tests only verify missing/invalid keys and a minimal dict without `queued`, so they do not exercise the actual payload shape that includes a datetime. A regression here would break cycle prevention silently at runtime.; apply the proposed fix to resolve this testing.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants